fix(tools): single ordered driver lane for the sub-dispatch scheduler; validate the cap
Responding to ds-review-bot round 2 on #658 (three critical findings, one warning — all rooted in the pump/commit split racing ordered stages): - ONE driver lane now owns every ordered stage: the start append, prepare (pre-execute/guards), and the head-of-line commit (post-execute, context deferral, settle append). start() is awaited before the next entry can start, so concurrent submissions can no longer run pre-execute pipelines concurrently; only the around-dispatch/body stage overlaps, matching the native loop's fillPool sequencing. - An exclusive call's barrier now holds through its COMMIT: later starts wait for the exclusive pipeline (post-execute included) to finish, the native exclusive-group semantics. - drainDispatches() awaits the driver run itself, so a commit already mid-flight when the program returns is drained before run_code closes the turn — the settle event and deferred contexts land inside it. - maxParallelSubCalls is resolved and validated at construction (positive integer), so direct construction can no longer wedge the pool with 0. New tests: overlapping-submission ordered-prepare, barrier-through-commit, drain-mid-commit, cap rejection. 96 keyless snapshots replay unchanged; Agent Note updated (both languages).
This commit is contained in:
@@ -15,7 +15,7 @@ Two gaps remained after the first two PRs. Sub-call rows appeared only when each
|
||||
**One lifecycle pair, one scheduling contract, shared with native.**
|
||||
|
||||
- **Event pair**: `tool/code-dispatch-start` (parent/sub ids, name, normalized args) is appended when the scheduler actually starts a call — not at submission, so a queued call abandoned by run settlement logs nothing. The existing `tool/code-dispatch` settles the pair (same `subCallId`); every started call settles exactly once (aborts settle as `isError` outcomes through the pipeline). Timing = the two events' `time` fields. Both stay log-only; model context is untouched; format stays v0.
|
||||
- **Bridge scheduler**: submitted calls are classified at submission via `registry.executionMode` (the SAME fail-closed `isConcurrencySafe` contract the loop uses) and start strictly in submission order. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (a validated registry `Config` field, default 10 — the loop scheduler's own default; `1` restores serial dispatch); an exclusive call drains the pool, runs alone, and bars later calls. This is the loop's group semantics adapted to calls that arrive over time instead of in one parsed batch. Run settlement aborts in-flight dispatches and abandons queued-unstarted ones (binding rejection, no events), then drains to quiescence before the outer result closes the turn.
|
||||
- **Bridge scheduler**: submitted calls are classified at start time via `registry.executionMode` (the SAME fail-closed `isConcurrencySafe` contract the loop uses) and start strictly in submission order. One single-lane driver owns every ORDERED stage — the start append, `prepare` (pre-execute/guards), the head-of-line `finalize`/`finish` commit (post-execute + context deferral + settle append) — so ordered policy stages never overlap each other and only the around-dispatch/body stage runs concurrently, exactly the native loop's sequencing (`fillPool` awaits `startCall` then `commitReady`). Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (a `Config` field validated by the Loader schema AND re-validated at direct construction, default 10 — the loop scheduler's own default; `1` restores serial dispatch); an exclusive call drains the pool, runs alone, and holds its barrier until its COMMIT completes (post-execute included), like a native exclusive group. Run settlement aborts in-flight dispatches and abandons queued-unstarted ones (binding rejection, no events), then drains to quiescence — including a commit already mid-flight when the program returned — before the outer result closes the turn.
|
||||
- **Client**: `CodeSubCall` widens to `RunningToolCall | ToolResultNode` — a start event lands the running shape in the dispatch index (rows derive the running ring from the shape, exactly as for native in-flight calls), and its settle replaces the entry in place, preserving start order under parallel completion and carrying the start's `time` as `callTime` (duration source). A settle with no observed start (window cut mid-pair, or a pre-start-event log) appends directly, so old logs keep rendering.
|
||||
- **SDK prompt**: the model-facing "calls execute sequentially" sentence is replaced with the true contract (independent safe calls may overlap under `Promise.all`; dependent work sequences with `await`) — a model-visible change, re-recorded across every code-mode snapshot.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user