# Serve sandboxed Claude and Codex agents from AgentOS with no new endpoints

> Agno AgentOS sends every `POST /agents/{agent_id}/runs` for a sandboxed `ClaudeAgent` or `CodexAgent` to its durable queue, in foreground, streaming and background modes, and a retry with the same `Idempotency-Key` gets the original run back.

- Published: 2026-10-16
- Author: Yash Pratap Solanky
- Categories: Changelog
- Canonical: https://www.agno.com/blog/serve-sandboxed-claude-and-codex-agents-from-agentos-with-no-new-endpoints
- Markdown: https://www.agno.com/blog/serve-sandboxed-claude-and-codex-agents-from-agentos-with-no-new-endpoints.md

Agno AgentOS accepts a sandboxed `ClaudeAgent` or `CodexAgent` only on a durable queue with Redis coordination, because any replica may run the next turn. Register the agent the way you register any other and the run route does the rest.

```python
from agno.agents.claude import ClaudeAgent
from agno.db.postgres import PostgresDb
from agno.job_queue.config import QueueConfig, RedisCoordination
from agno.os import AgentOS
from agno.sandbox.checkpoint import FileCheckpointStore
from agno.sandbox.docker import DockerSandbox

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")
agent = ClaudeAgent(
    id="coder",
    permission_mode="bypassPermissions",
    sandbox=DockerSandbox(
        "agno-sandbox:local",
        network="isolated",
        checkpoints=FileCheckpointStore("/shared/checkpoints"),
    ),
)
agent_os = AgentOS(
    agents=[agent],
    db=db,
    queue=QueueConfig(durable=True, db=db, redis=RedisCoordination(url="redis://localhost:6379/0")),
)
```

A foreground request waits on the queued run and returns it, `stream=true` tails its events, and `background=true` answers 202 with a `run_id`. A client that drops reconnects with `/runs/{run_id}/resume` and its `last_event_index`, and `/runs/{run_id}/cancel` works from any replica. When the cancel settles on another replica, the run row records the run as cancelled and keeps the user's message.

## Retries with an Idempotency-Key

A client that retries with the same `Idempotency-Key` gets the run it already started. If the key comes back with another session, user or agent, AgentOS answers 409, and the same goes for changed input, run options or attachment bytes. Changing only `stream` or `background` still returns the original run.

We ran the release's AgentOS through FastAPI's `TestClient` on an embedded Postgres, with fakeredis for Redis and a stand-in sandbox that has no compute, since every check here happens before a computer starts.

![Real output from four POSTs to /agents/coder/runs with Idempotency-Key ticket-42 on a sandboxed ClaudeAgent, using a stand-in sandbox. The first request returns 202 with run_id df2b2787 and status PENDING. A retry with the same body returns 202 with the same run_id df2b2787, PENDING. The same key with a new message returns 409 Idempotency-Key already has different input or options. The same key with a new session_id returns 409 Idempotency-Key belongs to a different submission scope. Below, building AgentOS with the sandboxed agent and a SqliteDb raises ValueError: A sandboxed agent requires a PostgresDb or AsyncPostgresDb, on the agent or its AgentOS.](https://www.agno.com/images/v3-1-4-sandbox-agentos-idempotency.png)

## Mistakes caught before a turn runs

A sandboxed agent coordinates every turn through Postgres, so `AgentOS(...)` raises at construction when neither the agent nor AgentOS has a `PostgresDb` or `AsyncPostgresDb`. Once a queue worker is active, AgentOS also refuses a sandbox left on the default `~/.agno/sandbox/checkpoints` folder, which other replicas can't read. That submission came back as a 500 in the test run, and AgentOS cancelled the ticket before it could execute.

AgentOS validates each sandboxed agent's `<session_table>_sandbox_bindings` table at startup and again before a submission creates its ticket. A table left by an earlier build fails with its remedy, telling you to drop or rename that table or pass `PostgresDb(sandbox_table=...)` to create a new one. A `user_id` that doesn't match the session's owner gets a 404 before any ticket exists, and a foreground job that failed before its run row was written returns that job's error as a 500.

See the [cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/frameworks/sandbox/docker/sandbox_claude_agentos.py), and learn more about [the durable queue](https://docs.agno.com/agent-os/background-execution/durable-queue) and [idempotency keys](https://docs.agno.com/agent-os/background-execution/durable-queue#idempotency-keys) in the documentation.
