title: ClawQL Streams — Specification v0.2
ClawQL Streams — Specification v0.2
Hands-on walkthrough: Streams getting started (Learn) — recommended reading order through DO, celld, cellrt, and TEE specs.
Status: Draft · August 2026 · v0.2
Package: clawql-streams (planned)
Depends on: clawql-core · mcp-api-adapter · clawql-inference · clawql-payments · clawql-ouroboros · celld (self-hosted DO) · clawql-cellrt (ClawQL-owned runtime, planned) · NATS JetStream (K8s path) · OpenBenchTrace / RTP (training emission)
Related: mcp-api-adapter · clawql-inference · clawql-durable-objects.md · clawql-celld.md · clawql-cellrt.md · Ouroboros · OpenBenchTrace collection · celld docs · limitations · security · Cloudflare compat
1. What this is
ClawQL Streams is the event-driven autonomous agent execution layer for ClawQL. It extends the existing schedule tool pattern from time-based triggers to event-based triggers — arbitrary event sources fire, ClawQL processes them, and agents act without any human initiating the session.
Together with ClawQL Core (any protocol → MCP) and mcp-api-adapter (MCP → any protocol), Streams completes the Protocol Fabric: MCP as the common intermediate representation, plus an event loop that can act on world events — not only interactive agent sessions.

