Upgrade: the post-commit child phase (CP-106 MR A)
A companion tool call now runs only after the commit that holds the call. Before, a step's companion__spawnAgent started its child inside the step, so a step a stop refused could leave a child running with a sub-session ref and no committed call (CP-106). Now the call is announced with the step, the step commits, and only then does the call write its ref and start its child.
This is a major release of @helix-agents/core, @helix-agents/runtime-js and @helix-agents/runtime-cloudflare (all at 1.0 or later). It is a breaking change for them:
- a custom
SessionStateStoremust implement the fenced ref write below; - a step's companion calls now run after its commit, so the order of its stream chunks changes.
Every store package (@helix-agents/store-memory, @helix-agents/store-redis, @helix-agents/store-postgres, @helix-agents/store-cloudflare) takes a minor: they are 0.x, where a breaking change is a minor (release process).
Design: 2026-10-14-cp106-post-commit-child-phase-design.md (read its "As built (MR A)" section first).
Who is affected
- Apps on runtime-js or the CF Durable Object that use
persistentAgents(thecompanion__*tools). - Custom
SessionStateStoreimplementations. - Clients or tests that read the order of a step's stream chunks.
CF Workflows, Temporal and DBOS are unchanged in this release. Remote and ephemeral sub-agent calls are unchanged too (they move into the phase in MR B).
Deploying
- Run the store migrations (below) before the new runtimes: the fenced ref write stores the parent run's id.
- Deploy with or after the D-2 request-key release. A child start in the phase sends a request key (
X-Helix-Request-Keyon the CF DO's child/startand/resume).
1. What changed for companion calls
- Order. A step's calls are still announced in call order (
tool_start). A companion call's body runs after the step commit (or after the suspension commit, when the step also suspends), one call at a time in call order. A blocking spawn orwaitForResultlets the step's next companion call start as soon as its own child started. Its result is appended in its ownappendcommit after the step's. - Stream order. A companion call's
tool_endnow follows the step'sstep_committed. - A stop. A stop that refuses the step commit leaves no ref and no child. A stop recorded before a call writes its ref ends the phase: the call's result row is the stop pairing (
helixSynthetic.reason: 'run_stopped', as for any call a stop leaves unfinished). A stop recorded while a child was starting stops that child, by its consult, and ends the phase the same way. On the CF DO, a child stop that does not land is owed to the run's stop cascade and retried. finishWithin the same step. It runs after the step's companion results, in the same iteration.- Recovery. A CF DO run evicted inside the phase re-enters it on its wake, under the same run. A
resume()of a run that suspended re-enters the previous run's unfinished companion calls. Either way the replay check (a committed result row) and the keyed child start make each call run once: one child run, one result row. - A call's start is keyed on the run that committed it and its call id (core
childStartRequestKey/childResumeRequestKey). On the CF DO, a key whose run already finished counts as a start of that run.
2. Custom stores
addSubSessionRefs(sessionId, refs, opts?) takes AddSubSessionRefsOptions:
interface AddSubSessionRefsOptions {
/** Refuse with `RunSupersededError` unless this run is the session's current live run. */
fenceRunId?: string;
/** With `fenceRunId`: refuse with `CommitStoppedError` when that run has a stop record. */
refuseIfStopped?: boolean;
}- Check the fence and the stop in the same atomic step as the insert. A refusal writes nothing.
refuseIfStoppedwithoutfenceRunIdis avalidation_error(coreassertValidAddSubSessionRefsOptions).- Stamp each inserted ref's
SubSessionRef.parentRunIdwithfenceRunId(else the session's current run). - A ref reopen (a respawn reset, a continue, a resumed companion in the run-start commit) re-stamps
parentRunIdwith the reopening run (corereopenParentRunStamp). - A re-add of an existing
subSessionIdstays the insert-if-absent no-op it was.
Run the store contract suites from @helix-agents/core/testing: subSessionRefOperationTests ("the fenced add and the reopen stamp, CP-106") and runStartOperationTests ("commit.start-companion-ref-stamps-parent-run").
3. Migrations
| Store | Migration | Change |
|---|---|---|
| Postgres | V24 | __agents_sub_session_refs.parent_run_id TEXT DEFAULT NULL |
| D1 | V28 | sub_session_refs.parent_run_id TEXT |
| DO-SQLite | none | the column exists already |
| Redis | none | a parentRunId hash field |
| Memory | none |
No backfill: a ref written before the upgrade has no parentRunId.