feat(subagent): durable child catalog and list_agents

Implements the durable-subagent-catalog RFC: SubagentControlService.listChildren()
enumerates a parent's direct continuable children from one sessionQuery trace,
validates each child's sole subagent/descriptor event (now carrying the durable
creation label), and returns one ordered SubagentListEntry[] with per-child
corrupt/unsupported/unavailable diagnostics. The list_agents tool ships as a
separately loadable plugin of dsh-tool-subagent-control requiring sessionQuery
at load; send_message stays usable without it.
This commit is contained in:
Dudu-0223
2026-07-26 02:32:34 +08:00
committed by Tianyi Cui
parent c7acc8fc6c
commit 4240c7dd7b
55 changed files with 1128 additions and 94 deletions

View File

@@ -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-21-continuable-background-subagents.md
2026-07-21-continuable-background-subagents.md: 513ee668a9e04c05bb50f946016c460e09d1ddcd
2026-07-21-continuable-background-subagents.zh.md: 88cdd00582b18a2092c6993507fe3d4b92f237ae
2026-07-21-continuable-background-subagents.md: b914533e759e46a3f8641ac2c9b49badc2377c5c
2026-07-21-continuable-background-subagents.zh.md: 9f3dace04fe6f13e2136a8f3b96cc2ef4363e82d

View File

@@ -27,7 +27,7 @@ durable child Session
Foreground delegation keeps its one-shot behavior. Continuation covers background in-process spawn and fork children. Each `tool-subagent` instance selects `backgroundMode: 'one-shot' | 'continuable'`; configured continuable mode requires the mounted provider's `resume` capability, while a resumable provider may still use one-shot background policy. ACP children remain one-shot until the deferred ACP continuation work below is complete.
`ctx.subagents` is the only public service. Ordinary `start` stays collection-, Task-, and persistence-agnostic: it validates provider capabilities, dispatches one activation, observes run lifecycle, and returns a holder-owned run. An injected internal continuation manager owns stable child ids, descriptor persistence and lookup, Task-backed activation, and routing through `startContinuable` and `followup`; provider start and resume dispatch use private closures after the manager resolves continuation state. The provider-bound `@deepseek-ai/dsh-tool-subagent` plugin and human-facing adapters call the intent operations for continuable background work; foreground and one-shot background delegation use ordinary `start`. The globally named model tool is a thin optional adapter in `@deepseek-ai/dsh-tool-subagent-control`, and its presence does not decide whether continuable work starts. Parent-to-child enumeration and `list_agents` belong to a separate durable-catalog proposal.
`ctx.subagents` is the only public service. Ordinary `start` stays collection-, Task-, and persistence-agnostic: it validates provider capabilities, dispatches one activation, observes run lifecycle, and returns a holder-owned run. An injected internal continuation manager owns stable child ids, descriptor persistence and lookup, Task-backed activation, and routing through `startContinuable` and `followup`; provider start and resume dispatch use private closures after the manager resolves continuation state. The provider-bound `@deepseek-ai/dsh-tool-subagent` plugin and human-facing adapters call the intent operations for continuable background work; foreground and one-shot background delegation use ordinary `start`. The globally named model tool is a thin optional adapter in `@deepseek-ai/dsh-tool-subagent-control`, and its presence does not decide whether continuable work starts. Parent-to-child enumeration and `list_agents` belong to the [durable subagent catalog](2026-07-22-durable-subagent-catalog-and-list-agents.md).
### Task and cancellation ownership

View File

