From edd2eee9e92c76c8a1d8ccfdb981ab7e413dd990 Mon Sep 17 00:00:00 2001 From: Turtle Date: Fri, 6 Nov 2026 21:22:09 +0800 Subject: [PATCH] docs(tools): document parallel read-only tool contracts Record the MCP resource provider concurrency requirement, the working-directory recovery notice, and why lsp, goal, job_output and job_kill stay exclusive; fix unbound-method lint in declaration tests. Refs #8084 Co-authored-by: liukx0205 <69756503+liukx0205@users.noreply.github.com> Co-authored-by: LegGasai <75920107+LegGasai@users.noreply.github.com> --- ...-10-parallel-tool-call-execution.i18n.yaml | 8 ++++---- ...2026-07-10-parallel-tool-call-execution.md | 6 +++--- ...6-07-10-parallel-tool-call-execution.zh.md | 6 +++--- docs/subsystems/mcp.md | 4 +++- docs/subsystems/mcp.zh.md | 4 +++- packages/extensions/tool-cordis/src/index.ts | 1 + .../extensions/tool-cordis/tests/host.spec.ts | 12 +++++++++++ .../tool-cordis/tests/tools.spec.ts | 20 ------------------- packages/fs/tool-fs-search/README.i18n.yaml | 4 ++-- packages/fs/tool-fs-search/README.md | 2 ++ packages/fs/tool-fs-search/README.zh.md | 2 ++ packages/jobs/tool-jobs/README.i18n.yaml | 4 ++-- packages/jobs/tool-jobs/README.md | 2 ++ packages/jobs/tool-jobs/README.zh.md | 2 ++ .../jobs/tool-jobs/tests/tool-jobs.spec.ts | 6 ++++-- packages/mcp/mcp-resources/src/index.ts | 4 +++- packages/mcp/mcp-resources/src/tools.ts | 2 +- .../tool-schedule/tests/tool-schedule.spec.ts | 6 ++++-- .../tool-subagent/tests/list-models.spec.ts | 1 + .../tool-agent-team/tests/tool-team.spec.ts | 6 ++++-- 20 files changed, 58 insertions(+), 44 deletions(-) delete mode 100644 packages/extensions/tool-cordis/tests/tools.spec.ts diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml index 07e5bdeaa1..5736f23e73 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml @@ -18,11 +18,11 @@ en: 16c4ee08b65ead23 zh: df80d6e49982615b /agent-note-parallel-tool-call-execution-by-per-call-safety/configuration-and-declarations: - en: 11cbaa5f4037c058 - zh: 1918e44d73ffe75d + en: 082e5a02b092b656 + zh: ca674a08323d8cc1 /agent-note-parallel-tool-call-execution-by-per-call-safety/verification: - en: 224b6838130c113f - zh: f3d7eb68ab5932fb + en: 20d0e1724ebc1247 + zh: baa89694cea135c8 /agent-note-parallel-tool-call-execution-by-per-call-safety/alternatives-considered: en: 17becc038bb7df7c zh: 673d9a9bdfa39edf diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md index a30947c1b4..03e35dba6b 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md @@ -60,13 +60,13 @@ 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](../../../../docs/config-catalog.md). -The shipped declarations are conservative. Web search, web fetch, filesystem read, glob, grep, the session-query trace/read tools, and subagent delegation opt in — delegation because a child works in its own session and its run never mutates the parent session, with sibling workspace coordination owned by the model ([parallel subagent Agent Note](../../archived/feature/2026-08-09-parallel-subagent-delegations.md)). Read-only queries also opt in: the background job list, the subagent tree and model listings, scheduled task and Team task reads, Cordis Inspect queries, and MCP resource reads. Filesystem writes and edits, bash tools, `job_output` because it consumes a read cursor, the session-query search tools, workflow, user interaction, todo mutation, PTC mode, and Cordis mutation tools remain exclusive. Bash has no proven input-sensitive classifier and remains exclusive. +The shipped declarations are conservative. Web search, web fetch, filesystem read, glob, grep, the session-query trace/read tools, and subagent delegation opt in — delegation because a child works in its own session and its run never mutates the parent session, with sibling workspace coordination owned by the model ([parallel subagent Agent Note](../../archived/feature/2026-08-09-parallel-subagent-delegations.md)). Read-only queries also opt in: the background job list, the subagent tree and model listings, scheduled task and Team task reads, Cordis Inspect queries, and MCP resource reads. Filesystem writes and edits, bash tools, `job_output` because it consumes a read cursor, the session-query search tools, workflow, user interaction, todo mutation, PTC mode, and Cordis mutation tools remain exclusive. Bash has no proven input-sensitive classifier and remains exclusive. Read-only `lsp` stays exclusive because its language-server pool already serializes queries per workspace, so overlap would gain nothing, and the goal tools stay exclusive under the [goal tools Agent Note](2026-07-19-model-facing-goal-tools.md). -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`. Filesystem read, glob, and grep also share a working-directory recovery exception: when the Session's recorded directory is unset or missing, the body's `ctx.workingDirectory.ensure()` appends `working-directory/change`, and the working-directory service serializes those appends per Session. +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`. Filesystem read, glob, and grep also share a working-directory recovery exception: when the Session's recorded directory is unset or missing, the body's `ctx.workingDirectory.ensure()` appends `working-directory/change` and, when it restores a missing directory, injects a model-visible notice; the working-directory service serializes that recovery per Session. ## Verification -Unit coverage pins fail-closed classification, typed argument validation, grouping, barriers, rejection of unstarted calls after registry replacement, the rolling cap, distinct execution objects, middleware order, ordered results and context, abort draining, and scheduler-failure quiescence. First-party tests pin each parallel declaration. +Unit coverage pins fail-closed classification, typed argument validation, grouping, barriers, rejection of unstarted calls after registry replacement, the rolling cap, distinct execution objects, middleware order, ordered results and context, abort draining, and scheduler-failure quiescence. First-party tests pin each parallel declaration, and the working-directory service tests pin per-Session serialization of concurrent directory changes. Snapshot coverage pins the visible multi-call transcript: pending calls may overlap while completed results remain model-ordered. PTC mode coverage pins its serial boundary. No provider-backed e2e is required because scheduling is deterministic loop behavior. diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md index c03a20b28a..38db00b5ff 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md @@ -60,13 +60,13 @@ Status: implemented `maxParallelToolCalls` 是 AgentLoop 的正整数部署上限,由工厂创建的所有 agent(智能体)共享。默认值为 `10`;`1` 保持串行执行。字段和默认值的精确定义见生成的[配置目录](../../../../docs/config-catalog.zh.md)。 -当前实现中的声明保持保守。Web 搜索、Web 获取、文件系统读取、glob、grep、会话查询的 trace/read 工具和 subagent 委派选择并行;委派之所以并行,是因为子 agent 在自己的会话中工作,其运行绝不变更父会话,并列委派间的工作区协调由模型负责([并行 subagent Agent Note](../../archived/feature/2026-08-09-parallel-subagent-delegations.md))。只读查询也选择并行:后台任务列表、subagent 树与模型列表、定时任务与 Team 任务读取、Cordis Inspect 查询以及 MCP 资源读取。文件系统写入与编辑、bash 工具、`job_output`(因为它会消费读取游标)、会话查询的 search 工具、工作流、用户交互、todo 变更、PTC mode 以及 Cordis 变更工具仍按独占方式执行。Bash 没有已证明的输入敏感分类器,因此仍按独占方式执行。 +当前实现中的声明保持保守。Web 搜索、Web 获取、文件系统读取、glob、grep、会话查询的 trace/read 工具和 subagent 委派选择并行;委派之所以并行,是因为子 agent 在自己的会话中工作,其运行绝不变更父会话,并列委派间的工作区协调由模型负责([并行 subagent Agent Note](../../archived/feature/2026-08-09-parallel-subagent-delegations.md))。只读查询也选择并行:后台任务列表、subagent 树与模型列表、定时任务与 Team 任务读取、Cordis Inspect 查询以及 MCP 资源读取。文件系统写入与编辑、bash 工具、`job_output`(因为它会消费读取游标)、会话查询的 search 工具、工作流、用户交互、todo 变更、PTC mode 以及 Cordis 变更工具仍按独占方式执行。Bash 没有已证明的输入敏感分类器,因此仍按独占方式执行。只读的 `lsp` 仍按独占方式执行,因为其语言服务器池已按工作区串行化查询,重叠执行不会带来收益;goal 工具依照 [goal 工具 Agent Note](2026-07-19-model-facing-goal-tools.zh.md) 仍按独占方式执行。 -文件系统读取依赖一个范围很窄的记录器例外:其同步观察更新可以不按顺序结算,但写入和编辑在变更前会重新检查已观察的版本,因此陈旧状态只会导致 `FS_STALE_VERSION`。文件系统读取、glob 和 grep 还共享一个工作目录恢复例外:当会话记录的目录未设置或不存在时,工具主体中的 `ctx.workingDirectory.ensure()` 会追加 `working-directory/change`,而工作目录服务按会话串行化这些追加。 +文件系统读取依赖一个范围很窄的记录器例外:其同步观察更新可以不按顺序结算,但写入和编辑在变更前会重新检查已观察的版本,因此陈旧状态只会导致 `FS_STALE_VERSION`。文件系统读取、glob 和 grep 还共享一个工作目录恢复例外:当会话记录的目录未设置或不存在时,工具主体中的 `ctx.workingDirectory.ensure()` 会追加 `working-directory/change`,并在恢复不存在的目录时注入一条模型可见的通知;工作目录服务按会话串行化该恢复过程。 ## 验证 -单元测试固定了按安全侧原则进行的分类、类型化参数验证、分组、屏障、注册表替换后拒绝尚未开始的调用、滚动上限、独立执行对象、中间件顺序、有序结果与上下文、中止排空,以及调度器故障后的完全停稳。第一方测试固定了每项并行声明。 +单元测试固定了按安全侧原则进行的分类、类型化参数验证、分组、屏障、注册表替换后拒绝尚未开始的调用、滚动上限、独立执行对象、中间件顺序、有序结果与上下文、中止排空,以及调度器故障后的完全停稳。第一方测试固定了每项并行声明,工作目录服务测试固定了按会话串行化并发目录变更。 快照测试固定了可见的多调用 transcript(文本记录):待处理调用可以重叠执行,已完成结果仍按模型顺序排列。PTC mode 测试固定了其串行边界。此调度属于确定性循环行为,因此无需依赖提供方的 e2e 测试。 diff --git a/docs/subsystems/mcp.md b/docs/subsystems/mcp.md index a07100c4e7..da3c4539a3 100644 --- a/docs/subsystems/mcp.md +++ b/docs/subsystems/mcp.md @@ -86,7 +86,9 @@ type McpResourceRequest = /** One configured server's resource access, owned by its MCP connection plugin. */ interface McpResourceProvider { /** - * Run an operation against one live connection generation. + * Run an operation against one live connection generation. The resource tools + * dispatch in parallel, so one provider may receive several concurrent requests; + * each must settle independently. * @param request - MCP resource method and parameters. * @param exec - caller identity and cancellation for this invocation. * @returns the protocol result as lossless JSON. diff --git a/docs/subsystems/mcp.zh.md b/docs/subsystems/mcp.zh.md index 216db4413a..a2eecf3f11 100644 --- a/docs/subsystems/mcp.zh.md +++ b/docs/subsystems/mcp.zh.md @@ -86,7 +86,9 @@ type McpResourceRequest = /** One configured server's resource access, owned by its MCP connection plugin. */ interface McpResourceProvider { /** - * Run an operation against one live connection generation. + * Run an operation against one live connection generation. The resource tools + * dispatch in parallel, so one provider may receive several concurrent requests; + * each must settle independently. * @param request - MCP resource method and parameters. * @param exec - caller identity and cancellation for this invocation. * @returns the protocol result as lossless JSON. diff --git a/packages/extensions/tool-cordis/src/index.ts b/packages/extensions/tool-cordis/src/index.ts index 5527990512..9e7cd9bd77 100644 --- a/packages/extensions/tool-cordis/src/index.ts +++ b/packages/extensions/tool-cordis/src/index.ts @@ -32,6 +32,7 @@ export function apply(ctx: Context): void { schema: { type: 'json' }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }], }, + // Listing reads the Provider registry without mutating it. isConcurrencySafe: () => true, execute(_args, _exec): Promise { return Promise.resolve({ providers: ctx.cordisInspect.list() } as unknown as JsonValue) diff --git a/packages/extensions/tool-cordis/tests/host.spec.ts b/packages/extensions/tool-cordis/tests/host.spec.ts index d9aa417110..87830a4036 100644 --- a/packages/extensions/tool-cordis/tests/host.spec.ts +++ b/packages/extensions/tool-cordis/tests/host.spec.ts @@ -5,6 +5,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRegistry from '@deepseek-ai/dsh-tools' import { describe, expect, it } from 'vitest' import * as CordisInspectProviders from '../src/host.ts' +import * as CordisInspectTools from '../src/index.ts' /** * The Host inspect providers are one process-global set: the host entry @@ -35,3 +36,14 @@ describe('the cordis-inspect-providers host entry', () => { expect(ctx.cordisInspect.list()).toEqual([]) }) }) + +describe('the cordis inspect tools', () => { + it('declares both read-only tools parallel-safe', async () => { + const ctx = await host() + const fiber = ctx.plugin(CordisInspectTools) + await fiber + expect(ctx.tools.get('cordis_inspect_list')?.isConcurrencySafe?.({})).toBe(true) + expect(ctx.tools.get('cordis_inspect_query')?.isConcurrencySafe?.({ platform: 'host', provider: 'Service', method: 'get' })).toBe(true) + await fiber.dispose() + }) +}) diff --git a/packages/extensions/tool-cordis/tests/tools.spec.ts b/packages/extensions/tool-cordis/tests/tools.spec.ts deleted file mode 100644 index e7d3d35668..0000000000 --- a/packages/extensions/tool-cordis/tests/tools.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' -import { Context } from '@deepseek-ai/cordis' -import { CordisInspectRegistryService } from '@deepseek-ai/dsh-cordis-host-runner' -import SystemPrompt from '@deepseek-ai/dsh-system-prompt' -import ToolRegistry from '@deepseek-ai/dsh-tools' -import { describe, expect, it } from 'vitest' -import * as CordisInspectTools from '../src/index.ts' - -describe('the cordis inspect tools', () => { - it('declares both read-only tools parallel-safe', async () => { - const ctx = new Context() - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(NativeToolExecutor) - await ctx.plugin(CordisInspectRegistryService, 10_000) - await ctx.plugin(CordisInspectTools) - expect(ctx.tools.get('cordis_inspect_list')?.isConcurrencySafe?.({})).toBe(true) - expect(ctx.tools.get('cordis_inspect_query')?.isConcurrencySafe?.({ platform: 'host', provider: 'Service', method: 'get' })).toBe(true) - }) -}) diff --git a/packages/fs/tool-fs-search/README.i18n.yaml b/packages/fs/tool-fs-search/README.i18n.yaml index b1c75185bb..ad15c4ec16 100644 --- a/packages/fs/tool-fs-search/README.i18n.yaml +++ b/packages/fs/tool-fs-search/README.i18n.yaml @@ -21,8 +21,8 @@ en: a5a01d14e0bec814 zh: 409298cfafd91b05 /deepseek-ai-dsh-tool-fs-search/use-this-package/the-tools: - en: 1932fbef1abcac77 - zh: 6d8060990b5f2f08 + en: d4a0419665d3c3b3 + zh: 7fe718b32401b4cd /deepseek-ai-dsh-tool-fs-search/use-this-package/configuration: en: 3e84e19a22aeb6c8 zh: 8ac44b21eb697f2f diff --git a/packages/fs/tool-fs-search/README.md b/packages/fs/tool-fs-search/README.md index 4c26ffa018..e181e4a53e 100644 --- a/packages/fs/tool-fs-search/README.md +++ b/packages/fs/tool-fs-search/README.md @@ -52,6 +52,8 @@ A subprocess backend, then the tools; the spill backend is optional and makes ca Routine budgets stay out of the model-facing schema: a model that needs surrounding context reads the matched file with `read`, and one that needs later results follows the returned spill locator's retrieval hint. +Both tools opt into concurrent scheduling with sibling parallel-safe calls. A search records no file observation, over-cap spilling runs in the model-ordered post-execute phase, and working-directory recovery is serialized per Session by the working-directory service. + ### Configuration `sampleOverCapGlobResults` is required; the remaining keys are optional search caps with the defaults below. diff --git a/packages/fs/tool-fs-search/README.zh.md b/packages/fs/tool-fs-search/README.zh.md index 10cae42308..212689eb4b 100644 --- a/packages/fs/tool-fs-search/README.zh.md +++ b/packages/fs/tool-fs-search/README.zh.md @@ -52,6 +52,8 @@ kind: "package-reference" 常规预算不进入面向模型的 schema:需要周边上下文的模型用 `read` 读取匹配文件,需要后续结果的模型遵循返回的 spill locator 检索提示。 +两个工具都选择与同批并行安全调用并发调度。搜索不记录文件观察,超出上限的 spill 在按模型顺序执行的 post-execute 阶段运行,工作目录恢复由工作目录服务按会话串行化。 + ### 配置 `sampleOverCapGlobResults` 为必填;其余键是可选的搜索上限,默认值如下。 diff --git a/packages/jobs/tool-jobs/README.i18n.yaml b/packages/jobs/tool-jobs/README.i18n.yaml index b4ec668330..7d8836390e 100644 --- a/packages/jobs/tool-jobs/README.i18n.yaml +++ b/packages/jobs/tool-jobs/README.i18n.yaml @@ -18,8 +18,8 @@ en: 823e94910a15e2e1 zh: c23e0b605f32f0bd /deepseek-ai-dsh-tool-jobs/use-this-package/the-three-tools: - en: 6f20f54e60bcf637 - zh: 6279c685d9d0bb16 + en: 618467d160f47332 + zh: f064a74fe4b08d3e /deepseek-ai-dsh-tool-jobs/use-this-package/completion-notices: en: 99577924f3d65112 zh: 521dac5540f9c1b7 diff --git a/packages/jobs/tool-jobs/README.md b/packages/jobs/tool-jobs/README.md index f97394300e..870d49be0f 100644 --- a/packages/jobs/tool-jobs/README.md +++ b/packages/jobs/tool-jobs/README.md @@ -35,6 +35,8 @@ Load this plugin in any composition where the agent should start, observe, and s The three tools return `{ text, job }`, `PublicJobSnapshot[]`, and `{ outcome: 'cancellation-requested' | 'already-finished', job }` respectively. A public snapshot carries id, kind, label, status with the live progress line or the terminal detail, and start/finish times, and omits ownership and ring offsets. All three render through generic UI cards: `read` for output and list, `execute` for kill. +`job_list` opts into concurrent scheduling with sibling parallel-safe calls because it reads a status snapshot. `job_output` stays exclusive because each read consumes the stream cursor and the one-time result delivery, and `job_kill` stays exclusive because it requests cancellation. + ### Completion notices When a job finishes, the owning agent receives `background job (: