Agent orchestration:
a layer over the OpenClaw runtime
OpenClaw is an open-source CLI agent runtime designed to run on a personal machine. I built the layer that turned it into a multi-tenant service — an HTTP orchestration API, per-user agent assignment, a custom tool extension and an MCP server — deployed across two products.
Internal platform. Built on the open-source OpenClaw agent runtime.
THE RUNTIME — NOT MINE
OpenClaw
An existing open-source CLI agent runtime, authored by its own maintainers and designed to run for one person on one laptop.
Single-user by design
No concept of organisations, no per-user agent ownership, no HTTP service boundary.
Everything described on this page sits above that boundary. The runtime itself was consumed as a dependency, not modified into one.
THE ORCHESTRATION LAYER — WHAT I BUILT
Two FastAPI orchestration backends
OpenClawApi and NetworkChainApi expose the runtime over HTTP — agent CRUD, chat, scheduled jobs, knowledge contexts, integrations and billing.
Custom TypeScript tool extension
265 tools across 57 integrations, loaded into the runtime as an extension rather than forked into it.
MCP server
40 tools over stdio or SSE, a second front-door onto the same surface.
WebSocket JSON-RPC client
One persistent multiplexed connection drives the runtime, replacing per-session process control.
Billing and metering
Usage metered against a versioned price book, with wallet gates on top.
CORE CAPABILITIES
Multi-Tenancy Over a Single-User Runtime
Per-user and per-org agent assignment on a runtime designed to run for one person on one laptop — row-level org and user scoping, soft deletes, federated identity.
Two Front-Doors, One Tool Surface
A custom TypeScript tool extension exposing 265 tools and an MCP server exposing 40 over stdio or SSE, both resolving to the same underlying surface.
57 Integrations
36 of them OAuth 2.0 providers, with every credential value Fernet-encrypted at rest.
Ed25519 Device Handshake
A persistent multiplexed WebSocket JSON-RPC link into the runtime, authenticated by a signed protocol-v3 payload.
Qdrant Agent Memory
One agent_memory collection holding both third-party integration data and manual knowledge contexts, separated by payload filter.
Scheduled Agent Jobs
Celery and Celery-Beat run agent jobs and a daily integration sync, backed by a dead-letter queue and hourly retry.
PER-USER AGENT ASSIGNMENT
Per-user / per-org assignment
Agents are assigned to users and organisations on a runtime that had no concept of either.
Row-level scoping
Org and user scoping applied at the row level, with soft deletes so removals stay recoverable.
Federated identity
Identity comes from an upstream backend behind a constant-time shared-secret check — no local user table.
RBAC at the proxy
Filtering is applied at the proxy, so a foreign agent id returns an empty result rather than leaking that it exists.
TOOL SURFACE
TypeScript extension — 265 tools
The broad surface, spanning 57 integrations.
MCP server — 40 tools
Exposed over stdio or SSE for external MCP clients.
Per-agent lazy filtering
Tools are resolved per agent behind a 5-second cache.
Cache-invalidation route
A newly assigned integration appears on the agent's next attempt rather than waiting out the TTL.
Public Q&A guard
Hides all but 5 read-only context tools, so a founder's private integration data can never surface on a public page.
AGENT RUNTIME LINK
Multiplexed WebSocket
One persistent JSON-RPC connection carrying every agent session.
device_id
The sha256 of the raw 32-byte Ed25519 public key.
Signed payload
A pipe-joined protocol-v3 string, signed on handshake.
The runtime is driven as a remote peer over a single persistent connection, so every agent session is multiplexed rather than holding its own transport.
KNOWLEDGE & MEMORY
agent_memory
A single 1536-d Qdrant collection on text-embedding-3-small.
Two sources, one collection
Third-party integration data and manual knowledge contexts, separated by payload filter.
Chunking
800-character chunks with 100-character overlap.
Daily integration sync
Celery-Beat keeps integration data current, with a dead-letter queue and hourly retry.
TENANCY & BILLING CONTROLS
Locked model selection
The model is chosen at agent creation from the allowlist and never updated by PATCH.
Local validation
An unknown model fails fast with a 400 instead of the gateway erroring mid-chat.
Fernet-encrypted credentials
Every stored OAuth credential value is encrypted at rest, across 36 providers.
Versioned price book
Usage metered against a versioned price book so historical spend never shifts under a repricing.
Wallet gates
Balance gates on usage, with automatic cron disabling on a negative balance.
PER-AGENT MODEL ALLOWLIST
The model is locked at agent creation and validated locally, so an unknown value fails fast with a 400 instead of the gateway erroring mid-chat.
Status
The per-user agent-assignment layer is dormant. It ran in production across both products; the surrounding products have since moved on.
TECHNICAL ARCHITECTURE
backend
Python + FastAPI
Two orchestration backends over the runtime
SQLAlchemy 2 + Alembic
Tenancy, agents, integrations and billing
PostgreSQL + Redis
Primary store and broker
platform
TypeScript tool extension
265 tools across 57 integrations
MCP server
40 tools over stdio or SSE
Celery + Celery-Beat
Scheduled jobs with a dead-letter queue
retrieval
Qdrant
agent_memory collection, 1536-d
text-embedding-3-small
800-character chunks, 100-character overlap
Docker
Containerised services end to end