@@ -27,7 +27,7 @@ durable child Session
前台委派保持一次性行为。继续执行覆盖后台的进程内 spawn 和 fork child。每个 `tool-subagent` 实例都会选择 `backgroundMode: 'one-shot' | 'continuable'`;配置为可继续模式时,所挂载提供方必须具备 `resume` 功能,而可恢复的提供方仍可采用一次性后台策略。在下述 ACPAgent Client Protocol后续工作完成前ACP child 仍保持一次性行为。
`ctx.subagents` 是唯一的公开服务。普通 `start` 不感知 child 集合、Task 与持久化:它校验提供方功能、分发一次激活、观察 run 生命周期,并返回由持有方负责的 run。注入的内部继续执行管理器负责管理稳定的 child id、描述符持久化与查找、由 Task 支撑的激活,以及通过 `startContinuable``followup` 进行的路由;管理器解析继续执行状态后,提供方的 start 与 resume 分发通过私有闭包进行。按提供方绑定的 `@deepseek-ai/dsh-tool-subagent` 插件及面向用户的适配器调用这些意图操作来处理可继续后台工作;前台和一次性后台委派使用普通 `start`。全局命名的模型工具是 `@deepseek-ai/dsh-tool-subagent-control` 中的可选轻量适配器它是否存在不会决定是否启动可继续工作。parent 到 child 的枚举与 `list_agents` 属于单独的持久化目录提案
`ctx.subagents` 是唯一的公开服务。普通 `start` 不感知 child 集合、Task 与持久化:它校验提供方功能、分发一次激活、观察 run 生命周期,并返回由持有方负责的 run。注入的内部继续执行管理器负责管理稳定的 child id、描述符持久化与查找、由 Task 支撑的激活,以及通过 `startContinuable``followup` 进行的路由;管理器解析继续执行状态后,提供方的 start 与 resume 分发通过私有闭包进行。按提供方绑定的 `@deepseek-ai/dsh-tool-subagent` 插件及面向用户的适配器调用这些意图操作来处理可继续后台工作;前台和一次性后台委派使用普通 `start`。全局命名的模型工具是 `@deepseek-ai/dsh-tool-subagent-control` 中的可选轻量适配器它是否存在不会决定是否启动可继续工作。parent 到 child 的枚举与 `list_agents` 属于[持久化 subagent 目录](2026-07-22-durable-subagent-catalog-and-list-agents.md)
### Task 与取消的所有权

View File

@@ -0,0 +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 .agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md
2026-07-22-durable-subagent-catalog-and-list-agents.md: 634cb8708f951dfd62e1345f96420cba0d7baed9
2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: a3d282d3de63ca3bc081c071e66c2713422ebcc2

View File

