Agent Sandbox Registry

python-sdk

Hosted agents

docs/en/guide/hosted-agents.md

Hosted agents

The user guide and the runnable case are in Agents API. This page is the route reference.

Codex and Qwen Code are supported today. Adapter names that are not supported yet, including Claude Code, Cursor, Qoder, and the generic command adapter, return HTTP 404 not_found_error with x-should-retry: false.

The routes live on the sandbox server (agent_sandbox.server), in the opt-in agents capability group. They stay off until ABX_SERVER_ENABLED_GROUPS includes agents. With the group disabled, the same paths answer 404 and the body says the route is disabled.

export ABX_SERVER_ENABLED_GROUPS=agents
python -m agent_sandbox.server

The template server listens on port 9000. Point an OpenAI-shaped client at base_url=http://127.0.0.1:9000/v1. When ABX_SERVER_TOKEN is set, agents routes accept that token as X-Access-Token or Authorization: Bearer. Other routes still use X-Access-Token only.

Best practices

Everything is local, and says so

No operation here runs at OpenAI. Each is a local process or local state, and each is labeled:

differences code Meaning
queued_not_steered Input sent during a turn queues a new turn. Neither CLI accepts bytes into a running process.
usage_adapter_best_effort usage is what the CLI reported, and null when it reported nothing.
redaction_best_effort Redaction covers registered secrets and common token shapes only.
update_in_place, snapshot_at_session_start A definition updates in place. A session copies it when it is created.
hosted_request_converted, nothing_provisioned An empty openai_hosted environment request became self_hosted. An environment is a directory binding.
local_workspace, no_overwrite Paths are relative to the environment's workspace directory. A different existing file is refused.
local_blob A file id is a blob in server memory.
not_hosted_template, applies_env_and_files_only A template is {name, env, inline files} and nothing else.
memory_only Secrets and vault records live in memory and are lost on restart.
secret_in_agent_env Code the agent runs can read a credential from the CLI environment.
snapshot_copy, reported_paths_only, delete_removes_snapshot_only Artifacts are copies of paths the CLI reported. Deleting one never touches the file.
trace_from_journal_timing_only Spans come from journal timestamps and carry no text.
ids_from_cli_only, turns_grouped_from_parent_stream, turns_from_rollout Subagent ids are the CLI's own.
summary_not_encrypted_compaction compact is a local summary.
response_is_cli_turn, model_passthrough, stream_replayed_after_turn, queued_steer Responses semantics.
local_signer This server signs and delivers webhook events.
not_implemented_locally On the 404 of a route with no local capability.

Refusals follow one rule. A field or variant this server cannot honor is 400 invalid_request_error with param naming it and x-should-retry: false. It is never accepted and ignored. A route with no local capability is 404 not_found_error with x-should-retry: false, never 501 and never an empty 200.

Adapters

Selector in the JSON body Canonical id Binary on PATH Install, only when you use it
codex codex codex npm install -g @openai/codex
qwen-code, qwen, qwencode qwen-code qwen npm install -g @qwen-code/qwen-code

Selection order:

  1. X-ABX-Adapter header, or a body field adapter / runtime (top-level or under agent)
  2. agent.model or model when that string is one of the names above (a real model id such as gpt-5-codex is passed through to the CLI and does not choose an adapter)
  3. Config agents.default: the ABX_AGENTS_DEFAULT environment variable, or in ABX_AGENTS_CONFIG a JSON string at default or agents.default
  4. Otherwise the first runtime in the product order: codex, then qwen-code

GET /v1/agents/runtimes defaults to order=desc. order=asc is the product order above. default: true marks the runtime step 4 selected. It does not follow the array index. A real model id (gpt-5) is passed to that CLI and does not change the adapter. Name the other runtime in the request to use it.

Sessions, events, items, turns

Errors on these routes use the OpenAI envelope {"error": {"message", "type", "param", "code"}}.

Create a session:

curl -s -X POST http://127.0.0.1:9000/v1/agents/sessions \
  -H 'Content-Type: application/json' \
  -d '{"agent":{"model":"codex"}}'

GET /v1/agents/sessions lists sessions (filter with agent_id). GET and DELETE /v1/agents/sessions/{id} read and remove one; delete also drops the artifact snapshots. POST /v1/agents/sessions/{id} updates metadata only: agent, vault_ids and environment are 400, and so is any other key. The session object is agent.session with environment.type self_hosted. environment.workspace_directory defaults to /workspace.

A session takes either an inline agent or an agent_id from definitions, plus optional environment: {"type": "self_hosted", "id": "env_..."} and vault_ids. The inline agent is validated like a definition: tools other than mcp, multi_agent.enabled, service_tier outside auto|default and a non-text text.format are 400.

Sessions, definitions, environments, vaults, templates, responses and webhook endpoints belong to the caller. With a server policy, that is the identity id; without one, a single local owner. A foreign id answers exactly like an unknown id.

Queue a turn. The body is an input message; the response is HTTP 202 with an empty body. The same Idempotency-Key and the same body replay that 202 and do not start a second turn. The same key with a different body is 409 idempotency.mismatch (x-should-retry: false). input.tool_result, computer_use_approval_request_result, input_image and input_file are 400.

