feat(agent-loop): run safe tool calls in parallel

This commit is contained in:
Dudu-0223
2026-07-13 11:02:21 +08:00
parent 4cda9dd03d
commit 7ea1bf119f
48 changed files with 1542 additions and 141 deletions

View File

@@ -11,6 +11,8 @@ Each tool is registered independently; a product that wants only one disables th
| `web_search` | `query` (string) | Discovery. Returns an optional answer plus source URLs. `max_results` is **not** model-facing — the tool sets the bound (the `searchMaxResults` config, default 8) and passes it to the seam. |
| `web_fetch` | `url` (string) | Retrieves a specific URL. HTML bodies are rendered to markdown-ish text; text bodies pass through. A non-2xx status is reported, not an error. The tool-call timeout is deployment policy (`dsh-timeout-policy`), not a model argument. |
Both tools declare `isConcurrencySafe: () => true` — they are read-only (fetch a provider/URL, return content, mutate no parent-agent state), so the agent loop may run sibling web calls in parallel.
## Config
| Key | Default | Meaning |

View File

@@ -99,6 +99,9 @@ export function applyWebFetchTool(ctx: Context, timeoutMs: number): void {
url: { type: 'string', required: true, description: 'The HTTP(S) URL to fetch.' },
},
timeoutMs,
// Read-only: fetching a URL returns content and mutates no parent-agent
// state — safe to run concurrently with sibling calls.
isConcurrencySafe: () => true,
async execute(args, exec): Promise<ContentBlock[]> {
const input = parseFetchArgs(args)
const result = await ctx.web.fetch(

View File

@@ -109,6 +109,9 @@ export function applyWebSearchTool(ctx: Context, maxResults: number, timeoutMs:
query: { type: 'string', required: true, description: 'The search query.' },
},
timeoutMs,
// Read-only: a search hits the provider and returns content, mutating no
// parent-agent state — safe to run concurrently with sibling calls.
isConcurrencySafe: () => true,
async execute(args, exec): Promise<ContentBlock[]> {
const input = parseSearchArgs(args)
const result = await ctx.web.search(

View File

@@ -166,6 +166,10 @@ describe('tool-web registration', () => {
const names = ctx.tools.schemas().map(s => s.name)
expect(names).toContain('web_search')
expect(names).toContain('web_fetch')
expect(ctx.tools.executionMode({ callId: CallId('search-safe'), name: 'web_search', arguments: { query: 'q' } }))
.toEqual({ kind: 'parallel' })
expect(ctx.tools.executionMode({ callId: CallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } }))
.toEqual({ kind: 'parallel' })
await fiber.dispose()
expect(ctx.tools.schemas().map(s => s.name)).not.toContain('web_search')
})