Architecture

How OpenClawMU works.

One Node process, many isolated tenants. Five steps from npm install to your first multi-tenant inbound message.

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.

Tenant isolation in OpenClawMU A single OpenClawMU gateway authenticates tenants by token, then routes each tenant to its own isolated session store, sandbox, memory, and quota meter. WhatsApp · Telegram Slack · Discord Signal · iMessage Teams · Matrix · … openclaw gateway :18789 · token auth tenant: acme tk_acme_9F2A···B1E7 ⎯ /tenants/acme ✓ sandbox · memory · quota tenant: globex tk_globex_4C7D···E2A9 ⎯ /tenants/globex ✓ sandbox · memory · quota tenant: initech tk_initech_72BA···CC03 ⎯ /tenants/initech ✓ sandbox · memory · quota channels in → token-routed dispatch isolated workloads
Each tenant gets its own session store, memory, sandbox, cron jobs, device pairings, and quota meter — behind a single gateway port.

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.

bash
# 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.

bash
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.

bash
# 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

filesystem layout
~/.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.

EXFOLIATE!

Run your own gateway today.

Apache-2.0, self-hosted, no SaaS layer between you and your users. Install the CLI, create your first tenant, mint a token — you're routing traffic in 60 seconds.