Files
clawmates/deploy/clawmates-runtime/README.md
T
Omar SobhandClaude Opus 4.8 bd982943b8
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled
chore: commit ZeroClaw per-tenant runtime spike + architecture doc
Saves earlier-phase artifacts that were sitting untracked:
- docs/agent-engine-architecture.md — per-tenant containerized ZeroClaw runtime
  decision doc (clawmates = §15 control plane; zeroclaw = per-tenant runtime).
- deploy/clawmates-runtime/ — slim runtime Dockerfile, dev compose, example
  agent config, README (the proven Phase-1 drive recipe).
- tools/runtime-spike/drive.mjs — Node WS drive client for the spike.

No secrets (only env-var names / commented placeholders).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-15 21:29:16 -07:00

84 lines
4.4 KiB
Markdown

# 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.
```bash
# 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
```bash
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_start``chunk*`
`tool_call`/`tool_result``approval_request` (the §15 hook) → `done`.
Drive client: `tools/runtime-spike/drive.mjs` (Node ≥22). Example:
```bash
PAIR_CODE=<code> node tools/runtime-spike/drive.mjs http://<host>:42617 scout "hi"
```
## 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.