Skip to main content
PlatformDurable ObjectsDraft

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:

  1. Ephemeral compute per event — spawn → work → destroy; pay for what runs.
  2. Ephemeral model credentials — virtual key bind-on-create, expire-on-destroy.
  3. Three sidecars — forensic audit, inference, training-data emission — without conflating them.
  4. 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:

  1. Allocates doInstanceId (and stable celld/Cloudflare name, e.g. sess:\{subscriptionId\}:\{eventId\})
  2. Asks clawql-inference to issue virtualKeyId scoped to doInstanceId
  3. Writes WORM DO_CREATED with \{ doInstanceId, virtualKeyId, subscriptionId, eventHash, manifestId \}
  4. If credit-gated: DeductionService.hold(estimatedCost)
  5. 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 — never setInterval (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 allowedTools enforced 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 (fetch to clawql-inference only as designed)
  • rtpConsent JWT 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

  1. Bundle fit: streams-slim + thin fetch clients under 64 MiB today; optional future Workers-safe clawql-api / adapter slim (Streams §15, evidence).
  2. Whether SubscriptionDO and AgentSessionDO share a DO namespace or separate script names for IAM (Cloudflare) / fleet isolation (celld).
  3. Miniflare maturity vs celld diagnose smoke for CI parity.
  4. Batching: one AgentSessionDO per batch window vs N DOs with a parent batch coordinator.
  5. celld multi-tenant: separate fleets per org until upstream scheduler exists.

Further reading