Files
clawmates/deploy/clawmates-runtime/agent.config.example.toml
T
Omar SobhandClaude Opus 4.8 cbfa0ff24f feat: agent-to-agent platform on ZeroClaw 0.8.2 — rooms, delegation, A2A ingress
Builds on the v0.8.2 runtime. Four workstreams, all behind the §15 MCP door:

- Group rooms (Phase 1): migration 0026; N-way threads repo with a DM/room
  count-guard; chat.send {room} + room.create/invite/leave tools; RoomMessage
  -> room.message SSE; /api/claw-chat/rooms* APIs; Observer room badge.
- Per-claw door identity: door caller_agent resolves the X-ZeroClaw-Agent
  header (set by the fork) to the specific claw, falling back to roster[0].
- Gated delegation bridge (Phase 3): clawmates__delegate door tool drives a
  sibling via the existing /ws/chat ZeroClawDriveExecutor (not A2A); self-deny,
  per-workspace hourly budget, audit trail, untrusted-banner result. Native
  in-daemon delegation stays off (it would bypass the door).
- A2A tenant ingress (Phase 2): migration 0027 (workspace_a2a + a2a_tokens);
  runtime_provision enable_a2a_server/publish_claw; routes/a2a.rs tenant-aware
  proxy (per-workspace tokens, injected internal bearer, daemon stays internal,
  cards URL-rewritten to the cm-api edge); a2a.invoked taxonomy.

Tests: cm-db room repos, cm-runtime chat tools, door units. sqlx cache updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-28 16:11:01 -07:00

211 lines
10 KiB
TOML

