Skip to content

ly0/cma

Repository files navigation

CMA-Local — Local Claude Managed Agents Orchestrator

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> │
└────────────────────────────────────────────────────┘

Repo layout

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

Prerequisites

  • Node.js 20+
  • pnpm 10+
  • Docker (Desktop or Engine reachable via the standard socket)
  • ANTHROPIC_API_KEY environment variable for real Claude inference

一键启动 (./scripts/up.sh)

幂等的安装 + 构建 + 启动脚本。第一次运行会跑 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 status

LLM 后端:脚本会按你的 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=1

Grand tour 大型 example

examples/grand-tour.ts端到端 12 phase,把所有组件串起来跑一遍并打印 [✓]/[~]/[✗]

  1. memory store + 3 条偏好(含矛盾 + 已弃用)
  2. agent + system prompt + 版本递增
  3. environment (cloud / unrestricted)
  4. session 挂载 memory store (read_write)
  5. user.message → SSE 流 → status_idle(统计 event-type 频次)
  6. workspace 文件树 + Monaco 风格的文件内容读取(/v1/sessions/:id/files
  7. files API 上传 + scope 过滤
  8. memory_versions 版本历史
  9. user.define_outcome + 等 grader 评估(真 LLM 才跑)
  10. dreams:input store → output store 端到端
  11. dream cancel + archive 状态机
  12. 全资源 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 完成。

Build & start

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:8787

LLM backend

The 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=1

Then 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.

Quickstart (using @cma/client)

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.

Dashboard UI

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.

Dev (auto-reload, vite + server in parallel)

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.

Production (single process, same origin)

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.

What it covers (full Console-D parity)

  • 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 in requires_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.

Auth

  • 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 in localStorage and added to every request as x-api-key.

Theme

Dark by default (matches Anthropic console). Toggle in the topbar; choice persists in localStorage.

E2E test

pnpm -C packages/web exec playwright install chromium    # one-time
pnpm -C packages/web exec playwright test

Spins 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.

Quickstart (cma CLI)

The CLI mirrors ant beta:* ergonomics, including YAML stdin via heredoc and YAML/JSON flow-style inline values.

Inline flags

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}}'

YAML heredoc (matches ant beta:* <<YAML ... YAML)

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.
YAML

YAML files

pnpm -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.yaml

A full template set lives under examples/templates/. Every template has a 1:1 counterpart in the official Anthropic Managed Agents docs.

REST API

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

Event types emitted

  • agent.message (text content blocks)
  • agent.thinking
  • agent.tool_use / agent.tool_result
  • agent.mcp_tool_use / agent.mcp_tool_result
  • agent.custom_tool_use
  • agent.thread_context_compacted
  • session.status_running / status_idle (with stop_reason) / status_rescheduled / status_terminated
  • session.error
  • span.model_request_start / span.model_request_end

session.status_idle.stop_reason.typeend_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.

Configuration map (Agent → Agent SDK)

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_confirmationuser.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

Harness internals provided by @anthropic-ai/claude-agent-sdk

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

Environment variables

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.

Networking

  • unrestricted (default) — container joins the default Docker bridge.
  • limited + allowed_hosts — container joins user-defined network cma-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_managers are accepted and stored.

Pre-installed packages

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.

Development

pnpm install
pnpm -C packages/core build
pnpm -r run typecheck
pnpm vitest run

What's not implemented in v0

These remain on the roadmap; the schema accepts them but they are no-ops:

  • skills, vault_ids, callable_agents (multi-agent), outcomes, define_outcome events
  • 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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors