Upgrade: the CF Durable Object companion consult protocol (DO companion lifecycle MR A)
A Cloudflare Durable Object parent now records a persistent companion's outcome only from the child's own committed run for the consult the parent's ref tracks (CP-39, CP-125). It is a major release of @helix-agents/runtime-cloudflare (LLM-facing tool answers change, and a DO-SQLite migration runs), a minor release of @helix-agents/core and @helix-agents/runtime-js, and a patch of @helix-agents/agent-server.
Design: 2026-10-15-do-companion-lifecycle-design.md. Reference: the CF DO runtime page.
Deploying
- One
subAgentNamespace. A companion notifies its parent through its ownsubAgentNamespace(idFromName(parent.sessionId)), so the parent and every companion must live in that one namespace. A companion DO with none logscompanion.notice.no_namespaceat warn and cannot notify: its parent learns the outcome only when the model touches that child. - Migration. DO-SQLite migration 17 adds four nullable
sub_session_refscolumns (consult_tool_call_id,child_run_id,outcome,verify_owed_at). It runs on the DO's first request after the deploy. There is no legacy-data handling. - The new DO route
POST /companion/child-settledis DO to DO. It is not in the worker handler's public allowlist; do not expose it.
What the model sees now (prompts and tests that match tool results)
- An unreachable child. After
companionPollFailureWindowMs(30 s) of failed reads of a child DO, a blockingcompanion__spawnAgentanswers{ name, status: 'unreachable', message }andcompanion__waitForResultanswers{ name, status: 'unreachable', currentStatus, message }, both successful tool results. Before, both were tool errors, and the ref was writtenfailedand the child aborted. Now the ref staysrunninguntil the child's own outcome arrives or the model callscompanion__terminateChild. - A finished non-blocking companion is recorded
completedon its parent without a parent tool call, its result is injected on the parent's next model call, and a same-namecompanion__spawnAgentre-consults it. - Cadence. The blocking park and
companion__waitForResultread the child every 5 s (they read every 250 ms before); a recorded outcome wakes them sooner.
Code that reads the companion wire
/subagent/:type/statusaddsrunId,runStatus,parentToolCallIdandcommittedOutcome. ItsrunIdis now the persisted current run's, and a failed read of these fields answers 503 instead of a 200 from the status row alone. This reaches every caller, theDOStubTransportremote sub-agent status poll included./statuserror(root and/subagent/:type/status) is sanitized, aserrorDetail.messageis (do-status-error-sanitizedchangeset). The DO's SSEfailframe and stream metadata still carry the raw text (FU-RAW-ERROR-TEXT-SURFACES).
Known limits
- A replay answers from the recorded outcome only when it reuses the call id. A DO recovery re-calls the model, and a real provider mints a new tool-call id, so a recovered parent's re-emitted spawn is a new call: "already running" for a live child, or a continue of a completed one. FU-DO-COMPANION-ORPHANED-CONSULT-ADOPTION (MR C) adopts the orphaned consult.
- A continue can still lose an undelivered result (as before). A same-name
companion__spawnAgentor acompanion__sendMessagethat continues a child whose result the parent has not delivered yet (the child ended after the parent's completion notifier ran, e.g. during the model call) starts the new consult over it, and that first result never reaches the model. MR B delivers it with the continue's answer (priorResult, FU-SPAWN-CONTINUE-ENTRY-OUTCOME-PARITY).