# Run Claude and Codex agents in the background and cancel them mid-turn

> Agno's ClaudeAgent and CodexAgent take background=True, which gives clients a run_id to poll, a stream they can reconnect to, and a /cancel endpoint that interrupts the Claude or Codex turn.

- Published: 2026-10-12
- Author: Ashpreet Bedi
- Categories: Changelog
- Canonical: https://www.agno.com/blog/run-claude-and-codex-agents-in-the-background-and-cancel-them-mid-turn
- Markdown: https://www.agno.com/blog/run-claude-and-codex-agents-in-the-background-and-cancel-them-mid-turn.md

When a `ClaudeAgent` or `CodexAgent` run arrives with `background=True`, Agno's AgentOS writes a PENDING run, answers `202` at once and hands the SDK turn to a worker. From there the run moves to RUNNING and ends as COMPLETED, ERROR or CANCELLED on the same background task, worker slot and event-stream code that native agents use.

Picture a coding agent asked to refactor a large module. The request should return right away, the UI should show progress, and a user who changes their mind should be able to stop it halfway through. On the server, the agent needs a database, and the durable queue makes each accepted run a committed job:

```python
from agno.agents.claude import ClaudeAgent
from agno.db.postgres import PostgresDb
from agno.os import AgentOS, QueueConfig

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")
agent = ClaudeAgent(id="claude", name="Claude", db=db)

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

A client then sends `background=true` and `stream=false` with the run, receives its `run_id` and `session_id`, polls `GET /agents/claude/runs/{run_id}?session_id={session_id}`, and stops it with `POST /agents/claude/runs/{run_id}/cancel`. In Python, `await agent.arun(..., background=True)` returns the PENDING run and `await agent.acancel_run(run_id)` stops it.

![Diagram of a background run on ClaudeAgent or CodexAgent in three rows across Client, AgentOS and the Claude or Codex SDK. First, a POST to the agent runs endpoint with background=true makes AgentOS write the run and answer 202 with status PENDING, and with the durable queue the job is a committed row; nothing runs in the SDK yet. Second, the client polls the run endpoint or streams and reconnects through /resume with last_event_index, while a worker takes a slot and starts the turn, every streamed event carries an event_index, the status is RUNNING, and the SDK side keeps a live handle per run: its own ClaudeSDKClient for Claude and the turn handle for Codex. Third, a POST to the cancel endpoint or acancel_run in Python stores the cancel intent, the process that owns the run calls interrupt() on that handle to stop the turn, and the run is saved as CANCELLED.](https://www.agno.com/images/v3-1-3-external-background-cancel.png)

## Cancelling a turn that is already running

Cancellation reaches the SDK itself. Each Claude run gets its own `ClaudeSDKClient`, and `CodexAgent` keeps the handle of the Codex turn, so the process that owns the run can call `interrupt()` on it. A cancel request stores the intent first, and the owning process acts on it, whether the call came through that process or through the shared cancellation backend.

We served a `ClaudeAgent` from AgentOS with the durable queue on an embedded Postgres. No Claude login was available, so a stand-in `claude_agent_sdk` client streamed one text block and then waited until it was interrupted. Polling, cancelling and polling again printed this (real output):

```text
POST /agents/claude/runs background=true -> 202 {"run_id":"16eef695-8c75-48a0-9687-be689c355c1d","session_id":"s1","status":"PENDING"}
GET run -> PENDING
GET run -> 200 RUNNING
POST cancel -> 200 {}
GET run -> 200 CANCELLED | 'Run cancelled'
SDK calls: ['connect', 'query', 'interrupt', 'disconnect']
```

The last line is the order of calls the adapter made on the SDK client: it connected, sent the prompt, received the interrupt and disconnected. Codex works the same way through its turn handle, and an interrupted Codex turn is stored as CANCELLED.

## Streaming and reconnecting

Send `stream=true` together with `background=true` and the response is an SSE stream in which every event carries an `event_index`. If the client drops, `POST /agents/{agent_id}/runs/{run_id}/resume` with the last `event_index` it saw sends the missed events and then continues live. Closing the stream does not cancel the run, which keeps going until it finishes or someone calls `/cancel`.

A background run needs a database on the agent, and `arun` raises a `ValueError` without one. Running more than one replica needs a shared event stream and cancellation backend, which `QueueConfig(redis=...)` sets up.

See the [cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/frameworks/claude-agent-sdk/background_cancel.py), and learn more about [background execution](https://docs.agno.com/agent-os/background-execution/overview) in the documentation.
