> ## Documentation Index
> Fetch the complete documentation index at: https://agno-v2-docs-agentos-durable-background-execution.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Background Execution

> Accept runs with background=true, poll or stream them, and make acceptance durable with a database-backed job queue.

Submit a run with `background=true` and AgentOS answers immediately with a `run_id`. The run executes on the server while your client polls, streams, or disconnects. Add `QueueConfig(durable=True)` and that acceptance becomes a committed database row that survives crashes and deploys.

**Your database is where truth lives, `queue.redis` is how replicas talk to each other, and `durable=True` turns acceptance into a promise.**

| Part         | Setting                     | What it holds                                                                                                                             |
| ------------ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Truth        | `AgentOS(db=...)`           | Sessions, run rows, and the `agno_jobs` queue table. Losing any other component never loses a run.                                        |
| Coordination | `QueueConfig(redis=...)`    | Sets both the event stream (`RedisEventStream`) and the cancellation manager (`RedisRunCancellationManager`) on every replica. Ephemeral. |
| Promise      | `QueueConfig(durable=True)` | A committed job row per accepted run. Whichever replica claims it executes it.                                                            |

## Quickstart

```bash theme={null}
pip install "agno[os]" openai psycopg
```

Run Postgres:

```bash theme={null}
docker run -d \
  -e POSTGRES_DB=ai \
  -e POSTGRES_USER=ai \
  -e POSTGRES_PASSWORD=ai \
  -p 5532:5432 \
  --name pgvector \
  agnohq/pgvector:18
```

```python durable_queue.py theme={null}
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS, QueueConfig

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")

agent = Agent(
    name="Durable Agent",
    id="durable-agent",
    model=OpenAIResponses(id="gpt-5.5"),
    db=db,
)

agent_os = AgentOS(
    agents=[agent],
    db=db,
    queue=QueueConfig(durable=True),
)
app = agent_os.get_app()

if __name__ == "__main__":
    agent_os.serve(app="durable_queue:app", reload=True)
```

Submit a run:

```bash theme={null}
curl -X POST localhost:7777/agents/durable-agent/runs \
  -F "message=Write a haiku about queues" \
  -F "background=true" \
  -F "stream=false"
```

```json theme={null}
{"run_id": "20a47fb2-...", "session_id": "72b8bc0c-...", "status": "PENDING"}
```

The `202` is returned after the queue row commits. Poll for the result:

```bash theme={null}
curl "localhost:7777/agents/durable-agent/runs/{run_id}?session_id={session_id}"
```

The response carries `status` (`PENDING`, `RUNNING`, `PAUSED`, `COMPLETED`, `CANCELLED`, or `ERROR`) and, once finished, `content`.

## The 202 contract

Every background submission, durable or not, returns the same shape:

| Field        | Meaning                                                                                              |
| ------------ | ---------------------------------------------------------------------------------------------------- |
| `run_id`     | Identifier for polling, resuming, cancelling, and continuing. Never changes for the life of the run. |
| `session_id` | Session the run belongs to. Required on poll.                                                        |
| `status`     | `PENDING` on a fresh acceptance.                                                                     |

The `background` and `stream` form fields select the execution mode:

| `background` | `stream` | Response                                                                                                                                 |
| :----------: | :------: | ---------------------------------------------------------------------------------------------------------------------------------------- |
|    `true`    |  `false` | `202` with `run_id`. Poll `GET /agents/{agent_id}/runs/{run_id}`.                                                                        |
|    `true`    |  `true`  | SSE stream of the run's events. Disconnect and reconnect through `POST /agents/{agent_id}/runs/{run_id}/resume` with `last_event_index`. |
|    `false`   |    any   | Inline execution. A client disconnect cancels the run.                                                                                   |

Teams and workflows use the same fields under `/teams/{team_id}/runs` and `/workflows/{workflow_id}/runs`. See [Background Execution](/background-execution/overview) for the SDK-level `arun(background=True)` API and the SSE resume protocol.

<Note>
  Background execution requires a `db` on the agent, team, or workflow. Submissions without one are refused with `400`.
</Note>

