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]>
213 lines
14 KiB
Markdown
213 lines
14 KiB
Markdown
# Agent Engine Architecture — per-tenant containerized runtime
|
||
|
||
_Status: accepted direction (Phase 0). Supersedes the assumption that Clawmates' own
|
||
`cm-runtime` is the long-term execution engine. This document is the source of truth for the
|
||
backend agent-execution strategy; implementation proceeds in the phases at the end._
|
||
|
||
## Problem
|
||
|
||
Clawmates must let each customer build their own multi-agent "company/team" and run it at
|
||
scale: many agents, across machines, with full lifecycle management (provision → run →
|
||
suspend → resume → retire) — without weakening the §15 safety contract (per-tool human
|
||
approval, taint, secret broker, single audited journal) that is the product's moat and is
|
||
acceptance-blocking.
|
||
|
||
Clawmates' current `cm-runtime` (`crates/cm-runtime/src/runtime.rs`) is a real agent loop, but
|
||
it is single-host: one server process drives all agents. It does not scale horizontally or
|
||
isolate tenants at the infrastructure layer.
|
||
|
||
## Options considered
|
||
|
||
| Engine | Verdict |
|
||
|---|---|
|
||
| **clawverse** (Rust, distributed mission orchestrator) | Closest to "distributed agent fleet," but mission/repo-shaped, partly WIP, and re-implements node scheduling/allocation/health that Kubernetes already provides. **Shelved** — revisit only if one tenant must span many nodes/GPUs, or for its mission/self-improvement IP. |
|
||
| **openclaw** (Node agent OS) | Most complete *single-host* agent OS, but Node (our stack is Rust), single-tenant by design, and we lack confirmed source + license to fork/ship it. **Not the base.** |
|
||
| **zeroclaw** (`~/projects/zeroclaw`, Rust, MIT/Apache) | Multi-agent-per-daemon runtime; ownable, single-binary, embeddable, ~80 providers, 6-layer sandbox, gateway/RPC drive API. **Chosen as the per-tenant runtime.** |
|
||
|
||
## Decision
|
||
|
||
**Run one containerized ZeroClaw daemon per workspace/tenant.** The unit of scaling becomes the
|
||
*tenant container*, placed and lifecycle-managed by a standard orchestrator (Kubernetes or Fly
|
||
Machines) — which replaces a bespoke node/allocation layer. Clawmates remains the **control
|
||
plane**: tenancy, auth, billing, the audited SSE gateway, and the **§15 safety engine**. The
|
||
tenant's agents reach the outside world **only** through Clawmates-gated capabilities.
|
||
|
||
ZeroClaw **v0.8.0** makes this viable nearly out of the box:
|
||
- **Multi-agent per daemon** — isolated workspace/memory/model/policy/persona per agent → one
|
||
container = one tenant's whole team.
|
||
- **RPC transport** (local socket / remote WSS + token) with **restart-surviving, tmux-style
|
||
sessions** → clean drive channel and clean scale-to-zero/resume.
|
||
- **Schema-V3 config CRUD** over the gateway (`/api/config/*`) → declarative provisioning.
|
||
- **Per-agent tool allowlists + MCP client + private-host allowlists** → the hook to make
|
||
Clawmates the only outbound door.
|
||
- **Attribution-aware structured logs** + **per-agent/per-model cost tracking** → feed the
|
||
audited journal and billing.
|
||
- **Lean channel bundle as Cargo features** (`crates/zeroclaw-channels/Cargo.toml`:
|
||
`default-channels = acp-server, email, telegram, webhook`) → tiny, fast-cold-start image.
|
||
- **MIT/Apache license** → we may fork, containerize, and ship it.
|
||
|
||
## Existing infrastructure & what we reuse (this is already proven here)
|
||
|
||
The per-tenant ZeroClaw-in-a-container model is **already running in production** in this fleet
|
||
(ClawBooks, EasyA, gr33t). Clawmates reuses that proven stack rather than introducing K8s/Fly:
|
||
|
||
- **Substrate:** Docker + Nginx/Traefik + a per-app **orchestrator** on Hetzner VMs (Tailscale mesh).
|
||
The orchestrator (`:3500`) provisions/deprovisions a container per user; a webhook bridge (`:3501`)
|
||
reacts to Clerk `user.created`; Nginx routes `/ws/agent/{userId}` → the container.
|
||
- **Per-user container:** ZeroClaw binary + a small domain sidecar + plugins/skills.
|
||
- **Per-user volume:** `/data/users/{id}/` = `config.toml`, `domain.db` (SQLite), `brain.h5`
|
||
(ZeroClaw's HDF5/EdgeHDF5 memory) , `workspace/`, `channels/`. Backup = **2 portable files**
|
||
(`domain.db` + `brain.h5`) rsynced offsite — trivial tenant move/restore.
|
||
- **Image registry:** private registry at `web-01:5000` (Tailscale-only).
|
||
- **Auth + billing:** Clerk + Stripe (Clawmates already supports Clerk auth mode).
|
||
- **Bootstrap scripts (reuse):** `redclawsystems/zeroclaw → deploy/clawbooks/bootstrap/`
|
||
(`scale-up.sh` = Hetzner provision + bootstrap + deploy in ~5 min; `bootstrap-node.sh`;
|
||
`deploy-to-node.sh`).
|
||
- **Secrets:** Infisical at `icarus.lan:8443` (`HETZNER_API_TOKEN_RW/RO`, `ANTHROPIC/OPENAI/…`,
|
||
Clerk, Stripe, Cloudflare, Tailscale). ⚠️ Some are still plaintext in the vault and flagged for
|
||
rotation — rotate before production.
|
||
|
||
### Resources / deploy target (live check)
|
||
|
||
The 6-server Hetzner fleet (Helsinki) has ample headroom; **no new server is needed**. Dev/test
|
||
target: **gw-05** (8c / 15Gi, ~188G free, lightly loaded) — or gw-01 (~234G free). Estimated
|
||
capacity ~50–100 concurrent per-tenant containers across the fleet without new hardware.
|
||
|
||
## Target architecture
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ Clawmates control plane (Rust SaaS) — responsibilities UNCHANGED │
|
||
│ • tenancy, auth, billing, product UI, the single audited SSE gateway │
|
||
│ • §15 engine: approvals · taint · gated categories · single-use │
|
||
│ grants · secret broker (cm-safety, cm-secrets, cm-api) │
|
||
│ • Clawmates MCP server: the ONLY outbound door (email/slack/pay/…) │
|
||
│ • tenant-runtime manager: provision/suspend/resume one container/tenant│
|
||
└───────────────┬────────────────────────────────────────────────────────┘
|
||
│ (a) provision agents via /api/config
|
||
│ (b) run turns via sessions/prompt (SSE) or RPC
|
||
│ (c) pull cost + logs
|
||
│ (d) agent's only egress = our gated MCP tools
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ Per-tenant ZeroClaw daemon (slim container, our fork) │
|
||
│ • N named agents (= N clawmates): per-agent workspace/memory/model │
|
||
│ • NO native outbound channels/secrets; tools.allow locked │
|
||
│ • MCP client → Clawmates MCP server for every sensitive capability │
|
||
└───────────────┬────────────────────────────────────────────────────────┘
|
||
│ spawns sandboxes for shell/browser
|
||
▼
|
||
Per-agent sandboxes — sibling pods / gVisor, brokered via socket-proxy
|
||
▲
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ Substrate: Hetzner VMs (Tailscale) · Docker · Nginx/Traefik · │
|
||
│ per-app orchestrator (:3500) + Clerk webhook bridge (:3501) │
|
||
│ — the EXISTING proven ClawBooks/EasyA stack (replaces clawverse). │
|
||
│ K8s/Fly only if scale later demands it. │
|
||
└──────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Concept mapping
|
||
|
||
| Clawmates | ZeroClaw |
|
||
|---|---|
|
||
| workspace / tenant | one daemon (one container) |
|
||
| claw (agent) | one named agent (isolated workspace/memory/model/policy) |
|
||
| chat session / turn | `POST /api/sessions/new` + `POST /api/session/{id}/prompt` (SSE), or an RPC session |
|
||
| the claw's "computer" / files | `/api/agents/{alias}/workspace/*` + a sibling sandbox for shell/browser |
|
||
| routine | a zeroclaw cron job bound to that agent |
|
||
| credits / billing | `/api/cost` (per-agent / per-model splits, cached-input tokens) |
|
||
| audit journal (`run_events`) | gateway attribution-aware logs, mirrored into `run_events` |
|
||
| §15 outbound action | a **Clawmates MCP tool** the agent calls — we gate + broker + journal |
|
||
|
||
## §15 — the safety contract stays in Clawmates
|
||
|
||
Two independent layers:
|
||
|
||
1. **No native edges.** Each tenant agent is provisioned with `tools.allow` excluding native
|
||
outbound/dangerous tools and with **no real channel or secret config**. It physically cannot
|
||
email, post, pay, or exfiltrate on its own.
|
||
2. **One gated door.** Clawmates exposes an **MCP server** of the sensitive capabilities (send
|
||
email, post to Slack, move money, delete data, change access, grant infra access). When an
|
||
agent invokes one:
|
||
- the call lands in Clawmates → classify `Effect` + `TaintSource` (reuse `cm-tools` policy),
|
||
- if gated → create an `approvals` row, render a preview, **suspend**, await a human decision,
|
||
- on approve → consume the single-use `execution_grants` row, **broker-execute with the
|
||
secret** (`cm-secrets`, never exposing the credential), and journal to `run_events`.
|
||
|
||
This reuses the existing approval state machine (`cm-safety`), secret broker (`cm-secrets`),
|
||
and the audited gateway (`cm-api`) verbatim. Defense-in-depth: run agents at ZeroClaw's
|
||
**Supervised** risk profile so its own per-dispatch allowlist + `ask_operator` back-stop us.
|
||
|
||
The §15 properties (taint-by-default, gated categories, single audited channel, single-use
|
||
grants, broker isolation) are therefore **unchanged** — they move from gating `cm-runtime`'s
|
||
in-process tools to gating the MCP boundary.
|
||
|
||
## Slim per-tenant image
|
||
|
||
- Fork/vendor zeroclaw; build with only needed features
|
||
(`--no-default-features --features channel-webhook` + MCP client), no native chat channels.
|
||
Push to the private registry at `web-01:5000`.
|
||
- Gateway bound to an in-pod socket / WSS with a per-tenant token; Nginx routes
|
||
`/ws/agent/{userId}` → the container (existing convention).
|
||
- Per-tenant **persistent volume** at `/data/users/{id}/` (`config.toml`, `domain.db`,
|
||
`brain.h5`, `workspace/`) — the proven 2-file (`domain.db` + `brain.h5`) backup/restore applies.
|
||
- Provider keys injected at start via env (`ZEROCLAW_*`) from Infisical / the Clawmates broker,
|
||
not baked into the image.
|
||
|
||
## Risks / open questions
|
||
|
||
- **Idle economics** — a container per user is costly; the existing orchestrator can **stop idle
|
||
containers** and start them on next request (per-tenant volume persists; v0.8 restart-surviving
|
||
sessions make resume clean). Tune idle TTL / wake latency; K8s+KEDA or Fly auto-stop only if we
|
||
outgrow the orchestrator.
|
||
- **Nested sandboxing** — tool sandboxes inside a tenant container; prefer **sibling** sandbox
|
||
pods via the existing `socket-proxy` pattern over privileged DinD; evaluate gVisor/sysbox.
|
||
- **§15 coverage** — must guarantee *no* un-gated path to the outside; audit zeroclaw's native
|
||
tool set per build and lock `tools.allow`.
|
||
- **Fork maintenance** — zeroclaw is v0.8 **beta**, workspace `publish=false`; maintain a thin
|
||
fork/vendor and track upstream.
|
||
- **Provisioning contract** — Schema-V3 config CRUD is the provisioning API; pin its behavior.
|
||
|
||
## Phased delivery
|
||
|
||
- **Phase 0 — this document.** Decision + architecture + §15-via-MCP + risks. ✅
|
||
- **Phase 1 — Spike (on gw-05).** Build a slim zeroclaw container (push to `web-01:5000`); from a
|
||
small Clawmates-side client, provision one agent via `/api/config`, run one turn via
|
||
`sessions/prompt`, stream events back, and prove a §15 gate by routing one outbound action
|
||
through a stub Clawmates MCP tool. Reuse the ClawBooks `deploy/clawbooks/bootstrap/` scripts as
|
||
the starting point.
|
||
- **Phase 2 — Provisioning + lifecycle.** Adapt the existing orchestrator (`:3500`) + Clerk
|
||
webhook (`:3501`) + Nginx pattern to provision/stop/resume a Clawmates tenant container +
|
||
per-claw agent on workspace/claw create.
|
||
- **Phase 3 — §15 MCP server + journal/cost bridge.** Real gated MCP capabilities (`cm-safety` /
|
||
`cm-secrets`); map zeroclaw attribution logs + `/api/cost` into `run_events` / billing.
|
||
- **Phase 4 — Idle stop/resume + per-agent sandboxes (sibling containers via socket-proxy).**
|
||
- **Phase 5 — Cutover** from `cm-runtime` to the containerized runtime behind a feature flag.
|
||
|
||
> Reuse source-of-truth: `redclawsystems/zeroclaw → deploy/clawbooks/bootstrap/` and the
|
||
> ClawBooks/EasyA orchestrator. Rotate the plaintext Hetzner/Cloudflare/Clerk tokens (vault
|
||
> `Infrastructure Overview.md`) into Infisical before production.
|
||
|
||
## Verification
|
||
|
||
- Phase 1: end-to-end on a dev box — one claw answers a turn on a containerized zeroclaw via
|
||
Clawmates, events journaled, one approval gate fires through the Clawmates MCP door.
|
||
- Keep Clawmates' E2E + §15 suites (`p0`–`p7`, approval/secret-broker tests) green against the
|
||
new backend behind the flag before cutover.
|
||
- Confirm slim-image cold-start and resume-from-sleep preserve sessions.
|
||
|
||
## References
|
||
|
||
- Clawmates runtime + safety: `crates/cm-runtime/src/runtime.rs`, `crates/cm-safety/`,
|
||
`crates/cm-secrets/`, `crates/cm-api/`, `crates/cm-tools/`, `migrations/0001_init.sql`,
|
||
spec `docs/spec.md` §15.
|
||
- ZeroClaw: `~/projects/zeroclaw` — `crates/zeroclaw-channels/Cargo.toml` (channel features),
|
||
`crates/zeroclaw-gateway/src` (drive API: config / sessions / workspace / cost / cron),
|
||
multi-agent runtime + RPC transport (v0.8.0).
|
||
- Infra & proven pattern (Obsidian vault `~/projects/Valhalla`): `20 Infrastructure/Infrastructure
|
||
Overview.md`, `20 Infrastructure/Tailscale Network Map.md`, `20 Infrastructure/10 Architecture/
|
||
SaaS Platform Template.md`, `20 Infrastructure/30 Runbooks/Credentials Inventory.md`.
|
||
- Reuse: `redclawsystems/zeroclaw → deploy/clawbooks/bootstrap/`; private registry `web-01:5000`;
|
||
Infisical `icarus.lan:8443`; dev/test box `gw-05`.
|