Claude Managed Agents compatibility
OpenMA implements a Claude Managed Agents (CMA)–compatible control-plane API and the Environment Worker protocol boundary on top of it. It is not Anthropic’s hosted CMA product: “compatible” here means protocol and wire-shape compatibility, not feature parity with the hosted service.
Two properties keep that claim honest:
- Request and response contracts in
packages/managed-agents-api/src/contracts/are Zod schemas checked withsatisfiesagainst the official@anthropic-ai/sdk@0.120.0beta/*resource types. When the SDK’s Managed Agents surface changes, the contracts stop compiling until OpenMA catches up. - Every endpoint below exists in
packages/managed-agents-api/src/routes/and is mounted on both hosts — the Cloudflare Worker (apps/main) and the Node server (apps/main-node) — under/v1. OpenMA-specific extensions live under/v1/oma/*so the compatible surface stays clean.
The Anthropic SDK sends the required anthropic-beta flags automatically. When
calling endpoints by hand, send the flag listed in the matrix (for example
anthropic-beta: managed-agents-2026-04-01) or the route rejects the request.
API surface matrix
Section titled “API surface matrix”| CMA surface | OpenMA | Where in code |
|---|---|---|
| Agents CRUD + versioning | Implemented — create, list, get, update, delete, archive, versions list + get | routes/agents.ts → /v1/agents |
| Sessions | Implemented — create, list, get, update, delete, archive | routes/sessions.ts → /v1/sessions |
| Session events | Implemented — POST /events, GET /events (JSON), GET /events/stream (SSE) | routes/session-events.ts |
| Session resources | Implemented — attach, list, update, remove file, github_repository, memory_store | routes/session-resources.ts |
| Session threads | Implemented — list, get, archive; thread events + stream live on the same router | routes/session-threads.ts (/:sessionId/threads/:threadId/events, /stream) |
| Session outputs | Implemented (OpenMA extension) — GET /outputs, GET /outputs/:filename | routes/session-outputs.ts |
| Environments | Implemented — CRUD + archive | routes/environments.ts → /v1/environments |
| Environment Work (worker API) | Implemented — list, work/poll, get, update, ack, heartbeat, stop, work/stats | routes/environment-work.ts → /v1/environments/:id/work |
| Memory stores | Implemented — store CRUD + archive, memories CRUD with CAS precondition, memory_versions history + redact | routes/memory-stores.ts, memories.ts, memory-versions.ts → /v1/memory_stores (anthropic-beta: agent-memory-2026-07-22) |
| Skills | Implemented — skill CRUD, versions, version content download | routes/skills.ts, skill-versions.ts → /v1/skills (anthropic-beta: skills-2025-10-02) |
| Files | Implemented — upload, retrieve, content, delete | routes/files.ts → /v1/files (anthropic-beta: files-api-2025-04-14) |
| Vaults & credentials | Implemented — vault CRUD + archive, credential CRUD + archive, mcp_oauth_validate | routes/vaults.ts, credentials.ts → /v1/vaults |
| Models | Implemented (read-only) — list + retrieve | routes/models.ts → /v1/models; projected from tenant Model Cards |
| Deployments & deployment runs | Implemented — deployment CRUD, run, run list/get | routes/deployments.ts, deployment-runs.ts → /v1/deployments, /v1/deployment_runs |
| Dreams | Implemented — anthropic-beta: dreaming-2026-04-21 | routes/dreams.ts → /v1/dreams |
| MCP tunnels | Implemented — tunnel CRUD, certificates, reveal_token, rotate_token | routes/tunnels.ts, tunnel-certificates.ts → /v1/tunnels (anthropic-beta: mcp-tunnels-2026-06-22) |
| User profiles | Implemented — anthropic-beta: user-profiles-2026-08-18 | routes/user-profiles.ts → /v1/user_profiles |
| MCP servers | Implemented — as the mcp_servers field on Agent config (URL/HTTP/SSE/stdio); there is no standalone MCP-server resource in the SDK surface | routes/agents.ts, contracts/agent-input-components.ts |
| Callable agents | Implemented — callable_agents on Agent config derives call_agent_* tools | routes/agents.ts, contracts/agents.ts; OMA integrations (GitHub/Slack/Linear/Feishu) are an extension under /v1/oma/integrations/* |
/openai/v1 adapter | Implemented — OpenAI Agents API projection over the same native sessions | packages/openai-agents-api, openai-agents-compat; mounted at /openai in apps/main/src/index.ts |
Sendable session events
Section titled “Sendable session events”POST /v1/sessions/:id/events accepts the official sendable union
(sendableEventSchema in contracts/session-event-inputs.ts):
| Event | Direction |
|---|---|
user.message | User turn (text or file blocks). Mid-run: steers into the current turn at the next model/tool boundary (not a next-turn outbox). |
user.interrupt | Interrupt a running turn, optionally scoped to session_thread_id. After interrupt, a following user.message starts a new turn. |
user.tool_confirmation | allow/deny a permission-gated tool call |
user.custom_tool_result | Result for a custom tool that paused the session |
user.tool_result | Result for a client-side tool call |
user.define_outcome | Outcome rubric for evaluation (max_iterations ≤ 20) |
system.message | System-injected text blocks |
There is no separate POST .../pending/:id/steer extension. Ordinary mid-run
user.message is steer. The DO-local pending_events table has been
retired: queued mid-turn inputs live as unstamped rows in the append-only
events log (processed_at null until consumed). See
docs/implementation/cma-steer-coherent-turn.md.
Session semantics that matter for clients
Section titled “Session semantics that matter for clients”These behaviors are part of the CMA wire contract OpenMA follows. Details also live under API Endpoints → Session budgets.
requires_action and budget_reached are settle-only
Section titled “requires_action and budget_reached are settle-only”While the session (or thread) is idle with
stop_reason: {"type": "requires_action"} (outstanding event_ids) or
{"type": "budget_reached"}, only settle events are accepted:
user.tool_confirmationuser.tool_resultuser.custom_tool_resultuser.interrupt(accepted; ignored for budget pauses)
A user.message in that state returns 400 invalid_request_error. It is
not deferred or queued. requires_action outranks budget_reached when both
could apply.
Session budgets
Section titled “Session budgets”A session may carry
{"budget": {"type": "limit", "max_list_cost": {"amount": "500", "currency": "USD"}}}
(amount in whole USD cents, integer string). The budget is active only when the
session has max_list_cost and its model card has pricing. Crossing the
cap emits idle/usage with stop_reason: budget_reached. PATCH rules
(create-only add, raise-only max_list_cost, one-way null clear) are
documented on the API reference page.
Retry / exhausted hold
Section titled “Retry / exhausted hold”While retry_status: retrying / session.status_rescheduled, mid-turn inputs
are held (not flushed, not started as a new turn). On
retries_exhausted, unprocessed mid-turn rows are retained and advance with
the next round of events (deliberate divergence from SDK wording that says
queued inputs are flushed).
Worker protocol boundary
Section titled “Worker protocol boundary”The sandbox/worker boundary follows the invariants in
docs/claude-managed-agents-sandbox-provider-audit.md:
- An Environment is a Work queue. A worker polls
GET /v1/environments/{id}/work/pollcontinuously, or is woken by a signed webhook (session.status_run_started, Standard Webhooks signature, at most three attempts). The webhook is a hint only — Work is claimed by the poll, never by webhook delivery. - One credential path. Environment-scoped
oma_env_service keys are Bearer-only, valid only on that Environment’s/work/**routes, rotatable by create/revoke, and never authorize Session resources. Every claimed Work carries a separate claim-boundsessions_token(delivered insideANTHROPIC_WORK_SECRET); a reclaim rotates it and fences the old owner. - ACK/heartbeat/stop ownership is explicit. Three embedded activation
strategies exist:
claim_then_acquire,poll_unacked_then_dispatch, andacquire_then_session_poll. - The event log is the source of truth. Work and webhook payloads carry
only the Session ID — the worker then fetches the claimed Session with its
sessions_tokenand replays the append-only log. - Credentials are designed to stay out of the sandbox. Enforced
transparent HTTPS interception exists only on the
cloudflaremanaged-runtime driver; everywhere else the boundary keeps secrets out of the session but does not claim network enforcement — theoma-vaultenv proxy on Node/Docker is advisory, and other drivers reportunsupported. - External workers are a first-class lane.
external_workermode exposes the unchanged Work API and starts no OpenMA sandbox.
Driver lanes
Section titled “Driver lanes”Per MANAGED_RUNTIME_PROVIDER_CONFORMANCE in
packages/managed-runtime-host/src/provider-conformance.ts
every managed-runtime driver supports the ama_worker lane. Most also support
openma_supervised:
| Provider driver | ama_worker | openma_supervised |
|---|---|---|
node-docker | yes | yes |
cloudflare | yes | yes |
cloudflare-bridge | yes | no |
e2b | yes | yes |
daytona | yes | yes |
litebox | yes | yes |
boxrun | yes | yes |
blaxel | yes | yes |
sprites | yes | yes |
vercel | yes | no |
modal | yes | yes |
superserve | yes | yes |
OpenSandbox ships as packages/sandbox-adapter-opensandbox and is not yet a
row in that managed-runtime conformance table. Per-driver lifecycle/workspace
capabilities live in the same file — it is the machine-readable source of
truth; the Managed Runtime Host page
narrates it.
What is not implemented
Section titled “What is not implemented”- Warm pools — planned. No portable warm-pool surface exists. The one real
binding is the GKE dispatch Port’s
warmPoolfield (packages/environment-dispatch-gke). fork— planned. There is no session/sandbox fork surface.- General egress allowlist proxy — planned (tracked in
#297).
Environment.config.networkingtype: "limited"withallowed_hostsis enforced only inside theweb_fetchtool.bash/curl,web_search, and MCP calls are not restricted by it. - Credential egress is enforced on one driver. Only
managed-runtime-cloudflaredeclares enforced live HTTPS interception. Every other driver reportsunsupportedunless the operator injects a certified implementation viaoptions.credentialEgress. - No process-memory checkpoint/restore.
runtimeCheckpointsis[]for every driver in the conformance table. Provider pause/suspend primitives appear asretained_runtimeworkspace strategies; they are not portable OpenMA checkpoints. - The stateless Messages API.
/v1/messagesis the core Anthropic API, not a Managed Agents surface, and OpenMA does not implement it.
Known deltas vs hosted CMA
Section titled “Known deltas vs hosted CMA”- Beta flags are enforced per resource family. Requests missing the
documented
anthropic-betavalue get a 400 (requireBetainpackages/managed-agents-api/src/beta.ts). - Session list filters.
metadata_key/metadata_valuequery params are rejected with a 400 — list filters coveragent_id,agent_version,created_atbounds,deployment_id,memory_store_id,statuses[],order, andinclude_archived. - Exhausted mid-turn retain. On
retries_exhausted, OpenMA retains unprocessed mid-turn inputs instead of flushing them (see steer doc above). - OpenAI adapter limits. The
/openai/v1adapter supports sandbox-free sessions (environment: { "type": "none" }) but does not accept advanced environment configuration, plugins, or runtime file operations. Hosted OpenAI dynamic sub-agent control is unavailable (the Node runtime provides it). Details:docs/openai-agents-compatibility-status.md. - OMA extensions share the host.
/v1/oma/*(API keys,me, tenants, integrations, Matter/Loops, evals, stats, MCP/inference proxies) and convenience routes likePOST /v1/sessions/:id/messagesare OpenMA additions, not CMA surface — see API Endpoints.