Skip to content

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 SessionStateStore implementations: implement updateRemotePendingCalls, and stop writing remotePendingCalls from saveState, commitState, saveStateAndPromoteStaging and the run-start commit (keep the stored value). Core's SESSION_STATE_STORE_REQUIRED_METHODS (isSessionStateStore) and every runtime's construction guard now require it (core REMOTE_PENDING_CALLS_STORE_METHODS): a store without it is refused at construction with framework_not_supported, naming the method.
  • Tests and fixtures that seed parked calls: seed through updateRemotePendingCalls, not saveState. 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 ​

ts
updateRemotePendingCalls(
  sessionId: string,
  change: RemotePendingCallsChange,
  opts?: RemotePendingCallsOptions
): Promise<RemotePendingCallsResult>;
  • One atomic step, no version move, no checkpoint.
  • change: add (entries as given), repark (the same child parking a call again: an answered entry gets reparks + 1 and loses answeredAt), remove, removeSubSessions, markAnswered (stamped at the store's clock; an id with no entry is skipped) and clearAnswered.
  • Conflicts: an add whose id belongs to another child, or to the session's own pendingClientToolCalls / clientToolCallOwnership, writes NOTHING and returns the ids in conflicts.
  • Fences: fenceRunId (the run must be current and live, else RunSupersededError) or fenceIdle: { runId } (saveState's idle rule, core assertRunIsCurrentIdle).
  • Size: the written map is measured against maxStateBytes (StateSizeExceededError, nothing written).
  • Result: { map, added, removed, marked, cleared, conflicts }, map being 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 is core/src/testing/remote-pending-calls-operations.ts (RPC-*).

Unchanged ​

  • The status CAS off live (compareAndSetStatus to failed / completed / interrupted) still clears the whole map (CP-157; FU-STORE-TERMINAL-CAS-DROPS-DESCENDANT-PARK).
  • cloneSession starts the clone with no map.
  • A terminal commit drops what core remotePendingCallsAfterTerminal drops (CP-279). The runtimes now remove those ids through the op (core removeParkedCallsAfterTerminal) instead of writing the map in the terminal state.

Released under the MIT License.