Skip to content

Remote Agents (Cloudflare DO → DO)

This example demonstrates the cross-service remote-agent protocol between two Cloudflare Workers: a consumer worker whose agent delegates to a producer worker's agent tree over a zero-hop service binding. Both sides run on Durable Objects.

Architecture

┌──────────────────────────────┐   service binding    ┌───────────────────────────────┐
│  consumer worker (8787)      │ ───────────────────► │  producer worker              │
│                              │  serviceBinding-     │                               │
│  writer agent (DO)           │  Transport →         │  /internal/agents  RAW mount  │
│   └ subagent__research ──────┼─ /internal/agents ──►│  /agents  PROJECTED mount     │
│                              │                      │   (Bearer auth, lossy state,  │
│  frontend (public/)          │                      │    billing-unit usage)        │
│   • live nested tree         │                      │                               │
│   • late-join via snapshots  │                      │  planner agent (DO)           │
│   • usage rollup             │                      │   └ thread-researcher × N     │
│  GET /api/child-snapshot ────┼─ getSnapshot(child) ►│     (nested child DOs)        │
└──────────────────────────────┘                      └───────────────────────────────┘

The writer (consumer) delegates a research report to the planner (producer) via createRemoteSubAgentTool() over serviceBindingTransport. The planner in turn fans out to thread-researcher children, each in its own Durable Object — a nested tree below the remote boundary.

What it demonstrates

  • createRemoteAgentWorkerHandler with a raw internal mount and a projected external mount (projection + usageProjection, Bearer auth)
  • serviceBindingTransport({ binding, basePath, agentType }) — zero-hop DO→DO
  • transport.getSnapshot() / transport.getUsage() (checkpoint-pinned baselines)
  • RemoteSubAgentConfig.maxFoldStateBytes / resumeDeadProducer
  • AfterSubAgentPayload.childCustomState fold (idempotent SET fold)
  • Consumer-side output re-validation against the tool's outputSchema
  • Chunk remoteSource provenance: the frontend demuxes child state by agentId and dedups forwarded child patches on (childSessionId, childSequence)
  • The child-snapshot proxy route (GET /api/child-snapshot?sessionId=) — the only way a consumer frontend obtains a remote child baseline
  • Usage rollup including the remote child (SubAgentUsageEntry.remoteRollup)

Mount fidelity

The producer exposes the same Durable Objects on two mounts:

MountFidelityAuth
/internal/agentsRaw — full state_patch frames, real customState in snapshotsnone (service-binding only)
/agentsProjectedstate_patch suppressed, lossy state, billing unitsBearer token

Mount isolation is auth-only: idFromName is mount-agnostic, so both mounts reach the same DOs. Fidelity separation exists only at the worker layer. Executor-to-executor integrations must consume raw mounts — a projected mount would fold the lossy view into the parent as the child's customState.

Run it

bash
npm install                                    # from the repo root (workspace)
cd examples/remote-agents-cloudflare-do

# Wrangler resolves `.dev.vars` NEXT TO EACH wrangler.toml, so each worker
# needs its own copy — a `.dev.vars` at the example root is never read.
cp .dev.vars.example consumer/.dev.vars        # set a real OPENAI_API_KEY
cp .dev.vars.example producer/.dev.vars        # set a real OPENAI_API_KEY + PRODUCER_API_KEY

npm run dev                            # wrangler dev: consumer + producer
npm test                               # multi-worker Miniflare smoke test (no API key)

npm run dev runs wrangler dev -c consumer/wrangler.toml -c producer/wrangler.toml; wrangler wires the PRODUCER service binding to the local producer automatically. Open http://localhost:8787.

npm test runs a vitest-pool-workers smoke test through real workerd, real Durable Objects, a real service binding and real SSE — only the LLM is mocked (MockLLMAdapter), so no API key is needed. It asserts SDK invariants only: completion, the afterSubAgent state fold (cross-checked against the producer's own snapshot of the child), remoteSource provenance and dedup, the snapshot protocol's terminal streamSequence: -1 sentinel, the recursive usage fold including the remote child, and the projected mount's auth + lossiness.

See also

Released under the MIT License.