Agent Sandbox Registry

python-sdk

Decision: Hosted Agents slice — Codex and Qwen Code

无中文版

.agents/notes/proposed/feature/2026-10-12-hosted-codex-qwen.md

Decision: Hosted Agents slice — Codex and Qwen Code

Status: proposed

Problem

The server already has an agent runtime, Codex and Qwen Code adapters, and OpenAI-shaped session objects, but nothing registered HTTP routes. Callers could not create a session or stream a turn. A first slice exposed that runtime without vaults, the Responses API, or the other adapters. The maintainer then asked for every operation that docs/en/design/server-sdk-v2/reviews/ops-can-do.md marks BUILD-LOCAL-SEMANTICS (66 operations) for those two adapters, with the label contract of ops-cannot.md, and for the three CANNOT operations to stay 404.

Decision

Register routes_agents.py on CapabilityGroup.AGENTS (still off unless ABX_SERVER_ENABLED_GROUPS includes agents). routes_agents.py holds registration and the session/event/item/turn handlers; the other handlers live in agents/api_*.py and are registered only in routes_agents.py.

The body may select codex or qwen-code (aliases qwen, qwencode, qwen_code). Otherwise the server uses config agents.default (ABX_AGENTS_DEFAULT, or default / agents.default in ABX_AGENTS_CONFIG). If neither is set, the response is 400 agents.adapter_required. Any other adapter name is 404 not_found_error with x-should-retry: false.

The maintainer confirmed on 2026-10-12: do not integrate the Claude Code, Cursor, or Qoder adapters in this round. Those names are other CLIs behind the same Agents API (claude, cursor-agent, qodercli). They are not a separate product. The adapter modules may stay in the tree, but no new routes, docs, or examples should mount them until the maintainer says to.

What the second round added

Label contract (agents/api_common.py): every response carries X-Agent-Sandbox-Local: 1 and every object x_agent_sandbox: {local, compute, differences} with compute in local_process | local_state | none and stable codes from one table (DIFFERENCES). The already shipped session, turn, item and runtime objects now carry it too. A foreign id and an unknown id answer identically; ownership is the policy identity id, or one local owner without a policy.

Operations Where Notes
A-01..A-05 definitions api_definitions.py Opaque agent_ ids. Only mcp over http. 400 for other tool types, stdio, multi_agent.enabled, service_tier outside auto\|default, text.verbosity, reasoning.summary, reasoning.effort on Qwen. Session create takes agent_id and validates an inline agent the same way. Each session gets its own spec name, so sessions no longer overwrite each other's spec.
A-06..A-10, O-03 environments, files, blobs api_environments.py, workspace_files.py Directory binding, files through O_NOFOLLOW directory fds, no overwrite (400 agents.file_exists), agent-config locations refused, size cap, listing of regular files with an opaque page token. POST /v1/files keeps user_data blobs in memory.
A-11..A-15 templates api_environments.py {name, env, files} only. Hosted-only fields are 400 on create and update, so environment_template_id can be applied.
A-18 session update api_sessions.py metadata only; agent, vault_ids, environment and unknown keys are 400.
A-27..A-31 artifacts, traces artifacts.py, api_sessions.py A turn hook in AgentRuntime copies the paths the CLI reported (Codex file_change, Qwen write_file/replace/edit) at completion. Content is served as an attachment. Traces are empty unless ABX_AGENTS_TRACES=1, then timing-only.
A-32..A-37 subagents subagents.py, api_sessions.py, parsers Codex collab_tool_call.receiver_thread_ids and the session's own rollout; child items and turns from the child rollout under CODEX_HOME. Qwen parent_tool_use_id events (previously dropped, and child tool calls leaked into the parent items) are collected per subagent. Unknown ids are 404.
A-38..A-47 vaults, credentials api_vaults.py Memory-only, owner-bound, write-only. Injected per turn as an Authorization header through the existing env carrier. A deleted or expired credential fails the next event with 400.
R-01..R-05, R-07 responses api_responses.py A labeled one-shot CLI turn. Output items are what the CLI emitted (native tools are tool_call, never hosted tool shapes). previous_response_id only from the latest. Compact is Qwen /compress; Codex is 400.
R-08 responses.connect api_responses.py The client-event engine (ResponsesSocket: response.create, response.steer queued) is implemented and tested. The upgrade cannot be done in a route handler: GET /v1/responses answers 400 agents.websocket_upgrade_required. Needs a transport change in app.py.
O-01, O-02 models api_other.py Definitions and the two adapters, owned_by: agent-sandbox-local, 404 model_not_found otherwise, no side effect.
O-06..O-14 webhooks api_other.py, webhooks.py Local endpoints, whsec_ secret returned once, Standard Webhooks signing, session created/in_progress/idle/failed delivery. https only, no private hosts, no redirects.

CANNOT, labeled 404 not_found_error (agents.not_implemented_locally, x-should-retry: false): R-06 POST /v1/responses/input_tokens, DELETE /v1/models/{id}, and O-04/O-05 (registered on guessed paths POST /v1/agents/environments/{id}/register and GET .../{id}/connect, because the real paths are unpublished).

Variant refusals stay 400 with param (ops-can-do section 3): hosted-only environment and template fields, environment.type=openai_hosted with content, credential environment_variable, mcp_oauth.refresh, networking.unrestricted and foreign limited hosts, tools other than mcp, multi_agent.enabled, service_tier, input.tool_result, images and files, Responses conversation, prompt, text.format, include, non-latest previous_response_id, and a file_id this server did not mint.

Known gaps, outside this slice's files

API Design

Alternatives considered

Dependencies

Reuses AgentRuntime, adapters/codex.py, adapters/qwen_code.py, and wire_sessions.py. AgentRuntime gained turn_hooks and per-session subagents; the Codex and stream-json parsers gained a subagent event kind. No change to the files or commands route tables, and no edit to server/core/**, policy.py, guard.py, router.py, app.py or cli/.

Test Strategy

server-sdk/tests/test_server/test_routes_agents.py (first round) and test_routes_agents_local.py put fake codex and qwen scripts on PATH. The local suite drives handlers through the default route table (so registration order is real) plus a live-server sweep. It covers: the label and header on every group; the four CANNOT routes; other adapters staying 404; refused variants with param; secret canaries absent from every response, SSE frame, error, argv, and present only in the child environment; symlink, .git, .. and size refusals; artifact immutability and snapshot-only delete; unreported and symlinked paths never published; subagent ids from fake JSONL and fake rollouts, an unrelated rollout never listed, hostile ids never reaching the filesystem; ownership (a foreign id equals an unknown id); Responses create, retrieve, delete, cancel, stream, idempotency, store: false, native tools never shaped as hosted tools; compact on both adapters; signed webhook delivery verified with the Standard Webhooks algorithm.

Acceptance criteria