@@ -1,6 +1,6 @@
# Agent Note: Durable subagent catalog and list_agents
Status: proposed
Status: implemented
English | [中文](2026-07-22-durable-subagent-catalog-and-list-agents.zh.md)
@@ -10,9 +10,9 @@ Continuable background subagents expose a stable child id and persist the recons
Enumeration must cross-check immutable session lineage, descriptor validity, and the live-preferred session corpus without loading or resuming an Agent merely to display it. It must also define how missing, corrupt, deleted, or unsupported children affect the list and whether repeatedly loading many child logs needs an index.
## Proposal
## Decision
Treat parent-to-child enumeration and `list_agents` as one separately reviewed feature built on the durable child-session contract. `SubagentService.listChildren(parentSessionId: SessionId)` must:
Parent-to-child enumeration and `list_agents` are one separately reviewed feature built on the durable child-session contract. `SubagentService.listChildren(parentSessionId: SessionId)` does the following:
- use `ctx.sessionQuery.traceSession(parentSessionId)` to obtain the parent's direct live-preferred child sessions;
- read and validate each candidate's `subagent/descriptor` event without activating the child;
@@ -21,7 +21,7 @@ Treat parent-to-child enumeration and `list_agents` as one separately reviewed f
- report a live child as `running` and a persisted-only child as `complete`;
- return every resulting child in stable `createdAt` ascending, child-id ascending order.
Descriptor persistence, by-id lookup, direct-parent authorization, and provider-independent cold resume remain owned by the implemented Activation contract. This proposal extends the descriptor with a durable `label` and requires listing to diagnose duplicate descriptor events; it cannot weaken the existing facts or invent a second descriptor representation.
Descriptor persistence, by-id lookup, direct-parent authorization, and provider-independent cold resume remain owned by the implemented Activation contract. The catalog extends the descriptor with a durable `label` and requires listing to diagnose duplicate descriptor events; it does not weaken the existing facts or invent a second descriptor representation.
### Enumeration decision
@@ -46,7 +46,7 @@ If measured scale later requires an index, that index is derived state: session
- `kind: 'child'` carries readonly `id: SessionId`, durable `label: string`, and `status: 'running' | 'complete'`;
- `kind: 'diagnostic'` carries readonly `id: SessionId` and `reason: 'corrupt' | 'unsupported' | 'unavailable'`.
A valid descriptor produces one child entry, a per-child inspection failure produces one diagnostic entry, and a candidate without a descriptor produces no entry. Child status `running` means that the logical record is live in `ctx.sessions`; `complete` means that it exists only in persistence. These values are neither `AgentStatus` nor the manager's internal Activation state, and the result does not expose the internal `createdAt` sorting key. Exact Activation states and durable outcomes such as successful completion, failure, cancellation, and stop reason require a separate durable activation record and are outside this proposal.
A valid descriptor produces one child entry, a per-child inspection failure produces one diagnostic entry, and a candidate without a descriptor produces no entry. Child status `running` means that the logical record is live in `ctx.sessions`; `complete` means that it exists only in persistence. These values are neither `AgentStatus` nor the manager's internal Activation state, and the result does not expose the internal `createdAt` sorting key. Exact Activation states and durable outcomes such as successful completion, failure, cancellation, and stop reason require a separate durable activation record and are outside this feature.
The model-facing `list_agents` tool takes no arguments, derives `parentSessionId` from the current execution Agent, and is a thin adapter in `@deepseek-ai/dsh-tool-subagent-control`. It renders entries in array order as `<id> [<status>] — <label>` for a child and `<id> [diagnostic: <reason>]` for a diagnostic; an empty array renders `(no subagents)`.
@@ -82,27 +82,18 @@ The first version has no child deletion operation. If later product behavior del
**Paginate or cap the query now (deferred).** This bounds one result, but makes model discovery stateful and can hide older children unless the model follows a cursor. The first version has no cursor, page arguments, or candidate-limit configuration and returns the complete stably ordered set; a service-level bound remains a later decision if measured scale requires it.
## Acceptance criteria
## Testing
- `listChildren(parentSessionId: SessionId)` uses `ctx.sessionQuery.traceSession(parentSessionId)`, accepts a live or persisted target, considers only direct descendants, and does not duplicate corpus merging, lineage reconstruction, or sibling ordering.
- Listing loads no Agent, materializes no Activation, and appends no catalog or descriptor event itself. After the initial trace it calls `listEvents()` once per candidate and calls `readEvent()` only for a candidate with exactly one descriptor; persisted reads may trigger interrupted-tail repair, and compacted and uncompacted logs return the same children.
- The session header gains no subagent `kind`; a supported valid descriptor is the sole subagent discriminator and includes the delegation's durable `label`. Ordinary session forks and one-shot children lack that descriptor and are omitted without a diagnostic.
- Initial creation writes exactly one descriptor event, cold resume writes none, and a candidate with more than one descriptor event is diagnosed as `corrupt`.
- `listChildren()` returns one ordered `SubagentListEntry[]`; its closed `kind: 'child' | 'diagnostic'` union carries only the fields defined above. Each valid child or per-child diagnostic occupies its traced candidate's `createdAt`-then-id position, while a missing descriptor produces no entry. Diagnostics are transient and require no query beyond the candidate operation that produced them.
- `list_agents` takes no arguments, derives the parent id from the current execution Agent, and renders the complete result without a cursor. Child, diagnostic, and empty results use the fixed text forms defined above.
- A live logical session is `running`; a persisted-only logical session is `complete`. The result does not inspect the process-local Activation map or provider availability and leaves delivery-time authority and residency checks to `send_message`.
- Parent resume does not activate children. A child is absent until its session is published, and listing may race publication, disposal, or later delivery without weakening `send_message`'s execution-time checks.
- `list_agents` uses only `corrupt`, `unsupported`, or `unavailable` diagnostic reasons and never exposes descriptor contents in a diagnostic.
- After a successful initial trace, a corrupt, unsupported, disappeared, or unreadable descriptor candidate cannot hide healthy siblings: it is omitted with an id-and-reason diagnostic. Corpus-level persistence, header-consistency, or lineage failure during that initial trace fails the whole call.
- Per-child session-query failures map deterministically: invalid surfaces, exact-load source conflicts, immutable-header or direct-parent mismatches against the trace, and a changed read target are `corrupt`; missing sessions or events and persistence failures are `unavailable`; unknown descriptor versions are `unsupported`; and missing descriptors are omitted as non-continuable children.
- The list tool requires `sessionQuery` at plugin load; a direct `listChildren()` call without it fails before enumeration with `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`, while by-id `send_message` remains usable without that service.
- Keyless tests cover fresh and compacted discovery, ordinary fork and one-shot exclusion, live-to-complete transition, unmanaged-live-session snapshots, provider absence without child omission, durable `label` values, stable ordering, restart, direct-child tracing, duplicate descriptor rejection, isolated child diagnostics, phase-dependent persistence failure, load repair, snapshot races, and scan behavior. The model-facing complete-list-plus-diagnostics result has runnable snapshot coverage.
- `packages/subagent/subagent/tests/list-children.spec.ts` drives the real stack (agent loop, JSONL persistence, spawn/fork providers, the subagent service, and a concrete session-query service) keylessly: fresh discovery through a real `startContinuable()` child; 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 `complete`; 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; and the `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` no-service contract.
- `packages/subagent/tool-subagent-control/tests/list-agents.spec.ts` pins the `list_agents` schema (no parameters), 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.
- The keyless ACP snapshot scenario `subagent-list-agents` (examples/acp-agent) pins the model-visible transcript: a background delegation settles, and `list_agents` executes for real against the subagent service, session query, and JSONL persistence, rendering `<id> [complete] — <label>`.
## Risks
## Consequences
- Session tracing observes the complete logical corpus, then descriptor validation reads every direct-child log once and candidates with exactly one descriptor twice. In the persisted-only worst case, work is O(D × C + Σ L_i), not merely O(D), because each exact read rescans persistence and loads and clones the full candidate log. A later derived index must preserve the same authorization, per-child diagnostic, and fallback behavior.
- Corpus construction is an all-or-nothing trust boundary: one live/persisted header conflict can fail the initial trace and hide otherwise healthy siblings. Per-child isolation begins only after that trace succeeds.
- Corpus construction is an all-or-nothing trust boundary: one live/persisted header conflict fails the initial trace and hides otherwise healthy siblings. Per-child isolation begins only after that trace succeeds.
- Session-query reads may repair interrupted child logs and persist synthetic closing events even though listing creates no Agent. This is the existing persistence-load contract, not a hidden catalog write.
- The first version has no deletion operation, so persisted children remain listed for as long as their sessions remain in persistence even though live Agent resources remain bounded by resident Activations.
- The query returns every direct continuable child and diagnostic without a service cursor or candidate cap. Stable ordering makes the result deterministic but does not bound model-context growth; service pagination or deletion remains a later product decision.
- `running` and `complete` are process-local corpus snapshots, not delivery promises. Another process may activate a persisted child while this process reports it as `complete`; cross-process accuracy requires a shared lease.
- Making the durable `label` a required descriptor field is a pre-release format change: the descriptor version stays 1, and logs written by pre-label builds fail descriptor validation as `corrupt` (consistent with the repo's no-compatibility stance before the first tagged release).

View File

@@ -1,6 +1,6 @@
# Agent Noteagent 决策记录):持久化 subagent 目录与 list_agents
Status: proposed
Status: implemented
[English](2026-07-22-durable-subagent-catalog-and-list-agents.md) | 中文
@@ -10,9 +10,9 @@ Status: proposed
枚举必须交叉核对不可变的会话谱系、描述符有效性与实时优先的会话语料,而不能仅为展示就加载或恢复 Agent。它还必须定义缺失、损坏、已删除或不受支持的 child 如何影响列表,以及反复加载大量 child 日志是否需要索引。
## 提案
## 决策
parent 到 child 的枚举 `list_agents` 作为一个基于持久化 child Session 契约、单独评审的功能。`SubagentService.listChildren(parentSessionId: SessionId)` 必须
parent 到 child 的枚举 `list_agents` 一个基于持久化 child Session 契约、单独评审的功能。`SubagentService.listChildren(parentSessionId: SessionId)` 执行以下操作
- 使用 `ctx.sessionQuery.traceSession(parentSessionId)` 获取 parent 的直接且实时优先的 child 会话;
- 读取并校验每个候选会话的 `subagent/descriptor` 事件,但不激活 child
@@ -21,7 +21,7 @@ Status: proposed
- 将存活 child 报告为 `running`,只存在于持久化存储中的 child 报告为 `complete`
- 按 `createdAt` 升序、再按 child id 升序稳定返回所有结果 child。
描述符持久化、按 id 查找、直接 parent 鉴权和不依赖提供方的冷恢复仍归已实现的 Activation 契约负责。本提案会为描述符增加持久化 `label`,并要求列表查询诊断重复的描述符事件;它不削弱现有事实,也不发明第二种描述符表示。
描述符持久化、按 id 查找、直接 parent 鉴权和不依赖提供方的冷恢复仍归已实现的 Activation 契约负责。该目录会为描述符增加持久化 `label`,并要求列表查询诊断重复的描述符事件;它不削弱现有事实,也不发明第二种描述符表示。
### 枚举决策
@@ -46,7 +46,7 @@ subagent 服务将 `sessionQuery` 保持为可选依赖,因此没有该服务
- `kind: 'child'` 携带只读的 `id: SessionId`、持久化 `label: string``status: 'running' | 'complete'`
- `kind: 'diagnostic'` 携带只读的 `id: SessionId``reason: 'corrupt' | 'unsupported' | 'unavailable'`
有效描述符产生一个 child 条目,逐 child 检查失败产生一个 diagnostic 条目缺少描述符的候选不产生条目。child 状态 `running` 表示逻辑记录在 `ctx.sessions` 中存活;`complete` 表示该记录只存在于持久化存储中。这些值既不是 `AgentStatus`,也不是管理器内部的 Activation 状态,结果不公开内部 `createdAt` 排序键。成功完成、失败、取消和停止原因等精确 Activation 状态与持久化结果需要单独的持久化激活记录,不在本提案范围内。
有效描述符产生一个 child 条目,逐 child 检查失败产生一个 diagnostic 条目缺少描述符的候选不产生条目。child 状态 `running` 表示逻辑记录在 `ctx.sessions` 中存活;`complete` 表示该记录只存在于持久化存储中。这些值既不是 `AgentStatus`,也不是管理器内部的 Activation 状态,结果不公开内部 `createdAt` 排序键。成功完成、失败、取消和停止原因等精确 Activation 状态与持久化结果需要单独的持久化激活记录,不在本功能范围内。
面向模型的 `list_agents` 工具不接受参数,从当前正在执行的 Agent 推导 `parentSessionId`,并作为 `@deepseek-ai/dsh-tool-subagent-control` 中的轻量适配器。它按数组顺序将 child 渲染为 `<id> [<status>] — <label>`,将 diagnostic 渲染为 `<id> [diagnostic: <reason>]`;空数组渲染为 `(no subagents)`
@@ -82,27 +82,18 @@ diagnostic 是瞬时查询结果,不属于会话事件或目录状态。推导
**立即为查询分页或设置上限(暂缓)。** 这可以限制一次结果的大小,但会使模型发现成为有状态操作,而且除非模型继续跟随 cursor否则可能隐藏更早的 child。第一版没有 cursor、分页参数或候选数量上限配置而是返回经稳定排序的完整集合如果实测规模需要限制服务级限制仍留待后续决策。
## 验收标准
## 测试
- `listChildren(parentSessionId: SessionId)` 使用 `ctx.sessionQuery.traceSession(parentSessionId)`,接受存活或只存在于持久化存储中的目标,只考虑直接后代,并且不重复实现语料合并、谱系重建或 sibling 排序
- 列表查询不会加载 Agent、物化 Activation也不会自行追加目录或描述符事件。初始追踪完成后它会对每个候选调用一次 `listEvents()`,且只对恰好含有一个描述符的候选调用 `readEvent()`;持久化读取可能触发中断尾部修复,且经过压缩和未经压缩的日志会返回相同的 child
- 会话 header 不新增 subagent `kind`;受支持且有效的描述符是唯一的 subagent 判别信息,并包含委派的持久化 `label`。普通会话 fork 和一次性 child 缺少该描述符,因此会被排除且不产生 diagnostic
- 初始创建恰好写入一个描述符事件,从持久化存储恢复时不写入任何描述符;如果候选包含多个描述符事件,则将其诊断为 `corrupt`
- `listChildren()` 返回一个有序的 `SubagentListEntry[]`;其封闭的 `kind: 'child' | 'diagnostic'` 联合类型只携带上文定义的字段。每个有效 child 或逐 child diagnostic 占据其追踪候选按 `createdAt`、再按 id 排列的位置缺少描述符则不产生条目。diagnostic 是瞬时结果,除了产生该结果的候选操作外不需要其他查询。
- `list_agents` 不接受参数,从当前正在执行的 Agent 推导 parent id并在没有 cursor 的情况下渲染完整结果。child、diagnostic 和空结果使用上文定义的固定文本形式。
- 存活的逻辑会话为 `running`;只存在于持久化存储中的逻辑会话为 `complete`。结果不检查进程内 Activation map 或提供方可用性,并将消息送达时的鉴权与驻留状态检查留给 `send_message`
- 恢复 parent 不会激活 child。child 会话发布前不会出现列表查询可能与发布、dispose 或后续消息送达发生竞态,但不会削弱 `send_message` 在执行时进行的检查。
- `list_agents` 只使用 `corrupt``unsupported``unavailable` 作为 diagnostic 原因,且绝不在 diagnostic 中暴露描述符内容。
- 初始追踪成功后,描述符损坏、不受支持、已消失或无法读取的候选不能隐藏健康的 sibling系统会排除该候选并生成一条含 id 和原因的 diagnostic。初始追踪期间发生的语料级持久化、header 一致性或谱系失败会让整次调用失败。
- 逐 child 会话查询失败采用固定映射:无效 surface、精确加载时的来源冲突、相对于追踪结果的不可变 header 或直接 parent 不匹配,以及已变化的读取目标映射为 `corrupt`;会话或事件缺失以及持久化失败映射为 `unavailable`;未知描述符版本映射为 `unsupported`;缺少描述符则作为不可继续 child 排除。
- 列表工具在插件加载时要求 `sessionQuery`;直接调用 `listChildren()` 时如果缺少该服务,则会在枚举前以 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 失败,而按 id 的 `send_message` 在没有该服务时仍可使用。
- 无密钥测试覆盖压缩前后的发现、排除普通 fork 和一次性 child、从存活到 complete 的转换、未受管理的存活会话快照、提供方缺失时不排除 child、持久化 `label` 值、稳定排序、重启、直接 child 追踪、重复描述符拒绝、单个 child diagnostic 隔离、依阶段而异的持久化失败、加载修复、快照竞态和扫描行为。面向模型的完整列表加 diagnostic 结果具有可运行的快照覆盖。
- `packages/subagent/subagent/tests/list-children.spec.ts` 以无密钥方式驱动真实栈agent loop、JSONL 持久化、spawn/fork 提供方、subagent 服务,以及一个具体的会话查询服务):通过真实 `startContinuable()` child 的全新发现;只存在于持久化存储中(重启形态)的 parent 目标;带有人工构造并列项的按 `createdAt` 再按 id 排序;排除普通 fork 和 fork seed 中祖先描述符且不产生 diagnostic存活 `running` 与持久化 `complete` 的对比;重复描述符、载荷格式错误、无效 surface、header 不匹配和读取目标已变化的损坏 diagnostic 均不隐藏健康的 sibling不受支持版本与逐 child unavailable diagnostic提供方缺失时不排除 child压缩与未压缩的孪生 child 列表结果一致;排除孙代会话;追踪阶段失败导致整次调用失败而候选阶段失败只隔离到单个 child配置窗口错误和无法识别的失败作为操作失败向上传播以及 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 缺服务契约
- `packages/subagent/tool-subagent-control/tests/list-agents.spec.ts` 固定 `list_agents` 的 schema无参数、childdiagnostic空结果的固定文本形式、带持久化 label 的已结束 child 端到端列表、无调用 agent 时的拒绝、加载时的 `sessionQuery` 注入,以及 HMR dispose
- 无密钥 ACP 快照场景 `subagent-list-agents`examples/acp-agent固定模型可见的转写一次后台委派结束后`list_agents` 针对 subagent 服务、会话查询和 JSONL 持久化真实执行,渲染 `<id> [complete] — <label>`
## 风险
## 影响
- 会话追踪会观察完整的逻辑语料,随后描述符校验会读取每个直接 child 的日志一次,并对恰好含有一个描述符的候选读取两次。对于只存在于持久化存储中的最坏情况,工作量为 O(D × C + Σ L_i),而不只是 O(D),因为每次精确读取都会重新扫描持久化存储,并加载和克隆候选的完整日志。后续的派生索引必须保持相同的鉴权、逐 child diagnostic 和回退行为。
- 语料构建是一个全有或全无的信任边界:一处存活/持久化 header 冲突就可能导致初始追踪失败,并隐藏原本健康的 sibling。只有初始追踪成功后逐 child 隔离才会生效。
- 语料构建是一个全有或全无的信任边界:一处存活/持久化 header 冲突就导致初始追踪失败,并隐藏原本健康的 sibling。只有初始追踪成功后逐 child 隔离才会生效。
- 会话查询读取可能修复中断的 child 日志并持久化合成的结束事件,即使列表查询不创建 Agent。这是现有的持久化加载契约而非隐藏的目录写入。
- 第一版没有删除操作,因此只要 child 会话仍保留在持久化存储中,它们就会继续出现在列表里,但存活 Agent 资源仍由驻留 Activation 数量限制。
- 查询会返回每个直接可继续 child 和 diagnostic不设服务 cursor 或候选数量上限。稳定排序可使结果确定,但不会限制模型上下文的增长;服务分页或删除仍是后续的产品决策。
- `running``complete` 是进程内语料快照,而非消息送达承诺。另一个进程可能在当前进程将某个持久化 child 报告为 `complete` 时激活它;跨进程准确性需要共享租约。
- 将持久化 `label` 设为描述符必填字段是一次发布前的格式变更:描述符版本保持为 1加入 label 之前的构建所写入的日志会在描述符校验中判为 `corrupt`(与仓库首个正式版本之前不做兼容承诺的立场一致)。

View File

@@ -1,6 +0,0 @@
# 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 .agents/notes/proposed/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md
2026-07-22-durable-subagent-catalog-and-list-agents.md: 23d5b3924a20ae84132048b26e12779a98a6f2bb
2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: cac14271515d031a39bf6690f199c97be7bb9fa9