# Phase-1 runtime config for a tenant: one provider model + a multi-agent
# role-cast. ONE container hosts the whole cast; Clawmates' cm-orchestrator owns
# the topology graph and drives a specific role by alias over /ws/chat?agent=...
#
# Secrets are NOT committed — the API key is injected at runtime via env
# ZEROCLAW_providers__models__groq__default__api_key=<key>
# (double-underscore = config nesting). Swap groq→anthropic for prod.
# Mark onboarding complete (headless equivalent of the browser Quickstart) so
# the agents will answer; otherwise /ws/chat returns NEEDS_ONBOARDING.
[onboard_state]
quickstart_completed = true
# Reasoning backend = the local Claude Code CLI (native `claude_cli` provider).
# The daemon spawns `claude -p --output-format json` per turn, authed by
# CLAUDE_CODE_OAUTH_TOKEN (injected at runtime) — i.e. the Claude subscription,
# no per-minute API TPM ceiling. Leave model unset to use the subscription
# default; set e.g. model = "haiku" to force a model.
[providers.models.claude_cli.default]
model = "haiku"
# claude_cli ACTING through the §15 door (subscription agent that ACTS, not just
# reasons). claude -p has no tool API ZeroClaw can drive, so instead we let
# Claude Code's OWN MCP client reach the door: `--mcp-config` points at a JSON
# file describing the door as an http MCP server; `tools` (--allowedTools)
# auto-approves the door tool; `disallowed_tools` (--disallowedTools) locks out
# claude's native tools so the gated door is the ONLY actuator.
# Create /zeroclaw-data/clawmates-mcp.json (NOT committed; token injected):
# { "mcpServers": { "clawmates": { "type": "http",
# "url": "http://clawmates_server_1:8080/mcp",
# "headers": { "Authorization": "Bearer <per-workspace door token>" } } } }
# NOTE: model MUST be set via env (ZEROCLAW_providers__models__claude_cli__door__model)
# — the TOML sub-table parses empty (resolve_default_model gotcha).
[providers.models.claude_cli.door]
tools = ["mcp__clawmates__email_send"]
disallowed_tools = ["Bash", "BashOutput", "KillShell", "Write", "Edit", "NotebookEdit", "Read", "Glob", "Grep", "WebFetch", "WebSearch", "Task"]
mcp_config = "/zeroclaw-data/clawmates-mcp.json"
# An agent that ACTS through the door (uncomment; it needs NO ZeroClaw
# mcp_bundles — the door comes via claude's own MCP client):
# [agents.subdoor]
# model_provider = "claude_cli.door"
# risk_profile = "toolfree"
# --- Extra worker LLMs: Kimi Code (native CLI) and GLM (Zhipu) ---
# Run a topology WORKER on another model.
#
# Kimi Code: subscription-billed. Its coding endpoint (api.kimi.com/coding) is
# coding-AGENT gated (User-Agent enforced) — a raw OpenAI-compat client gets 403,
# so we drive Kimi through its OWN CLI via the `kimi_cli` provider (mirrors
# `claude_cli`): it spawns `kimi -p` headless on the Kimi membership, with Kimi's
# own agent loop + sub-agents. Auth is the CLI's own ~/.kimi-code/config.toml
# (default_model + [providers."managed:kimi-code"] type="kimi" with the key) or
# `kimi login` — no key is passed by ZeroClaw. (HOME=/zeroclaw-data, so the file
# lives at /zeroclaw-data/.kimi-code/config.toml in the runtime volume.)
# [providers.models.kimi_cli.default]
# # binary_path = "kimi" # default; resolved on PATH
# # yolo = true # -y: auto-approve tool calls (needed for a door agent
# # # that acts via Kimi's own MCP client, mcp.json)
# [agents.worker_kimi]
# model_provider = "kimi_cli.default"
# risk_profile = "toolfree"
#
# GLM (Zhipu) — like Kimi, the GLM Coding Plan is subscription-billed and its
# raw OpenAI/SDK endpoint is ToS-THROTTLED ("1113 Insufficient Balance"); it is
# only served inside OFFICIALLY SUPPORTED tools, and Claude Code is the #1 one.
# So GLM rides our existing `claude_cli` provider via a z.ai env override — no
# new provider, no image rebuild. Needs a fresh HOME + CLAUDE_CODE_OAUTH_TOKEN=""
# so the container's cached Claude subscription doesn't override the routing
# (pre-create <HOME>/.claude.json = {"hasCompletedOnboarding":true}). The z.ai
# key is read from the daemon env via the `$NAME` indirection (keep it out of
# config). Models: glm-5.2 (Opus-class), glm-4.7 (Sonnet-class), glm-4.5-air.
# [providers.models.claude_cli.glm]
# model = "glm-4.7"
# env = { HOME = "/zeroclaw-data/glm-home", CLAUDE_CODE_OAUTH_TOKEN = "", ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic", ANTHROPIC_AUTH_TOKEN = "$ZAI_API_KEY", API_TIMEOUT_MS = "3000000" }
# [agents.worker_glm]
# model_provider = "claude_cli.glm"
# risk_profile = "toolfree"
# Flagship tier (Opus-class): a second alias on glm-5.2 (same z.ai env). Costs
# 2-3x quota vs glm-4.7 — reserve for hard reasoning roles, not routine ones.
# [providers.models.claude_cli.glm5]
# model = "glm-5.2"
# env = { HOME = "/zeroclaw-data/glm-home", CLAUDE_CODE_OAUTH_TOKEN = "", ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic", ANTHROPIC_AUTH_TOKEN = "$ZAI_API_KEY", API_TIMEOUT_MS = "3000000" }
# [agents.worker_glm5]
# model_provider = "claude_cli.glm5"
# risk_profile = "toolfree"
#
# Gemini (Google) — built-in `gemini` API family (no image rebuild). Key via env
# ZEROCLAW_providers__models__gemini__default__api_key=<GEMINI_API_KEY>; model in
# config. gemini-2.5-flash = stable + high free-tier RPM (throttle-friendly);
# gemini-3-pro-preview is the flagship.
# [providers.models.gemini.default]
# model = "gemini-2.5-flash"
# [agents.worker_gemini]
# model_provider = "gemini.default"
# risk_profile = "toolfree"
#
# Groq — built-in `groq` family (key via ZEROCLAW_providers__models__groq__default__api_key).
# Fast, but LOW free-tier TPM: each turn carries a ~9.5k-tok system prompt, so 2
# turns/min can exceed 12k TPM → 429. Keep Groq to light/sequential roles.
# [providers.models.groq.default]
# # model via ZEROCLAW_providers__models__groq__default__model=llama-3.3-70b-versatile
# [agents.worker_groq]
# model_provider = "groq.default"
# risk_profile = "toolfree"
# --- Phase C: heterogeneous cross-model topologies ---
# The orchestrator maps a topology node's `role` → an agent alias via the SERVER
# env ZEROCLAW_AGENT_MAP="role=alias,role=alias" (+ ZEROCLAW_DEFAULT_AGENT for
# unmapped roles). Point each semantic role at a different model to run ONE
# topology across vendors, e.g.:
# ZEROCLAW_AGENT_MAP="coordinator=coordinator,researcher=worker_glm,analyst=worker_kimi,writer=worker_gemini,actor=worker_groq"
# Then POST /api/topologies/run {task, graph} with those role names. QUOTA CARE:
# GLM/Kimi plans have 5h/weekly caps + low concurrency (GLM Lite ~1 project at a
# time) — prefer pipeline (sequential) over swarm/mesh, keep tasks short, and ask
# the model for brief replies to cap output tokens.
# Tool-free profile — agents can only reason + call the MCP door (tools inject
# AFTER this allowlist filter). `level = "full"` removes ZeroClaw's OWN approval
# prompts so the agent is autonomous; governance happens at the Clawmates door
# (auto-policy, no human — "agents control their destiny"). Use "supervised" if
# you want ZeroClaw to also gate sensitive calls with an in-channel approval.
[risk_profiles.toolfree]
level = "full"
allowed_tools = []
excluded_tools = ["shell", "file_read", "file_write", "http_request", "browser", "composio"]
# The role-cast. node.role → agent alias is configured Clawmates-side via
# ZEROCLAW_AGENT_MAP (e.g. "analyst=researcher"); `scout` is the default
# fallback (ZEROCLAW_DEFAULT_AGENT) for any unmapped role.
[agents.coordinator]
model_provider = "claude_cli.default"
risk_profile = "toolfree"
mcp_bundles = ["clawmates_door"]
[agents.researcher]
model_provider = "claude_cli.default"
risk_profile = "toolfree"
mcp_bundles = ["clawmates_door"]
[agents.writer]
model_provider = "claude_cli.default"
risk_profile = "toolfree"
mcp_bundles = ["clawmates_door"]
[agents.worker]
model_provider = "claude_cli.default"
risk_profile = "toolfree"
mcp_bundles = ["clawmates_door"]
# Default fallback alias for roles not present above.
[agents.scout]
model_provider = "claude_cli.default"
risk_profile = "toolfree"
mcp_bundles = ["clawmates_door"]
# Step 1B — the Clawmates §15 MCP door. Tool-free agents reach it as their ONLY
# actuator; MCP tools inject AFTER the empty allowlist above. The door enforces
# §15 (audit, broker credential custody) but auto-decides via policy (no human;
# default allow-all → agents are autonomous). The Authorization bearer is a
# per-workspace door token (a Clawmates session token) — injected on gw-04, NOT
# committed; the placeholder below is replaced at deploy time.
[mcp]
enabled = true
deferred_loading = false
[[mcp.servers]]
name = "clawmates"
transport = "http"
url = "http://clawmates_server_1:8080/mcp"
tool_timeout_secs = 120
headers = { Authorization = "Bearer REPLACE_WITH_DOOR_TOKEN" }
[mcp_bundles.clawmates_door]
servers = ["clawmates"]
# ── A2A ingress (Phase 2) ───────────────────────────────────────────────────
# ZeroClaw's Agent2Agent server. These props are set at RUNTIME by cm-api
# (runtime_provision::enable_a2a_server / publish_claw) when a workspace opts in
# via POST /api/a2a/settings — they are NOT committed here. Shown for reference:
#
# [a2a.server]
# enabled = true
# bind = "0.0.0.0" # internal interface only
# port = 42617
# public_base_url = "https://api.clawmates.work/api/a2a/<workspace-id>"
# # the cm-api EDGE, never this daemon
# [agents.<alias>.a2a]
# published = true
# exposed_skills = ["search", "summarize"]
#
# SECURITY (must hold at deploy): the daemon's :42617 is NEVER host-published or
# Traefik-routed. Only cm-api is internet-facing; Traefik routes /api/a2a/** (and
# the rest of /api/**) to the cm-api service, which authenticates the external
# caller per-workspace (a2a_tokens), then injects the internal ZEROCLAW_TOKEN
# when proxying to this daemon's /a2a/{alias}. A leaked global door bearer must
# never reach an internet-exposed :42617. Cards advertise only the edge URL via
# public_base_url. Ingress throttle: CLAWMATES_A2A_POLICY=deny (kill switch),
# CLAWMATES_A2A_RATE_LIMIT=<n>/hour.
# NOTE: `claude_cli` is a TEXT-ONLY provider — `claude -p` doesn't surface
# tool-calls back to ZeroClaw, so claude_cli agents reason but can't invoke the
# door. An agent that ACTS through the door needs a tool-capable provider
# (groq/anthropic/openai). Example actor agent (uncomment + provide groq creds
# via env ZEROCLAW_providers__models__groq__default__{model,api_key}):
# [providers.models.groq.default]
# [agents.actor]
# model_provider = "groq.default"
# risk_profile = "toolfree"
# mcp_bundles = ["clawmates_door"]