docs: hand-off refresh — container-tier work shipped, stale guidance corrected
deploy / test (push) Successful in 4m46s
deploy / build (push) Successful in 1m1s

TOOL-CALL-ARCHITECTURE.md said "switch claude_cli to stream-json" as the
cheapest fix. That was wrong and is now marked so, with what actually
happened: zero tool.call events with the parser working perfectly, because
TurnEvent::ToolCall only fires for tools ZeroClaw itself executes. Hooks
sidestep that entirely, and the doc now leads with the resolution rather
than the theory. A fresh session is pointed at this file, so leaving the
wrong recommendation on top would have sent it down the same path.

NEXT-SESSION.md: state header, and the ordered list rewritten — items 1-3
are done or superseded. "Give the direct-session tier a tap" is dropped with
its reason: that tier is dormant (CLAWMATES_MISSION_EXECUTOR unset), and
checking before building saved the work. New top item is watching the first
production mission, since the gate and tap are proven locally and unproven
in prod.

Added an operational section for the things that cost the most time: the
403 actions-log API, gw-04's legacy docker-compose, the socket proxy, disk
contention between manual builds and CI, and Clerk-only prod auth.

Also flagged that SKILL-USE-BASELINE.md's Trigger column is now stale in a
good way — tool calls are observable on the container tier, so Trigger can
be scored from behaviour instead of prose. That is the highest-value
follow-up.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-21 07:22:01 -07:00
co-authored by Claude Opus 5
parent 5a11fae0d6
commit 0b4d91889a
2 changed files with 120 additions and 30 deletions
+66 -28
View File
@@ -1,14 +1,20 @@
# Where this left off — 2026-08-20 # Where this left off — 2026-08-21
Read `CAPABILITY-REVIEW.md` for the system picture, Read `CAPABILITY-REVIEW.md` for the system picture,
`TOOL-CALL-ARCHITECTURE.md` for the current investigation, and `TOOL-CALL-ARCHITECTURE.md` for how mission tools actually work (and the two
`SKILL-USE-BASELINE.md` for the measurement. wrong theories that preceded it), and `SKILL-USE-BASELINE.md` for the
measurement — noting that its Trigger column is now out of date in a good way
(see "Next, in order" §2).
## State of the tree ## State of the tree
**Everything is pushed.** `main` is at `128b423`; the fork's Everything is pushed. `main` is at `5a11fae`; the fork's
`merge/upstream-v0.8.4` is at `db1c50966`. The local suite is green — 106 test `merge/upstream-v0.8.4` is at `be9c34b1c`. Local suite green: **107 test
binaries, zero build errors, frontend builds. binaries, 412 lib tests**, frontend builds.
**CI is green and production is current.** Run 507 passed `test` and `build`,
and gw-04 rolled to it. Production also runs `clawmates-runtime:hooks` for
per-mission containers (see below).
## Container-tier tool gate + telemetry — SHIPPED 2026-08-21 ## Container-tier tool gate + telemetry — SHIPPED 2026-08-21
@@ -74,32 +80,48 @@ services actually pushed — the failing runs had pushed `server`, aborted on
**Operational note:** do not build images by hand on gw-04 while CI may run. **Operational note:** do not build images by hand on gw-04 while CI may run.
## Then, in order ## Next, in order
1. **Prove the PreToolUse gate in a real VM.** `vm_tool_gate` is unit-tested and 1. **Watch the first production mission.** Nothing has run since the runtime
shell-tested on the host; it has never run in a guest. Four of its bugs were flip, so the container-tier gate and tap are proven locally and unproven in
found only by executing the generated script, and two of them (syntax error, prod. Prod auth is Clerk, so a mission cannot be launched from here.
`IFS`) produce a gate that looks installed while being fully closed or fully ```
open. Check `/root/toolgate/denied.jsonl` after a mission that tries ssh gw-04 'docker logs clawmates_server_1 2>&1 | grep -E "per-mission runtime image|drained .* tool call"'
something denied. ssh gw-04 'docker exec <cm-runtime-mission-…> cat /root/toolhooks/tap/tools.jsonl | head'
```
If tools appear in `mission_events`, the loop is closed. If not, check the
three failure shapes listed above — each looked correct from every other
angle.
2. **Give the direct-session tier a tap.** It is the one tier with no 2. **Now that tool calls are observable, redo the Skill-Use measurement.**
observability at all — `session_executor::run_session` is a single `docs/SKILL-USE-BASELINE.md` reports Trigger as `not_observable` on the
`container_exec::exec` returning a string. Same settings document, written container tier because there was no tool evidence. There is now.
into a container instead of a VM. `mission_events` carries `tool.call` and `file.touch` per phase, so
Trigger can be scored from behaviour instead of prose — which is what the
paper actually measures. This is the single highest-value follow-up: it
turns the baseline from "the first honest number" into a real one.
3. **Deploy the door we already built** (`docs/TOOL-CALL-ARCHITECTURE.md` §3). 3. **Give the microVM tier the same treatment, or retire the difference.** It
Create `/zeroclaw-data/clawmates-mcp.json`, add a door-shaped provider alias, has the gate and a tap already, but by a different route (`vm_tool_tap`
bind mission claws to it. This is config, not code — the provider feature is installs into the guest, `microvm_executor` drains inside the turn). Two
ours and shipped. It would also make `clawmates_skills` genuinely reachable, mechanisms for one job is how they drift. Worth folding onto
which is the precondition for moving skills from inlined bodies to `container_tool_hooks` once the fleet is back — tank and morpheus have been
progressive disclosure and making Skill-Use **Trigger** measurable the way offline for over a week, so the microVM tier cannot be tested at all today.
the paper defines it.
4. **Pull upstream's egress policy** — `0db7d999a feat(plugins): add shared 4. **Deploy the door** (`docs/TOOL-CALL-ARCHITECTURE.md` §3). Config, not code:
egress policy foundation (#9137)`. We are 218 commits behind; this is the one `/zeroclaw-data/clawmates-mcp.json` plus a door-shaped provider alias. Now
item identified as worth taking, and it is defence for a problem we have not less urgent than it looked — the gate no longer depends on it — but it is
solved. still the precondition for `clawmates_skills` being reachable, and therefore
for skills moving from inlined bodies to progressive disclosure.
5. **Pull upstream's egress policy** — `0db7d999a feat(plugins): add shared
egress policy foundation (#9137)`. We are ~220 commits behind; this is the
one item identified as worth taking, and it is defence for a problem we have
not solved.
**Dropped from this list:** "give the direct-session tier a tap". That tier is
dormant — `CLAWMATES_MISSION_EXECUTOR` is unset in production, so it never
runs. Checking that before building for it saved the work.
## Open decisions that are yours ## Open decisions that are yours
@@ -126,6 +148,22 @@ services actually pushed — the failing runs had pushed `server`, aborted on
- **Graph memory / `clawhdf5-agent`** — in the workspace manifest, used by no - **Graph memory / `clawhdf5-agent`** — in the workspace manifest, used by no
crate. Measure against a baseline before migrating. crate. Measure against a baseline before migrating.
## Operational facts that cost time to learn
- The Gitea **actions-log API returns 403** for the token in
`deploy/compose/.env`. Every CI failure this week was debugged blind because
of it. Steps now write to `/tmp/ci-logs` on the runner host as a workaround;
**a token with the `actions` scope** remains the highest-value thing to
obtain.
- **gw-04 uses legacy `docker-compose`**, not the v2 plugin. `docker compose`
fails there.
- **Do not build images by hand on gw-04 while CI may run** — same 150G volume,
and the frontend image build is what loses.
- The server reaches Docker through a **socket proxy** (`DOCKER_HOST`). Use
`container_exec::connect()`, never `connect_with_local_defaults()`.
- Prod auth is **Clerk**; the bootstrap password in `deploy/compose/.env` works
only against the local stack.
## Two corrections made this session, worth remembering ## Two corrections made this session, worth remembering
- **"Missions can't call tools at all" was wrong.** They call `Bash` and `Write` - **"Missions can't call tools at all" was wrong.** They call `Bash` and `Write`
+54 -2
View File
@@ -118,9 +118,61 @@ We are **218 commits behind** `upstream/master`. Scanning for anything relevant:
worth pulling: it is defence for the egress problem we have not solved, and worth pulling: it is defence for the egress problem we have not solved, and
it is hardening we would otherwise write ourselves. it is hardening we would otherwise write ourselves.
## What to do, cheapest first ## RESOLVED 2026-08-21 — hooks, not the transport
### 1. Observability — switch `claude_cli` to `stream-json` *(small, proven)* Everything above the line is still an accurate account of how mission tools
work. The recommendation that followed it was wrong, and this is what actually
happened.
### `stream-json` was necessary-looking and useless
Shipped, deployed, and driven with a real mission: **zero `tool.call` events**,
with the parser working perfectly. `TurnEvent::ToolCall` — the thing the
gateway turns into a frame — is emitted from `tool_execution.rs`, i.e. only for
tools **ZeroClaw itself executes**. Claude Code runs its tools inside its own
subprocess, so the event never fires. A provider that *knows* about the calls
changes nothing.
Worse, v1 was harmful: it returned the observed calls as
`ChatResponse.tool_calls`, meaning "execute these", so the loop tried to run
`Bash`, got `Unknown tool: Bash`, and fed that back to the model — an extra LLM
round-trip per call, telling the agent its own work failed. The mission
completed correctly anyway, which is why it would have gone unnoticed. Fixed in
fork `295d6d3b8` (log them, never return them); the corrected image
`streamjson2` was built and **deliberately not deployed**, since it delivers
nothing on its own.
### What worked: hooks
`claude -p --settings <doc>` honours `PreToolUse` and `PostToolUse`, so the gate
blocks and the tap records **without ZeroClaw being involved at all**. That
sidesteps the entire `TurnEvent` problem instead of trying to solve it.
Shipped, and verified end to end on a real mission:
```
tool.call 10 Bash 6, Read 3, Write 1
file.touch 4 research/tapproof.md
```
- `--settings` on `claude_cli` — fork `be9c34b1c`
- `container_tool_hooks` writes both hook scripts and one settings document
into the mission container
- `set_claude_cli_settings` points the provider at it
- `phase_runner::drain_finished_container_phases` collects the tap into
`mission_events`, idempotent by truncation
### The lesson worth keeping
Two of the three ranked recommendations below were written from reading the
code, and the first one was confidently wrong in a way that took a deployment
and a live mission to discover. The gate (§2) was right. The transport fix (§1)
was not — the evidence for it was an absence, and an absence had another
explanation.
## The original recommendations, kept for the record
### 1. ~~Observability — switch `claude_cli` to `stream-json`~~ *(WRONG — see above)*
In our fork: `--output-format stream-json --verbose`, parse `tool_use` / In our fork: `--output-format stream-json --verbose`, parse `tool_use` /
`tool_result` blocks into `ChatResponse.tool_calls` instead of `Vec::new()`. `tool_result` blocks into `ChatResponse.tool_calls` instead of `Vec::new()`.