title: ClawQL Durable Objects — Implementation Spec v0.1.1
ClawQL Durable Objects — Implementation Spec v0.1.1
Hands-on walkthrough: Streams getting started (Learn) — §2 Durable Objects reading order; Labs 1–3 for event backbone today.
Status: Draft · August 2026 · v0.1.1 (companion to Streams v0.2)
Package surface: Cloudflare Durable Objects (hosted) · celld (self-hosted Workers API) · clawql-cellrt (ClawQL-owned, planned) · Kubernetes HPA (regulated)
Depends on: clawql-streams · clawql-celld.md · clawql-cellrt.md · clawql-inference · OpenBenchTrace / RTP
Related: mcp-api-adapter · PorTAL flywheel · celld docs
1. Purpose
This document specifies how ClawQL Streams agent sessions run inside Durable Objects.
Self-hosted DO runtimes: celld (Apache 2.0, Workers/DO API parity) and planned clawql-cellrt (ClawQL-owned Rust + Wasmtime). Not a custom ClawQL runtime on Node worker_threads. Hosted path remains Cloudflare Workers / Durable Objects. Regulated / air-gapped path remains Kubernetes HPA until a DO runtime is production-stable. Integration detail: clawql-celld.md · clawql-cellrt.md. Product decisions: Streams Specification v0.2.
Goals:
- Ephemeral compute per event — spawn → work → destroy; pay for what runs.
- Ephemeral model credentials — virtual key bind-on-create, expire-on-destroy.
- Three sidecars — forensic audit, inference, training-data emission — without conflating them.
- Parity path — same session contract on K8s workers when DOs are unavailable (regulated / air-gapped).
The Durable Objects API is the session contract. Cloudflare implements it on the edge; celld implements it on operator machines with SQLite + LTX replication to an S3-compatible bucket (RPO=0). Miniflare remains a CI / local approximation, not a production self-hosted fleet.
Do not build a custom ClawQL DO runtime on Node worker_threads.
2. Object types
| DO class | Lifetime | Responsibility |
|---|---|---|
SubscriptionDO |
Long-lived (hibernates) | Holds WebSocket/source connection, significance filter, subscription config + rtpConsent, event buffer |
AgentSessionDO |
Ephemeral (per event) | Runs one agent session with three sidecars; destroys itself on exit |
On celld, a GatewayDO (or Gateway Worker) may sit in front for webhook routing and idempotent naming — see clawql-celld.md §4.
Gateway routes by topic → SubscriptionDO. SubscriptionDO spawns AgentSessionDO when the significance filter passes.
3. Sidecars (AgentSessionDO)
AgentSessionDO
├─ AuditSidecar → WORM forensic trail
├─ InferenceSidecar → clawql-inference + virtual key
└─ TrainingDataSidecar → RTP turnSequence → OBT envelope → export
Sidecars remain logical concerns (in-process modules or HTTP). Under celld, session bookkeeping maps to storage.put on the cell SQLite, which celld replicates as LTX to the fleet bucket (platform RPO=0). That LTX stream is durable cell state, not the compliance clawql-audit trail — AuditSidecar / Lab 5b dual-write consequential events to host clawql-audit (HTTP or process WORM). Under Cloudflare, the same storage.put API writes to platform DO storage. Under Kubernetes, bookkeeping may use Postgres/JSONL; the logical event schema is unchanged.
3.1 AuditSidecar
Append-only forensic writer for compliance events. Threads virtual key ID through every entry (§5). Does not store PII event bodies — payload hashes only (Streams §6). On celld, do not equate LTX-backed SQLite with this trail — dual-write to host clawql-audit; see streams-celld-evidence.md and clawql-celld.md §5 / §6.
3.2 InferenceSidecar
Calls clawql-inference over fetch() (not subprocess, not embedded in the DO bundle) with a constrained surface:
- No unrestricted filesystem (DO storage / SQLite only)
- HTTP egress only to approved model endpoints (policy manifest via inference gateway)
- Virtual key required for every
/v1/*call - PAL routing + semantic cache + Langfuse/OTel as on the standalone gateway
DO/cell is an additional deployment context for sessions that use clawql-inference alongside stdio / HTTP / K8s. Bundle limits (64 MiB code / 1 MiB env) require inference to stay out of process — Streams v0.2 §3.1.
3.3 TrainingDataSidecar
Accumulates RTP six-node turnSequence in DO SQLite as tools and model calls complete. On session close, wraps in OpenBenchTrace outer envelope and flushes to the configured export destination (Streams §7). Distinct from WORM: training structure vs forensic chain.
4. Spawn contract (gateway)
Before the AgentSessionDO exists, the gateway (not the DO) atomically:
- Allocates
doInstanceId(and stable celld/Cloudflare name, e.g.sess:\{subscriptionId\}:\{eventId\}) - Asks clawql-inference to issue
virtualKeyIdscoped todoInstanceId - Writes WORM
DO_CREATEDwith\{ doInstanceId, virtualKeyId, subscriptionId, eventHash, manifestId \} - If credit-gated:
DeductionService.hold(estimatedCost) - Spawns DO with environment bindings:
DO_INSTANCE_ID,VIRTUAL_KEY,SUBSCRIPTION_ID,MANIFEST_ID,RTP_CONSENT_JWT,POLICY_ALIAS,INFERENCE_URL
If key issuance fails, no DO is spawned and no hold is left open.
5. Virtual key lifecycle
| Phase | Action |
|---|---|
| Create | Issue key scoped to DO instance ID; budget = budgetTokens or derived from budgetUsd; TTL = maxTurns × estimated_turn_duration; PAL from subscription model alias |
| Run | Every model call uses only this key via fetch(clawql-inference); call-store rows include virtual_key_id |
| Close | Capture actual spend; flush training record; WORM DO_DESTROYED; expire key → subsequent use is 401; DO destroys itself |
| Abnormal | Eviction / timeout / cell migration: hold expires with key TTL; WORM best-effort close; key still expired by TTL |
Security property: compromised credentials from a finished session are already dead. Long-lived service accounts across sessions are out of scope for Streams agent DOs.
6. WebSocket and hibernation
- SubscriptionDO uses WebSocket (or DO-compatible transport) so hibernation can sleep between events while keeping the connection (Cloudflare and celld).
- Outbound source WebSockets: persist reconnect intent in SQLite and use
setAlarm— neversetInterval(throws on celld). Outbound sockets do not survive celld node migration — reconnect after activation (clawql-celld.md§3). - Agent sessions that need a persistent client channel (e.g. mcp-api-adapter sixth surface) also prefer WebSocket for hibernation-friendly sessions; Streamable HTTP remains the fallback for clients that cannot do WebSocket.
- HTTP-only long polls do not hibernate cleanly on Cloudflare/celld DOs — document as a DO-path limitation; K8s path may keep HTTP workers warm instead.
7. SQLite schema (AgentSessionDO, illustrative)
| Table | Contents |
|---|---|
session_meta |
doInstanceId, subscriptionId, virtualKeyId, manifestId, startedAt, exitReason |
rtp_turns |
ordered RTP nodes (Intent → … → Verdict) as JSON rows |
inference_calls |
mirror of call-store fields needed for OBT (tier, tokens, cache) |
tool_calls |
tool name, args hash, ATR result |
export_status |
pending / flushed / failed + destination |
On celld, append-only worm:* keys/tables in the same SQLite are replicated via LTX and constitute the forensic trail.
8. Session close sequence
1. Agent loop exits (converged | maxTurns | budget | error | timeout)
2. TrainingDataSidecar: finalize RTP Verdict node; wrap OBT; export
3. DeductionService.capture(actual) or release on failure (if held)
4. AuditSidecar: DO_DESTROYED + spend summary (storage.put → LTX on celld)
5. InferenceSidecar: expire virtual key
6. Clear sensitive SQLite; DO stub destroys / becomes reclaimable
Order matters: export and capture before key expiry so the last inference metadata is still attributable; key expiry before destroy so no orphan process can reuse the binding.
9. Self-hosted parity
| Concern | Cloudflare DO | celld | K8s session worker |
|---|---|---|---|
| Isolation | Per-object isolate | Per-cell isolate (single writer) | Pod / Job per event (or pool with hard reset) |
| SQLite | DO storage | DO storage + LTX → operator bucket | Ephemeral volume or better-sqlite3 in worker |
| Hibernation | Native WS hibernation | Same DO state model | Scale-to-zero / idle timeout (not identical) |
| Virtual key | Same clawql-inference API | Same | Same |
| Sidecars | In-process modules | In-process modules (same code) | In-process modules (same code) |
| WORM | Platform DO storage | LTX on fleet bucket (sqlite3 audit) |
Postgres / JSONL |
| Scale signal | DO platform | Fleet density / alarms | NATS consumer lag → HPA (Streams §9) |
Parity target: same session contract and WORM/RTP schemas. Exact hibernation and cold-start numbers will differ — document in operator runbooks. Prefer celld or clawql-cellrt over inventing a Node DO runtime; prefer K8s HPA when alpha / single-app-fleet limits are unacceptable (clawql-celld.md §8, §11).
10. mcp-api-adapter on DOs
Optional: a long-lived DO wraps one MCP upstream and serves OpenAPI / GraphQL / /mcp / gRPC / WebSocket from SQLite-cached ListTools. Shipped Lab 5b path: cells call mcp-api-adapter out-of-process via fetch(CLAWQL_MCP_ADAPTER_URL) (thin REST client in the Worker; Express/gRPC stay off-bundle). A future Workers-safe adapter slim could embed later under the 64 MiB budget — that is not what docs/examples/streams-celld ships today. gen-cli stays build-time. Catalog refresh on wake avoids re-ListTools on every request after hibernation. See Streams §3.1 / §12, clawql-celld.md §5, and mcp-api-adapter.
11. Security checklist
- Virtual key never logged in plaintext; WORM stores key ID only
- ATR
allowedToolsenforced on every tool call inside the DO - Event payload hashed for WORM; full body only in TTL-bounded buffer / encrypted cold store
- Egress allowlist for InferenceSidecar (
fetchto clawql-inference only as designed) -
rtpConsentJWT validated before TrainingDataSidecar export - Manifest ID injected from Cosign-signed release, not free-form client input
- celld: peers on private/WireGuard mesh; ingress TLS; bucket creds scoped (
clawql-celld.md§8)
12. Open questions
- Bundle fit:
streams-slim+ thin fetch clients under 64 MiB today; optional future Workers-safeclawql-api/ adapter slim (Streams §15, evidence). - Whether SubscriptionDO and AgentSessionDO share a DO namespace or separate script names for IAM (Cloudflare) / fleet isolation (celld).
- Miniflare maturity vs
celld diagnosesmoke for CI parity. - Batching: one AgentSessionDO per batch window vs N DOs with a parent batch coordinator.
- celld multi-tenant: separate fleets per org until upstream scheduler exists.
Further reading
docs/streams/clawql-streams.md— Streams Specification v0.2docs/streams/clawql-celld.md— celld integration (constraints, LTX, deploy)docs/streams/clawql-cellrt.md— ClawQL-owned Rust + Wasmtime cell runtimedocs/streams/clawql-tee.md— hardware TEE + attestation-gated secretsdocs/streams/clawql-tee-airgap-audit.md— QR air-gap audit transportdocs/inference/clawql-inference.md— virtual keys, PAL, call storedocs/benchmarks/openbench-trace-collection.md— OBT + RTP- celld · docs · limitations · security
- Essay: OpenBenchTrace and RTP
- Essay: What Convergence Week actually proved