OpenClawMU is a single Node 22+ process. It exposes a JSON-RPC gateway
(default 127.0.0.1:18789), a tenant-scoped HTTP API, and a
control-plane HTTP API. Everything else — channel adapters, sandboxes,
memory, plugins — runs inside that process or in subprocesses it spawns.
1. Provision a tenant
A tenant is a namespace: a directory on disk, a SHA-256-hashed token, a quota budget, a set of channel pairings. You create one with the CLI.
# create a tenant — issues a token, creates /tenants/acme
openclaw tenants create acme
# list all tenants
openclaw tenants list
# rotate the token if it ever leaks
openclaw tenants token rotate acme 2. Authenticate with the token
Every JSON-RPC call and every HTTP request carries the tenant token in
the Authorization header. The gateway hashes the inbound
token with SHA-256 and compares the hash with constant-time
crypto.timingSafeEqual — no early exits on prefix
mismatches, so token-fishing attacks are infeasible.
curl -X POST http://127.0.0.1:18789/rpc \
-H "Authorization: Bearer tk_acme_9F2A...B1E7" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"sessions.create","params":{}}' 3. The dispatcher routes by tenant
Once a request is authenticated, the dispatcher looks at the method
name (e.g. sessions.create, terminal.spawn,
cron.add) and routes it to the per-tenant handler. The
gateway exposes 59 tenant-scoped methods covering tenant management,
terminal, config, agents, sessions, cron, skills, channels, voice
wake, devices, nodes, and health.
Methods that touch state always operate within the tenant's directory — there is no shared state path. Path-traversal is rejected at the boundary; even a method that takes an arbitrary path string will refuse anything that escapes the tenant root.
4. Sandboxes spawn per workload
When an agent run needs to execute code (run a script, install a package, render a chart), the gateway spawns a sandbox. Two modes:
- bubblewrap (Linux only, no root) — the lightweight default. Mount namespace + user namespace + seccomp filter; ~30 ms cold start; suitable for trusted-but-isolated code paths.
- Docker — full container with seccomp, cgroups, and an unprivileged user. Slower cold start; suitable for genuinely untrusted code from external tenants.
Pick per workload, not per cluster. Both modes have identical capability surface so you can move a workload between them with a config flag.
5. Account the cost
Every model call records a token-usage row scoped to the tenant. Cost snapshots roll up token counts × your configured rate card into per-period totals. Quota enforcement is at the request boundary — exceeded quotas return a 429 with the next-window timestamp.
# usage report for the current month
openclaw billing report acme --period current-month --csv > acme.csv
# rate card lives in the gateway config; YAML or JSON
cat ~/.openclaw/config.yaml | yq .billing.rate_card Where everything lives on disk
~/.openclaw/
├── config.yaml # gateway-wide config
├── tenants/
│ ├── acme/
│ │ ├── sessions/ # per-tenant chat sessions
│ │ ├── memory/ # per-tenant vector store (sqlite-vec)
│ │ ├── plugins/ # per-tenant installed skills
│ │ ├── sandbox/ # per-tenant sandbox roots
│ │ ├── cron/ # per-tenant scheduled jobs
│ │ ├── channels/ # per-tenant channel credentials
│ │ └── config.yaml # tenant overlay (admin keys protected)
│ └── globex/
│ └── (same layout)
└── gateway.log That's the whole product surface. Everything else — channel adapters, LLM clients, agent runtime — composes on top of these primitives.