Skip to main content
PlatformcelldDraft

ClawQL Celld Integration — Spec v0.1

Status: Draft · August 2026 · v0.1
Package surface: celld (self-hosted Durable Objects) for ClawQL Streams
Depends on: clawql-streams v0.2 · clawql-durable-objects.md · clawql-inference · clawql-core · mcp-api-adapter
Related: clawql-cellrt.md (ClawQL-owned Rust runtime) · celld docs · limitations · security · Cloudflare compat · denoland/celld (Apache 2.0)


1. What this is

This document specifies how ClawQL Streams runs on celld — Deno's self-hosted, S3-backed Durable Objects runtime. celld exposes the same Workers / Durable Object JavaScript API as Cloudflare, with SQLite per cell and LTX replication to an operator-owned bucket (RPO=0).

ClawQL's decision (Streams v0.2): do not build a custom DO runtime on Node worker_threads. Adopt celld for Workers/DO API–compatible self-hosted Durable Objects; keep Cloudflare for hosted; keep Kubernetes HPA for regulated until a DO runtime is production-stable. The ClawQL-owned production runtime is clawql-cellrt (Rust + Wasmtime) — complementary to celld, not a Node rewrite.

Why celld vs build-own

OptionEffortAPI parity with CF DOsReplication / WORMOps burdenVerdict
celldIntegrate + constrain bundleHigh (Workers DO surface)Built-in LTX → bucket, RPO=0Install ~58 MB binary; fleet via bucket leasesAdopt
Custom Node worker_threads + SQLiteLarge (hibernation, ownership, WS, alarms)Partial / drift-proneHomegrownOwn failure detector, placement, backupDo not build
Miniflare onlyLow for CIDev approximationLocalNot a production fleetCI / unit tests only
Cloudflare onlyLow for SaaSNativePlatformVendor tenancy / pricingHosted path
K8s HPA onlyMediumDifferent modelPostgres / NATSFamiliar regulated opsRegulated until celld GA

Facts of record: Apache 2.0 · ~58 MB binary · ~$0.05 / resident cell-month · ~1000 resident cells / 8 GB node · RPO=0 LTX · one application per fleet (alpha).


2. Runtime constraints

Source of truth: Cloudflare compatibility and limitations. Unknown keys/APIs fail loudly at deploy or first use.

Available (use these)

API / capabilityNotes for ClawQL
Module Workers + DO bindingsGateway + named DO classes
fetch / Request / ResponseInference + webhooks + egress
DO SQLite storageSync storage ops; session + WORM rows
setAlarm / alarm handlerTTL, reconnect, api_poll, batch windows
Inbound hibernatable WebSocketsSubscriptionDO client channels
Outbound ws: / wss:Stream sources (persist intent — §3)
JS RPC on DO stubsSpawn / coordinate sessions
Web Crypto (partial)digest, HMAC, AES-GCM, Ed25519/ECDSA sign, getRandomValues, randomUUID
node:buffer, path, stream, assert, events, util, timers/promisesBundle-friendly subsets
Static assetsOptional admin UI from fleet bucket
Worker Loader (experimental)64 MiB code / 1 MiB env limits still apply

Unavailable or unsafe (avoid + workaround)

GapBehavior on celldClawQL workaround
setIntervalThrowssetAlarm + SQLite intent
child_process / worker_threadsInert stub / not implementedIn-process MCP; fetch(clawql-inference)
node:http(s), net, tls, dnsInert stubsfetch / WebSocket only
Cache API (caches)NoInference semantic cache stays on clawql-inference
deriveKey / deriveBits / wrap-unwrapMissingPre-derive outside DO; or HMAC/AES-GCM only
R2 / KV bindingsOut of scope (R2 methods throw)Fleet bucket via celld; app data via fetch to object APIs if needed
Platform scheduled / cronNo handlersetAlarm chains for cron sources
TLS on peer protocolPlain HTTP + HMACWireGuard / Tailscale / private net; ingress TLS
TCP sockets (cloudflare:sockets)Silent inert stubDo not use; prefer HTTP/WS
Facets / undeclared DO classes via ctx.exportsAbsentDeclare all DO classes in wrangler.json(c)
Multi-app fleet schedulerOne app per fleetSeparate bucket/fleet per ClawQL deployment

3. Patterns: alarms, fetch, WebSocket reconnect

3.1 setAlarm instead of intervals

// api_poll / session TTL / reconnect backoff
await this.ctx.storage.put("alarm_intent", { kind: "api_poll", url, intervalMs });
await this.ctx.storage.setAlarm(Date.now() + intervalMs);

async alarm() {
  const intent = await this.ctx.storage.get<AlarmIntent>("alarm_intent");
  // do work…
  await this.ctx.storage.setAlarm(Date.now() + intent.intervalMs);
}