curl -s -D - -o /dev/null -X POST \
  http://127.0.0.1:9000/v1/agents/sessions/$ID/events \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: turn-1' \
  -d '{"events":[{"type":"agent.session.input.message","input":"Say hello"}]}'

GET /v1/agents/sessions/{id}/events with Accept: text/event-stream streams the mapped events (text/event-stream, id is the runtime sequence). Omit after to tail from now; the stream stays open until the session is idle again after a turn you observed. ?after=0 (or ?after=evt_<seq>) replays the ring buffer and closes once it has delivered the idle event and nothing newer is queued.

GET .../items, GET .../turns, GET .../turns/{turn_id}, and GET .../turns/{turn_id}/items return the list envelope {object, data, first_id, last_id, has_more}. limit is 1–100 (default 20) and order is asc or desc (default desc).

GET /v1/agents/runtimes lists codex and qwen-code only, including whether each binary is on PATH. Each object has default. That flag is true for the runtime used when the request does not name one. order=asc is codex, then qwen-code. The default order is desc.

Agent definitions

POST /v1/agents, GET /v1/agents, GET, POST (update) and DELETE /v1/agents/{agent_id}. The id is opaque (agent_...). model is required and is passed to the CLI as -m unless it names an adapter. Update is in place; a running session keeps the copy it started with, and deleting a definition never stops a session.

Only mcp tools over an http transport are real. These are 400: other tool types, stdio transports, allowed_tools, request_metadata, required, connection_origin other than environment, a URL with userinfo, a query string or a fragment, multi_agent.enabled, service_tier outside auto|default, text.verbosity other than medium, reasoning.summary, and reasoning.effort on Qwen Code (Codex honors it). MCP header values are write-only: a response shows "***".

Environments, files, templates

Vaults and credentials

/v1/vaults and /v1/vaults/{vault_id}/credentials follow the official shapes. Vaults live in memory (memory_only), count against ABX_AGENTS_MAX_VAULTS (default 100 per owner) and show only id, name, metadata, created_at.

A credential is static_bearer or mcp_oauth with an access token. No response, error message or event contains a value, prefix, length or hash of the secret. These are 400: environment_variable (no egress proxy exists, so the only delivery would hand the secret to code the agent runs), mcp_oauth.refresh (no refresh broker), networking.unrestricted, limited hosts other than the credential's own host, an mcp_server_url that is not https or points at a private host, and a token containing a newline.

Attach vaults with vault_ids on a session (or an environment). At every turn a credential is injected into the session's MCP tool whose URL has the same scheme, host and port, as an Authorization: Bearer header carried in the child environment (Codex env_http_headers, Qwen ${VAR} placeholders in .qwen/settings.json), never in argv. A tool without a matching credential runs unauthenticated; two matches need an explicit credential_id. Rotating a credential applies from the next turn. A credential or vault deleted since the session was created, or an expired expires_at, makes the next event 400 (agents.credential_unavailable, agents.credential_expired) instead of silently running unauthenticated. Every credential object carries secret_in_agent_env.

Artifacts, traces, subagents

Responses

POST /v1/responses runs one turn of a local CLI and returns a response object. model is the id of a stored definition, or it passes through as the CLI model (gpt-5-codex); a runtime name such as qwen means "the CLI's default model" and the object says model_resolved: false. Without a previous_response_id a new CLI session is created in ABX_AGENTS_RESPONSES_WORKSPACE (default /workspace). With one, the turn resumes that conversation, but only from the latest response of it (the CLI session is linear): otherwise 400 agents.previous_response_not_latest.

Models and webhooks

GET /v1/models lists the caller's definitions and the two adapters with owned_by: agent-sandbox-local and x_agent_sandbox.kind / installed. GET /v1/models/{id} is 404 model_not_found for anything not listed and has no side effect.

Webhook endpoints (/v1/webhook_endpoints, create, retrieve, update, list, delete, rotate_secret, test; GET /v1/webhook_event_types) are local records. The whsec_ signing secret is returned once on create and rotate. The server sends agent.session.created, in_progress, idle and failed for the owner's sessions, signed the Standard Webhooks way (webhook-id, webhook-timestamp, webhook-signature), so client.webhooks.unwrap verifies them. agent.session.action_required never occurs and is not accepted. The URL must be https, with no userinfo or query, and not a private or loopback host; redirects are not followed. For local development set ABX_AGENTS_WEBHOOK_ALLOW_HTTP=1 and ABX_AGENTS_ALLOW_PRIVATE_URLS=1. keep_old_secret_active_for_24_hours is 400.

No local capability: 404

Operation Why
POST /v1/responses/input_tokens No tokenizer matches the model the CLI will call, and the real count is only known after a generation.
DELETE /v1/models/{id} There are no fine-tuned models. Delete a definition with DELETE /v1/agents/{agent_id}.
Executor registration and the executor channel (environment.remote_url) They are clients of OpenAI's hosted harness. POST /v1/agents/environments/{id}/register and GET .../{id}/connect answer the same labeled 404 (the real registration path is unpublished).

Each answers 404, type: not_found_error, code: agents.not_implemented_locally, x-should-retry: false, X-Agent-Sandbox-Local: 1.

Clients that only demonstrate the calls, against a server you already started: hosted_codex.py and hosted_qwen.py. local_files_and_session.py uploads one file and opens one session against a local server. It places a local program named codex on PATH and does not read a model API key. See examples/agents/README.md.