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
createRemoteAgentWorkerHandlerwith a raw internal mount and a projected external mount (projection+usageProjection, Bearerauth)serviceBindingTransport({ binding, basePath, agentType })— zero-hop DO→DOtransport.getSnapshot()/transport.getUsage()(checkpoint-pinned baselines)RemoteSubAgentConfig.maxFoldStateBytes/resumeDeadProducerAfterSubAgentPayload.childCustomStatefold (idempotent SET fold)- Consumer-side output re-validation against the tool's
outputSchema - Chunk
remoteSourceprovenance: the frontend demuxes child state byagentIdand 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:
| Mount | Fidelity | Auth |
|---|---|---|
/internal/agents | Raw — full state_patch frames, real customState in snapshots | none (service-binding only) |
/agents | Projected — state_patch suppressed, lossy state, billing units | Bearer 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
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
- Cross-Service Remote Agents guide — the full protocol reference
- Remote Agents (Temporal) — the same delegation pattern over HTTP + SSE
- Source:
examples/remote-agents-cloudflare-do/