3.2 fetch() to clawql-inference — not subprocess

Model calls never spawn a process. The DO holds a virtual key and calls the inference gateway:

const res = await fetch(env.INFERENCE_URL + '/v1/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${virtualKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(payload),
})

PAL, provider SDKs, and credential vaults stay in clawql-inference — outside the 64 MiB cell bundle.

3.3 WebSocket reconnect via SQLite intent

Outbound sockets keep a cell resident and do not continue when the cell moves nodes (limitations). Persist intent and reconnect after activation:

await this.ctx.storage.put('ws_intent', {
  reconnect: true,
  sourceUrl,
  authRef,
  lastEventId,
  backoffMs: 1000,
})

// on close / after constructor wake:
await this.ctx.storage.setAlarm(Date.now() + backoffMs)

Prefer pinning ingress for a cell to its owner node when latency matters; cross-node WS close/reconnect coverage is thinner than single-node.


4. DO architecture

Logical types match clawql-durable-objects.md; celld is the self-hosted runtime.

DO classLifetimeResponsibility
GatewayDOLong-lived / entryRoute webhooks and admin; resolve subscription names; issue spawn to AgentSessionDO
SubscriptionDOLong-lived (hibernates)Source connection, significance filter, config + rtpConsent, ambient buffer stats
AgentSessionDOEphemeral (per event)One agent session + Audit / Inference / Training sidecars; self-exit
Ingress (TLS terminator)


Gateway Worker / GatewayDO

    ├─ idFromName("sub:" + subscriptionId) → SubscriptionDO

    └─ on significance pass:
         idFromName("sess:" + subscriptionId + ":" + eventId) → AgentSessionDO
              ├─ AuditSidecar  → storage.put (LTX WORM)
              ├─ InferenceSidecar → fetch(clawql-inference)
              └─ TrainingDataSidecar → RTP/OBT in SQLite → export

4.1 Naming for idempotency

Stable DO names make replay safe:

ObjectName patternEffect
Subscriptionsub:\{subscriptionId\}One cell per subscription
Sessionsess:\{subscriptionId\}:\{eventId\}Same event + sub → same cell; second spawn is idempotent wake

Gateway still allocates doInstanceId / virtualKeyId before first spawn and writes DO_CREATED once (guard with a spawned flag in session SQLite).

4.2 SQLite schemas (illustrative)

SubscriptionDO

Table / keyContents
configprompt, significance, allowedTools, model alias, budgets, rtpConsent
ws_intentreconnect fields (§3.3)
last_eventid, hash, timestamp
buffer_statspending counts for ambient delivery
worm:*subscription-level reactive audit rows

AgentSessionDO — same contract as DO companion:

TableContents
session_metadoInstanceId, subscriptionId, virtualKeyId, manifestId, startedAt, exitReason
rtp_turnsordered RTP nodes as JSON rows
inference_callstier, tokens, cache, virtual_key_id
tool_callstool name, args hash, ATR result
export_statuspending / flushed / failed
worm:*append-only forensic trail

5. Bundle architecture

celld deploy (esbuild)
  └─ Worker + DO classes
        ├─ clawql-streams (router, filter, stream_* MCP)
        ├─ clawql-core (search / execute / memory_*)
        └─ mcp-api-adapter (protocol surfaces)
  env / vars ≤ 1 MiB
  code ≤ 64 MiB

Provider specs: ship a slim default set in the bundle; load additional OpenAPI/GraphQL specs via fetch into SQLite on first use or at subscription create — do not embed full enterprise catalogs in env.

5.1 esbuild 64 MiB CI check

# clawql streams celld bundle-check
celld deploy . --bucket "$CELLD_BUCKET" --dry-run   # or esbuild metafile
# Fail the job if Worker/DO artifact size > 67108864 bytes

CI must fail closed on oversize bundles. Prefer:

  • Externalize clawql-inference (always)
  • Tree-shake unused providers
  • Avoid Node polyfills that pull fs / http
  • Optional Streams-slim build profile if full Core exceeds budget (open question in Streams §15)

6. Bucket layout

celld uses one fleet bucket as administrative authority (deployments, SQLite/LTX, ownership leases, peer secret). ClawQL still separates concerns:

Bucket / prefixPurpose
s3://clawql-streams-state (fleet CELLD_BUCKET)celld deployments, cell SQLite + LTX WORM, ownership, node leases
Team vault sync bucket (existing ClawQL R2/S3)Obsidian vault / memory_syncnot the celld fleet bucket
Training export (optional)RTP/OBT datasets (HF / dedicated prefix) — distinct from fleet authority

Do not reuse fleet-bucket credentials for vault sync or public dataset upload. Scope each credential to one role (security).


7. Deployment

7.1 Install

curl -fsSL https://celld.dev/install.sh | sh
# Pin: CELLD_VERSION=vX.Y.Z curl -fsSL https://celld.dev/install.sh | sh
gh attestation verify --repo denoland/celld   # build attestation

