From a977ef30eecc44f47156531f41bde257e7ae5075 Mon Sep 17 00:00:00 2001 From: Dudu-0223 Date: Fri, 31 Jul 2026 14:02:03 +0800 Subject: [PATCH] fix(subagent): preserve published run failures --- ...eneric-long-running-tool-runtime.i18n.yaml | 6 +- ...06-20-generic-long-running-tool-runtime.md | 2 +- ...20-generic-long-running-tool-runtime.zh.md | 2 +- ...07-12-agent-scope-runtime-design.i18n.yaml | 4 +- .../2026-07-12-agent-scope-runtime-design.md | 18 +++--- ...026-07-12-agent-scope-runtime-design.zh.md | 18 +++--- ...6-06-21-subagent-capability-seam.i18n.yaml | 4 +- .../2026-06-21-subagent-capability-seam.md | 6 +- .../2026-06-21-subagent-capability-seam.zh.md | 6 +- ...-07-08-background-subagent-tasks.i18n.yaml | 6 +- .../2026-07-08-background-subagent-tasks.md | 4 +- ...2026-07-08-background-subagent-tasks.zh.md | 4 +- ...subagent-catalog-and-list-agents.i18n.yaml | 4 +- ...urable-subagent-catalog-and-list-agents.md | 7 ++- ...ble-subagent-catalog-and-list-agents.zh.md | 7 ++- ...6-merge-subagent-control-service.i18n.yaml | 4 +- ...26-07-26-merge-subagent-control-service.md | 2 +- ...07-26-merge-subagent-control-service.zh.md | 2 +- docs/cordis-catalog/events.md | 16 ++--- docs/cordis-catalog/services.md | 7 ++- docs/core-data-structures/subagent.i18n.yaml | 4 +- docs/core-data-structures/subagent.md | 30 ++++----- docs/core-data-structures/subagent.zh.md | 30 ++++----- examples/acp-agent/tests/acp.snapshot.ts | 11 ++++ .../fixtures/subagent-durability-failure.ts | 27 +++++++- .../subagent-published-run-failure/input.json | 7 +++ .../replay.override.json | 22 +++++++ .../session.jsonl | 26 ++++++++ .../stdout.expected.jsonl | 4 ++ .../cordis/tool-cordis/src/api-catalog.ts | 14 ++--- .../subagent-inprocess/README.i18n.yaml | 4 +- .../subagent/subagent-inprocess/README.md | 8 +-- .../subagent/subagent-inprocess/README.zh.md | 8 +-- .../subagent/subagent-inprocess/src/index.ts | 45 ++++++++------ .../tests/subagent-inprocess.spec.ts | 44 +++++++++++-- packages/subagent/subagent/README.i18n.yaml | 4 +- packages/subagent/subagent/README.md | 10 +-- packages/subagent/subagent/README.zh.md | 10 +-- packages/subagent/subagent/src/index.ts | 15 ++--- packages/subagent/subagent/src/invariant.ts | 2 +- packages/subagent/subagent/src/lifecycle.ts | 2 +- packages/subagent/subagent/src/types.ts | 28 +++++---- .../tests/list-agents.spec.ts | 21 ++++++- .../subagent/tool-subagent/README.i18n.yaml | 4 +- packages/subagent/tool-subagent/README.md | 2 +- packages/subagent/tool-subagent/README.zh.md | 2 +- packages/subagent/tool-subagent/src/index.ts | 61 +++++++++++++------ .../tool-subagent/tests/tool-subagent.spec.ts | 55 +++++++++++++++++ .../workflow-workerthread/src/protocol.ts | 2 +- .../workflow-workerthread/src/session.ts | 2 +- .../workflow-workerthread/src/types.ts | 2 +- .../tests/workflow-workerthread.spec.ts | 2 +- packages/workflow/workflow/README.i18n.yaml | 4 +- packages/workflow/workflow/README.md | 2 +- packages/workflow/workflow/README.zh.md | 2 +- packages/workflow/workflow/src/index.ts | 4 +- packages/workspace/workspace/tsconfig.json | 3 - 57 files changed, 444 insertions(+), 208 deletions(-) create mode 100644 examples/acp-agent/tests/snapshots/subagent-published-run-failure/input.json create mode 100644 examples/acp-agent/tests/snapshots/subagent-published-run-failure/replay.override.json create mode 100644 examples/acp-agent/tests/snapshots/subagent-published-run-failure/session.jsonl create mode 100644 examples/acp-agent/tests/snapshots/subagent-published-run-failure/stdout.expected.jsonl diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml index db80fbcfa9..341ea482c0 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -2026-06-20-generic-long-running-tool-runtime.md: 313d687b49da0d08b0ec321bcb655b642f7a5af3 -2026-06-20-generic-long-running-tool-runtime.zh.md: 6be129b7b16ff01d73dc94f7ce6d299ee2c10e55 +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +2026-06-20-generic-long-running-tool-runtime.md: cb9d9487cd274696ae20dddab8c6888b4cf4b833 +2026-06-20-generic-long-running-tool-runtime.zh.md: 77538105ba2841479238045327de493c5a835b7f diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md index 313d687b49..cb9d9487cd 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md @@ -93,7 +93,7 @@ The bash seam exposes `resolve`, `run`, and `start`. `start(spec)` returns a `Ba For background bash, `dsh-tool-bash` registers the calling agent as owner. Its hooks map `kill()` to cancellation, `done` to a completed or killed `TaskOutcome`, and `readOutput()` to the process's bounded incremental output plus spill and sandbox notices. Generic task tools own ids, status lines, listing, waiting, and completion notices. -For background subagents, `dsh-tool-subagent` creates a task-owned `AbortController` and begins provider startup inside the task starter. Cancellation aborts the same signal before or after provider readiness. `done` awaits both the child result and child disposal, maps completed output to a final result, maps abort to `killed`, and maps other stop reasons or infrastructure failures to `failed`. Intermediate child history remains in the child session and is not exposed through `readOutput()`. +For background subagents, `dsh-tool-subagent` creates a task-owned `AbortController` and begins provider startup inside the task starter. Cancellation aborts the same signal before or after provider publication. `done` awaits both the child result and child disposal, maps completed output to a final result, maps abort to `killed`, and maps other stop reasons or infrastructure failures to `failed`. Intermediate child history remains in the child session and is not exposed through `readOutput()`. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md index 6be129b7b1..77538105ba 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md @@ -93,7 +93,7 @@ bash seam 暴露 `resolve`、`run` 和 `start`。`start(spec)` 返回一个 `Bas 对于后台 bash,`dsh-tool-bash` 将调用方 agent 注册为所有者。其钩子将 `kill()` 映射为取消,将 `done` 映射为 completed 或 killed 的 `TaskOutcome`,并将 `readOutput()` 映射为进程的有界增量输出,以及溢出文件与沙箱通知。通用任务工具拥有 id、状态行、列表、等待和完成通知。 -对于后台 subagent,`dsh-tool-subagent` 创建由任务拥有的 `AbortController`,并在任务 starter 内启动提供方。无论提供方就绪前后,取消都会中止同一个 signal。`done` 同时等待子运行结果和子运行释放,将已完成输出映射为最终结果,将中止映射为 `killed`,并将其他停止原因或基础设施失败映射为 `failed`。中间子历史保留在子会话中,不通过 `readOutput()` 暴露。 +对于后台 subagent,`dsh-tool-subagent` 创建由任务拥有的 `AbortController`,并在任务 starter 内启动提供方。无论提供方发布前后,取消都会中止同一个 signal。`done` 同时等待子运行结果和子运行释放,将已完成输出映射为最终结果,将中止映射为 `killed`,并将其他停止原因或基础设施失败映射为 `failed`。中间子历史保留在子会话中,不通过 `readOutput()` 暴露。 ## 备选方案 diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml index 82b4452be3..d05f034c0f 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md -2026-07-12-agent-scope-runtime-design.md: a0a9a90bcac6e8f2ed0e06f3fbccb7b1244da278 -2026-07-12-agent-scope-runtime-design.zh.md: 09912162808f2f71d9ea49892cc61bbbeb1cf172 +2026-07-12-agent-scope-runtime-design.md: d6b865977a76061784c88dbad089fa5963be8c7e +2026-07-12-agent-scope-runtime-design.zh.md: 5b6b9b1ca582d83a267a30f8f76e38ea87d52e9b diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md index a0a9a90bca..d6b865977a 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md @@ -260,19 +260,19 @@ Skill registry definitions and approval policies are readonly same-process contr Skill still validates external skill files and parsed provider output, routes catalogs through the calling agent's tool view, and disposes registrations exactly. Approval still resolves policy, observes cancellation, routes `approval/request` by `request.agent`, records the durable audit pair, and contains answerer and post-commit observer failures. -## Subagents: readiness is the start promise +## Subagents: publication is the start promise -Subagent startup has one ownership transfer. The provider owns partial resources until its start promise fulfills with a ready published run; the caller owns the returned run and must dispose it. +Subagent startup has one ownership transfer. The provider owns unpublished resources until its start promise fulfills with a published run; the caller owns the returned run and must dispose it. ### The service contract has one cancellation channel -`SubagentProvider.start()` and `SubagentService.start()` return `Promise`. The promise fulfills only after the backend has established the child it promises, so callers and `subagent/start` observers never need a second `run.started` readiness promise. +`SubagentProvider.start()` and `SubagentService.start()` return `Promise`. The promise fulfills after the backend crosses its publication boundary, so callers and `subagent/start` observers never need a second `run.started` promise. Provider work that fails before publication rejects `start()`; prompt, turn, cancellation, and infrastructure outcomes after publication settle through `SubagentRun.result` without hiding the child id, as required by the [durable catalog decision](../feature/2026-07-22-durable-subagent-catalog-and-list-agents.md). -`SubagentStartRequest.signal` is required. Aborting it requests cancellation during startup and after readiness. `SubagentRun.dispose()` also requests cancellation and awaits quiescence. There is no separate public `run.cancel()` channel. +`SubagentStartRequest.signal` is required. Aborting it requests cancellation during startup and across the published run's remaining readiness or turn work. `SubagentRun.dispose()` also requests cancellation and awaits quiescence. There is no separate public `run.cancel()` channel. -Optional `SubagentRun.steer()` supports a live backend that can confirm steering admission. Optional `SubagentProvider.resume()` returns `Promise` because a reconstructed child has the same asynchronous readiness boundary. +Continuable conversations use their separate creation and follow-up operations and have no `SubagentRun`; their manager owns each resident `AgentHandle`. -The service validates provider capabilities and request semantics before calling the provider. A provider rejection cleans any partial resources before the rejection escapes and emits no `subagent/start`/`subagent/end` pair. After fulfillment, the service attaches result observation, emits scoped start, and returns the run. Provider removal prevents later starts but does not revoke a run already accepted by the provider. +The service validates provider capabilities and request semantics before calling the provider. A provider rejection cleans unpublished resources before the rejection escapes and emits no `subagent/start`/`subagent/end` pair. After fulfillment, the service attaches result observation, emits scoped start, and returns the run; a post-publication result rejection closes that pair. Provider removal prevents later starts but does not revoke a run already accepted by the provider. ### In-process providers reuse the core transaction @@ -318,7 +318,7 @@ The design is enforced at types, runtime escape points, generated contracts, and ### Types make the ordinary path hard to misuse -Readonly contracts describe borrowed same-process values. `Scoped` marks event receivers, `agentEvents()` fuses carrier and subject, tool inputs omit registry-owned tokens, and subagent async return types expose readiness directly. +Readonly contracts describe borrowed same-process values. `Scoped` marks event receivers, `agentEvents()` fuses carrier and subject, tool inputs omit registry-owned tokens, and subagent async return types expose publication and settlement directly. TypeScript cannot govern JavaScript casts, direct Cordis dispatch, process messages, or durable files, so runtime enforcement remains at those escape points. @@ -356,7 +356,7 @@ Parallel sentinels can all mirror whether one operation is live. One transaction ### Keep synchronous subagent start plus `run.started` -This splits provider acceptance from readiness and forces every consumer to register a partial run, attach result observation, await readiness, and clean up readiness failure. An async start promise makes provider-to-caller ownership transfer the readiness boundary itself. +This splits provider acceptance from publication and forces every consumer to register a partial run, attach result observation, await publication, and clean up publication failure. An async start promise keeps provider-to-caller ownership transfer at publication; the existing result promise owns any remaining readiness instead of adding another lifecycle promise. ### Restore selected prompt or tool contributions after assembly @@ -378,7 +378,7 @@ The implementation is smaller and its proof follows the same shape as its owners - Durable, queued, model, worker, process, and wire values are owned at their real boundary; typed same-process values follow readonly contracts. - ToolRegistry's presentation, lookup, and execution resolve the same live view before expert assembly transforms, and committed results have one immutable observation point. - Registry contributions are deterministic inputs, while the trusted assembly waterfall owns the final model-visible composition. -- Subagent start returns only a ready run, required signals cancel pending or live work, and disposal reaches the backend's quiescence contract. +- Subagent start returns only a published run, required signals cancel pending or live work, and disposal reaches the backend's quiescence contract. - Worker/process result precedence and cleanup remain correct under death, late messages, and bounded teardown. ### Costs and limits diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md index 0991216280..5b6b9b1ca5 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md @@ -260,19 +260,19 @@ Skill 注册表定义和 approval 策略是 readonly 的同进程契约。它们 Skill 仍然验证外部 skill 文件和解析的提供方输出,通过调用 agent 的工具视图路由目录,并精确 dispose 注册。Approval 仍然解析策略、观察取消、按 `request.agent` 路由 `approval/request`、记录持久化审计对,并隔离应答者和提交后观察者的失败。 -## Subagent:就绪即 start promise +## Subagent:发布即 start promise -Subagent 启动有一次所有权转移。提供方拥有部分资源直到其 start promise 以一个就绪的已发布 run 兑现;调用方拥有返回的 run 并必须 dispose 它。 +Subagent 启动有一次所有权转移。提供方拥有未发布资源,直到其 start promise 以一个已发布 run 兑现;调用方拥有返回的 run 并必须 dispose 它。 ### 服务契约有一个取消通道 -`SubagentProvider.start()` 和 `SubagentService.start()` 返回 `Promise`。Promise 仅在后端建立了它所承诺的子级之后才兑现,因此调用方和 `subagent/start` 观察者从不需要第二个 `run.started` 就绪 promise。 +`SubagentProvider.start()` 和 `SubagentService.start()` 返回 `Promise`。Promise 会在后端跨过发布边界后兑现,因此调用方和 `subagent/start` 观察者从不需要第二个 `run.started` promise。提供方工作如果在发布前失败,`start()` 就会被拒绝;发布后的提示词、轮次、取消与基础设施结果会通过 `SubagentRun.result` 结算,且不会隐藏 child id,这也是[持久化目录决策](../feature/2026-07-22-durable-subagent-catalog-and-list-agents.md)所要求的契约。 -`SubagentStartRequest.signal` 是必需的。中止它会在启动期间和就绪之后请求取消。`SubagentRun.dispose()` 也请求取消并等待完全停稳。没有单独的公开 `run.cancel()` 通道。 +`SubagentStartRequest.signal` 是必需的。中止它会在启动期间,以及已发布 run 的剩余就绪或轮次工作中请求取消。`SubagentRun.dispose()` 也请求取消并等待完全停稳。没有单独的公开 `run.cancel()` 通道。 -可选的 `SubagentRun.steer()` 支持能够确认 steering 准入的活跃后端。可选的 `SubagentProvider.resume()` 返回 `Promise`,因为重建的子级有相同的异步就绪边界。 +可继续对话使用各自独立的创建和后续操作,并且没有 `SubagentRun`;其管理器拥有每个驻留中的 `AgentHandle`。 -服务在调用提供方之前验证提供方能力和请求语义。提供方拒绝在拒绝逃出之前清理所有部分资源,且不发射 `subagent/start`/`subagent/end` 对。兑现之后,服务附加结果观察、发射作用域 start 并返回 run。提供方移除阻止后续 start,但不撤销提供方已接受的 run。 +服务在调用提供方之前验证提供方能力和请求语义。提供方 rejection 在逃出之前清理未发布资源,且不发射 `subagent/start`/`subagent/end` 对。兑现之后,服务附加结果观察、发射作用域 start 并返回 run;发布后的结果 rejection 会结束该事件对。提供方移除会阻止后续 start,但不撤销提供方已接受的 run。 ### 进程内提供方复用核心事务 @@ -318,7 +318,7 @@ Worker 边界仍然序列化请求和结果。宿主保留首个终端结果仲 ### 类型使常规路径难以误用 -Readonly 契约描述借用的同进程值。`Scoped` 标记事件接收器,`agentEvents()` 融合载体和主体,工具输入省略注册表拥有的 token,subagent 异步返回类型直接暴露就绪性。 +Readonly 契约描述借用的同进程值。`Scoped` 标记事件接收器,`agentEvents()` 融合载体和主体,工具输入省略注册表拥有的 token,subagent 异步返回类型直接暴露发布与结算。 TypeScript 无法管控 JavaScript 强制转换、直接 Cordis dispatch、进程消息或持久化文件,因此运行时强制保留在这些逃逸点。 @@ -356,7 +356,7 @@ TypeScript 无法管控 JavaScript 强制转换、直接 Cordis dispatch、进 ### 保留同步 subagent start 加 `run.started` -这将提供方接受与就绪分离,迫使每个消费方注册部分 run、附加结果观察、等待就绪并清理就绪失败。异步 start promise 使提供方到调用方的所有权转移本身成为就绪边界。 +这将提供方接受与发布分离,迫使每个消费方注册部分 run、附加结果观察、等待发布并清理发布失败。异步 start promise 将提供方到调用方的所有权转移保持在发布边界;现有的结果 promise 负责所有剩余就绪工作,无需增加另一个生命周期 promise。 ### 在 assembly 之后恢复选定的提示词或工具贡献 @@ -378,7 +378,7 @@ Worker 消息、进程死亡和持久化输入确实跨越所有权和序列化 - 持久化、队列、模型、worker、进程和协议格式的值在其真实边界处被拥有;类型化的同进程值遵循 readonly 契约。 - ToolRegistry 的展示、查找和执行在专家 assembly 变换之前解析相同的活跃视图,已提交的结果有一个不可变的观察点。 - 注册表贡献是确定性输入,而可信的 assembly waterfall 拥有最终的模型可见组合。 -- Subagent start 仅返回就绪的 run,必需的 signal 取消待定或活跃的工作,dispose 到达后端的完全停稳契约。 +- Subagent start 仅返回已发布的 run,必需的 signal 取消待定或活跃的工作,dispose 到达后端的完全停稳契约。 - Worker/进程结果优先级和清理在死亡、迟到消息和有界拆除下保持正确。 ### 代价与局限 diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml index 39fa3f91b7..feb73d00dd 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md -2026-06-21-subagent-capability-seam.md: d47d8fea1b4c03b19891e6af2d5b1d933feeb553 -2026-06-21-subagent-capability-seam.zh.md: e2fce78d0f76892e03136ab374a116152717f848 +2026-06-21-subagent-capability-seam.md: 043092884731c403a11b71ef8b5a410e9e5af7e0 +2026-06-21-subagent-capability-seam.zh.md: 6a4a5798199ca7d6d7c011668a319d65a0553208 diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md index d47d8fea1b..0430928847 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md +++ b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md @@ -38,12 +38,12 @@ A new package group `packages/subagent/`: ### The primitive: async `start → SubagentRun` -A provider exposes `start(request) → Promise`. Fulfillment publishes a ready child and transfers its run handle to the caller. One signal covers cancellation before and after readiness; `dispose()` cancels remaining work and awaits quiescence. A rejected start cleans partial resources and emits no lifecycle event. `start` is transport-neutral; `spawn` names only the fresh in-process backend. +A provider exposes `start(request) → Promise`. Fulfillment publishes a child and transfers its run handle to the caller. Work that fails before publication rejects `start()`, while prompt, turn, cancellation, and infrastructure outcomes after publication settle through `run.result` without hiding the child id. One signal covers cancellation before and after publication; `dispose()` cancels remaining work and awaits quiescence. A rejected start cleans unpublished resources and emits no lifecycle event, while a post-publication result failure closes the published lifecycle pair. `start` is transport-neutral; `spawn` names only the fresh in-process backend. ### Two kinds of optional capability, discovered two ways - **Start-time features** (`outputSchema`, `depthLimit`, `toolFilter`, `persona`) ride on a static `provider.capabilities` descriptor. The service checks every requested one BEFORE delegating and **rejects loud** (`SubagentError('UNSUPPORTED_CAPABILITY')`) if the provider lacks it — never accepted-then-ignored. They must be checked before a run exists, which is why they cannot be runtime methods. -- **Runtime features** are optional methods at their owning seams: confirmed live delivery is `SubagentRun.steer`, while persisted reconstruction is `SubagentProvider.resume`. Method presence is the capability and TypeScript narrowing is the discovery mechanism, so no separate flags object can drift from the implementation. +- **Continuable creation** is the optional `SubagentProvider.prepareContinuable` method; presence is the capability and TypeScript narrowing is the discovery mechanism, so no separate flag can drift from the implementation. The continuation manager owns later delivery and cold resume directly through `AgentHandle`, while one-shot `SubagentRun` has no steering or resume operation, as refined by [continuable subagents](2026-07-28-continuable-subagent-conversations.md). ### Fork vs. fresh are separate backends, not a flag @@ -55,7 +55,7 @@ Each subagent runs in its **own `Session`** (own id, `parentSession` lineage), p ### Synchronous collect (first cut) -`dsh-tool-subagent` passes its execution signal to `start()`, awaits the child result, and disposes the run in `finally`. Non-completed outcomes become error results rather than successful partial output. This foreground consumer does not use the run's optional steering method. +`dsh-tool-subagent` passes its execution signal to `start()`, awaits the child result, and disposes the run before reporting. Non-completed outcomes become error results rather than successful partial output, and independent result and disposal rejections retain both diagnostics. ### Provider selection is config, not model-facing diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md index e2fce78d0f..6a4a579819 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md +++ b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md @@ -38,12 +38,12 @@ bash seam([能力 seam](../architecture/2026-06-13-capability-seams.md))在 ### 原语:异步 `start → SubagentRun` -提供方暴露 `start(request) → Promise`。完成时发布一个就绪的子 agent 并将其运行句柄转交给调用方。一个信号覆盖就绪前后的取消;`dispose()`(资源释放)取消剩余工作并等待完全停稳。启动失败时清理部分资源,不发出生命周期事件。`start` 与传输方式无关;`spawn` 仅指代全新的进程内后端。 +提供方暴露 `start(request) → Promise`。完成时发布一个子 agent,并将其运行句柄转交给调用方。发布前失败的工作会拒绝 `start()`,而发布后的提示词、轮次、取消与基础设施结果会通过 `run.result` 结算,且不会隐藏 child id。同一个信号覆盖发布前后的取消;`dispose()`(资源释放)取消剩余工作并等待完全停稳。启动被拒绝时会清理未发布资源,且不发出生命周期事件;发布后的结果失败则会结束已经发布的生命周期事件对。`start` 与传输方式无关;`spawn` 仅指代全新的进程内后端。 ### 两类可选能力,两种发现方式 - **启动时功能**(`outputSchema`、`depthLimit`、`toolFilter`、`persona`)挂在静态的 `provider.capabilities` 描述符上。服务在委派之前检查每个被请求的功能,如果提供方不支持则**大声拒绝**(`SubagentError('UNSUPPORTED_CAPABILITY')`),绝不接受后静默忽略。这些功能必须在 run 存在之前检查,因此不能是运行时方法。 -- **运行时功能**是在其所属 seam 上定义的可选方法:提供确认语义的在线投递对应 `SubagentRun.steer`,持久化重建对应 `SubagentProvider.resume`。方法的存在本身即为能力,TypeScript 类型收窄即为发现机制,因此不需要可能与实现失同步的独立 flags 对象。 +- **可继续创建**使用可选的 `SubagentProvider.prepareContinuable` 方法;方法是否存在本身即为能力,TypeScript 类型收窄即为发现机制,因此不需要可能与实现失同步的独立 flag。继续执行管理器直接通过 `AgentHandle` 负责后续投递与从持久化存储恢复,而一次性 `SubagentRun` 没有 steering 或 resume 操作,具体由[可继续 subagent](2026-07-28-continuable-subagent-conversations.md)细化。 ### Fork 与 fresh 是独立后端,而非一个 flag @@ -55,7 +55,7 @@ bash seam([能力 seam](../architecture/2026-06-13-capability-seams.md))在 ### 同步收集(首版) -`dsh-tool-subagent` 将其执行信号传给 `start()`,等待子 agent 结果,并在 `finally` 中 dispose 该 run。非完成态的结果变为错误结果,而非成功的部分输出。这个前台消费方不使用 run 的可选 steering 方法。 +`dsh-tool-subagent` 将其执行信号传给 `start()`,等待子 agent 结果,并在报告前 dispose 该 run。非完成态的结果变为错误结果,而非成功的部分输出;相互独立的结果与 dispose rejection 会保留两项 diagnostic。 ### 提供方选择是配置,不面向模型 diff --git a/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.i18n.yaml b/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.i18n.yaml index fb39bd26db..d50b2323e3 100644 --- a/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -2026-07-08-background-subagent-tasks.md: 12b34f2a28cfa311a48904cd5396ec16d9123641 -2026-07-08-background-subagent-tasks.zh.md: 58f035a14d55bc0b4e7f670111909074c3f53b2b +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md +2026-07-08-background-subagent-tasks.md: 679c57c3173a32b8cfccc6611b4ba74487e7f7e3 +2026-07-08-background-subagent-tasks.zh.md: 487943ab5cfe78226cde912854099fc56a5833a6 diff --git a/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md b/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md index 12b34f2a28..679c57c317 100644 --- a/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md +++ b/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md @@ -23,7 +23,7 @@ For a background call, the tool validates the parent and refuses an already-abor The task registration maps the subagent seam as follows: - `kind` is `subagent`, `label` is the model-supplied description, and `owner` is the parent agent. -- `cancel(reason?)` aborts the task-owned controller. The same signal covers pending provider startup and the ready child. +- `cancel(reason?)` aborts the task-owned controller. The same signal covers pending provider startup and the published run's remaining work. - `done` awaits provider startup, the child result, and `run.dispose()`. Completed runs return final text, aborted runs become `killed`, and other stop reasons become `failed`. Startup, result, and disposal failures become failed outcomes rather than rejected task promises. - `readOutput` is absent. While live, `task_output` returns status only; after settlement, it returns final output idempotently. Intermediate child activity remains in the child session. @@ -57,7 +57,7 @@ Streaming child history into the parent would blur the log boundary and make pro ## Testing -Unit coverage pins stop-reason mapping, dispose-before-report behavior, startup and result failures, pre-aborted refusal, detachment from the starting call's signal, cancellation before and after provider readiness, collection through the real task tools, the no-surface preflight fence, missing-runtime failure, and per-instance schema gating. Snapshot coverage pins the model-facing schemas. +Unit coverage pins stop-reason mapping, dispose-before-report behavior, startup and result failures, pre-aborted refusal, detachment from the starting call's signal, cancellation before and after provider publication, collection through the real task tools, the no-surface preflight fence, missing-runtime failure, and per-instance schema gating. Snapshot coverage pins the model-facing schemas. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.zh.md b/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.zh.md index 58f035a14d..487943ab5c 100644 --- a/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.zh.md +++ b/.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.zh.md @@ -23,7 +23,7 @@ subagent 需要与其他长时间运行的工具相同的启动、收集、列 任务注册按以下方式映射 subagent seam: - `kind` 为 `subagent`,`label` 为模型提供的描述,`owner` 为父 agent(智能体)。 -- `cancel(reason?)` 中止任务自有的控制器。同一个信号同时覆盖尚未完成的提供方启动和已就绪的子级。 +- `cancel(reason?)` 中止任务自有的控制器。同一个信号同时覆盖尚未完成的提供方启动和已发布 run 的剩余工作。 - `done` 等待提供方启动、子级结果和 `run.dispose()`。已完成的运行返回最终文本,已中止的运行变为 `killed`,其他停止原因变为 `failed`。启动、结果和资源释放失败会转换为失败结果,而不是被拒绝的任务 Promise。 - `readOutput` 不存在。任务存活期间,`task_output` 只返回状态;结算后,它以幂等方式返回最终输出。中间的子级活动仍保留在子会话中。 @@ -57,7 +57,7 @@ agent 和日志可能以会话为作用域,但任务注册表和可预测 id ## 测试 -单元测试覆盖固定了停止原因映射、在报告前释放资源、启动与结果失败、对预中止的拒绝、从启动调用信号分离、在提供方就绪前后取消、通过真实任务工具收集、无控制接口的预检防线、运行时缺失失败,以及每实例 schema 开关。快照覆盖固定了面向模型的 schema。 +单元测试覆盖固定了停止原因映射、在报告前释放资源、启动与结果失败、对预中止的拒绝、从启动调用信号分离、在提供方发布前后取消、通过真实任务工具收集、无控制接口的预检防线、运行时缺失失败,以及每实例 schema 开关。快照覆盖固定了面向模型的 schema。 ## 影响 diff --git a/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.i18n.yaml b/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.i18n.yaml index fb32d23928..aefda46d41 100644 --- a/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md -2026-07-22-durable-subagent-catalog-and-list-agents.md: c150b7417ac80505d74f8ea477e3bf92f0762720 -2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: 49f2fbffc854dd4301f05d90c8f39b1625869dad +2026-07-22-durable-subagent-catalog-and-list-agents.md: 0dd7eebac74689004014248c7178dba540ef4662 +2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: 33b0296cf9914d1975fb1dc84564b498a09bd511 diff --git a/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md b/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md index c150b7417a..0dd7eebac7 100644 --- a/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md +++ b/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md @@ -29,7 +29,7 @@ The first implementation consumes `ctx.sessionQuery.traceSession(parentSessionId Corpus construction precedes per-child descriptor inspection. A failure while building the initial trace, including persistence listing failure, a live/persisted header conflict anywhere in the observed corpus, or invalid target lineage, fails the whole `list_agents` call because no trustworthy candidate set exists. Only failures after a successful trace are isolated to one candidate; "corrupt child" in that per-child contract therefore means corrupt loaded event surface or descriptor data, not a corpus-level header conflict. -Session lineage is broader than subagent identity: an ordinary `ctx.sessions.fork()` also creates a direct child. The session header gains no `kind` discriminator; each candidate must instead contain exactly one valid `subagent/descriptor` event in its own suffix. `SubagentService.start()` resolves `{ mode: 'one-shot', provider, label? }` before ordinary provider dispatch, while the continuation manager snapshots and seeds `{ mode: 'continuable', ...composition }` during initial child creation. The local in-process one-shot driver appends its resolved descriptor only during initial creation, and cold resume appends no further descriptor; a second event is corruption rather than evidence of another Activation. The event is the sole evidence that a traced child is a session-backed subagent. A candidate without it is an ordinary fork, a remote child without a local session record, or another non-subagent session and is omitted without a diagnostic. +Session lineage is broader than subagent identity: an ordinary `ctx.sessions.fork()` also creates a direct child. The session header gains no `kind` discriminator; each candidate must instead contain exactly one valid `subagent/descriptor` event in its own suffix. `SubagentService.start()` resolves `{ mode: 'one-shot', provider, label? }` before ordinary provider dispatch, while the continuation manager snapshots and seeds `{ mode: 'continuable', ...composition }` during initial child creation. The local in-process one-shot driver appends its resolved descriptor only during initial creation, and cold resume appends no further descriptor; a second event is corruption rather than evidence of another Activation. Agent creation is the one-shot publication boundary: a rejection means no child was published, while prompt, turn, cancellation, and infrastructure outcomes after publication settle through the returned run without hiding its id. The descriptor event is the sole evidence that a traced child is a session-backed subagent. A candidate without it is an ordinary fork, a remote child without a local session record, or another non-subagent session and is omitted without a diagnostic. The published logical record is also the activity source: `SessionRecord.live` means `running`, while `live: false, persisted: true` means `inactive`. Activity comes directly from the trace and causes no additional child-log load. `inactive` encodes neither successful completion nor resumability: it may describe settled one-shot history or a continuable child for which `send_message` can materialize another Activation. Conversely, `running` says only that the session is live: a live continuable Agent outside the continuation manager's matching Activation still appears as `running`, but `send_message` rejects it as an ownership conflict. A child is not visible before its session is published, and no process-local Activation entry is added as a second candidate or activity source. Listing is a snapshot that may race publication, disposal, or a later message; `send_message` remains the authoritative delivery-time operation. @@ -90,10 +90,11 @@ The first version has no child deletion operation. If later product behavior del ## Testing -- `packages/subagent/subagent/tests/service.spec.ts` pins descriptor v2 parsing for both modes and proves an unlabeled raw start resolves a one-shot descriptor before provider dispatch. `packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts` proves the local driver appends that descriptor inside the initial turn. Delegation-tool tests pin propagation of their existing display description. +- `packages/subagent/subagent/tests/service.spec.ts` pins descriptor v2 parsing for both modes and proves an unlabeled raw start resolves a one-shot descriptor before provider dispatch. `packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts` proves the local driver appends that descriptor inside the initial turn, returns the published id when cancellation lands in the factory-to-run handoff, and keeps result and handle-disposal failures on separate channels. Delegation-tool tests pin propagation of their existing display description and preserve independent result and disposal diagnostics. - `packages/subagent/subagent/tests/list-children.spec.ts` pins a query-only composition with sessions, `subagents`, and `sessionQuery` but no `agents`, then drives the full real stack (agent loop, JSONL persistence, spawn/fork providers, the subagent service, and a concrete session-query service) keylessly: one-shot and continuable children from one real trace; a persisted (restart-shaped) parent target; `createdAt`-then-id ordering with authored ties; ordinary-fork and fork-seed ancestor-descriptor exclusion without diagnostics; live `running` vs persisted `inactive`; duplicate-descriptor, malformed-payload, invalid-surface, mismatched-header, and changed-read-target corruption diagnostics that leave healthy siblings visible; unsupported-version and per-child unavailable diagnostics; provider absence without child omission; compacted/uncompacted twins listing identically; grandchild exclusion; trace-phase failure failing the whole call while candidate-phase failures isolate to one child; configuration/window and unrecognized failures propagating as operation failures; forwarded trace/exact-read cancellation with stable `CANCELLED` normalization; and the `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` no-service contract. `packages/subagent/subagent/tests/optional-session-query.spec.ts` rejects eager evaluation of the optional runtime while importing the ordinary subagent surface. -- `packages/subagent/tool-subagent-control/tests/list-agents.spec.ts` pins the `list_agents` schema (no parameters), the continuable-only projection that omits a healthy one-shot sibling while preserving diagnostics, the fixed child/diagnostic/empty text forms, an end-to-end settled-child listing with its durable label, the no-agent rejection, load-time `sessionQuery` injection, and HMR disposal. +- `packages/subagent/tool-subagent-control/tests/list-agents.spec.ts` pins the `list_agents` schema (no parameters), the continuable-only projection that omits a healthy one-shot sibling while preserving diagnostics, the fixed child/diagnostic/empty text forms, an end-to-end settled-child listing with its durable label, forwarding of the tool cancellation signal, the no-agent rejection, load-time `sessionQuery` injection, and HMR disposal. - The keyless ACP snapshot scenario `subagent-list-agents` (examples/acp-agent) fences its second parent turn on a snapshot-only `subagent/end` marker, then executes `list_agents` for real against the subagent service, session query, and JSONL persistence, rendering ` [complete] —