Upgrade: parked remote calls get a field-scoped store method (CP-282)
A call parked on a remote child (SessionState.remotePendingCalls) is now written only by a new, field-scoped store method, updateRemotePendingCalls. Full-state writes (saveState, commitState, the step commit, the run-start commit) never write it. This is a major release of @helix-agents/core, every store package and every runtime: a new REQUIRED store method, saveState no longer writing a field it used to write, and every runtime's construction guard refusing a store without the method (as SDK-2's late-answer methods were). Upgrade every store package together with core.
Why
The submit router stamped a parked call's answer (answeredAt) on the root with a plain saveState, which moves the session's version. The root's own step commit is pinned to the version it read, so an answer landing while that commit was in flight failed it, and the root's turn failed with state_concurrency_conflict (runtime-js, the CF Durable Object and CF Workflows, whose step save is pinned the same way). The parked map now moves no version, so the answer and the root's commit compose.
Who has to change code
- Apps on a shipped store (memory, Redis, Postgres, D1, the CF Durable Object): nothing. No migration: the column or field is unchanged.
- Custom
SessionStateStoreimplementations: implementupdateRemotePendingCalls, and stop writingremotePendingCallsfromsaveState,commitState,saveStateAndPromoteStagingand the run-start commit (keep the stored value). Core'sSESSION_STATE_STORE_REQUIRED_METHODS(isSessionStateStore) and every runtime's construction guard now require it (coreREMOTE_PENDING_CALLS_STORE_METHODS): a store without it is refused at construction withframework_not_supported, naming the method. - Tests and fixtures that seed parked calls: seed through
updateRemotePendingCalls, notsaveState. A seed through a full-state write is silently dropped now. A test that asserts the map is gone after a terminal then passes vacuously, so assert the seed landed first.
The method
updateRemotePendingCalls(
sessionId: string,
change: RemotePendingCallsChange,
opts?: RemotePendingCallsOptions
): Promise<RemotePendingCallsResult>;- One atomic step, no
versionmove, no checkpoint. change:add(entries as given),repark(the same child parking a call again: an answered entry getsreparks + 1and losesansweredAt),remove,removeSubSessions,markAnswered(stamped at the store's clock; an id with no entry is skipped) andclearAnswered.- Conflicts: an add whose id belongs to another child, or to the session's own
pendingClientToolCalls/clientToolCallOwnership, writes NOTHING and returns the ids inconflicts. - Fences:
fenceRunId(the run must be current and live, elseRunSupersededError) orfenceIdle: { runId }(saveState's idle rule, coreassertRunIsCurrentIdle). - Size: the written map is measured against
maxStateBytes(StateSizeExceededError, nothing written). - Result:
{ map, added, removed, marked, cleared, conflicts },mapbeing the stored map after the change. - A store whose read and compare-and-set are separate steps retries a lost compare-and-set and gives up with a typed, retryable
state_concurrency_conflict. - Core's pure
applyRemotePendingCallsChange(current, change, now, ownedCallIds)is the rule; a store applies it inside its atomic step. The contract suite iscore/src/testing/remote-pending-calls-operations.ts(RPC-*).
Unchanged
- The status CAS off live (
compareAndSetStatustofailed/completed/interrupted) still clears the whole map (CP-157; FU-STORE-TERMINAL-CAS-DROPS-DESCENDANT-PARK). cloneSessionstarts the clone with no map.- A terminal commit drops what core
remotePendingCallsAfterTerminaldrops (CP-279). The runtimes now remove those ids through the op (coreremoveParkedCallsAfterTerminal) instead of writing the map in the terminal state.