fix(agent-loop): tighten parallel tool-call safety
This commit is contained in:
@@ -35,12 +35,17 @@ sequenceDiagram
|
||||
Driver->>Hooks: <code>agent/step-result</code> waterfall
|
||||
Driver->>Session: <code>assistant/message</code>
|
||||
Driver->>Tools: group calls by executionMode
|
||||
loop started tool calls (bounded pool)
|
||||
Driver->>Session: <code>tool/call</code> pending audit
|
||||
Driver->>Tools: ordered pre / pooled dispatch / ordered post
|
||||
Tools-->>Session: tool-owned events when applicable
|
||||
loop bounded rolling pool until group drains
|
||||
opt capacity available for an unstarted call
|
||||
Driver->>Session: <code>tool/call</code> pending audit
|
||||
Driver->>Tools: ordered pre / pooled dispatch
|
||||
Tools-->>Session: tool-owned events when applicable
|
||||
end
|
||||
opt next model-order result is ready
|
||||
Driver->>Tools: ordered post
|
||||
Driver->>Session: <code>tool/result</code>
|
||||
end
|
||||
end
|
||||
Driver->>Session: <code>tool/result</code> in model order
|
||||
Driver->>Session: <code>step/end</code>
|
||||
Driver->>Hooks: <code>agent/turn-continuation</code> waterfall
|
||||
Driver->>Hooks: <code>agent/turn-stop</code> serial terminal checkpoint
|
||||
|
||||
@@ -84,10 +84,9 @@ forever:
|
||||
'assistant/message'
|
||||
schedule tool calls by ctx.tools.executionMode (exclusive = barrier;
|
||||
consecutive parallel-safe = one rolling-pool group, <= maxParallelToolCalls in flight):
|
||||
each started call:
|
||||
'tool/call'
|
||||
tools/pre-execute -> monotonic guards -> tools/execute -> tools/post-execute -> tools/result
|
||||
'tool/result' committed in model order (slot-buffered)
|
||||
while the bounded pool has work:
|
||||
capacity available -> 'tool/call' -> tools/pre-execute -> monotonic guards -> tools/execute
|
||||
next model-order slot ready -> tools/post-execute -> 'tool/result'
|
||||
append post-tool context (model order) and steering
|
||||
'step/end'
|
||||
agent/turn-continuation
|
||||
|
||||
@@ -37,11 +37,17 @@ Source: [`packages/ui/acp/src/index.ts:203`](../packages/ui/acp/src/index.ts)
|
||||
* deployment persona (forwarded to the system-prompt plugin); `toolOrder` is
|
||||
* the explicit model-facing tool order (forwarded to the system-prompt plugin);
|
||||
* `tools` is the tool registry's config (its presentation `mode`, forwarded
|
||||
* through agent-spine-demo); `persistenceRoot` is the JSONL backend's directory.
|
||||
* through agent-spine-demo); `maxParallelToolCalls` configures the bundled
|
||||
* agent loop; `persistenceRoot` is the JSONL backend's directory.
|
||||
*/
|
||||
export interface Config {
|
||||
/** Model name for ACP-created agents (must have a registered adapter). */
|
||||
model: string
|
||||
/**
|
||||
* Concurrent parallel-safe tool-call cap for the bundled agent loop. A
|
||||
* positive integer; the loop defaults it when omitted and `1` is serial.
|
||||
*/
|
||||
maxParallelToolCalls?: number
|
||||
/** Deployment persona (the system-prompt plugin's `persona` config). */
|
||||
persona?: string
|
||||
/** Explicit model-facing tool order (the system-prompt plugin's `toolOrder` config; see dsh-system-prompt). */
|
||||
@@ -61,7 +67,7 @@ export interface Config {
|
||||
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
|
||||
|
||||
Source: [`packages/examples/acp-demo/src/index.ts:31`](../packages/examples/acp-demo/src/index.ts)
|
||||
Source: [`packages/examples/acp-demo/src/index.ts:32`](../packages/examples/acp-demo/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-agent-loop`
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Status: implemented
|
||||
|
||||
## Problem
|
||||
|
||||
An assistant message may contain several sibling `tool-call` blocks. Running them serially adds the latency of independent reads, web requests, and subagent runs even though the model has already requested them together.
|
||||
An assistant message may contain several sibling `tool-call` blocks. Running them serially adds the latency of independent reads and web requests even though the model has already requested them together.
|
||||
|
||||
Concurrency is a host scheduling concern, not model-facing tool metadata. The loop needs to decide which calls may overlap without hardcoding tool names or exposing scheduler policy in the JSON schema.
|
||||
|
||||
@@ -58,12 +58,10 @@ Any shared state touched during execution must be concurrency-safe. This include
|
||||
|
||||
`maxParallelToolCalls` is a positive AgentLoop deployment cap shared by every agent the factory creates. It defaults to `10`; `1` preserves serial execution. Exact fields and defaults live in the generated [configuration catalog](../../../config-catalog.md).
|
||||
|
||||
The shipped declarations are conservative. Web search, web fetch, filesystem read, and foreground subagent calls opt in. Background subagent starts remain exclusive because they register parent-owned task state. Filesystem writes and edits, bash tools, workflow, user interaction, todo mutation, Code Mode, and Cordis mutation tools also remain exclusive. Bash stays exclusive until its owning package supplies a proven input-sensitive classifier.
|
||||
The shipped declarations are conservative. Web search, web fetch, and filesystem read opt in. Filesystem writes and edits, bash tools, subagent delegation, workflow, user interaction, todo mutation, Code Mode, and Cordis mutation tools remain exclusive. A subagent may share its parent's workspace or external resources, and the unary classifier cannot prove that sibling delegations have disjoint effects. Bash stays exclusive until its owning package supplies a proven input-sensitive classifier.
|
||||
|
||||
Filesystem read relies on a narrow recorder exception: its synchronous observation updates may settle out of order, but write and edit re-check the observed version before mutation, so stale state only produces `FS_STALE_VERSION`.
|
||||
|
||||
The subagent declaration requires providers to accept concurrent `start()` calls for independent runs. A provider may queue, enforce its own capacity, or return a typed failure instead of requiring the parent loop to serialize every subagent call.
|
||||
|
||||
## Verification
|
||||
|
||||
Unit coverage pins fail-closed classification, typed argument validation, grouping, barriers, the rolling cap, distinct execution objects, middleware order, ordered results and context, and abort draining. First-party tests pin each parallel declaration.
|
||||
@@ -98,6 +96,6 @@ Parallel calls may begin in cases where serial execution would have aborted befo
|
||||
|
||||
Ordered commits may hold a fast result behind a slow earlier sibling. This preserves replay and model-history order while live surfaces still show pending progress.
|
||||
|
||||
Concurrent subagents and external calls can compete for quota or process capacity. Providers own their capacity controls; the loop cap only limits calls from one agent step.
|
||||
Concurrent external calls can compete for quota or process capacity. Providers own their capacity controls; the loop cap only limits calls from one agent step.
|
||||
|
||||
Tool registration is a scheduling boundary. The scheduler currently plans all groups before dispatch, so an earlier registry mutation can make a later classification stale. Binding dispatch to the classified definition or reclassifying after exclusive barriers remains a named correctness gap.
|
||||
|
||||
@@ -357,7 +357,7 @@ Source: [`packages/skill/tool-skill/src/index.ts`](../packages/skill/tool-skill/
|
||||
|
||||
### `subagent`
|
||||
|
||||
Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. You may issue several subagent calls in one message to run independent tasks concurrently when their work scopes do not overlap. Set `run_in_background: true` to return a task id; collect with `task_output` and stop with `task_kill`.
|
||||
Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. Set `run_in_background: true` to return a task id; collect with `task_output` and stop with `task_kill`.
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user