Marketing landing: clawql.com/streams. WebMCP (navigator.modelContext) is an emerging browser-native source — Chrome preview only; ClawQL Core ingests page-declared tools the same way it ingests REST or GraphQL. HTMX / MCP-UI is the inverse: MCP catalog → human-facing forms on the adapter surface.
v0.2 changes (from v0.1.x)
| Decision | v0.1.x | v0.2 |
| ---------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ---------- | ----------------------------------------- |
| Self-hosted DO runtime | Custom Node worker_threads / Miniflare approximation | celld (Apache 2.0, denoland/celld) |
| Bundle contents | Ambiguous; subprocess spawn to Claude / local tools | In-process clawql-core/streams-slim (audit/cache/hash-chain); MCP + mcp-api-adapter via fetch() (full clawql-streams package still planned) |
| Model calls | Subprocess (claude -p) or mixed | fetch() to clawql-inference only — no child_process |
| WORM replication | Postgres / JSONL (K8s) or DO storage | On celld: LTX → S3 replicates DO SQLite (RPO=0 platform durability) for cell state; compliance WORM remains host clawql-audit (tip-load / dual-ack) — Lab 5b dual-writes via CLAWQL_AUDIT_WORM_URL |
| Scaling backends | kubernetes \ | durable-objects | **kubernetes \ | celld \ | cloudflare** (+ planned **cellrt**) |
Do not build a custom ClawQL DO runtime on Node worker_threads. Use celld for Workers/DO-compatible self-hosted Durable Objects; Cloudflare Workers/DOs for hosted; Kubernetes HPA for regulated / air-gapped until a DO runtime is production-stable. clawql-cellrt is the ClawQL-owned Rust + Wasmtime production runtime (security-first, embedded Vault/inference/observability) — not a Node rewrite.
2. Problem statement
Every major AI lab and enterprise running autonomous agents at scale has built the same thing independently:
- Stripe Minions — Slack reaction → context prefetch → Claude subprocess → PR. Custom internal build. ~500-tool MCP server (Toolshed) built from scratch. Goose fork. No sovereignty, no reuse outside Stripe.
- Anthropic Managed Agents — Cron/API trigger → Claude on Anthropic servers → built-in tools. Metered session-hour pricing. No operator-owned WORM audit. No sovereignty. Multi-agent coordination still research preview.
- OpenAI Codex / Agents SDK — Pipeline anomaly or API call → agent subprocess. Managed runtime. No sovereignty, no WORM Merkle trail, no multi-protocol surface.
All three implement the same logical pattern: external event → context fetch → agent reasoning → tool execution → audit. All three built it for themselves, for one trigger type, on their own infrastructure, with no sovereignty option.
ClawQL Streams is that pattern as a platform: any event source, any trigger type, WORM audit on every action, self-hosted (celld or K8s) or Cloudflare-deployed, infinite scale via cells/DOs or Kubernetes HPA, full ClawQL tool surface available to every agent session.
3. Core architecture
3.1 Cell stack (shipped Lab 5b vs planned)
On the celld / Cloudflare path, each agent cell is a Workers-safe slim core plus out-of-process MCP/adapter/inference — not a sidecar fleet of containers, and not a full Express mcp-api-adapter embed (that would blow the 64 MiB budget and pull Node APIs).
Shipped today (docs/examples/streams-celld): Gateway / Subscription / AgentSession DOs, in-process clawql-core/streams-slim, optional fetch(CLAWQL_MCP_URL) + fetch(CLAWQL_MCP_ADAPTER_URL), LTX audit flush. Evidence matrix: streams-celld-evidence.md.
Still planned: the clawql-streams coordination package (stream_* MCP tools) and a future optional Workers-safe clawql-api slim for offline search.
┌─────────────────────────────────────────────────────────┐
│ celld Durable Object / Cloudflare DO (one cell) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ Streams router │ │ clawql-core/streams-slim │ │
│ │ (filter/spawn; │ │ (audit / cache / hash-chain) │ │
│ │ stream_* TBD) │ │ — in-process │ │
│ └────────┬────────┘ └──────────────┬───────────────┘ │
│ │ │ │
│ └────────────┬─────────────┘ │
│ │ fetch() │
│ ▼ │
│ AgentSessionDO logic │
│ storage.put → SQLite → LTX (WORM) │
│ setAlarm (reconnect / TTL / batch) │
└───────────────────────┬─────────────────────────────────┘
│ fetch() only
▼
clawql-inference (HTTP)
(PAL · virtual keys · call store)
Constraints (Workers / celld Code Mode):
| Limit | Value | Implication |
|---|---|---|
| Bundle size | 64 MiB code | Tree-shake; exclude Node-only deps; CI fails if esbuild output exceeds 64MB |
| Env / bindings | 1 MiB env | Provider specs and secrets must stay lean; large catalogs live in SQLite or remote fetch |
| Process model | No child_process |
Model + catalog tools via fetch(); slim audit/cache stay in-process |
| Timers | No setInterval |
Use setAlarm + SQLite intent rows |
clawql-inference stays out of process — called over HTTP — so the DO bundle does not embed model SDKs, credential stores, or PAL routing tables that change independently of Streams releases.
See clawql-celld.md for the full runtime matrix and workarounds.
3.2 Event loop
Event source (WebSocket / NATS / webhook / cron / API poll / …)
│
▼
Streams router (Gateway Worker / GatewayDO — thin, stateless or named)
│
per event:
├─ publish / buffer (NATS on K8s; SQLite + LTX on celld)
├─ WORM append (always) — payload hashed, not stored plaintext
│
└─ significance filter (local, fast — no model call)
│
├─ below threshold: buffer only (Reactive + Ambient)
│
└─ above threshold (Autonomous):
│
├─ spawn AgentSessionDO / cell
│ (virtual key bind-on-create)
│
└─ agent runs in-process MCP tools:
memory_recall · search · execute
notify · stream_read · memory_ingest
+ adapter-exposed upstream tools
└─ fetch(clawql-inference) for every model turn
ClawQL Streams is the event loop itself. The MCP client-initiated limitation only applies when pushing to Cursor/Claude Desktop; Streams does not wait for an interactive client to start work.
3.3 Three delivery modes
Every event source supports all three simultaneously:
| Mode | Mechanism | Best for |
|---|---|---|
| Reactive | Event → WORM immediately | Audit trail, compliance, always-on recording |
| Ambient | Event → buffer → delivered on next MCP tool call | Agent awareness without spawning a session |
| Autonomous | Event → significance filter → AgentSessionDO + inference | Act on events without human initiation |
Autonomous mode is the primary new capability. Reactive and ambient modes extend existing ClawQL behavior (WORM audit and buffered delivery).
3.4 Significance filter
Before spawning an agent session, ClawQL runs a local significance check — fast, cheap, no model call. Only events that pass are escalated to autonomous execution. Everything still goes to the durable buffer and WORM regardless.
Filter types (configurable per subscription):
- Threshold — numeric value crosses a boundary (price delta, error rate, queue depth)
- Pattern — event content matches a regex or JSON path expression
- Rate — N events within T seconds (burst detection)
- Composite — AND/OR of the above
- Always — every event spawns an agent (high-value, low-volume sources)
- Never — buffer and ambient delivery only (high-volume, audit-only sources)
3.5 Agent DO session
When the significance filter passes, Streams spawns an AgentSessionDO. The model layer is always clawql-inference via fetch() — not a raw Anthropic/OpenAI credential and not a subprocess. The model field on stream_subscribe is a policy alias that resolves through PAL routing (Frugal → Standard → Frontier).
export class AgentSessionDO extends DurableObject {
async fetch(request: Request): Promise<Response> {
const env = this.env as AgentEnv
const body = await request.json<SpawnPayload>()
// WORM: bind IDs before any model call
await this.ctx.storage.put('session_meta', {
doInstanceId: body.doInstanceId,
virtualKeyId: body.virtualKeyId,
subscriptionId: body.subscriptionId,
eventHash: body.eventHash,
startedAt: Date.now(),
})
// Schedule hard TTL (no setInterval)
await this.ctx.storage.setAlarm(Date.now() + body.ttlMs)
const result = await runAgentLoop({
prompt: body.prompt,
context: body.context,
tools: body.allowedTools,
maxTurns: body.maxTurns,
// Tools: fetch(CLAWQL_MCP_URL) / fetch(CLAWQL_MCP_ADAPTER_URL) — not Express embed
mcp: env.MCP_FETCH,
// Model: fetch only — never child_process
inference: async (req) =>
fetch(env.INFERENCE_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${body.virtualKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(req),
}),
onWorm: async (entry) => {
// Append-only trail; LTX ships to bucket (celld) / DO storage (CF)
const seq = ((await this.ctx.storage.get<number>('worm_seq')) ?? 0) + 1
await this.ctx.storage.put(`worm:${seq}`, entry)
await this.ctx.storage.put('worm_seq', seq)
},
})
await this.closeSession(result)
return Response.json(result)
}
async alarm(): Promise<void> {
// TTL / reconnect / batch drain — same handler, intent in SQLite
const intent = await this.ctx.storage.get<AlarmIntent>('alarm_intent')
if (intent?.kind === 'session_ttl') {
await this.closeSession({ exitReason: 'timeout' })
}
}
}
Every session gets:
- Policy manifest enforcement before any model call
- PAL routing based on task complexity
- WORM call-store correlation for every inference call (virtual key ID threaded)
- Budget enforcement via a per-session virtual key
- Full ClawQL MCP tool surface, scoped by ATR claims on the subscription — event content cannot expand scope
3.6 WebSocket source + reconnection
Outbound WebSockets from a cell keep the cell resident. On celld, an outbound socket does not survive cell migration to another node (limitations). Pattern:
- Persist connection intent in SQLite (
sourceUrl,authRef,lastEventId,backoffMs). - Open the WebSocket from the DO constructor /
fetchwake path. - On close or ownership change: write intent, call
setAlarmwith exponential backoff — neversetInterval(throws on celld). - On alarm: reconnect, resume from
lastEventIdwhen the source supports it.
async alarm(): Promise<void> {
const intent = await this.ctx.storage.get<WsIntent>("ws_intent");
if (!intent?.reconnect) return;
try {
await this.connectWebSocket(intent);
await this.ctx.storage.put("ws_intent", { ...intent, backoffMs: 1_000 });
} catch {
const next = Math.min((intent.backoffMs ?? 1_000) * 2, 60_000);
await this.ctx.storage.put("ws_intent", { ...intent, backoffMs: next });
await this.ctx.storage.setAlarm(Date.now() + next);
}
}
Inbound hibernatable WebSockets remain preferred for client-facing SubscriptionDO channels (Cloudflare and celld both support them).
3.7 Virtual key lifecycle
Credentials for the model layer are ephemeral and bound to the session (DO/cell) instance:
stream_subscribe event passes significance filter
│
▼
Gateway creates AgentSessionDO id + asks clawql-inference for virtual key
│
├─ key scoped to: this DO / session instance ID
├─ budget: subscription.budgetTokens (or budgetUsd → tokens)
├─ TTL: maxTurns × estimated_turn_duration
├─ PAL policy: from subscription.model alias
│
├─ WORM DO_CREATED { doInstanceId, virtualKeyId, eventHash, … }
└─ spawn DO with VIRTUAL_KEY binding (never log plaintext)
Power of 10 / correctness: maxTurns and budgetTokens (or equivalent USD→token budget) must be required on autonomous subscriptions — not optional soft defaults. An unbound consumer loop is the agentic equivalent of an unbounded flight-software loop. See correctness-by-construction.md §2 / §6. Failed WORM append after session start → fail closed (halt cell), never continue unaudited.
Session completes (converged / budget / timeout / eviction)
│
├─ DeductionService capture (actual) if credit-gated
├─ TrainingDataSidecar flushes RTP/OBT (§7)
├─ WORM DO_DESTROYED (includes virtual key ID)
└─ clawql-inference expires key → further calls 401
cell / DO becomes reclaimable
Why this matters: a leaked key from a destroyed session is useless — scope is the instance ID, and TTL is seconds to minutes. Managed agent runtimes typically reuse long-lived service credentials across sessions; Streams does not.
Gateway generates both the DO instance ID and the virtual key ID before spawn, writes DO_CREATED to WORM with both IDs, then injects them. That avoids an audit gap between create and key issuance.
Credit-gated subscriptions (budgetUsd): virtual key creation triggers DeductionService.hold; destroy triggers capture(actual) (or hold TTL expiry on abnormal destroy).
4. Event sources
All event sources implement the StreamSource interface:
interface StreamSource {
connect(): Promise<void>
disconnect(): Promise<void>
on(event: 'message', handler: (msg: StreamMessage) => void): void
topic: string
sourceType: StreamSourceType
}
type StreamSourceType =
| 'websocket'
| 'nats'
| 'webhook'
| 'cron' // existing schedule tool, unified here
| 'api_poll' // polling via setAlarm, not setInterval
| 'grpc_stream' // gRPC server streaming (K8s path; DO via adapter where feasible)
| 'sse' // Server-Sent Events
| 'qr' // optical air-gap — see clawql-qr-stream-transport.md
| 'kafka' // optional, enterprise — open question §15
| 'kinesis' // optional, AWS regulated
4.1 WebSocket source
stream_subscribe({
source: 'wss://stream.example.com/events',
topic: 'market.AAPL',
sourceType: 'websocket',
auth: { type: 'bearer', tokenEnv: 'STREAM_API_KEY' },
prompt: 'Analyze this price event. If delta > 2% notify #trading-desk.',
recallQuery: 'AAPL prior positions and thresholds',
significance: {
type: 'threshold',
path: '$.delta_pct',
operator: '>',
value: 1.0,
},
model: 'claude-sonnet-4-6',
maxTurns: 5,
budgetTokens: 4000,
allowedTools: ['memory_recall', 'notify', 'memory_ingest'],
auditLevel: 'WORM',
})
4.2 Webhook source
ClawQL exposes POST /streams/webhook/\{subscriptionId\} as an inbound HTTP endpoint. Any system that can POST JSON fires events into the subscription.
stream_subscribe({
sourceType: 'webhook',
topic: 'github.pr_merged',
prompt: 'A PR was merged. Update the vault with what changed and why.',
significance: { type: 'always' },
allowedTools: ['memory_ingest', 'web_fetch'],
})
// Returns: { webhookUrl: 'https://your-clawql/streams/webhook/sub_abc123' }
4.3 Cron source (existing schedule unified)
The existing schedule tool is a special case of stream_subscribe with sourceType: "cron". Both interfaces remain valid; schedule continues to work unchanged. Internally both use the same fiber and WORM infrastructure. On celld, cron wake uses setAlarm (no platform scheduled handler — see compat).
4.4 NATS source
Subscribe to a NATS subject as an event source — useful when other ClawQL instances or external systems already publish to NATS (primary on the Kubernetes scaling backend):
stream_subscribe({
sourceType: 'nats',
topic: 'clawql.idp.document_processed',
prompt:
'A document finished processing. Ingest the extraction summary to vault.',
significance: { type: 'always' },
allowedTools: ['memory_ingest', 'knowledge_search_onyx'],
})
4.5 API poll source
Poll a REST endpoint on an interval, treating each changed response as an event. On celld/Cloudflare, the interval is implemented with setAlarm, not setInterval:
stream_subscribe({
sourceType: 'api_poll',
url: 'https://api.example.com/status',
intervalMs: 30000,
topic: 'system.status',
changeDetection: 'json_diff',
prompt: 'System status changed. If any service is degraded, notify #ops.',
significance: { type: 'pattern', path: '$.status', pattern: 'degraded|down' },
allowedTools: ['notify', 'memory_ingest'],
})
5. MCP tools
CLAWQL_ENABLE_STREAMS=1 (default on when any stream source is configured).
Management tools
| Tool | Description |
|---|---|
stream_subscribe |
Create a subscription with source, prompt, significance filter, and tool scope |
stream_unsubscribe |
Stop a subscription by ID |
stream_list |
List active subscriptions with status, event counts, and last-fire timestamp |
stream_status |
Health and buffer depth for a subscription |
stream_pause / stream_resume |
Temporarily suspend without destroying the subscription |
Consumption tools
| Tool | Description |
|---|---|
stream_read |
Read buffered events for a topic (manual drain) |
stream_replay |
Replay events from a time window |
stream_pending |
Return count of unread events across all active subscriptions |
Ambient delivery
On every MCP tool call, if CLAWQL_STREAMS_AMBIENT_DELIVERY=1, ClawQL checks the buffer for pending events and appends a pendingStreamEvents field to the tool response:
{
"result": { "...normal tool result..." },
"pendingStreamEvents": [
{
"topic": "market.AAPL",
"count": 3,
"summaries": ["AAPL +2.3% on earnings beat", "volume spike 3.2x"],
"lastEventAt": "2026-08-06T14:23:11Z",
"subscriptionId": "sub_abc123"
}
]
}
The agent decides whether to call stream_read for the full events or continue with its current task.
6. Durable cell state via LTX (and host WORM)
On celld, every acknowledged storage.put is replicated as LTX (Litestream replica format) to the operator-owned S3-compatible bucket with RPO=0 — celld does not acknowledge a write before the data is in the bucket. That replication stream is the platform durability for DO SQLite (session meta, ring snapshots, subscription config).
It is not a substitute for clawql-audit WORMAuditTrail (tip-load on restart, dual-ack, Merkle). Compliance-grade forensic events for Streams sessions must append to the host trail (Lab 5b: fetch(CLAWQL_AUDIT_WORM_URL) → POST /entries). An operator reading only LTX audit:ring rows does not get no-fork-on-restart guarantees.
Auditor note: you can still use sqlite3 on bucket LTX artifacts to inspect cell state; use clawql-audit query / /chain/verify for the compliance chain.
| Property | Behavior |
|---|---|
| Append model | SQLite rows keyed by monotonic worm:* / event tables; never update-in-place for audit rows |
| Replication | LTX segments → fleet bucket |
| Auditor workflow | Download cell SQLite / LTX from bucket; inspect with sqlite3 (and grep on ownership records) — no vendor status page |
| Payload policy | Event bodies hashed for WORM; full body only in TTL-bounded buffer / encrypted cold store |
WORM event types
| WORM event | Includes |
|---|---|
STREAM_EVENT_RECEIVED |
topic, significance result, payload hash |
DO_CREATED |
virtual key ID + subscription ID + event hash |
INFERENCE_CALL |
virtual key ID + PAL tier + tokens + cache hit/miss |
TOOL_CALL |
virtual key ID + tool name + ATR check result |
BUDGET_EXHAUSTED |
virtual key ID + tokens consumed |
DO_DESTROYED |
virtual key ID + exit reason + total spend |
VIRTUAL_KEY_EXPIRED |
key ID (from inference gateway) |
On Cloudflare, DO storage is the source of truth with platform replication. On Kubernetes, WORM continues to use Postgres (multi-replica) or JSONL (single replica) as in v0.1 — LTX applies specifically to the celld backend.
Logical AuditSidecar remains the in-session writer API; under celld it maps to storage.put + LTX. See clawql-durable-objects.md and clawql-celld.md.
7. Training data emission (RTP + OpenBenchTrace)
Every agent session is a potential training example with cryptographic provenance.
7.1 RTP as the inner structure
OpenBenchTrace (OBT) is the collection envelope. RTP is the reasoning schema (six-node sequence). They compose: OBT wraps RTP.
| RTP node | Streams / DO source |
|---|---|
| Intent | Subscription prompt + summarized event |
| Retrieval | memory_recall / search tool calls before reasoning |
| Reasoning | Tool-selection chain + clawql-inference metadata (PAL tier, cache hit, tokens, model id) |
| Execution | Tool calls with arguments (also in WORM) |
| Delta | State before/after via WORM hashes |
| Verdict | Session outcome (converged / timeout / budget) + optional Ouroboros evaluator |
WORM is append-only forensic evidence. RTP is structured training data. Both are written; destinations differ.
7.2 Consent at subscription time
stream_subscribe({
// ...
rtpConsent: {
scopes: ['community_model', 'dataset_licensing'], // dataset_licensing optional
},
})
Default scope: community_model. Every session inherits the subscription JWT. Consent is once per subscription; each session fulfills that consent.
7.3 Accumulation, export, flywheel
- InferenceSidecar runs model calls under the virtual key (
fetchto clawql-inference). - TrainingDataSidecar accumulates RTP
turnSequencein DO SQLite as tools execute. - On session close: wrap RTP in OBT envelope; flush to export (
r2/postgres/huggingface/none).
DO session → fetch(clawql-inference) (PAL + virtual key)
→ WORM / LTX (every inference + tool call)
→ RTP trace (reasoning + tools)
→ OBT envelope (manifest hash + virtual key ID)
→ fine-tune on verified traces
→ custom Frugal model in tier-map.json
→ next session PAL-routes to custom Frugal first
→ cheaper sessions → more sessions → more traces
8. Security
8.1 ATR scoping on subscriptions
Every subscription declares allowedTools. Panguard / ATR enforces this on every tool call within the session. Prompt injection in an event payload cannot grant execute if it is not in allowedTools.
8.2 Virtual keys
Bind-on-create / expire-on-destroy (§3.7). WORM stores key ID only — never plaintext. Egress from InferenceSidecar is allowlisted to approved model endpoints via clawql-inference policy.
8.3 celld alpha posture
celld is alpha (security):
- Not safe for hostile multi-tenant use
- Peer HTTP uses HMAC + body signature + clock/replay protection but does not terminate TLS — put peers on WireGuard/Tailscale/private net; terminate public TLS at ingress
- Fleet bucket credentials are root of authority — scope to one bucket
- Prefer Kubernetes HPA for regulated / multi-tenant until celld is production-stable
8.4 Available crypto (celld / Workers)
Partial Web Crypto: digest, HMAC sign/verify, AES-GCM, RSA-OAEP decrypt, Ed25519 / ECDSA-P256 sign, getRandomValues, randomUUID. Missing: deriveKey / deriveBits / wrap-unwrap, broader verify, DigestStream. Streams and sidecars must not depend on unavailable primitives — see clawql-celld.md §2.
8.5 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 via clawql-inference
-
rtpConsentJWT validated before TrainingDataSidecar export - Manifest ID injected from Cosign-signed release, not free-form client input
- celld peers not exposed on the public internet; ingress TLS separate
9. Scaling
| Backend | When | Mechanism |
|---|---|---|
| celld | Self-hosted DO parity | Cells = DOs; LTX to operator bucket; 1:1 object + hibernation (density: vendor ~1000 resident / 8 GB — re-measure before GTM) |
| Cloudflare | Hosted / SaaS | Native Durable Objects + hibernation |
| Kubernetes HPA | Regulated / air-gapped / until celld GA | NATS consumer lag → HPA; Postgres WORM |
Do not build a custom ClawQL DO runtime on Node worker_threads. celld (Apache 2.0, ~58 MB binary) is the self-hosted DO runtime; Cloudflare remains the hosted DO path; K8s HPA remains the regulated path.
9.1 Hibernation economics (structural — no dollar table)
Do not publish $/cell-month or CF-vs-celld dollar tables until ClawQL measures idle ratio and resident RAM on a real subscription mix. Vendor density (~1000 resident / 8 GB) is a capacity hint, not a ClawQL cost claim.
Cite today: celld’s 1:1 durable object + hibernate API for per-subscription affinity vs a K8s worker pool + queue (idle floor = min replicas). That architecture difference is defensible without inventing a price.
9.2 docker-compose fleet example
# compose fragment — celld fleet + shared bucket credentials
services:
celld-a:
image: ghcr.io/denoland/celld:latest # pin SHA in prod
command:
- --bucket=${CELLD_BUCKET}
- --endpoint=${S3_ENDPOINT}
- --region=${AWS_REGION}
- --listen=0.0.0.0:8080
- --advertise=celld-a:8080
environment:
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
networks: [celld-mesh]
celld-b:
image: ghcr.io/denoland/celld:latest
command:
- --bucket=${CELLD_BUCKET}
- --endpoint=${S3_ENDPOINT}
- --region=${AWS_REGION}
- --listen=0.0.0.0:8080
- --advertise=celld-b:8080
environment:
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
networks: [celld-mesh]
inference:
image: clawql-inference:local
# Agent cells fetch() here — not embedded in the 64MB bundle
ports: ['8787:8787']
networks:
celld-mesh:
# Prefer WireGuard / private overlay in real deployments
driver: bridge
9.3 Kubernetes HPA (regulated path)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: clawql-streams-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: clawql-mcp-http
minReplicas: 1
maxReplicas: 50
metrics:
- type: External
external:
metric:
name: clawql_streams_nats_consumer_lag
target:
type: AverageValue
averageValue: '100'
Same stream_subscribe interface across all three backends. The deployment target determines which scaling backend fires.
10. Helm values
streams:
enabled: false # opt-in; CLAWQL_ENABLE_STREAMS=1 for env gate
scalingBackend: kubernetes # kubernetes | celld | cloudflare
hpa:
enabled: true
minReplicas: 1
maxReplicas: 50
targetConsumerLag: 100
celld:
enabled: false
bucket: '' # s3://clawql-streams-state
endpoint: ''
region: auto
advertiseMesh: wireguard # operator-managed
ambientDelivery: true # inject pendingStreamEvents on every tool response
defaults:
maxConcurrentSessions: 10
batchWindowMs: 0 # 0 = no batching
budgetTokens: 8000
maxTurns: 10
trainingData:
enabled: false
defaultRtpConsentScopes: [community_model]
export:
kind: none # none | r2 | postgres | huggingface
auditLevel: WORM # WORM | LOG | none
subscriptionStore: postgres # postgres | jsonl | celld-sqlite
11. CLI
clawql streams <subcommand>
subscribe Create a subscription (interactive or --config file)
unsubscribe Remove a subscription by ID
list List active subscriptions with status
status Show buffer depth and event counts for a subscription
pause Pause a subscription without removing it
resume Resume a paused subscription
read Drain buffered events for a topic
replay Replay events from a time window
pending Show total pending events across all subscriptions
worker Start the streams processing worker (K8s sidecar mode)
celld celld fleet helpers (wraps install / deploy / diagnose)
clawql streams celld
clawql streams celld install # curl install.sh | sh (or pinned CELLD_VERSION)
clawql streams celld deploy # celld deploy . --bucket … (esbuild on PATH)
clawql streams celld start # start node with --listen / --advertise
clawql streams celld diagnose # celld diagnose — leases + peer probes
clawql streams celld bundle-check # fail CI if Worker bundle > 64 MiB
Operational detail: clawql-celld.md §7.
12. Package dependencies
| Package / system | Role in Streams |
|---|---|
clawql-streams |
Coordination: subscriptions, filter, spawn, MCP stream_* (planned) |
clawql-core |
In-process streams-slim (audit/cache/hash-chain); full barrel stays off-Worker |
mcp-api-adapter |
Out of process — fetch(CLAWQL_MCP_ADAPTER_URL) REST POST /\{tool\} |
clawql-mcp / Core |
Out of process — fetch(CLAWQL_MCP_URL) Streamable HTTP for search/execute/memory_* |
clawql-inference |
Out of process — fetch() only; PAL, virtual keys, call store, cache |
| celld | Self-hosted Durable Objects runtime (Apache 2.0, Workers API) |
clawql-cellrt |
ClawQL-owned Rust + Wasmtime cell runtime (planned) |
| Cloudflare Workers | Hosted Durable Objects path |
| NATS JetStream | Durable event buffer for Kubernetes HPA path |
clawql-payments |
DeductionService for credit-gated agent sessions |
clawql-ouroboros |
Optional ensemble validation before kinetic actions |
| Panguard / ATR | Tool-scope enforcement inside agent sessions |
| OpenBenchTrace / RTP | Training-data emission |
clawql-streams coordination (planned package; Lab 5b skeleton today)
├─ DO / cell bundle
│ └─ clawql-core/streams-slim (in-process audit/cache/hash-chain)
├─ fetch → clawql-mcp (search / execute / memory_*)
├─ fetch → mcp-api-adapter (optional REST tool surface)
├─ fetch → clawql-inference
├─ celld | cellrt (planned) | Cloudflare | K8s HPA
├─ clawql-payments (optional holds)
└─ clawql-ouroboros (optional ensemble)
13. Comparison to alternatives
| Stripe Minions | Anthropic Managed Agents | OpenAI Agents SDK | ClawQL Streams | |
|---|---|---|---|---|
| Trigger | Slack reaction | Cron / API call | API call | WebSocket · NATS · webhook · cron · poll · gRPC · SSE · QR |
| Tool catalog | Custom (Toolshed, internal) | Built-in + custom | Built-in + custom | Any MCP server via mcp-api-adapter |
| Audit trail | Internal | Provider-managed | Provider-managed | Host clawql-audit WORM (+ LTX for cell state on celld; Postgres/JSONL on K8s) |
| Sovereignty | Internal only | Provider servers | Provider servers | celld · cellrt · air-gapped K8s · Cloudflare |
| Scale | Internal K8s | Provider-managed | Provider-managed | celld/cellrt cells · CF DOs · K8s HPA |
| Protocol surface | Internal | API only | API only | Any protocol both directions |
| Model | Goose + Claude Code | Claude only | OpenAI only | Any model via clawql-inference |
| DO runtime | N/A | Provider | Provider | celld (Workers API) · cellrt (owned Rust) · Cloudflare — not custom Node |
| Payments | x402 demo | None | None | Full economics stack |
| Open source | No | No | Partial | Apache 2.0 core + celld / cellrt Apache 2.0 |
| Multi-agent | No | Research preview | Yes | Ouroboros ensemble |
14. Positioning
ClawQL Streams is the self-sovereign alternative to Anthropic Managed Agents, with Stripe Minions-level tool integration and Agents SDK-level orchestration — triggered by any event source, audited to host clawql-audit WORM (celld LTX holds durable cell state, not a substitute compliance trail), deployable on celld, cellrt, Cloudflare, or Kubernetes.
Streams + Core + mcp-api-adapter is the Protocol Fabric with an event loop: world events enter, agents act under ATR and virtual keys, results leave on any protocol surface — without a human at the console and without building a custom Durable Object runtime on Node.
15. Open questions
- Bundle size. Resolved for core + MCP tools path:
clawql-core/streams-slim+ Effect ≈ 0.4 MiB indocs/examples/streams-celld;search/executeuse thinfetch(CLAWQL_MCP_URL)(noclawql-apiin the Worker). Remaining question: can a future offline Workers-safeclawql-apislim + Streams router still fit with aggressive tree-shaking? - celld alpha vs cellrt. When is celld production-stable enough to prefer over K8s HPA? When does Helm default self-hosted Streams to
cellrtinstead of (or alongside) celld? - Replay and idempotency. On buffer replay, significance may re-fire. Idempotency keys:
eventId + subscriptionId(and stable DO/cell names — see celld / cellrt naming). - Kafka / Kinesis. First-class
StreamSourceTypein v0.2 or defer to enterprise add-on? - Multi-tenant celld / cellrt. celld fleets are one application deployment (limitations) — how do we isolate ClawQL orgs (separate fleets/buckets vs wait for scheduler)? cellrt targets the same isolation model initially.
- Consent granularity. Subscription-level
rtpConsent(default) vs per-event re-consent for regulated tenants. - Cross-subscription coordination. Default isolated; shared subject for opt-in?
- Effect-TS → WASM. Feasibility of
@clawql/wasm-polyfills+@clawql/effect-wasmfor in-processclawql-core.wasminside cellrt (see cellrt §10).
Further reading
docs/streams/clawql-cellrt.md— ClawQL-owned Rust + Wasmtime cell runtime (monorepocrates/)docs/streams/clawql-tee.md— hardware TEE + attestation-gated secretsdocs/streams/clawql-tee-airgap-audit.md— QR air-gap audit transport (TEE)docs/streams/clawql-qr-stream-transport.md— 7th mcp-api-adapter surface + Streamsqrsource + election moduledocs/streams/clawql-celld.md— celld integration: constraints, DO classes, bucket layout, deploydocs/streams/clawql-durable-objects.md— session contract, sidecars, virtual keysdocs/mcp/mcp-api-adapter.md— MCP → APIs (inverse of ClawQL Core)docs/inference/clawql-inference.md— Agentic Gateway / virtual keys / PALdocs/benchmarks/openbench-trace-collection.md— OpenBenchTrace + RTP alignmentdocs/mcp/schedule-synthetic-checks.md— existing cron pattern Streams unifies- celld · docs · limitations · security · compat · denoland/celld
- Essay: What Convergence Week actually proved
- Essay: OpenBenchTrace and RTP