docs(research): missions DO call tools — the claim was wrong, and the truth is worse
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]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
771092b165
commit
ea0b989b3f
@@ -7,11 +7,22 @@
|
||||
//! gateway, opens `/ws/chat?agent=<alias>`, sends the role+task+context prompt,
|
||||
//! and streams the turn's events back into a [`TurnOutcome`].
|
||||
//!
|
||||
//! **§15 by construction:** the agents are provisioned tool-free (every
|
||||
//! sensitive capability is a gated Clawmates MCP tool — the "door"), so a turn
|
||||
//! takes no sandbox-leaving action here. If the gateway nonetheless emits an
|
||||
//! `approval_request`, we record it as a **blocked** `GatedAction` and end the
|
||||
//! turn — we never auto-approve.
|
||||
//! **These agents are NOT tool-free.** That claim stood here for months and is
|
||||
//! false — see `docs/TOOL-CALL-ARCHITECTURE.md`. It was inferred from a frame
|
||||
//! stream that carried no tool events, and the emptiness has a different cause:
|
||||
//! `claude_cli` runs `claude -p --output-format json`, which returns a single
|
||||
//! final result object, and the provider hardcodes `tool_calls: Vec::new()`.
|
||||
//! The agent calls Claude Code's own tools; the transport discards them.
|
||||
//! `--output-format stream-json` emits `tool_use`/`tool_result` blocks —
|
||||
//! verified against the deployed Claude Code 2.1.228.
|
||||
//!
|
||||
//! The door-shaped provider that WOULD make this true (`--mcp-config` +
|
||||
//! `--disallowedTools` on the natives) is built and documented in
|
||||
//! `agent.config.example.toml`, and is not deployed: mission claws bind to
|
||||
//! `claude_cli.default`, which sets none of it.
|
||||
//!
|
||||
//! If the gateway emits an `approval_request` we still record it as a
|
||||
//! **blocked** `GatedAction` and end the turn — we never auto-approve.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -136,9 +147,11 @@ pub struct ToolTrace {
|
||||
/// `chunk`, `done` and `session_start` and no tool frames at all. That is
|
||||
/// not a protocol mismatch — `tool_call` is in the deployed binary
|
||||
/// (`zeroclaw-gateway/src/ws.rs` emits `{"type":"tool_call","id","name",
|
||||
/// "args"}`) — it is §15: these agents are provisioned tool-free behind the
|
||||
/// MCP door, so they call nothing. The histogram is what let us tell those
|
||||
/// two apart, which was its whole purpose.
|
||||
/// "args"}`) — and it is NOT that the agents are tool-free, which is what
|
||||
/// this comment used to say. `claude_cli` asks for `--output-format json`,
|
||||
/// so the subprocess's tool calls never reach the gateway to be framed.
|
||||
/// The histogram still does its job: it distinguishes "no frames" from
|
||||
/// "frames we do not recognise", and the answer was the former.
|
||||
pub unmatched: std::collections::BTreeMap<String, u32>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user