Files
clawmates/deploy/clawmates-runtime
Omar SobhandClaude Opus 5 1c9be52291
deploy / test (push) Successful in 5m16s
deploy / build (push) Successful in 1m4s
build(runtime): pin Claude Code 2.1.276 and Kimi 0.41.0; missions run the image we ship
Two things found while asking "what version of Claude Code do missions run?"

Mission containers are created from CLAWMATES_RUNTIME_IMAGE, which on both
stacks still pointed at clawmates-runtime:hooks — zeroclaw 0.8.4, Claude Code
2.1.237 (2.1.228 locally), built 2026-08-21. The v0.8.5 upgrade on 09-06
rebuilt only the persistent clawmates-runtime container, which container-tier
missions do not drive their turns through. Every measured mission this month
ran on 0.8.4/2.1.237; the measurements stand (one image throughout) but the
version attached to them in the handoff and in memory was wrong, and has been
corrected there.

The drift itself came from `npm install -g @anthropic-ai/claude-code` with no
version: each rebuild takes whatever npm has that day, so two builds three
weeks apart shipped two CLIs and nothing recorded either. The changelog shows
why that is not merely untidy — 2.1.265 and 2.1.275 each broke every turn on
ANTHROPIC_BASE_URL endpoints, the path the GLM and Kimi backends use — and
today's floating local build silently took Kimi 2.0.1, a major version. Both
are ARGs now, defaulting to what was verified.

Verified before promoting: two local missions on 0.8.5 + 2.1.276 — tool
arguments recorded on 40/40 and 123/123 calls, gate.installed, skill reads,
judge met and independent with the correction loop closing, spend rows with
provider and model, and with delegation forced, 4 Agent spawns → 45 subagent
calls across 4 ids all typed general-purpose (44/4 on the old CLI). The hook
payload did not move. Prod's .env now names clawmates-runtime:v085-cc276,
built on tank from the fork at 57635deb with this Dockerfile.

The local override's CLAWMATES_RUNTIME_IMAGE points at :toolchain, which is
the same lineage plus cmake/python3-dev for `cargo test` on cmake-driven deps.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
2026-09-18 12:26:49 -05:00
..

Clawmates per-tenant runtime (ZeroClaw) — Phase 1 spike

Goal of Phase 1: stand up one Clawmates tenant runtime container (a slim ZeroClaw daemon) on gw-04, drive it from a small Clawmates-side client (provision an agent → run a turn → stream events), and prove one §15 gate by routing an outbound action through a stub Clawmates MCP tool. See docs/agent-engine-architecture.md for the full design.

Confirmed runtime contract (from the live MindHealth/ClawHealth images in prod)

  • Image entrypoint: zeroclaw, default cmd: daemon
  • Gateway port: 42617 (ZEROCLAW_GATEWAY_PORT)
  • Per-tenant data volume mounted at /zeroclaw-data (HOME=/zeroclaw-data, ZEROCLAW_WORKSPACE=/zeroclaw-data/workspace)
  • Config overrides via env: ZEROCLAW_<dotted__path>=value (double-underscore = nesting)

Build the slim image (off-box, push to the private registry)

ZeroClaw's own multi-stage Dockerfile builds the daemon; ZEROCLAW_CARGO_FEATURES selects channels. We drop the heavy defaults (lark/whatsapp) — Clawmates exposes outbound capabilities as gated MCP tools, not native channels.

# from a build host (this Mac, or Gitea CI) with the zeroclaw checkout:
docker build \
  -t web-01:5000/clawmates-runtime:dev \
  --build-arg ZEROCLAW_CARGO_FEATURES="channel-webhook" \
  ~/projects/zeroclaw
docker push web-01:5000/clawmates-runtime:dev      # registry is Tailscale-only, insecure HTTP

Build is heavy (zeroclaw is ~160k LOC Rust). Prefer building on a beefy box or CI, not gw-04; gw-04 only runs the image.

