Deep research into "missions can't call tools at all", which I wrote and
which is false. docs/TOOL-CALL-ARCHITECTURE.md has the full findings.
WHAT IS ACTUALLY TRUE
Three of the four mission paths end in `claude -p` with Claude Code's own
toolset and permissions PRE-ACCEPTED:
solo microVM Read Edit Write Bash Agent --permission-mode acceptEdits
composed microVM same, per node same
direct session Read Edit Write Bash acceptEdits
So the position is not "no tools". It is: mission agents run Bash and Write
with permissions pre-accepted, and nothing in this platform can gate them.
That is a stronger finding than the one it replaces — "can't call tools"
sounds like a missing feature; "calls tools freely, ungated, and mostly
unobserved" is a security posture, and it is ours.
Observe and gate are different and both are partial. vm_tool_tap is a
PostToolUse hook: it fires AFTER the tool ran and exit-0s unconditionally,
so it is telemetry and structurally cannot gate. The direct-session tier has
no tap at all. GatePolicy has exactly one enforcement site — the chat loop —
and its approvals key on (session_id, message_id), which no mission phase
can produce.
WHY THE CONTAINER TIER LOOKED TOOL-FREE
`claude_cli` runs `claude -p --output-format json`, which returns a single
final result object, and the provider hardcodes `tool_calls: Vec::new()`.
The calls happen; the transport discards them. The comment reading that
emptiness as "§15 by construction: agents are provisioned tool-free" was
inferring a design property from a serialization choice.
Verified against the deployed Claude Code 2.1.228 rather than assumed:
`--output-format stream-json --verbose` emits `tool_use` blocks with the
tool name and `tool_result` blocks. The calls are fully observable; we ask
for the wrong format.
THE DOOR WE ALREADY BUILT AND NEVER PLUGGED IN
claude_cli.rs is OURS — upstream zeroclaw-labs/zeroclaw has no such file —
and so is 88eef99d4 "claude_cli --mcp-config + allow/disallow tools (act via
door)". The provider already accepts mcp_config (claude's own MCP client
reaches our door), tools, and disallowed_tools (lock out the natives so the
gated door is the ONLY actuator). agent.config.example.toml documents the
whole shape.
In the live runtime: clawmates-mcp.json does not exist, there is no
[providers.*] block, and every mission claw binds to claude_cli.default
which sets none of it. My earlier "claude_cli cannot reach MCP, therefore
the skills server is unreachable" was wrong in its reasoning — the
capability is built, documented by us, and never deployed.
Related: we set `agents.<alias>.mcp_bundles`, which configures ZeroClaw's
OWN MCP client for its native loop. A claude_cli agent's actuator is the
claude subprocess, which reads `mcp_config` on the PROVIDER. We were turning
a knob wired to a loop that does not run.
UPSTREAM
218 commits behind. No upstream work on claude_cli (the file is ours). ACP
already exists in the fork; the three new commits are workspace-default and
localization fixes, not new capability. The one item worth pulling is
"feat(plugins): add shared egress policy foundation (#9137)" — a network
guard with DNS pinning and metadata-address blocking, defence for the egress
problem we have not solved.
Stale claims corrected in place, in topology_exec.rs and the runtime config,
so the codebase stops asserting the thing that is false.
Recommended order, cheapest first: stream-json for observability; the
PreToolUse hook for a real gate (it FIRES under claude -p per vm_stop_gate,
and has zero call sites); then deploy the door. The executor swap is NOT
recommended — the blockers are structural, not wiring, and the cheap fixes
deliver what it was wanted for.
Co-Authored-By: Claude Opus 5 <[email protected]>
provision_claw honours the template's bundle list as of the previous
commit, but a bundle an agent is assigned and the runtime config does not
define resolves to nothing — so the assignment had to be made to mean
something on the MCP side too.
Carries the caveat that matters at the point of use: this channel only
works for a provider that can surface tool calls, and mission claws run on
claude_cli, which is text-only. Their skills arrive as prompt text instead.
The entry is for tool-capable agents, and so that an assigned name resolves.
gitea_forge is left UNDEFINED on purpose, with a note. Six templates name
it and nothing defines it; a plausible-looking definition pointing at the
wrong URL would turn a name that resolves to nothing into a server that
fails at call time, which is harder to notice rather than easier.
Co-Authored-By: Claude Opus 5 <[email protected]>
Two things.
1. The architecture_mapper proposal, applied AND made durable.
The GLM proposal (019fddd9) was accepted in full: the agent's system_prompt now
carries the Mermaid-first constraint and its brain was rewritten. Both verified
against the live row and the .h5 file.
But `apply_identity` writes `UPDATE agents SET system_prompt` and
`apply_brain_consolidation` writes that agent's brain — neither touches the team
TEMPLATE. That agent is mission-scoped, so the improvement would have died with
the mission. The model's actual insight was sharp and worth keeping: "Mermaid
diagrams beat prose" lived in the brain SEED and not in the system PROMPT, so it
only applied when the agent happened to consult its brain. That constraint is
now in templates/teams/codebase_research.toml, where every future Codebase
Research team inherits it.
(The proposal's second item mostly restated anti-patterns the seed already
lists, so the seed is unchanged. Applying an LLM's suggestion is not the same as
agreeing with all of it.)
2. Gemini is gone.
Removed: the `gemini.default` provider alias and its `is_exact_provider_match`
prefix, GEMINI_API_KEY forwarding to agent containers, the evaluator's
gemini->gemini family row, the model selectors in claws/teams/planner and in
TeamWizard + AgentComputer, and the commented provider block in the runtime
config example (whose ZEROCLAW_AGENT_MAP example still mapped a worker_gemini
that no longer existed).
`provider_alias_for("gemini")` now returns claude_cli.default via the
unrecognised-model branch, which LOGS. A stray gemini binding degrades visibly
rather than resolving to a provider row we no longer ship. A test pins that, and
another pins that GEMINI_API_KEY is forwarded in NEITHER auth mode, so adding it
back to the list is a visible change rather than an accident.
Avatar generation is DELETED, not disabled — it called Gemini's image model, and
there is no alternative: Claude and Kimi are text-only, and z.ai answers
"Unknown Model" for cogview-3-flash and cogview-4 on our plan (measured, not
assumed). AvatarModal keeps UPLOAD, which never needed a provider; only the
prompt-generation half is gone.
240 backend lib tests, 89 frontend tests, clean tsc + eslint, build succeeds.
Two defects found by running a real coding mission (019fc372) rather than a
test. Both made a coding phase look like it produced nothing.
**Capture measured the wrong baseline.** It diffed the working tree against
HEAD, which is correct only while work stays uncommitted. `rust_sdlc` has a
*committer* role, so committing is the intended path — meaning a mission that
did its job properly leaves a clean tree and captured nothing. That is exactly
what happened: the agent created `DELIVERY_PROBE.md`, committed it as
`aa3be95`, and the artifact recorded `empty: true` beside a commit that
plainly contained the work.
`mission_workspace` now records the clone point in `.git/clawmates-base` (in
`.git/`, so it travels with the checkout, stays invisible to the repository,
and cannot be reached by an agent through its pinned workspace), refreshed
whenever `fetch_and_reset` moves HEAD. Capture diffs from there, covering
committed, staged and unstaged changes in one pass. Checkouts predating the
marker fall back to HEAD and say so via `base_recorded: false`.
**Agents could not create files.** `coding_readwrite` granted `file_edit` but
not `file_write`. `file_edit` replaces an exact existing string and rejects an
empty `old_string`, so creating a new file was impossible. The mission
transcript is unambiguous: "the tool rejected empty old_string... the shell is
restricted", after which the agent worked around it through `shell`. The
comment above that profile has claimed it grants file_write since the day it
was written; the list never contained it.
Also broadens capture from coding/benchmark/security_scan to every phase kind
of a repo-bearing mission: `phase_task_text` tells research phases to "save
findings under /mission/repo/research/", so filtering by kind would have
discarded every research brief such a mission produced.
Regression tests cover committed-only and committed-plus-uncommitted work
against a real git repo.
Co-Authored-By: Claude Opus 5 <[email protected]>
Mission agents were burning ~275K tokens producing nothing: the coder had
only file_read and its workspace was the empty ephemeral sandbox, so it
dumped a full spec inline instead of writing files. Two root causes:
1. Risk-profile allowlists used pre-0.8 tool names. `coding_readwrite`
allow-listed `file_write` (renamed to `file_edit` in ZeroClaw 0.8, and
`file_write` now refuses on ephemeral workspaces) and omitted file_edit
/ content_search / glob_search / git_operations — the exact tools the
phase prompt tells agents to use. Since allowed_tools is a strict
allowlist, agents were effectively read-only. Documents the correct
profiles in agent.config.example.toml (they only lived in host config;
the live runtime profiles were corrected via its config API).
2. workspace.path never got set. `agents.<alias>.workspace.path` is an
Option<PathBuf> the ZeroClaw Configurable macro skips from prop
enumeration, so provision_claw's set_prop always 404'd and the whole
call errored into a swallowed eprintln. Removes the dead set_prop and
pins the workspace out-of-band: MissionRuntimeProvisioner::
pin_agent_workspaces patches the shared config file on the per-mission
container (format-preserving via toml_edit, atomic temp+mv); the daemon
applies it on the same reload that surfaces the freshly-provisioned
claws. Covered by unit tests for the TOML stamp.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Single-role drafter on a fixed concision-weighted brief, one turn per backend,
judge-ranked: GLM-4.7 > Claude > Kimi > GLM-5.2. Notable inversion — the
flagship GLM-5.2 ranked last by over-writing the 25-word brief, underscoring
that the best model is role/task/rubric-dependent. Adds the worker_glm5
(glm-5.2 Opus-class) tier to the example config. Gemini excluded this run
(Google June-19 API-key-restriction 403; infra, not model).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Gemini via the built-in `gemini` API family (gemini-2.5-flash default,
gemini-3-pro-preview flagship) and Groq via `groq` — both wired as topology
worker aliases (worker_gemini, worker_groq), no image rebuild. Documents the
Phase C recipe: map semantic roles to per-model aliases via the server
ZEROCLAW_AGENT_MAP, then POST /api/topologies/run.
Five subscription/API backends now selectable per role: Claude, Kimi, GLM,
Gemini, Groq. Proven live on gw-04 — a 3-stage pipeline ran researcher=GLM-4.7
-> analyst=Kimi-K2 -> writer=Gemini-2.5-flash end-to-end (RunRecord persisted).
Quota-care notes added (5h/weekly caps, low concurrency -> prefer sequential
pipelines, short prompts).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
GLM Coding Plan has no CLI of its own and ToS-throttles raw SDK/API access
("1113 Insufficient Balance"); it is served only inside officially supported
tools, and Claude Code is the primary one. So GLM rides the existing
claude_cli provider with a z.ai env override (ANTHROPIC_BASE_URL=
api.z.ai/api/anthropic + ANTHROPIC_AUTH_TOKEN) — no new provider, no image
rebuild. Fresh HOME + CLAUDE_CODE_OAUTH_TOKEN="" so the container's cached
Claude subscription doesn't override the routing; key via $NAME indirection.
Deployed & verified on gw-04: worker_glm answers "I am GLM-4.7, built by
Zhipu AI" over /ws/chat. Three subscription models now co-reside on one
runtime — Claude (coordinator), Kimi (worker_kimi), GLM (worker_glm).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Runtime image now installs Node 22 (nodesource) + both agent CLIs —
@anthropic-ai/claude-code and @moonshot-ai/kimi-code — so the zeroclaw
daemon can spawn `kimi -p` for the new kimi_cli provider on a Kimi
membership, parallel to `claude -p` (claude_cli). Kimi needs Node >=22.19;
the image was on Node 18.
Documents the kimi_cli worker recipe in agent.config.example.toml (the
coding endpoint is User-Agent gated → drive Kimi through its own CLI, not a
raw OpenAI-compat client) and keeps GLM via the registry family.
Deployed & verified on gw-04: coordinator (claude_cli) answers "Anthropic
built me", worker_kimi (kimi_cli) answers "I am Kimi, built by Moonshot AI"
— heterogeneous Claude + Kimi on one runtime, over /ws/chat.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Phase A wiring (config-only): server registry kimi provider now targets the
Kimi Code coding endpoint (api.kimi.com/coding/v1, model kimi-for-coding,
subscription-billed via a Kimi Code Console key). Runtime example documents a
custom.kimi worker provider (OpenAI-compatible coding endpoint) + GLM, selectable
per topology role via ZEROCLAW_AGENT_MAP. Activates on KIMI_API_KEY; no rebuild.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
ZeroClaw already supports glm + moonshot (Kimi) as model providers; document the
pattern to run a topology worker on them (env model+key, agent on the alias,
role->alias via ZEROCLAW_AGENT_MAP).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds the claude_cli.door provider pattern (mcp_config + allowed/disallowed
tools) so a subscription claude_cli agent reaches the Clawmates door via Claude
Code's own MCP client, with native tools locked out (door = only actuator).
Validated live on gw-04: subdoor agent (claude_cli, subscription) → door →
outbox + agent|door.executed audit, cost 0.0; shell command denied.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Set the tool-free risk profile to level="full" so ZeroClaw raises no in-channel
approval (the human is removed; the Clawmates door auto-governs). Document that
claude_cli is text-only (claude -p doesn't surface tool calls), so agents that
ACT through the door need a tool-capable provider (groq/anthropic) — with a
commented actor-agent example.
Validated live: a groq-brained actor agent at full autonomy emitted
clawmates__email_send with no approval prompt; the door executed it (outbox +
agent|door.executed audit). No human in the loop.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Tool-free ZeroClaw agents get one actuator: an MCP server (POST /mcp, JSON-RPC
2.0, protocol 2024-11-05) that fronts the existing §15 machinery. The human
approver is replaced by an automated policy (default allow-all → agents are
autonomous; CLAWMATES_DOOR_POLICY=deny is a kill switch), but the governed parts
stay: every action is journaled to the append-only audit log, actions execute
through the runtime's gated-tool path, and broker credential-custody is wired in
for v2 tools. Synchronous execution returns the real result inline.
- crates/cm-api/src/mcp_door.rs: initialize/tools.list/tools.call handler; auth
(bearer -> workspace), classify effects, policy auto-decide, execute, audit.
v1 exposes email_send (-> outbox); slack/pay (broker+grant chain) is next.
- crates/cm-runtime: Runtime::{tool_descriptor_json, tool_gate_category,
execute_door_tool} — door-facing entry that builds ToolContext and consumes a
grant for runtime-executed gated tools.
- deploy/clawmates-runtime: agents now carry mcp_bundles=["clawmates_door"];
[[mcp.servers]] points at the door (bearer injected at deploy, not committed).
Validated live on gw-04: tools/call email_send -> isError:false + outbox row +
"agent|door.executed" audit, no human. 4 door unit tests + clippy clean.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Switch the per-tenant runtime to ZeroClaw's new native `claude_cli` model
provider (added in the zeroclaw repo): every agent turn spawns `claude -p`
headless, authed by CLAUDE_CODE_OAUTH_TOKEN — i.e. the Claude subscription, no
per-minute API TPM ceiling (the groq free tier 429'd multi-turn pipelines).
- Dockerfile: install the Claude Code CLI (@anthropic-ai/claude-code) into the
runtime image.
- agent.config.example.toml: role-cast on model_provider = "claude_cli.default".
NOTE: set the model via env (ZEROCLAW_providers__models__claude_cli__default__model)
not the TOML sub-table, else resolve_default_model() reads empty -> NEEDS_ONBOARDING.
- .gitignore: token.key (local Claude OAuth token, never committed).
Validated live on gw-04: a 2-node researcher->writer pipeline ran via the
deployed POST /api/topologies/run -> claude_cli -> real claude turns; 2-step
RunRecord, cost_usd 0.0 (subscription), no rate limiting.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
cm-orchestrator owns the topology graph; each turn now drives a real ZeroClaw
role-agent in a container via the proven gateway drive recipe, instead of a
tool-free cm-llm call.
- crates/cm-api/src/topology_exec.rs: ZeroClawDriveExecutor impl TurnExecutor —
pair (POST /pair + X-Pairing-Code, token cached) -> ws /ws/chat?agent=<alias>
-> send {type:message,content} -> drain chunk/done/approval_request/error.
approval_request is recorded as a BLOCKED GatedAction, never auto-approved (§15).
Role->alias via ZEROCLAW_AGENT_MAP, fallback ZEROCLAW_DEFAULT_AGENT (scout).
- POST /api/topologies/run {task,graph} -> execute() -> RunRecord, persisted
best-effort to the existing topology_runs table (no migration). compare stays
tool-free. from_env() is read in-handler so cm-api still boots unset.
- deploy/clawmates-runtime: example config now declares a tool-free multi-agent
role-cast; README documents the ZEROCLAW_* knobs + run endpoint.
tokio-tungstenite 0.26 (already in lock) + dev axum `ws` for the hermetic test.
3 lib tests green, clippy clean, SQLX_OFFLINE build clean.
Co-Authored-By: Claude Opus 4.8 <[email protected]>