Skip to content

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 own subAgentNamespace (idFromName(parent.sessionId)), so the parent and every companion must live in that one namespace. A companion DO with none logs companion.notice.no_namespace at 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_refs columns (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-settled is 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 blocking companion__spawnAgent answers { name, status: 'unreachable', message } and companion__waitForResult answers { name, status: 'unreachable', currentStatus, message }, both successful tool results. Before, both were tool errors, and the ref was written failed and the child aborted. Now the ref stays running until the child's own outcome arrives or the model calls companion__terminateChild.
  • A finished non-blocking companion is recorded completed on its parent without a parent tool call, its result is injected on the parent's next model call, and a same-name companion__spawnAgent re-consults it.
  • Cadence. The blocking park and companion__waitForResult read 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/status adds runId, runStatus, parentToolCallId and committedOutcome. Its runId is 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, the DOStubTransport remote sub-agent status poll included.
  • /status error (root and /subagent/:type/status) is sanitized, as errorDetail.message is (do-status-error-sanitized changeset). The DO's SSE fail frame 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__spawnAgent or a companion__sendMessage that 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).

Released under the MIT License.