Binary ~58 MB; replication is in-process (no external Litestream sidecar).

7.2 Configure object storage

export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=auto
export S3_ENDPOINT=https://ACCOUNT_ID.r2.cloudflarestorage.com
export CELLD_BUCKET=s3://clawql-streams-state

celld uses the AWS credential chain (not ~/.aws profiles/SSO).

7.3 Deploy application

# esbuild on PATH; wrangler.json or wrangler.jsonc (not .toml)
celld deploy . \
  --bucket "$CELLD_BUCKET" \
  --endpoint "$S3_ENDPOINT" \
  --region "$AWS_REGION"

Accepted config keys only: name, main, compatibility_date, compatibility_flags, durable_objects, migrations, assets, services, vars. Unknown keys abort deploy.

7.4 Start fleet

celld \
  --bucket "$CELLD_BUCKET" \
  --endpoint "$S3_ENDPOINT" \
  --region "$AWS_REGION" \
  --listen 0.0.0.0:8080 \
  --advertise node-a.internal:8080

Add nodes with the same bucket and distinct --advertise addresses. Discovery is via bucket leases — no join command.

7.5 Diagnose

celld diagnose \
  --bucket "$CELLD_BUCKET" \
  --endpoint "$S3_ENDPOINT" \
  --region "$AWS_REGION"

Reports expired leases, bad advertise addresses, unreachable peers, auth failures, protocol skew.

7.6 Helm

streams:
  scalingBackend: celld
  celld:
    enabled: true
    bucket: s3://clawql-streams-state
    endpoint: https://….r2.cloudflarestorage.com
    region: auto

CLI wrappers: clawql streams celld install|deploy|start|diagnose|bundle-check (Streams §11).


8. Security hardening

ControlRequirement
Peer trafficHMAC + body signature + clock/replay — no TLS; private net or WireGuard/Tailscale
Public ingressTerminate TLS at reverse proxy / mesh gateway; do not expose peer port
Bucket credsOne fleet bucket scope; rotate on suspicion; root of authority
Alpha caveatNot safe for hostile multi-tenant; fixes on latest release only
Build attestationgh attestation verify --repo denoland/celld on install
App authcelld does not authenticate end users — ClawQL ATR / OIDC / virtual keys remain mandatory
WORMLTX on operator bucket; auditors use sqlite3 locally

Regulated tenants that need hostile multi-tenant isolation or certified controls should use scalingBackend: kubernetes until celld exits alpha.


9. Cloudflare vs celld

ConcernCloudflare Durable Objectscelld
APIWorkers DOSame core DO/Workers surface
StatePlatform SQLiteSQLite + LTX → your bucket (RPO=0)
HibernationNativeResident / idle / hibernated / inactive (same model)
PricingCF DO request/duration~$0.05/resident cell-mo; inactive ≈ S3 only
DensityPlatform~1000 resident / 8 GB
KV / R2 bindingsAvailableNot provided as DO bindings
Cron triggersscheduledUse setAlarm
Peer / meshCloudflare edgeOperator mesh; peer HTTP plaintext+HMAC
Multi-tenantCF accountsOne app per fleet (alpha)
Local CIMiniflare / workerdMiniflare + celld diagnose smoke
ClawQL inferencefetchfetch (identical contract)

10. Testing

LayerToolingPurpose
Unit / DO logicMiniflare (or workerd)Alarm, storage, significance, idempotent names
Bundleclawql streams celld bundle-checkEnforce ≤64 MiB
Fleetcelld diagnoseLease + peer health
SmokeDeploy counter/example then Streams fixtureWebhook → SubscriptionDO → AgentSessionDO → fetch inference mock → WORM row present in SQLite/LTX
SecurityAttestation verify in CISupply chain

Do not treat Miniflare alone as production parity for LTX, peer HMAC, or cross-node WebSocket behavior.


11. Known gaps

Track against upstream celld alpha:

  1. TCP stubcloudflare:sockets connect() is a silent inert stub; Streams must not depend on raw TCP.
  2. WebSocket cross-node — thinner test coverage for close codes/reconnect across nodes; prefer owner-node ingress; always persist reconnect intent.
  3. Pressure shedding — off until safe defaults; tune CELLD_MAX_RESIDENT_CELLS / RSS manually.
  4. Manual updates — installer immutable releases + current pointer; no auto-update agent; pin CELLD_VERSION.
  5. One application per fleet — no multi-tenant scheduler; isolate ClawQL orgs with separate fleets/buckets.
  6. Crypto gaps — no deriveKey; design around digest/HMAC/AES-GCM/sign.
  7. Silent Node stubs — importing unimplemented node:* may not fail; lint/ban child_process, http, net in the DO package.

Further reading