feat(tools): live dispatch lifecycle + native-contract parallel sub-calls in Code Mode
The bridge replaces its serialization queue with a pool that reuses the native concurrency contract: submissions classify through registry.executionMode (fail-closed isConcurrencySafe), start strictly in submission order, overlap up to the validated maxParallelSubCalls config (default 10; 1 restores serial), and exclusive calls drain the pool, run alone, and bar later calls. Each started sub-call logs a tool/code-dispatch-start event at pool entry; the existing tool/code-dispatch settles the pair (started ⇔ settles exactly once; abandoned queued calls log neither). SDK prompt guidance now states the true Promise.all contract — re-recorded across every code/both-mode snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the required description arg). Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts land the running shape (rows wear the native running ring), settles replace in place preserving start order, callTime pairs to the start time. Fixture emits start/settle pairs; jsdom pins the running sub-row; runtime specs pin in-place settlement and out-of-order completion.
This commit is contained in:
@@ -42,7 +42,7 @@ This note owns Code Mode's presentation, composition, isolation, and settlement
|
||||
|
||||
Under `'code'` and `'both'` the registry owns `run_code` as a reserved presentation transport with one required parameter, `{ code: string }`. It is represented by a normal `ToolDefinition` for dispatch but stays outside the filterable capability layers, so restrictions cannot accidentally remove Code Mode's only entry point. Calls traverse the complete tool pipeline — `tools/pre-execute` → monotonic guards → `tools/execute` around dispatch → `tools/post-execute` → optional definition-owned `finalizeContent` → immutable `tools/result` notification — exactly like native calls; a permission plugin can inspect the program text before it runs, and final-result observers see the normalized outer outcome. Its `execute(args, exec)`:
|
||||
|
||||
1. **Build bindings.** One run-scoped signal follows outer cancellation and is aborted whenever the run settles. Each visible tool binding snapshots lossless-JSON arguments, waits on the serialization queue, executes with a deterministic call id and the outer token as `parent`, defers returned contexts through the outer execution, and logs `tool/code-dispatch` with the full rendered result content. Success returns the tool's final canonical JSON value; failure becomes the program-visible `ToolCallError`. Every sub-call retains its own immutable execution identity and traverses the full tool pipeline.
|
||||
1. **Build bindings.** One run-scoped signal follows outer cancellation and is aborted whenever the run settles. Each visible tool binding snapshots lossless-JSON arguments, enters the native-contract dispatch pool (the [live-parallel note](2026-07-26-code-mode-live-parallel-dispatch.md) owns the scheduling design), executes with a deterministic call id and the outer token as `parent`, defers returned contexts through the outer execution, and logs the `tool/code-dispatch-start`/`tool/code-dispatch` pair, the settle side carrying the full rendered result content. Success returns the tool's final canonical JSON value; failure becomes the program-visible `ToolCallError`. Every sub-call retains its own immutable execution identity and traverses the full tool pipeline.
|
||||
2. **Runs the program**: `ctx.codeRuntime.run({ program: args.code, bindings: [{ global: 'tools', functions }], signal: runController.signal })`. The runtime receives the run-scoped signal, not only the caller's outer signal, so any way the outer run settles also aborts work inside the runtime.
|
||||
3. **Settle after quiescence.** When the runtime settles, the bridge aborts outstanding work and drains the dispatch queue before returning. Success returns captured logs and the completion value as canonical output; the registry renders that value into durable `tool/result.content`, which the result card reads directly. A runtime failure becomes `CodeRunFailedError`; backend rejection uses the registry's normal error boundary. Both produce structured error results, and no sub-call can append after `run_code` settles.
|
||||
|
||||
@@ -54,7 +54,7 @@ Under `'code'` and `'both'` the registry owns `run_code` as a reserved presentat
|
||||
|
||||
### Observability: `tool/code-dispatch`
|
||||
|
||||
Each sub-dispatch appends a log-only `tool/code-dispatch` event containing parent and child call ids, tool identity, normalized arguments, and the complete rendered `content`/`isError` outcome. It remains outside model history but available to persistence and UIs. Appends occur inside the open `run_code` turn. Direct executions without an agent still run but cannot log the event.
|
||||
Each sub-dispatch appends a log-only `tool/code-dispatch-start` event at pool entry and a `tool/code-dispatch` settle event containing parent and child call ids, tool identity, normalized arguments, and the complete rendered `content`/`isError` outcome. It remains outside model history but available to persistence and UIs. Appends occur inside the open `run_code` turn. Direct executions without an agent still run but cannot log the event.
|
||||
|
||||
### The code-runtime seam
|
||||
|
||||
|
||||
Reference in New Issue
Block a user