A local, open-source clone of the architecture documented at https://platform.claude.com/docs/en/managed-agents/overview.
It gives you the four core concepts — Agent, Environment, Session,
Event — exposed over a REST API that mirrors api.anthropic.com, with the
agent loop powered by @anthropic-ai/claude-agent-sdk running inside a
per-session Docker container.
┌── CMA Control Plane (host) ───────────────────────┐
│ Hono + SQLite + dockerode + SSE event bus │
└──────────────┬────────────────────────────────────┘
│ HTTP per session
▼
┌── cma-runtime container (per session) ────────────┐
│ runtime-sidecar: │
│ POST /run, /input, /interrupt, /tool-decision │
│ GET /events (SSE of SDKMessage) │
│ workspace bind-mounted from data/sessions/<id> │
└────────────────────────────────────────────────────┘
packages/
core/ zod schemas + ids + tool-name mapping (shared types)
server/ control plane: REST + SSE + dockerode orchestrator
runtime-sidecar/ in-container daemon: query() wrapper + SSE
client/ @cma/client TypeScript SDK (mirrors @anthropic-ai/sdk shape)
cli/ `cma` command (commander)
docker/runtime/ Dockerfile for the sandbox image
examples/ quickstart + interrupt + custom-tool demos
- Node.js 20+
- pnpm 10+
- Docker (Desktop or Engine reachable via the standard socket)
ANTHROPIC_API_KEYenvironment variable for real Claude inference
幂等的安装 + 构建 + 启动脚本。第一次运行会跑 pnpm install、构建
docker 运行时镜像、构建所有 6 个包;后续启动会跳过已就绪的步骤。
# 1) 默认: setup + 前台启 server (REST + /ui dashboard 都 ready)
./scripts/up.sh
# → http://127.0.0.1:8787 REST API
# → http://127.0.0.1:8787/ui Dashboard
# 2) 仅做安装 / 构建,不启 server
./scripts/up.sh setup
# 3) 开发模式 — vite 热重载 + server 热重载
./scripts/up.sh dev
# → http://127.0.0.1:5173/ui/ (vite proxy /v1 → server)
# 4) 端到端 demo: 自动后台起 server → 跑 examples/grand-tour.ts → 关 server
./scripts/up.sh demo
# 也可以走 pnpm: pnpm demo
# 5) 停 / 状态
./scripts/up.sh stop
./scripts/up.sh statusLLM 后端:脚本会按你的 env 自动选模式。
# 真实 Anthropic
export ANTHROPIC_API_KEY=sk-ant-...
# Anthropic-compatible 代理 (DeepSeek 等)
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
export ANTHROPIC_AUTH_TOKEN=sk-...
export ANTHROPIC_MODEL='deepseek-v4-pro[1m]'
# 没真实 LLM 时 — 走 mock supervisor (容器内脚本化输出)
export CMA_MOCK_LLM=1
# 完全无 docker / CI / 仅测 REST
export CMA_NOOP_ORCHESTRATOR=1examples/grand-tour.ts — 端到端 12 phase,把所有组件串起来跑一遍并打印 [✓]/[~]/[✗]:
- memory store + 3 条偏好(含矛盾 + 已弃用)
- agent + system prompt + 版本递增
- environment (cloud / unrestricted)
- session 挂载 memory store (read_write)
user.message→ SSE 流 →status_idle(统计 event-type 频次)- workspace 文件树 + Monaco 风格的文件内容读取(
/v1/sessions/:id/files) - files API 上传 + scope 过滤
- memory_versions 版本历史
user.define_outcome+ 等 grader 评估(真 LLM 才跑)- dreams:input store → output store 端到端
- dream cancel + archive 状态机
- 全资源 archive
# 通过 demo 一键: server + grand-tour 都自动起
pnpm demo
# 或只跑 example (假定 server 已经在 8787)
pnpm tour样例输出 (mock 模式):
=== CMA-Local Grand Tour ===
base URL: http://127.0.0.1:8787
── 1. Memory store + seed memories ──
[✓] memory_store create — memstore_…
[✓] seeded memories — 3 entries
…
── 总结 ── 16 通过 · 4 跳过 · 0 失败 (共 20 步)
grand-tour 完成。
pnpm install
pnpm -C packages/core build # core types built once
docker build -f docker/runtime/Dockerfile -t cma-runtime:dev .
pnpm -C packages/server dev
# → control plane on http://127.0.0.1:8787The control plane forwards a standard set of Anthropic env vars to the sandbox container. Three backends work without any code change:
# 1) Real Anthropic (login or API key)
export ANTHROPIC_API_KEY=sk-ant-...
# Optional override: ANTHROPIC_AUTH_TOKEN=... (Claude Code OAuth bearer)
# 2) Anthropic-compatible proxy (e.g. DeepSeek's anthropic endpoint)
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
export ANTHROPIC_AUTH_TOKEN=sk-...
export ANTHROPIC_MODEL='deepseek-v4-pro[1m]'
export ANTHROPIC_SMALL_FAST_MODEL=deepseek-v4-flash
# 3) Scripted mock (no network, for CI / wiring tests)
export CMA_MOCK_LLM=1Then pnpm -C packages/server dev. Every session inherits these vars; the
container picks the matching glibc / musl Claude Code binary at startup
(docker/runtime/entrypoint.sh) and runs it as a non-root user so
--dangerously-skip-permissions succeeds.
import { CmaClient } from "@cma/client";
const client = new CmaClient({ baseURL: "http://127.0.0.1:8787" });
const agent = await client.beta.agents.create({
name: "Coding Assistant",
model: "claude-opus-4-7",
system: "You are a helpful coding assistant.",
tools: [{ type: "agent_toolset_20260401" }],
});
const env = await client.beta.environments.create({
name: "quickstart",
config: { type: "cloud", networking: { type: "unrestricted" } },
});
const session = await client.beta.sessions.create({
agent: agent.id,
environment_id: env.id,
});
const stream = client.beta.sessions.events.stream(session.id);
await client.beta.sessions.events.send(session.id, {
events: [{
type: "user.message",
content: [{ type: "text", text: "Make a fibonacci.txt" }],
}],
});
for await (const ev of stream) {
if (ev.type === "agent.message") {
for (const b of ev.content) process.stdout.write(b.text);
} else if (ev.type === "session.status_idle") break;
}End-to-end script: pnpm -C examples exec tsx quickstart.ts.
A React + Vite single-page dashboard that mirrors the Anthropic Managed
Agents console. Lives in packages/web and is served by the Hono control
plane at /ui once built.
pnpm dev:all
# → control plane http://127.0.0.1:8787
# → dashboard http://127.0.0.1:5173/ui/ (proxies /v1 /healthz to 8787)Both processes auto-restart on edits. Use pnpm dev:server / pnpm dev:web
if you want them separate.
pnpm -r build # builds core/client/server/runtime-sidecar/cli/web
pnpm -C packages/server start
# → http://127.0.0.1:8787/ui (SPA history fallback included)The Hono server only mounts /ui/* when packages/web/dist/index.html
exists — if you skip the web build the rest of the API is unaffected.
- Sessions: list with live status pill (idle / running /
requires_action / terminated), 4-tab detail (Timeline / Workspace /
Resources / Outcomes); composer for
user.message+ interrupt +define_outcome+ tool result/confirmation reply when the session is inrequires_action. - Workspace tab: tree view of the bind-mounted host workspace + Monaco preview (text only, ≤1MB; binaries are flagged).
- Memory stores: tree view (
/path/style), Monaco editor with sha256 precondition, version history drawer with redact. - Files: uploaded files + auto-grouped session deliverables, drag-style upload via the topbar action.
- Agents / Environments: list, version history (agents), create wizards.
- Dreams: list with cursor pagination + include_archived toggle, create wizard (input store + sessions multi-select + model + instructions), terminal-state archive / cancel actions.
- If the server is started without
CMA_API_KEY, the UI is open. No login page is shown. - If the server requires a key (
CMA_API_KEY=...), the first 401 redirects the user to/ui/login. The key is stored inlocalStorageand added to every request asx-api-key.
Dark by default (matches Anthropic console). Toggle in the topbar; choice
persists in localStorage.
pnpm -C packages/web exec playwright install chromium # one-time
pnpm -C packages/web exec playwright testSpins both servers automatically (server runs with
CMA_NOOP_ORCHESTRATOR=1 so docker is not required) and exercises a
happy-path: create agent → environment → memory store, navigate every
section, assert tabs and headings.
The CLI mirrors ant beta:* ergonomics, including YAML stdin via heredoc
and YAML/JSON flow-style inline values.
pnpm -C packages/cli dev agents create \
--name dev --model claude-opus-4-7 \
--tool '{type: agent_toolset_20260401}'
pnpm -C packages/cli dev environments create \
--name local \
--config '{type: cloud, networking: {type: unrestricted}}'pnpm -C packages/cli dev agents create <<'YAML'
name: Coding Assistant
model: claude-opus-4-7
system: You are a helpful coding assistant.
tools:
- type: agent_toolset_20260401
configs:
- name: web_fetch
enabled: false
YAML
pnpm -C packages/cli dev sessions create <<YAML
agent: $AGENT_ID
environment_id: $ENVIRONMENT_ID
YAML
pnpm -C packages/cli dev events send "$SESSION_ID" <<'YAML'
events:
- type: user.message
content:
- type: text
text: List the files in the working directory.
YAMLpnpm -C packages/cli dev agents create -f examples/templates/agent-permission-confirm.yaml
pnpm -C packages/cli dev environments create -f examples/templates/environment-data-analysis.yaml
pnpm -C packages/cli dev sessions create -f examples/templates/session-pinned.yamlA full template set lives under examples/templates/.
Every template has a 1:1 counterpart in the official Anthropic Managed Agents
docs.
All endpoints require the beta header anthropic-beta: managed-agents-2026-04-01.
| Method | Path | Notes |
|---|---|---|
POST |
/v1/agents |
create agent |
GET |
/v1/agents |
list |
GET |
/v1/agents/{id} |
latest version |
POST |
/v1/agents/{id} |
update → bumps version |
POST |
/v1/agents/{id}/archive |
archive |
GET |
/v1/agents/{id}/versions |
history |
POST |
/v1/environments |
|
GET |
/v1/environments |
|
GET |
/v1/environments/{id} |
|
POST |
/v1/environments/{id}/archive |
|
DELETE |
/v1/environments/{id} |
|
POST |
/v1/sessions |
|
GET |
/v1/sessions |
|
GET |
/v1/sessions/{id} |
|
POST |
/v1/sessions/{id}/archive |
|
DELETE |
/v1/sessions/{id} |
rejects running |
POST |
/v1/sessions/{id}/events |
accepts user.message, user.interrupt, user.custom_tool_result, user.tool_confirmation |
GET |
/v1/sessions/{id}/events |
history with types[], after, limit |
GET |
/v1/sessions/{id}/events/stream |
SSE; id, event, data lines per official spec |
agent.message(text content blocks)agent.thinkingagent.tool_use/agent.tool_resultagent.mcp_tool_use/agent.mcp_tool_resultagent.custom_tool_useagent.thread_context_compactedsession.status_running/status_idle(withstop_reason) /status_rescheduled/status_terminatedsession.errorspan.model_request_start/span.model_request_end
session.status_idle.stop_reason.type ∈ end_turn | max_turns |
interrupted | requires_action | error. When requires_action, the
event carries the event_ids array referencing pending
agent.custom_tool_use / agent.tool_use events.
The control plane derives runtime arguments for query() from the stored
agent config. See packages/server/src/orchestrator/agent-spec.ts.
| Managed-Agents field | Translated to |
|---|---|
model.id |
query({ model }) |
system |
query({ systemPrompt }) |
tools[].type=agent_toolset_20260401 |
allowedTools / disallowedTools (per-tool enabled) |
tools[].type=agent_toolset_20260401 default_config.permission_policy |
query({ canUseTool }) bridge — requires_confirmation ↔ user.tool_confirmation |
tools[].type=agent_toolset_20260401 configs[].permission_policy |
per-tool override, same bridge |
tools[].type=custom |
query({ mcpServers: { cma_custom: <SdkMcpServer> } }) whose handlers emit agent.custom_tool_use and await user.custom_tool_result |
mcp_servers[] (stdio / http / sse) |
query({ mcpServers }) |
Tool-name mapping (packages/core/src/tool-mapping.ts):
| CMA name | Agent SDK name |
|---|---|
bash / read / write / edit / glob / grep |
Bash / Read / Write / Edit / Glob / Grep |
web_fetch / web_search |
WebFetch / WebSearch |
These are inherited by every session — we don't reinvent them:
| Harness mechanism | Provided by |
|---|---|
| Multi-turn agent loop (assistant ↔ tool_use ↔ tool_result) | SDK query() |
Built-in toolset (Bash/Read/Write/Edit/Glob/Grep/WebFetch/WebSearch) |
SDK |
| Persistent bash session across turns | SDK |
| Prompt caching (5 min TTL) | SDK + Anthropic Messages API |
Context compaction (agent.thread_context_compacted) |
SDK |
| Streaming SDKMessage output | SDK |
query.interrupt() |
SDK |
| Token usage / cost reporting | SDK result.usage → CMA span.model_request_end + session.usage |
Custom tools via createSdkMcpServer + tool() |
SDK |
canUseTool callback for permission policies |
SDK |
| Var | Default | Description |
|---|---|---|
CMA_PORT |
8787 |
Control-plane HTTP port |
CMA_HOST |
127.0.0.1 |
Bind address |
CMA_DB_PATH |
./data/cma.db |
SQLite file |
CMA_DATA_DIR |
./data |
Per-session workspace root |
CMA_RUNTIME_IMAGE |
cma-runtime:dev |
Image used to spawn sandboxes |
ANTHROPIC_API_KEY |
— | Forwarded into the sandbox as-is. Used as x-api-key. |
ANTHROPIC_AUTH_TOKEN |
— | Forwarded into the sandbox. OAuth bearer token; takes precedence over ANTHROPIC_API_KEY. |
ANTHROPIC_BASE_URL |
— | Forwarded into the sandbox. Override for compatible proxies (e.g. https://api.deepseek.com/anthropic). |
ANTHROPIC_MODEL |
— | Forwarded; default model when an agent doesn't pin one. |
ANTHROPIC_SMALL_FAST_MODEL |
— | Forwarded; cheaper model for compaction etc. |
CMA_API_KEY |
unset | If set, clients must present this in x-api-key. Decoupled from Anthropic creds. |
CMA_MOCK_LLM |
unset | When 1, uses scripted mock supervisor inside the container. |
unrestricted(default) — container joins the default Docker bridge.limited+allowed_hosts— container joins user-defined networkcma-limited. Note: strict outbound enforcement (DNS allow-list, iptables) is intentionally left as best-effort in this release; the network is isolated logically but does not yet hard-block egress.allowed_hosts,allow_mcp_servers,allow_package_managersare accepted and stored.
environment.config.packages accepts arrays for apt, pip, npm, go,
cargo, gem. They run sequentially via docker exec after the container
becomes healthy and before the agent starts.
pnpm install
pnpm -C packages/core build
pnpm -r run typecheck
pnpm vitest runThese remain on the roadmap; the schema accepts them but they are no-ops:
skills,vault_ids,callable_agents(multi-agent),outcomes,define_outcomeevents- Strict
limited-network egress filtering (DNS allow-list, iptables) — the network is logically isolated but does not yet hard-block egress - A management UI (use the CLI / SQLite directly)
- Container checkpointing for inactivity windows
- Hook callbacks (PreToolUse / PostToolUse / SessionStart) — not exposed by Managed-Agents docs, but available via the underlying SDK if needed