Skip to main content
Submit long-running agent tasks and recipes, then retrieve results asynchronously via a jobs server. The user submits a long job; the agent runs asynchronously and returns when the job completes.

How It Works

The user submits a job, the server runs it in the background, and the result comes back on completion.

Choose a Result Mode

Pick how the result is delivered once the job finishes.

Quick Start

1

Submit via recipe helper

2

Submit via HTTP API

Persistent Store

Point the Jobs API at a SQLite file so job state and idempotency keys survive restarts — no code change, just one env var.
Construct the store yourself when you want explicit control:
SqliteJobStore and create_app are both exported from praisonai.jobs. _build_default_store() in praisonai/jobs/server.py selects the backend from the environment:

Which store should I use?

In production the Jobs API refuses to boot on the in-memory store. _build_default_store() raises RuntimeError with this message:
Jobs API refused to start with the in-memory store in production. Set PRAISONAI_JOBS_DB_PATH (e.g. /var/lib/praisonai/jobs.db) to persist jobs and idempotency keys across restarts, or explicitly pass store=InMemoryJobStore() to create_app().
The check fires only when ENVIRONMENT=production and PRAISONAI_JOBS_DB_PATH is unset. Passing store=InMemoryJobStore() explicitly to create_app() bypasses the guard — the documented escape hatch for ephemeral production workloads that accept losing job state on restart.

Atomic Idempotency

JobStore exposes save_if_absent(job) so a duplicate submit resolves to a single job and side effects run exactly once.
  • The default InMemoryJobStore implementation is a best-effort check-then-save: it looks up the idempotency key, then inserts if absent.
  • SqliteJobStore overrides save_if_absent() to be truly atomic via a UNIQUE index on idempotency_key. Two concurrent submits with the same key race on the INSERT; the loser catches IntegrityError and returns the winning job, so both callers receive the same job_id.
  • NULL idempotency keys are exempt from SQLite’s UNIQUE constraint, so keyless jobs are never de-duplicated against each other.

Restart Recovery

On SqliteJobStore startup, _reconcile_interrupted_jobs() marks any row still in QUEUED or RUNNING as FAILED, with error="Interrupted by service restart" and completed_at=now. A crashed executor leaves no worker to resume its in-flight jobs, so those rows would otherwise poll forever and pin an idempotency key to a job that can never complete. Terminal reconciliation gives callers — and idempotent retries — a definitive outcome.

Start Server

Submit Job

Submit a Recipe Job

Point the same endpoint at an installed recipe by adding recipe_name (and, optionally, recipe_config). The prompt field becomes the recipe’s input data.

Idempotency

Since PR #1673, the in-process store is safe to read concurrently with writes. You can safely share a single InMemoryJobStore instance between the FastAPI app and background tasks that periodically read stats.

Polling

SSE Streaming

Webhook Callback

Session Grouping

Cancel Job

List Jobs

Complete Example

CLI Usage


Best Practices

Pass Idempotency-Key (HTTP) or idempotency_key= (recipe helper) so duplicate submits return the same job instead of duplicating work.
For runs over a few minutes, set webhook_url and let your service react to completion instead of holding an open poll loop.
python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory — the in-process store is safe for concurrent reads after PR #1673.
Export PRAISONAI_JOBS_DB_PATH=/var/lib/praisonai/jobs.db so SqliteJobStore persists jobs and idempotency keys across restarts. With ENVIRONMENT=production and no path set, create_app() raises RuntimeError rather than silently losing state.

Background Tasks

Run agent work in-process without a separate jobs server.

Async Jobs CLI

Submit, stream, and cancel jobs from the terminal.

Durable Tool Runs

Persist and resume tool executions across restarts.