Architecture
oma is built around a meta-harness architecture. Instead of prescribing how to run an agent loop, it provides a platform that every agent loop needs: durable sessions, sandboxed execution, credential isolation, tools, memory, and crash recovery. You bring the harness (the agent loop itself) or use the built-in default.
High-level picture
Section titled “High-level picture”graph TB subgraph Harness["Harness (the brain — your code)"] direction TB H1["Reads events, builds context, calls the model"] H2["Decides HOW: caching, compaction, tool delivery"] H3["Stateless: crash → rebuild from event log → resume"] end
subgraph Platform["Meta-Harness (the platform — SessionDO)"] direction TB P1["Prepares WHAT is available: tools, skills, history"] P2["Manages lifecycle: sandbox, events, WebSocket"] P3["Crash recovery, credential isolation, usage tracking"] end
subgraph Infra["Infrastructure (Cloudflare or Node self-host)"] direction TB I1["Event log: Durable-Object SQLite (CF) or SQLite/Pg"] I2["Sandbox: CF Containers / subprocess / LiteBox / E2B / K8s"] I3["Storage: KV + R2 (CF) or local FS (self-host)"] end
Harness --> Platform --> Infra
style Harness fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px style Platform fill:#fff3e6,stroke:#d97a2e,stroke-width:2px style Infra fill:#f0f0f0,stroke:#666,stroke-width:2pxThe platform prepares what is available. The harness decides how to deliver it to the model.
| Platform manages | Harness decides |
|---|---|
| Event log persistence (SQLite) | Context engineering (filtering, ordering) |
| Sandbox lifecycle (containers) | Caching strategy (cache breakpoints) |
| Tool registration (built-in + MCP) | Compaction strategy (when to compress) |
| WebSocket broadcast | Retry strategy (backoff, transient detection) |
| Crash recovery | Stop conditions (max steps, completion signals) |
| Credential isolation (vaults) | System prompt construction |
| Memory (vector search) | Tool delivery (all at once vs. progressive) |
Deployment options
Section titled “Deployment options”graph LR subgraph CF["☁️ Cloudflare Deployment"] CFW[Workers + DO] --> CFCT[Cloudflare Containers] CFW --> D1[(D1 + KV + R2)] end
subgraph K8s["☸️ Kubernetes Deployment"] K8sB[k8s-bridge] --> K8sP[Sandbox Pods] K8sB --> K8sDB[(Postgres<br/>or SQLite)] end
subgraph Self["🐳 Self-host (Node)"] Node[Node Server] --> Sub[subprocess / LiteBox] Node --> SQL[(SQLite<br/>or Postgres)] end
User((User)) --> CF User((User)) --> K8s User((User)) --> Self
style CF fill:#e6faff,stroke:#4a90d9 style K8s fill:#f0f0ff,stroke:#6666cc style Self fill:#f5f5f5,stroke:#888Two deployment modes
Section titled “Two deployment modes”oma runs the same code in two different environments. The harness, business logic, and event-log model are identical — only the storage and sandbox backends differ.
| Self-host (Node) | Cloudflare | |
|---|---|---|
| Where it lives | Your VPS / Mac / Docker host / fly.io | Cloudflare Workers + DO + Containers |
| Storage | SQLite or Postgres + local FS | D1 + KV + R2 |
| Sandbox | LocalSubprocess / LiteBox / Daytona / E2B / BoxRun | Cloudflare Sandbox (Containers) |
| Startup | docker compose up (~2 min) | wrangler deploy (~10 min once configured) |
Same API. Same /v1/agents / /v1/sessions API. Same Console UI. Same crash-recovery semantics. Switch between them by changing env vars, not code.
Core data flow
Section titled “Core data flow”A typical request flows through the system like this:
sequenceDiagram participant User as User (HTTP/SSE) participant Main as Main Worker<br/>(apps/main) participant DO as SessionDO<br/>(Durable Object) participant Sandbox as Sandbox Container participant Vault as Vault Proxy
User->>Main: POST /v1/sessions/:id/events Main->>Main: Auth middleware (better-auth, API key) Main->>Main: Rate limiter Main->>Main: Tenant resolution (tenantDb from D1) Main->>DO: Route to SessionDO
DO->>DO: Append event to SQLite log DO->>DO: Harness reads events, builds context
DO->>DO: Call LLM with tools DO->>Sandbox: Execute tool (bash, read, write) Sandbox->>Vault: Outbound HTTP request Vault->>Vault: Look up credentials by hostname Vault->>External: Forward with auth header External-->>Vault: Response Vault-->>Sandbox: Response (token stripped) Sandbox-->>DO: Tool result
DO->>DO: Write result to event log DO-->>User: SSE / WebSocket event streamStep by step
Section titled “Step by step”-
Request arrives at
apps/main— the HTTP API worker. Auth is validated, the tenant is resolved, and the request is routed to the appropriate handler. -
SessionDO (
apps/agent) is a Durable Object that owns a single session’s state. Every session gets its own DO instance with a dedicated SQLite database for the event log. -
Harness runs inside the SessionDO. The default harness reads the event log, builds context, calls the LLM, processes tool calls, and writes new events back to the log.
-
Sandbox is a Cloudflare Container (or local subprocess in self-host mode) where tool commands actually execute. Each session gets its own sandbox instance.
-
Vault proxy sits between the sandbox and the internet. When a tool makes an HTTP request, the proxy intercepts it, looks up the session’s vault credentials, injects the auth header, and forwards. The sandbox never sees the raw token.
-
Events stream back to the user via SSE (
GET /v1/sessions/:id/events/stream). The stream replays history on connect and pushes new events in real-time.
Key components
Section titled “Key components”Main Worker (apps/main)
Section titled “Main Worker (apps/main)”The HTTP API layer: agent CRUD, session management, environment config, vaults, memory stores, skills, files, integrations webhooks, and the billing API proxy. All public REST endpoints live here.
It is a thin orchestration layer — it validates auth, resolves the tenant context, and delegates to the appropriate service or Durable Object.
Agent Worker (apps/agent)
Section titled “Agent Worker (apps/agent)”The execution layer: SessionDO (the per-session Durable Object), Sandbox (container orchestration), and the harness runtime. This is where the model is called, tools run, and events are logged.
Each SessionDO holds:
- An append-only event log in DO-storage-backed SQLite
- The harness instance currently driving the session
- Session metadata (agent config snapshot, environment, vault bindings)
SessionDO
Section titled “SessionDO”The heart of the system. A Durable Object that:
- Owns the append-only event log (SQLite via
ctx.storage.sql) - Runs the harness loop (model calls, tool execution)
- Manages sandbox lifecycle (start, keep-alive, stop)
- Broadcasts events to SSE clients
- Handles crash recovery (rebuilds context from the event log)
The event log is the source of truth. Every model message, tool call, tool result, and lifecycle event is durably written before being broadcast. If the DO crashes, a new instance reads the log, rebuilds the harness state, and resumes transparently.
Harness
Section titled “Harness”The agent loop itself. The DefaultHarness (built on the Vercel AI SDK’s generateText) works out of the box:
// Simplified — the harness:1. Reads events from the log2. Builds messages array for the model3. Calls the model with tools4. Executes tool calls in the sandbox5. Writes results back to the event log6. Repeats until stop condition is metYou can replace it with a custom harness — see Custom Integrations.
Sandbox
Section titled “Sandbox”Where tool commands execute. On Cloudflare, each session gets a per-session container. In self-host mode, the sandbox can be:
| Provider | Isolation | Use case |
|---|---|---|
subprocess | None | Fastest, local dev |
litebox | Firecracker microVM | Lightweight isolation |
daytona | Firecracker microVM | Managed sandbox |
e2b | Firecracker microVM | External provider |
boxrun | Container | Docker-based isolation |
cloud (CF) | Cloudflare Container | Production on Workers |
browser-vm | Browser tab (WASM VM) | Zero server compute — Cloudflare only, not_configured in self-host mode |
dynamic-workers | V8 isolate (Worker Loader) | Code Mode / pure JS eval — Cloudflare only, no filesystem or shell |
Vault & Credential Proxy
Section titled “Vault & Credential Proxy”Credentials never enter the sandbox. The outbound proxy intercepts HTTP requests from the sandbox, looks up the session’s vault for a matching credential (by hostname), injects the auth header, and forwards. The sandbox process never sees the raw token.
Supported credential types:
static_bearer— static tokens matched by hostnamemcp_oauth— OAuth tokens with auto-refresh on 401/403cap_cli— CLI tools (gh,aws,wrangler, etc.) authenticated at the network layer
See Vault & MCP for the full design.
Integrations Gateway (apps/integrations)
Section titled “Integrations Gateway (apps/integrations)”A separate worker that handles OAuth flows and webhooks for third-party integrations (Linear, GitHub, Slack). Each integration:
- Registers a webhook endpoint on the integrations worker
- Receives events (issue assigned, PR review requested, Slack @mention)
- Converts them to
user.messageevents on the appropriate session - The agent responds through MCP tools bound to the external service
Crash recovery
Section titled “Crash recovery”The event log makes crash recovery automatic:
- SessionDO crashes mid-execution
- A new DO instance is created on the next request
- The new instance reads the full event log from DO storage
- The harness rebuilds its context from the events
- Processing continues from where it left off
No data is lost because events are durably written to SQLite before being broadcast. The client sees a brief delay while the new instance boots, then the event stream resumes.
sequenceDiagram participant User as User participant DO1 as SessionDO (v1) participant Log as Event Log (SQLite) participant DO2 as SessionDO (v2)
Note over User,DO2: Normal flow User->>DO1: Send message DO1->>Log: Write event (durable) DO1->>DO1: Harness processes DO1->>Log: Write result event DO1-->>User: Broadcast via SSE
Note over User,DO2: Crash recovery User->>DO1: Send message DO1->>Log: Write event (durable) DO1-xDO1: 💥 DO crashes User->>DO2: Next request (new DO instance) DO2->>Log: Read full event log DO2->>DO2: Rebuild harness context DO2->>DO2: Continue processing DO2-->>User: Resume SSE streamMulti-agent delegation
Section titled “Multi-agent delegation”Agents can delegate work to other agents using callable_agents. When an agent has callable agents configured, the platform generates:
call_agent_<name>— one-at-a-time delegation (blocks until the child reaches idle)call_agents_parallel— fan-out to multiple children concurrently (capped at 5-10 parallel calls)
sequenceDiagram participant User participant Parent as Parent Agent participant Child1 as Sub-agent A participant Child2 as Sub-agent B participant Child3 as Sub-agent C
User->>Parent: "Research and summarize X" Parent->>Parent: call_agents_parallel
par Parallel delegation Parent->>Child1: Research topic A Parent->>Child2: Research topic B Parent->>Child3: Research topic C end
Child1-->>Parent: Results A Child2-->>Parent: Results B Child3-->>Parent: Results C
Parent->>Parent: Synthesize results Parent-->>User: Summary with all findingsEach child session gets its own SessionDO with its own event log, sandbox, and credential bindings. Results are aggregated back to the parent.
See Agent configuration for the callable_agents schema.
Session resources
Section titled “Session resources”Sessions can be augmented with external resources at runtime:
| Resource type | Description |
|---|---|
| File | Mount a file into the sandbox at a specific path |
| GitHub repository | Clone a repo into the sandbox (read-only or read-write) |
| Memory store | Mount a persistent memory store at /mnt/memory/<name>/ |
Resources are attached via POST /v1/sessions/:id/resources and removed via DELETE /v1/sessions/:id/resources/:resId.
Observability
Section titled “Observability”The platform emits structured events for every operation:
| Event | Description |
|---|---|
span.model_request_start | Model API call started |
span.model_request_end | Model API call completed (includes token usage) |
span.outcome_evaluation_start | Outcome evaluation began |
These flow through Cloudflare Analytics Engine and can be consumed for monitoring, billing, and debugging. The Console surfaces key metrics (session count, active seconds, credits) on the Dashboard.
Source map
Section titled “Source map”Two runtime flavors of the same control-plane API: apps/main (Cloudflare Worker) and apps/main-node (self-host Node.js server, same API, packaged for docker compose).
| Layer | Cloudflare | Self-host |
|---|---|---|
| API routes | apps/main/src/ | apps/main-node/src/ |
| Routes (shared) | packages/http-routes/ | packages/http-routes/ |
| Session DO | apps/agent/src/ | apps/agent/src/ |
| Harness interface | apps/agent/src/harness/ | apps/agent/src/harness/ |
| Sandbox adapters | packages/sandbox/ | packages/sandbox/ |
| Credential store | packages/credentials-store/ | packages/credentials-store/ |
| Integrations | apps/integrations/ | — (not yet) |
| Console UI | apps/console/ | apps/console/ |