Skip to content

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.

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:2px

The platform prepares what is available. The harness decides how to deliver it to the model.

Platform managesHarness 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 broadcastRetry strategy (backoff, transient detection)
Crash recoveryStop conditions (max steps, completion signals)
Credential isolation (vaults)System prompt construction
Memory (vector search)Tool delivery (all at once vs. progressive)
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:#888

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 livesYour VPS / Mac / Docker host / fly.ioCloudflare Workers + DO + Containers
StorageSQLite or Postgres + local FSD1 + KV + R2
SandboxLocalSubprocess / LiteBox / Daytona / E2B / BoxRunCloudflare Sandbox (Containers)
Startupdocker 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.

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 stream
  1. 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.

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

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

  4. Sandbox is a Cloudflare Container (or local subprocess in self-host mode) where tool commands actually execute. Each session gets its own sandbox instance.

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

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

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.

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)

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.

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 log
2. Builds messages array for the model
3. Calls the model with tools
4. Executes tool calls in the sandbox
5. Writes results back to the event log
6. Repeats until stop condition is met

You can replace it with a custom harness — see Custom Integrations.

Where tool commands execute. On Cloudflare, each session gets a per-session container. In self-host mode, the sandbox can be:

ProviderIsolationUse case
subprocessNoneFastest, local dev
liteboxFirecracker microVMLightweight isolation
daytonaFirecracker microVMManaged sandbox
e2bFirecracker microVMExternal provider
boxrunContainerDocker-based isolation
cloud (CF)Cloudflare ContainerProduction on Workers
browser-vmBrowser tab (WASM VM)Zero server compute — Cloudflare only, not_configured in self-host mode
dynamic-workersV8 isolate (Worker Loader)Code Mode / pure JS eval — Cloudflare only, no filesystem or shell

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 hostname
  • mcp_oauth — OAuth tokens with auto-refresh on 401/403
  • cap_cli — CLI tools (gh, aws, wrangler, etc.) authenticated at the network layer

See Vault & MCP for the full design.

A separate worker that handles OAuth flows and webhooks for third-party integrations (Linear, GitHub, Slack). Each integration:

  1. Registers a webhook endpoint on the integrations worker
  2. Receives events (issue assigned, PR review requested, Slack @mention)
  3. Converts them to user.message events on the appropriate session
  4. The agent responds through MCP tools bound to the external service

The event log makes crash recovery automatic:

  1. SessionDO crashes mid-execution
  2. A new DO instance is created on the next request
  3. The new instance reads the full event log from DO storage
  4. The harness rebuilds its context from the events
  5. 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 stream

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 findings

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

Sessions can be augmented with external resources at runtime:

Resource typeDescription
FileMount a file into the sandbox at a specific path
GitHub repositoryClone a repo into the sandbox (read-only or read-write)
Memory storeMount 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.

The platform emits structured events for every operation:

EventDescription
span.model_request_startModel API call started
span.model_request_endModel API call completed (includes token usage)
span.outcome_evaluation_startOutcome 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.

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

LayerCloudflareSelf-host
API routesapps/main/src/apps/main-node/src/
Routes (shared)packages/http-routes/packages/http-routes/
Session DOapps/agent/src/apps/agent/src/
Harness interfaceapps/agent/src/harness/apps/agent/src/harness/
Sandbox adapterspackages/sandbox/packages/sandbox/
Credential storepackages/credentials-store/packages/credentials-store/
Integrationsapps/integrations/— (not yet)
Console UIapps/console/apps/console/