<Note>
  The [AgentOS Control Plane](https://os.agno.com) submits every chat run with `background=true`. Runs started from the UI go through the same path as any other background submission: the concurrency cap applies, and the queue applies if your AgentOS is configured with `QueueConfig(durable=True)`.
</Note>

## Without durability

| Behavior            | Detail                                                                                                                    |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Bounded concurrency | At most `max_concurrency` background runs execute at once per replica (default 32, or `AGNO_BACKGROUND_MAX_CONCURRENCY`). |
| Waiting             | Runs beyond the cap are accepted as `PENDING`, wait for a slot, and can be cancelled while waiting.                       |
| Resumable streams   | Events are buffered in-process. Reconnect through `/resume`.                                                              |

The run lives in the memory of the replica that accepted it. If that process dies, every waiting and in-flight run on it is lost, and nothing marks them as failed. In a multi-replica deployment, `/resume` and `/cancel` only work on the replica that holds the run.

```python theme={null}
agent_os = AgentOS(
    agents=[agent],
    db=db,
    queue=QueueConfig(max_concurrency=16),
)
```

## With durability

`QueueConfig(durable=True)` writes each accepted run as a row in the queue table before the `202` is sent. A worker on every replica claims rows and executes them. If a replica dies, its runs are either reclaimed by another replica or marked failed, depending on `max_attempts`.

See [Durable queue](/agent-os/background-execution/durable-queue) for the exact guarantee, retry policy, idempotency keys, and configuration.

## With more than one replica

Set `QueueConfig(redis=...)` as soon as you run two or more replicas behind a load balancer. One setting installs both the event stream and the cancellation manager on each replica, backed by a shared Redis, so a run started on one replica can be watched, resumed, and cancelled from any other. No `set_cancellation_manager()` or `set_event_stream()` call is needed.

See [Multi-replica deployments](/agent-os/background-execution/multi-replica) for what Redis does here, and why `queue.redis` is a different job from `db=RedisDb`.

## Upgrading from v2

Background runs are capped at 32 per replica in v3. In v2 each submission spawned an unbounded `asyncio.create_task`; now runs beyond the cap wait as `PENDING`. Raise or disable the cap with `QueueConfig(max_concurrency=...)` or `AGNO_BACKGROUND_MAX_CONCURRENCY`. Durability, Redis coordination, idempotency keys, session ordering, and the `/queue` endpoints are opt-in through `QueueConfig`.

## Guides

<CardGroup cols={2}>
  <Card title="Durable queue" icon="database" href="/agent-os/background-execution/durable-queue">
    Acceptance as a committed row. Crash semantics, retries, idempotency, session ordering.
  </Card>

  <Card title="Multi-replica deployments" icon="server" href="/agent-os/background-execution/multi-replica">
    Wire events out and cancels in through Redis. Coordination versus job storage.
  </Card>

  <Card title="Human-in-the-loop continuations" icon="hand" href="/agent-os/background-execution/hitl-continuations">
    Continue a paused durable run through the queue under the same run\_id.
  </Card>

  <Card title="Operations and monitoring" icon="gauge" href="/agent-os/background-execution/operations">
    Dead-letter listing, requeue, queue stats, retention, admin gating.
  </Card>
</CardGroup>

## Limitations

| Area                      | Limit                                                                                                                                                                                                                                                                             |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Live stream view          | Best-effort. The run row is authoritative. A disconnect is harmless and reconnecting replays events, but continuity across retry attempts is not guaranteed. Event indices are strictly increasing, not gapless. Termination is signalled by run status, not by index arithmetic. |
| Non-queueable submissions | Media uploads, kwargs that plain JSON cannot store (for example an `output_schema` class), factory-backed components, and version-pinned lookups cannot ride the queue. They fall back to the bounded in-process path with a logged warning. The client still gets a `202`.       |
| Queue stores              | Postgres (sync and async) and `RedisDb`. Any other `db` raises at startup. `RedisCluster` clients are rejected for the queue store.                                                                                                                                               |
| Blocking work             | Lease heartbeats run on a dedicated thread, so a sync model client or sync tool cannot starve its own lease. Blocking the event loop still delays cancellation checkpoints, timeout enforcement, and event publishing. Keep blocking work in threads.                             |
| Development loop          | The queue behaves the same in development. A job accepted before a restart executes after it. Runs in flight at the default `max_attempts=1` are failed with `interrupted by worker shutdown` once the drain window closes.                                                       |

## Developer Resources

* [QueueConfig source](https://github.com/agno-agi/agno/blob/feat/v3.0/libs/agno/agno/job_queue/config.py)
* [Durable queue cookbook](https://github.com/agno-agi/agno/tree/feat/v3.0/cookbook/05_agent_os/background_tasks)
* [Background Execution (SDK)](/background-execution/overview)
