A lost remote sub-agent settles typed (S-C)
A parent that awaits a remote sub-agent (a createRemoteSubAgentTool call, or on the Cloudflare Durable Object any sub-agent tool, which the DO runs in a sibling DO) used to wait forever when it could not read the child's status on a resume or a recovery. That was the 2026-10-17 writer-chat incident's third gap: the child's state read as missing, the parent kept awaiting it, and a new message sent meanwhile could be dropped.
Now the parent settles a child it cannot find with a typed failure, and an entry that carries a new message never puts that message between a call and its result. This is a minor release of @helix-agents/core, @helix-agents/runtime-js and @helix-agents/runtime-cloudflare, with two behaviour changes (below). CF Workflows applies the same core rule (RM-80).
A child the parent cannot find settles framework_subagent_lost
On a resume or a recovery, reconcile reads each running remote child's status (transport.getStatus). When that read fails, the child is settled failed with a typed tool result (metadata.helixErrorDetail, new core error code framework_subagent_lost, category framework, non-retryable) when:
cause | When |
|---|---|
not_found | the child's producer does not know the session (RemoteAgentNotFoundError, a 404) |
refused:<code> | the producer refused the status read typed and non-retryable (<code> is its error code) |
status_unreachable | the status stayed unreadable past the child's own timeoutMs plus one minute of grace |
Any other failure is transient: it is logged at warn, and the next reconcile retries it. The model sees a failed tool result for the call and the run goes on. If you branch on tool-result error codes, add framework_subagent_lost.
Reconcile now reads the session's live sub-session refs. It used the resumed state's refs, which a resume from a checkpoint rebuilds from that checkpoint, so a child dispatched after the checkpoint was never reconciled. A remote ref is identified by ref.location === 'remote' (new core export isRemoteSubSessionRef(ref)), not by ref.remote (the child's stream identity, recorded only once the child answers its /start), so a child whose /start never answered is reconciled too.
An entry with a new message settles its remote children first
resume({ mode: 'with_message' }), a retry() with a message and a new turn (execute(), a chat send, the CF DO's /start) first settle each remote child whose call is still unpaired at the end of the transcript:
- a child that finished (
completed/failed) has its real result committed before the message; - a child that is lost (above) has its typed
framework_subagent_lostresult committed before the message; - a child that is still running:
- on a
resume/retrywith a message: behaviour change, the entry is refused busy (AgentAlreadyRunningError,state_already_running) and writes nothing. Hold the message and send it again once the child has finished. A chat client handles it like any refused send (409state_already_running). - on a new turn over a parent whose run ended: behaviour change, the child is stopped (a best-effort
transport.interrupt(), its ref setfailed) and its call keeps the continuation heal's not-executed (unpaired_on_continuation) result. Finish the child, orresumethe parent, before a new turn if you need its output. - on an entry with no new message (a
continueresume, a retry that re-appends nothing): nothing changes, and the run re-attaches to the child.
- on a
Cloudflare Durable Object
- Typed 404 from the status route. A DO with no session answers
/subagent/:agentType/statuswith the typed404 NOT_FOUNDprotocol envelope (it was plain text), soDOStubTransport.getStatusthrowsRemoteAgentNotFoundError. - DO-SQLite migration 19 adds the nullable
sub_session_refs.remote_jsoncolumn (SubSessionRef.remote). It is read through a schema: a malformed row is a typedInvalidStoredStateError. The DO used to drop this field, so a recovered parent re-sent its child's/startand re-read the child's stream from 0; it now re-attaches from the storedlastSequence. The migration applies on the DO's first access; nothing to run. - A
DOStateStore.loadStateof a session id that is not the DO's own logs "a foreign session id was read from this DO's store" with both ids.
See Cloudflare Durable Objects › A lost remote sub-agent settles typed and Remote agents.