Run the dev stack on gw-04

scp deploy/clawmates-runtime/compose.dev.yml gw-04:/home/redclaw/clawmates/
ssh gw-04 'cd ~/clawmates && docker compose -f compose.dev.yml up -d'

The runtime gateway is bound to 127.0.0.1:42617 on gw-04 (not public). Reach it for the spike over the SSH/Tailscale tunnel; production fronts it with Traefik on its own router, isolated from gr33t/cbh.

§15 model (unchanged moat)

The tenant agent is provisioned with no native outbound tools and no real channel/secret config (tools.allow locked). Every sensitive capability (email/slack/pay/delete/…) is a Clawmates MCP tool; the agent's only egress is that gated MCP door, where cm-safety + cm-secrets apply approvals, taint, single-use grants, broker execution, and journaling.

Confirmed drive recipe (Phase 1 — proven on tank)

A turn ran end-to-end (scout answered over /ws/chat). The working flow:

  1. Configure a tenant daemon. Two gotchas the spike uncovered:
    • Provider-map entries must be set via the prop/env path, NOT a TOML sub-table. [providers.models.groq.default]\nmodel="…" in config.toml parses to an empty entry (groq.default = {}, model <unset>). Set them via env instead: ZEROCLAW_providers__models__groq__default__model=…, ZEROCLAW_providers__models__groq__default__api_key=… (double-underscore = path; key from Infisical).
    • The agent references the provider by type.alias: [agents.scout] model_provider = "groq.default".
    • Onboarding gate is a flag — set [onboard_state] quickstart_completed = true (else /ws/chat returns NEEDS_ONBOARDING). onboard_state + agents.* DO load fine from config.toml.
  2. Pair for a bearer token: read the one-time code from the daemon's startup log (X-Pairing-Code: NNNNNN), then POST /pair with header X-Pairing-Code: <code>{token}.
  3. Run a turn over WebSocket /ws/chat?agent=<alias>&token=<token> (Node's global WebSocket can't set headers, so the token goes in the query — gateway accepts header > subprotocol > query). Send {"type":"message","content":"…"}; receive session_startchunk*tool_call/tool_resultapproval_request (the §15 hook) → done.

Drive client: tools/runtime-spike/drive.mjs (Node ≥22). Example:

PAIR_CODE=<code> node tools/runtime-spike/drive.mjs http://<host>:42617 scout "hi"

Driving topologies from Clawmates (POST /api/topologies/run)

cm-orchestrator owns the topology graph; for each turn it drives one ZeroClaw role-agent in this container over the recipe above, via ZeroClawDriveExecutor (crates/cm-api/src/topology_exec.rs). The server reads these env knobs (unset = the endpoint 500s; the rest of cm-api is unaffected):

  • ZEROCLAW_GATEWAY_URL — e.g. http://127.0.0.1:42617
  • ZEROCLAW_PAIRING_CODE — the one-time code from the daemon startup log
  • ZEROCLAW_AGENT_MAP — optional role=alias,role=alias (e.g. analyst=researcher)
  • ZEROCLAW_DEFAULT_AGENT — fallback alias for unmapped roles (default scout)

agent.config.example.toml declares the multi-agent role-cast (coordinator/researcher/writer/worker

  • scout), all tool-free. Manual E2E: bring up the stack, grab the pair code, sanity-drive one alias with drive.mjs, then POST /api/topologies/run {task, graph} (build a graph via /api/topologies/build) and confirm the RunRecord has real per-step outputs + per-turn tokens.

Next

  • Demonstrate a §15 gate: give scout a tool under the Supervised profile and confirm the approval_request round-trip over the socket (drive client already handles it).
  • Replace zeroclaw's native approval with the Clawmates MCP door (cm-safety/cm-secrets).
  • Map WS events → run_events journal; /api/cost → billing.
  • docker save the image → docker load on gw-04 and run compose.dev.yml there.