Kaynağa Gözat

fix(subagent): address steer review findings

Dudu-0223 1 ay önce
ebeveyn
işleme
3091bdc257
68 değiştirilmiş dosya ile 141 ekleme ve 556 silme
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md
  4. 2 2
      .agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml
  5. 3 3
      .agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md
  6. 3 3
      .agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md
  7. 2 2
      .agents/notes/implemented/feature/2026-08-10-continuable-subagent-policy-inheritance.i18n.yaml
  8. 2 2
      .agents/notes/implemented/feature/2026-08-10-continuable-subagent-policy-inheritance.md
  9. 2 2
      .agents/notes/implemented/feature/2026-08-10-continuable-subagent-policy-inheritance.zh.md
  10. 10 6
      apps/web/tests/scaffold.ts
  11. 1 1
      apps/web/tests/subagent-conversation.e2e.ts
  12. 1 1
      docs/event-producer-consumer.i18n.yaml
  13. 4 4
      docs/event-producer-consumer.md
  14. 2 2
      docs/subsystems/subagent.i18n.yaml
  15. 0 12
      docs/subsystems/subagent.md
  16. 0 12
      docs/subsystems/subagent.zh.md
  17. 2 2
      docs/tool-catalog.i18n.yaml
  18. 2 2
      docs/tool-catalog.md
  19. 2 2
      docs/tool-catalog.zh.md
  20. 1 11
      packages/extensions/tool-cordis/src/api-catalog.ts
  21. 2 2
      packages/subagent/subagent/README.i18n.yaml
  22. 1 1
      packages/subagent/subagent/README.md
  23. 1 1
      packages/subagent/subagent/README.zh.md
  24. 0 185
      packages/subagent/subagent/src/activation-setup-registry.ts
  25. 5 15
      packages/subagent/subagent/src/continuation.ts
  26. 1 22
      packages/subagent/subagent/src/index.ts
  27. 0 163
      packages/subagent/subagent/tests/activation-setup-registry.spec.ts
  28. 0 10
      packages/subagent/subagent/tests/service.spec.ts
  29. 2 2
      packages/subagent/tool-subagent-control/README.i18n.yaml
  30. 2 2
      packages/subagent/tool-subagent-control/README.md
  31. 2 2
      packages/subagent/tool-subagent-control/README.zh.md
  32. 5 5
      packages/subagent/tool-subagent-control/src/index.ts
  33. 5 0
      packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts
  34. 0 1
      scripts/gen-cordis-catalog.ts
  35. 2 2
      snapshots/sdk/bash-tool/tool-schemas.expected.json
  36. 2 2
      snapshots/sdk/subagent-continuable-inheritance/tool-schemas.1.expected.json
  37. 2 2
      snapshots/sdk/subagent-continuable/tool-schemas.1.expected.json
  38. 2 2
      snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json
  39. 2 2
      snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json
  40. 2 2
      snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json
  41. 2 2
      snapshots/sdk/subagent-list-agents/tool-schemas.1.expected.json
  42. 2 2
      snapshots/sdk/subagent-send-message/tool-schemas.1.expected.json
  43. 2 2
      snapshots/sdk/text-turn/tool-schemas.expected.json
  44. 4 4
      snapshots/session/agent-instructions/tool-schemas.expected.json
  45. 2 2
      snapshots/session/both-mode-turn/system-prompt.expected.md
  46. 2 2
      snapshots/session/both-mode-turn/tool-schemas.expected.json
  47. 4 4
      snapshots/session/compaction-recovery/tool-schemas.expected.json
  48. 2 2
      snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md
  49. 2 2
      snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json
  50. 2 2
      snapshots/session/fs-glob-sampling/tool-schemas.expected.json
  51. 2 2
      snapshots/session/lsp-definition/tool-schemas.expected.json
  52. 2 2
      snapshots/session/product-subagent-both/tool-schemas.expected.json
  53. 2 2
      snapshots/session/product-subagent-codex/tool-schemas.expected.json
  54. 2 2
      snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json
  55. 2 2
      snapshots/session/ptc-read-image/system-prompt.expected.md
  56. 2 2
      snapshots/session/ptc-turn/system-prompt.expected.md
  57. 2 2
      snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json
  58. 2 2
      snapshots/session/ralph-loop/tool-schemas.1.expected.json
  59. 2 2
      snapshots/session/ralph-loop/tool-schemas.2.expected.json
  60. 2 2
      snapshots/session/session-query-spill/tool-schemas.expected.json
  61. 2 2
      snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json
  62. 2 2
      snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json
  63. 2 2
      snapshots/session/text-turn/tool-schemas.expected.json
  64. 2 2
      snapshots/session/web-fetch/tool-schemas.expected.json
  65. 2 2
      snapshots/web/cordis-tool-round/tool-schemas.expected.json
  66. 2 2
      snapshots/web/fresh-round-trip/tool-schemas.expected.json
  67. 2 2
      snapshots/web/ptc-round/system-prompt.expected.md
  68. 2 2
      snapshots/web/schedule-catalog/tool-schemas.expected.json

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.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-08-27-adjacent-agent-steer-messaging.md
-2026-08-27-adjacent-agent-steer-messaging.md: e1ec18195be35bd9dde321f79e3f1fb76084bd79
-2026-08-27-adjacent-agent-steer-messaging.zh.md: 68c98ecfbd3af840e58c167693e91c23ae5e6b77
+2026-08-27-adjacent-agent-steer-messaging.md: a98bec9646ed38431fd9a26f50fc3a6a96972b5b
+2026-08-27-adjacent-agent-steer-messaging.zh.md: d651315f40eacde8144af1c3e51cc8b91a3defff

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md

@@ -50,7 +50,7 @@ Parents and children inherit the same definition in the same registry order. A c
 
 The instruction is guidance, not settlement enforcement. Sending does not end the child's turn, zero or several calls remain mechanically valid, and the runtime never rejects a child for staying silent. The manager-owned `subagent-settled` notice remains unconditional and separately attributed because it records how an Activation ended and preserves terminal output when the child cannot cooperate.
 
-Human browser prompts are not model-authored Agent messages. The remote prompt path keeps a private Queue delivery so each human prompt remains a distinct turn. Interrupt behavior, settlement delivery, and the continuation setup registry remain independent.
+Human browser prompts are not model-authored Agent messages. The remote prompt path keeps a private Queue delivery so each human prompt remains a distinct turn. Interrupt behavior and settlement delivery remain independent.
 
 ### Complete removal and reintroduction condition
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md

@@ -50,7 +50,7 @@ parent 与 child 以相同注册表顺序继承相同定义。child `toolFilter`
 
 该指令是指导,不是结算强制。发送不会结束 child 轮次,机制仍允许零次或多次调用,runtime 绝不会因 child 保持沉默而拒绝它。由管理器负责的 `subagent-settled` 通知仍无条件发送并采用独立来源,因为它记录 Activation 如何结束,并在 child 无法配合时保留终态输出。
 
-浏览器中的人类提示不是模型编写的 Agent 消息。远程提示路径保留私有 Queue 投递,使每条人类提示保持为独立轮次。中断行为、结算投递与继续执行设置注册表保持独立。
+浏览器中的人类提示不是模型编写的 Agent 消息。远程提示路径保留私有 Queue 投递,使每条人类提示保持为独立轮次。中断行为与结算投递保持独立。
 
 ### 完整移除与重新引入条件
 

+ 2 - 2
.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.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/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md
-2026-08-10-child-agents-join-their-parent-preset.md: 5a3d0d1c5e2b492b10a0274204c10e244c37196f
-2026-08-10-child-agents-join-their-parent-preset.zh.md: ce5d69404d4f8b6a9879668d2ed0cb1c9d133a40
+2026-08-10-child-agents-join-their-parent-preset.md: 5321d99bad35cf26ebc0e7842dc68c2775bc384b
+2026-08-10-child-agents-join-their-parent-preset.zh.md: f0d5b59c3787ef69c2c983da0c8ec0ff4a43e6e7

+ 3 - 3
.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md

@@ -22,7 +22,7 @@ This is a bind, not a mount, and both differences are load-bearing. The child ge
 
 `dsh-subagent` reaches the roster through `ctx.get('agentPresets')` with a type-only import and an optional peer dependency — the documented opportunistic-consumption pattern it already uses for `sandboxPolicy` and `approval`.
 
-Giving the child its parent's tools exposed a second defect the same agent-plane move introduced: `ToolRuntime` exempted SCOPED registrations from a restriction and filtered only the global layer, so once every model-facing row became an ancestor contribution, a child's `toolFilter` stopped constraining anything — and, with the global layer empty, `restrict()` rejected every name it was given as unknown, failing the child outright. The exempt set is the tools a scope registers ITSELF, not the tools that happen to live in the global layer; reading it the second way held only while those two sets coincided. `view()` now filters everything a scope inherits — the global layer and every ancestor layer — and exempts only its own. The own-layer exemption is load-bearing rather than incidental: the delegation runtime registers a child's `report` and structured-output tools into the child's own layer, and a filter naming the capabilities the child may use must not strip the machinery it answers through.
+Giving the child its parent's tools exposed a second defect the same agent-plane move introduced: `ToolRuntime` exempted SCOPED registrations from a restriction and filtered only the global layer, so once every model-facing row became an ancestor contribution, a child's `toolFilter` stopped constraining anything — and, with the global layer empty, `restrict()` rejected every name it was given as unknown, failing the child outright. The exempt set is the tools a scope registers ITSELF, not the tools that happen to live in the global layer; reading it the second way held only while those two sets coincided. `view()` now filters everything a scope inherits — the global layer and every ancestor layer — and exempts only its own. The own-layer exemption is load-bearing rather than incidental: the delegation runtime registers a child's structured-output tool into the child's own layer, and a filter naming the capabilities the child may use must not strip the machinery it answers through.
 
 ## Alternatives considered
 
@@ -30,11 +30,11 @@ Giving the child its parent's tools exposed a second defect the same agent-plane
 
 **Bind the child's key to the PARENT's key rather than to the standing mount.** Rejected because it changes what a child inherits: the parent's own scope layer carries its per-agent restrictions, which would then intersect into every descendant, and a child outliving its parent would hang off a disposed agent's key. Joining the standing mount gives the child its parent's composition and nothing else.
 
-**Extend the continuable activation setup registry to cover one-shot children.** Rejected because that registry's contribution type is synchronous `(childCtx) => () => void` with per-installation revocation, modelling deployment capabilities that come and go, while a preset join is a one-time bind with no revocation of its own. Widening it would have made the omission possible again for any driver that skipped the registry.
+**Introduce one shared child-setup registry for both drivers.** Rejected because a synchronous, revocable contribution models deployment capabilities that come and go, while a preset join is a one-time bind with no revocation of its own. Routing composition through an optional registry would make the omission possible again for any driver that skipped it.
 
 **Let `dsh-subagent` import `resolveSessionPreset` and mount by the resolved id.** Rejected because it makes the preset roster a hard module edge for a package that must work without one, and it lands back on the remount semantics above.
 
-**Filter every layer on the chain, including the scope's own.** Rejected because it makes a per-child capability filter delete that child's reporting and structured-output tools, which the delegation runtime registers into the child's own layer — an `allow` naming the capabilities a child may use would leave it unable to answer at all.
+**Filter every layer on the chain, including the scope's own.** Rejected because it makes a per-child capability filter delete that child's structured-output tool, which the delegation runtime registers into the child's own layer — an `allow` naming the capabilities a child may use would leave it unable to produce the requested result.
 
 **Leave the durable header alone and fix only the live join.** Rejected because the live child and the same child read cold would then disagree about which composition produced its history — the same class of defect, moved rather than fixed.
 

+ 3 - 3
.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md

@@ -22,7 +22,7 @@ Status: implemented
 
 `dsh-subagent` 以类型级导入加可选 peer 依赖的方式,通过 `ctx.get('agentPresets')` 触达 roster——这正是它对 `sandboxPolicy` 与 `approval` 已在使用的、有明确文档的机会性消费模式。
 
-把父方的工具交给子 agent 之后,暴露出同一次 agent 平面搬迁引入的第二个缺陷:`ToolRuntime` 把**作用域级**注册排除在限制之外、只过滤全局层,因此当所有面向模型的行都变成祖先贡献之后,子 agent 的 `toolFilter` 就不再约束任何东西——而且全局层为空时,`restrict()` 会把收到的每个名字都判为未知并直接让子 agent 创建失败。豁免集合应当是作用域**自己注册**的工具,而不是恰好位于全局层的工具;后一种读法只在这两个集合重合时才成立。`view()` 现在过滤作用域继承来的一切——全局层与每个祖先层——只豁免它自己那层。这条自身层豁免是承重的而非顺带的:委派运行时把子 agent 的 `report` 与结构化输出工具注册进子 agent 自己那层,而一个只点名子 agent 可用能力的过滤器绝不能把它回报所依赖的机制一并剥掉。
+把父方的工具交给子 agent 之后,暴露出同一次 agent 平面搬迁引入的第二个缺陷:`ToolRuntime` 把**作用域级**注册排除在限制之外、只过滤全局层,因此当所有面向模型的行都变成祖先贡献之后,子 agent 的 `toolFilter` 就不再约束任何东西——而且全局层为空时,`restrict()` 会把收到的每个名字都判为未知并直接让子 agent 创建失败。豁免集合应当是作用域**自己注册**的工具,而不是恰好位于全局层的工具;后一种读法只在这两个集合重合时才成立。`view()` 现在过滤作用域继承来的一切——全局层与每个祖先层——只豁免它自己那层。这条自身层豁免是承重的而非顺带的:委派运行时把子 agent 的结构化输出工具注册进子 agent 自己那层,而一个只点名子 agent 可用能力的过滤器绝不能把它产出请求结果所依赖的机制一并剥掉。
 
 ## 考虑过的替代方案
 
@@ -30,11 +30,11 @@ Status: implemented
 
 **把子 agent 的 key 绑到**父方的** key 而不是常驻挂载上。** 否决,因为这改变了子 agent 继承的内容:父方自己的 scope 层携带其逐 agent 限制,那些限制会就此与每个后代求交,而活得比父方久的子 agent 会挂在一个已 dispose 的 agent key 上。加入常驻挂载给到子 agent 的是父方的组装,仅此而已。
 
-**扩展可继续 activation setup 注册表以覆盖一次性子 agent。** 否决,因为该注册表的贡献类型是同步的 `(childCtx) => () => void` 并带有逐次安装的撤销,建模的是会来会走的部署能力,而 preset 加入是一次性认父、自身没有撤销可言。扩展它反而会让任何绕过该注册表的驱动重新具备遗漏的可能。
+**为两个驱动引入一份共享 child setup 注册表。** 否决,因为同步且可撤销的贡献建模的是会来会走的部署能力,而 preset 加入是一次性认父、自身没有撤销可言。让组合经由可选注册表完成,反而会让任何绕过它的驱动重新具备遗漏的可能。
 
 **让 `dsh-subagent` 导入 `resolveSessionPreset` 并按解析出的 id 挂载。** 否决,因为这会给一个必须在没有 roster 时也能工作的包引入硬模块边,而且最终仍落回上述的重新挂载语义。
 
-**过滤链上的每一层,包括作用域自身那层。** 否决,因为那会让逐子 agent 的能力过滤器把该子 agent 的回报与结构化输出工具一并删掉——它们由委派运行时注册进子 agent 自己那层——于是一个点名"子 agent 可用哪些能力"的 `allow` 会让它彻底无法回报。
+**过滤链上的每一层,包括作用域自身那层。** 否决,因为那会让逐子 agent 的能力过滤器删掉该子 agent 的结构化输出工具——它由委派运行时注册进子 agent 自己那层——于是一个点名“子 agent 可用哪些能力”的 `allow` 会让它无法产出所请求的结果。
 
 **只修活着的加入,不动持久化 header。** 否决,因为那样活着的子 agent 与冷读同一个子 agent 会对"哪份组装产出了这段历史"给出不同答案——同一类缺陷,只是被搬了个地方而不是被修掉。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-08-10-continuable-subagent-policy-inheritance.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-08-10-continuable-subagent-policy-inheritance.md
-2026-08-10-continuable-subagent-policy-inheritance.md: a33a211747cf19cd465c56b8bda6ebfe1d7b6e06
-2026-08-10-continuable-subagent-policy-inheritance.zh.md: af43bbdc10c926b5255e8e5af98f7ea950b60091
+2026-08-10-continuable-subagent-policy-inheritance.md: e567c0557be45ba9e0e0515d742bd6f99a5f10e7
+2026-08-10-continuable-subagent-policy-inheritance.zh.md: 5c9cc677e9154d5b14214979283c00fd28a4c2df

+ 2 - 2
.agents/notes/implemented/feature/2026-08-10-continuable-subagent-policy-inheritance.md

@@ -6,7 +6,7 @@ English | [中文](2026-08-10-continuable-subagent-policy-inheritance.zh.md)
 
 ## Problem
 
-The one-shot in-process driver has seeded parent sandbox/approval overrides into its children since the [in-process policy-inheritance decision](2026-07-25-subagent-policy-inheritance.md), but the continuable path never did: `SubagentContinuationManager` materialization applied only child composition and the activation setup registry. The default bundle wires both delegation tools as `backgroundMode: continuable`, so in a default deployment every background child silently fell back to deployment defaults — a parent switched to `danger-full-access` produced children stuck at `workspace-write` whose every out-of-workspace operation raised an approval prompt, and a parent's unattended `'never'` approval stance reverted to prompting ([dsh-external/issues#334](https://github.com/dsh-external/issues/issues/334)).
+The one-shot in-process driver has seeded parent sandbox/approval overrides into its children since the [in-process policy-inheritance decision](2026-07-25-subagent-policy-inheritance.md), but the continuable path never did: `SubagentContinuationManager` materialization applied only child composition. The default bundle wires both delegation tools as `backgroundMode: continuable`, so in a default deployment every background child silently fell back to deployment defaults — a parent switched to `danger-full-access` produced children stuck at `workspace-write` whose every out-of-workspace operation raised an approval prompt, and a parent's unattended `'never'` approval stance reverted to prompting ([dsh-external/issues#334](https://github.com/dsh-external/issues/issues/334)).
 
 ## Decision
 
@@ -16,7 +16,7 @@ The capture/append pair moved from the one-shot driver into the seam's shared ch
 
 ## Alternatives considered
 
-- **An activation-setup-registry contribution** (`registerContinuableSetup`) — rejected: a contribution receives only the child context, so it cannot capture the parent's overrides at the delegation boundary; the registry applies on cold resume as well as fresh creation, which would re-append or re-capture; and nothing ties a contribution's capture to the start call's synchronous prefix, so the pre-await capture guarantee would be lost.
+- **A generic child-setup contribution** — rejected: a contribution receives only the child context, so it cannot capture the parent's overrides at the delegation boundary; applying it on cold resume as well as fresh creation would re-append or re-capture; and nothing ties its capture to the start call's synchronous prefix, so the pre-await capture guarantee would be lost.
 - **Re-capturing the parent's overrides at cold resume** — rejected: a resumed child would silently change policy with the parent's later switches, breaking the snapshot-at-delegation semantic and making effective policy depend on resume timing instead of the child's own log. A parent that wants a resumed child under new policy re-delegates.
 - **Importing the one-shot driver's inline logic from the continuation manager** — rejected: the Service Definition package cannot depend on its own provider package, and duplicating the capture/append pair in `continuation.ts` invites drift; `child-agent.ts` already holds every other shared composition step.
 - **Seeding the events into the descriptor seed turn** — rejected: the capture value is not known when the seed is assembled for every caller, and the one-shot precedent already establishes unpublished-setup appends as the ordering that places inherited facts after fork history with `firstLiveSeq` intact.

+ 2 - 2
.agents/notes/implemented/feature/2026-08-10-continuable-subagent-policy-inheritance.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 ## 问题
 
-自[进程内策略继承决策](2026-07-25-subagent-policy-inheritance.zh.md)以来,一次性进程内驱动器一直会把父级的沙箱/审批覆盖项注入其子级,但可继续路径从未这样做:`SubagentContinuationManager` 的物化只应用子级组合与 Activation(激活)设置注册表。默认组合包把两个委派工具都配置为 `backgroundMode: continuable`,因此在默认部署中,每个后台子 agent(智能体)都静默回退到部署默认值:切换到 `danger-full-access` 的父级产出的子 agent 卡在 `workspace-write`,每次工作区外操作都会触发审批提示;父级无人值守的 `'never'` 审批立场也退回为发起提示的行为([dsh-external/issues#334](https://github.com/dsh-external/issues/issues/334))。
+自[进程内策略继承决策](2026-07-25-subagent-policy-inheritance.zh.md)以来,一次性进程内驱动器一直会把父级的沙箱/审批覆盖项注入其子级,但可继续路径从未这样做:`SubagentContinuationManager` 的物化只应用子级组合。默认组合包把两个委派工具都配置为 `backgroundMode: continuable`,因此在默认部署中,每个后台子 agent(智能体)都静默回退到部署默认值:切换到 `danger-full-access` 的父级产出的子 agent 卡在 `workspace-write`,每次工作区外操作都会触发审批提示;父级无人值守的 `'never'` 审批立场也退回为发起提示的行为([dsh-external/issues#334](https://github.com/dsh-external/issues/issues/334))。
 
 ## 决策
 
@@ -16,7 +16,7 @@ Status: implemented
 
 ## 考虑过的替代方案
 
-- **一项 Activation 设置注册表贡献**(`registerContinuableSetup`):不予采纳。贡献只接收子级上下文,因此无法在委派边界捕获父级的覆盖项;该注册表在冷恢复与全新创建时都会应用,会导致重复追加或重复捕获;而且没有任何机制把贡献的捕获绑定到 start 调用的同步前缀,await 前捕获的保证会因此丢失。
+- **一项通用 child 设置贡献**:不予采纳。贡献只接收子级上下文,因此无法在委派边界捕获父级的覆盖项;在冷恢复与全新创建时都应用它会导致重复追加或重复捕获;而且没有任何机制把它的捕获绑定到 start 调用的同步前缀,await 前捕获的保证会因此丢失。
 - **在冷恢复时重新捕获父级覆盖项**:不予采纳。恢复的子 agent 会随父级后续切换静默改变策略,这会破坏委派时快照的语义,并让生效策略取决于恢复时机而非子级自身的日志。希望恢复的子 agent 采用新策略的父级应重新委派。
 - **让继续执行管理器导入一次性驱动器的内联逻辑**:不予采纳。Service Definition 包不能依赖自己的提供方包,而在 `continuation.ts` 中复制捕获/追加这对函数会招致偏差;`child-agent.ts` 已经承载其余每个共享组合步骤。
 - **把这些事件写入描述符种子轮次**:不予采纳。种子为每个调用方组装时,捕获值尚不可知;而且一次性路径的先例已经确立:在未发布的设置阶段追加,才是把继承事实排在 fork 历史之后、同时保持 `firstLiveSeq` 不变的顺序。

+ 10 - 6
apps/web/tests/scaffold.ts

@@ -1174,14 +1174,14 @@ export async function captureStableAria(
  * @param page - the page under test.
  * @param selector - the region locator selector.
  * @param workspaceCwd - normalization input.
- * @param options - optional capture-state normalization.
+ * @param options - optional user-visible state to establish before capture.
  * @returns the stable normalized expanded snapshot.
  */
 export async function captureExpandedTurnProcessAria(
   page: Page,
   selector: string,
   workspaceCwd: string,
-  options: { omitBackToBottom?: boolean } = {},
+  options: { scrollToBottom?: boolean } = {},
 ): Promise<string> {
   const controls = page.locator('[data-turn-process]')
   const count = await controls.count()
@@ -1194,10 +1194,14 @@ export async function captureExpandedTurnProcessAria(
     opened.push(index)
   }
   try {
-    const snapshot = await captureStableAria(page, selector, workspaceCwd)
-    return options.omitBackToBottom === true
-      ? snapshot.replace('- button "Back to bottom":\n  - img\n', '')
-      : snapshot
+    if (options.scrollToBottom === true) {
+      const backToBottom = page.getByRole('button', { name: 'Back to bottom', exact: true })
+      if (await backToBottom.isVisible()) {
+        await backToBottom.click()
+        await expect.poll(() => backToBottom.count(), { timeout: 10_000 }).toBe(0)
+      }
+    }
+    return await captureStableAria(page, selector, workspaceCwd)
   } finally {
     for (const index of opened.reverse()) {
       const control = controls.nth(index)

+ 1 - 1
apps/web/tests/subagent-conversation.e2e.ts

@@ -442,7 +442,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
       page,
       '[class*="centerCol"]',
       scaffold.workspaceCwd,
-      { omitBackToBottom: true },
+      { scrollToBottom: true },
     )
     await compareOrRefreshGolden(AVAILABLE_CHILD_EXPANDED_EXPECTED, expanded, MODE)
     expect(tripwire.pageErrors).toEqual([])

+ 1 - 1
docs/event-producer-consumer.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 docs/event-producer-consumer.md
-event-producer-consumer.md: 4e60123ea2b32938371d4bc28b1885bb8a521a72
+event-producer-consumer.md: 96985242249de9a528a834f6540c69bf44d7b688
 event-producer-consumer.zh.md: 4c16d6742a288bbaef09b38bbf6c87fafb2d6a60

+ 4 - 4
docs/event-producer-consumer.md

@@ -52,10 +52,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:105`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `remotes` |
 | `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:92`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) |
 | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:298`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - |
-| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:175`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server`, [`subagent`](../packages/subagent/subagent) |
-| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:149`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:155`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:166`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) |
+| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:172`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server`, [`subagent`](../packages/subagent/subagent) |
+| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:146`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:152`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:163`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) |
 | `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`system-prompt`](../packages/core/system-prompt) |
 | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
 | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`), [`tools`](../packages/core/tools) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |

+ 2 - 2
docs/subsystems/subagent.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 docs/subsystems/subagent.md
-subagent.md: 7d04de9efedcb384e433ec6414a378a08a7b094d
-subagent.zh.md: ece7daf8232162df7e5620267133eb621c73b7a4
+subagent.md: 1a19931110c6c2be53f041bff2e765e98778ac29
+subagent.zh.md: 93af8e98e39fee0040d3e2af81aa708e21446235

+ 0 - 12
docs/subsystems/subagent.md

@@ -195,8 +195,6 @@ interface ContinuableStart {
 }
 ```
 
-An optional continuable-child setup contribution can install scope-local capabilities after base child composition and before Activation publication. The registry is ordered and transactional: a failed or revoked setup rolls back the unpublished Activation, child-scope disposal releases every installation, new registrations affect the next Activation, and registration removal revokes every resident installation immediately.
-
 When a resident Activation settles, the manager delivers one notice to the child's durable direct parent describing how that epoch ended and carrying its final assistant content. That delivery is unconditional for every child whose id a caller received, happens before the ownership release that would let the parent be judged settled, and reaches a resident parent through the same waking-admission accounting as an Agent message. A parent whose own lineage is already tearing down receives it without a wake, because waking a quiescent Agent starts a turn rather than queueing work. Its provenance is a distinct kind so a transcript never presents a runtime account as something the child wrote.
 
 ```ts type-equiv
@@ -534,16 +532,6 @@ async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[],
  */
 interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void
 
-/**
- * Compose one deployment capability into every continuable child's
- * unpublished creation context on fresh creation and cold resume. Grants wait
- * for the next Activation; removing the contribution revokes every resident
- * installation immediately.
- * @param contribution - synchronous child-scope installer.
- * @returns the exact Cordis effect disposer.
- */
-registerContinuableSetup(contribution: ContinuableSetupContribution): () => void
-
 /**
  * Close continuable admission below exact live parent Agents, stop only their
  * visible descendant Activations synchronously, then await admitted scoped

+ 0 - 12
docs/subsystems/subagent.zh.md

@@ -195,8 +195,6 @@ interface ContinuableStart {
 }
 ```
 
-可选的可继续 child 设置贡献可以在 child 基础组合完成后、Activation 发布前安装限定在作用域内的能力。该注册表按顺序执行且具有事务性:设置失败或被撤销时会回滚未发布的 Activation;child 作用域 dispose 时会释放所有安装;新注册项在下一个 Activation 生效;移除注册项时则会立即撤销每个驻留中的安装。
-
 当驻留 Activation 结算时,管理器会向该 child 持久化的直接 parent 投递一条通知,说明该 epoch 如何结束,并携带其最终 assistant 内容。对每个调用方拿到过 id 的 child,这条投递都是无条件的;它发生在会让 parent 被判定为已结算的所有权释放之前,并通过与 Agent 消息相同的唤醒准入记账到达驻留 parent。若 parent 自身所在的谱系已在拆卸中,这条通知会以不唤醒的方式送达,因为唤醒一个静息 Agent 是开启一个轮次,而不是排队等待工作。其来源信息使用一个独立的 kind,因此 transcript(文本记录)绝不会把运行时的记账呈现为 child 自己写下的内容。
 
 ```ts type-equiv
@@ -538,16 +536,6 @@ async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[],
  */
 interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void
 
-/**
- * Compose one deployment capability into every continuable child's
- * unpublished creation context on fresh creation and cold resume. Grants wait
- * for the next Activation; removing the contribution revokes every resident
- * installation immediately.
- * @param contribution - synchronous child-scope installer.
- * @returns the exact Cordis effect disposer.
- */
-registerContinuableSetup(contribution: ContinuableSetupContribution): () => void
-
 /**
  * Close continuable admission below exact live parent Agents, stop only their
  * visible descendant Activations synchronously, then await admitted scoped

+ 2 - 2
docs/tool-catalog.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 docs/tool-catalog.md
-tool-catalog.md: 95db1d025554c87608d861385b0de0f28d5babd2
-tool-catalog.zh.md: 03a2388883a6e2daccb0146e80a92ef519a690c5
+tool-catalog.md: 739c65d5ab729632686c293896b914cd204a00fe
+tool-catalog.zh.md: 55c6fe7fa044336753c34c7c0737011b3423efd4

+ 2 - 2
docs/tool-catalog.md

@@ -1639,7 +1639,7 @@ Source: [`packages/subagent/tool-subagent-control/src/list-agents.ts`](../packag
 
 ### `send_message`
 
-Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.
+Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.
 
 ```json
 {
@@ -1647,7 +1647,7 @@ Send a message to an adjacent agent by its agent id. The target must be your dir
   "properties": {
     "agent_id": {
       "type": "string",
-      "description": "The agent id of your direct parent or direct child."
+      "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
     },
     "message": {
       "type": "string",

+ 2 - 2
docs/tool-catalog.zh.md

@@ -1645,7 +1645,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
 
 ### `send_message`
 
-根据 agent id 向相邻 agent 发送消息。目标必须是你的直接 parent 或直接 child。如果它仍在工作,消息会 steer 其最近的 step;如果它处于 idle,消息会启动一个轮次。此调用不会返回该 agent 的答案,只会确认消息已投递。调用失败表示消息**未**投递。
+根据 agent id 向直接可继续 child 发送消息。如果你是可继续 agent,也可以把自己的直接 parent 作为目标。如果目标仍在工作,消息会 steer 其最近的 step;如果目标处于 idle,消息会启动一个轮次。此调用不会返回该 agent 的答案,只会确认消息已投递。调用失败表示消息**未**投递。
 
 ```json
 {
@@ -1653,7 +1653,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
   "properties": {
     "agent_id": {
       "type": "string",
-      "description": "The agent id of your direct parent or direct child."
+      "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
     },
     "message": {
       "type": "string",

+ 1 - 11
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -2207,12 +2207,6 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
         parameters: [{ name: 'targetSessionId', description: 'the durable child session id to interrupt.' }, { name: 'authority', description: 'the human parent address or exact live ancestor Agent.' }],
         throws: ['{SubagentError} `UNAUTHORIZED` when the authority does not own the live target.'],
       },
-      {
-        signature: 'registerContinuableSetup(contribution: ContinuableSetupContribution): () => void',
-        description: 'Compose one deployment capability into every continuable child\'s unpublished creation context on fresh creation and cold resume. Grants wait for the next Activation; removing the contribution revokes every resident installation immediately.',
-        parameters: [{ name: 'contribution', description: 'synchronous child-scope installer.' }],
-        returns: 'the exact Cordis effect disposer.',
-      },
       {
         signature: 'async drainContinuableDescendants(parents: readonly Agent[]): Promise<void>',
         description: 'Close continuable admission below exact live parent Agents, stop only their visible descendant Activations synchronously, then await admitted scoped materializations and release those forests child-first. The scoped cutoff lasts until each exact parent leaves the registry; unrelated parent trees remain live.',
@@ -3740,10 +3734,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'ContinuableCreateSpec',
     declaration: 'export interface ContinuableCreateSpec {\n    readonly seed?: readonly SessionEvent[];\n}',
   },
-  {
-    name: 'ContinuableSetupContribution',
-    declaration: 'export type ContinuableSetupContribution = (childCtx: Context) => () => void;',
-  },
   {
     name: 'ContinuableStart',
     declaration: 'export interface ContinuableStart {\n    readonly childId: SessionId;\n    readonly messageId: MessageId;\n}',
@@ -5470,7 +5460,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'SubagentRuntime',
-    declaration: 'export class SubagentRuntime extends TypertRemoteService {\n    constructor(ctx: Context);\n    async startContinuable(spec: ContinuableStartSpec): Promise<ContinuableStart>;\n    async sendMessage(sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions): Promise<MessageId>;\n    interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void;\n    registerContinuableSetup(contribution: ContinuableSetupContribution): () => void;\n    async drainContinuableDescendants(parents: readonly Agent[]): Promise<void>;\n    async drainContinuableChildren(parent: Agent, childIds: readonly SessionId[]): Promise<void>;\n    listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentListEntry[]>;\n    listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise<SubagentDescendantListEntry[]>;\n    @Remote(\'list\')\n    async remoteExportList(parentSessionId: SessionId, signal: AbortSignal): Promise<SubagentCatalog>;\n    @Remote(\'prompt\')\n    async prompt(request: SubagentPromptRequest, signal: AbortSignal): Promise<SubagentPromptReceipt>;\n    @Remote(\'interruptByParent\')\n    interruptByParent(childSessionId: SessionId, parentSessionId: SessionId, mode: \'continuable\'): SubagentInterruptReceipt;\n    registerProvider(provider: SubagentProvider): () => void;\n    getProvider(name: string): SubagentProvider | undefined;\n    list(): string[];\n    async start(name: string, request: SubagentStartRequest): Promise<Subag /* …truncated — full shape in source */',
+    declaration: 'export class SubagentRuntime extends TypertRemoteService {\n    constructor(ctx: Context);\n    async startContinuable(spec: ContinuableStartSpec): Promise<ContinuableStart>;\n    async sendMessage(sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions): Promise<MessageId>;\n    interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void;\n    async drainContinuableDescendants(parents: readonly Agent[]): Promise<void>;\n    async drainContinuableChildren(parent: Agent, childIds: readonly SessionId[]): Promise<void>;\n    listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentListEntry[]>;\n    listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise<SubagentDescendantListEntry[]>;\n    @Remote(\'list\')\n    async remoteExportList(parentSessionId: SessionId, signal: AbortSignal): Promise<SubagentCatalog>;\n    @Remote(\'prompt\')\n    async prompt(request: SubagentPromptRequest, signal: AbortSignal): Promise<SubagentPromptReceipt>;\n    @Remote(\'interruptByParent\')\n    interruptByParent(childSessionId: SessionId, parentSessionId: SessionId, mode: \'continuable\'): SubagentInterruptReceipt;\n    registerProvider(provider: SubagentProvider): () => void;\n    getProvider(name: string): SubagentProvider | undefined;\n    list(): string[];\n    async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>;\n}',
   },
   {
     name: 'SubagentSendMessageOptions',

+ 2 - 2
packages/subagent/subagent/README.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 packages/subagent/subagent/README.md
-README.md: 2e74b9b51d04034148e7b3928c3a73830a37dd3d
-README.zh.md: 43e0221ee23bec50b835dec34df5255a35806ffd
+README.md: cacdec2e803a46656682af89b1ef05e314c9351e
+README.zh.md: 6fef74221932cb179adabcc823845de95f4dbe9d

+ 1 - 1
packages/subagent/subagent/README.md

@@ -48,7 +48,7 @@ One-shot children run once and settle with a single result, plus an optional str
 
 ### Messaging, interrupting, and discovering
 
-Continuable parents and children use one `sendMessage()` operation across their direct edge. A working target receives the message through Steer at its nearest step; an idle target starts a turn, and only a direct child can be cold-resumed. The parent can also interrupt a running descendant or list its children at any time. Discovery covers both shapes: the service lists direct children and the full descendant tree — mode, activity, and lineage — reading live session state and optional persistence, without loading any child.
+Every exact live Agent can use `sendMessage()` with a direct continuable child; a continuable Agent can also use it with its direct parent. A working target receives the message through Steer at its nearest step; an idle target starts a turn, and only a direct child can be cold-resumed. The parent can also interrupt a running descendant or list its children at any time. Discovery covers both shapes: the service lists direct children and the full descendant tree — mode, activity, and lineage — reading live session state and optional persistence, without loading any child.
 
 ### Failure and recovery
 

+ 1 - 1
packages/subagent/subagent/README.zh.md

@@ -48,7 +48,7 @@ kind: "package-reference"
 
 ### 消息、中断与发现
 
-可继续 parent 与 child 跨直接边使用同一个 `sendMessage()` 操作。正在工作的目标通过 Steer 在最近 step 接收消息;空闲目标启动轮次,且只有直接 child 可以冷恢复。parent 也可以随时中断正在运行的后代或列举自己的子级。发现覆盖两种形态:服务列举直接子级与完整后代树——模式、活动状态与血缘——直接读取在线会话状态与可选持久化,不加载任何子 agent。
+每个确切在线 Agent 都可以对直接可继续 child 使用 `sendMessage()`;可继续 Agent 还可以对自己的直接 parent 使用它。正在工作的目标通过 Steer 在最近 step 接收消息;空闲目标启动轮次,且只有直接 child 可以冷恢复。parent 也可以随时中断正在运行的后代或列举自己的子级。发现覆盖两种形态:服务列举直接子级与完整后代树——模式、活动状态与血缘——直接读取在线会话状态与可选持久化,不加载任何子 agent。
 
 ### 失败与恢复
 

+ 0 - 185
packages/subagent/subagent/src/activation-setup-registry.ts

@@ -1,185 +0,0 @@
-/**
- * Internal registry of deployment capabilities composed into every continuable
- * child's unpublished creation context.
- *
- * A contribution grants a child-scoped capability without teaching the
- * continuation manager which capabilities exist. The manager owns residency;
- * this registry owns the join between plugin lifetime, unpublished setup, and
- * Activation disposal, so no installation outlives either owner and no removed
- * contribution can be installed after revocation reports completion.
- *
- * @module @deepseek-ai/dsh-subagent/activation-setup-registry
- */
-
-import type { Context } from '@deepseek-ai/cordis'
-import type { AgentSetupCommit } from '@deepseek-ai/dsh-agent'
-import { errorChain } from '@deepseek-ai/dsh-llm'
-import { SubagentError } from './error.ts'
-
-/**
- * One deployment capability installed into a continuable child's unpublished
- * creation context. It composes synchronously before publication and returns
- * the disposer for exactly that installation.
- * @param childCtx - the child's unpublished scoped context.
- * @returns the disposer revoking this installation.
- */
-export type ContinuableSetupContribution = (childCtx: Context) => () => void
-
-/** One contribution's live registration. */
-interface Registration {
-  readonly contribution: ContinuableSetupContribution
-  removed: boolean
-  readonly installations: Set<Installation>
-}
-
-/** One contribution installed into one child context. */
-interface Installation {
-  readonly registration: Registration
-  readonly childCtx: Context
-  readonly dispose: () => void
-  released: boolean
-  /** Present until the child reaches residency. */
-  transaction: TransactionState | undefined
-}
-
-/** One child's provisioning batch. */
-interface TransactionState {
-  readonly installations: Installation[]
-  invalidated: boolean
-}
-
-/** Re-read mutable removal state after a contribution may have revoked itself. */
-function isRemoved(registration: Registration): boolean {
-  return registration.removed
-}
-
-/**
- * Owns continuable-child setup registrations, installations, rollback, child
- * cleanup, and immediate live revocation.
- */
-export class SubagentActivationSetupRegistry {
-  /** Live contributions in installation order. */
-  private readonly registrations = new Set<Registration>()
-  /** Child context to its live installations. */
-  private readonly byChild = new Map<Context, Set<Installation>>()
-
-  /**
-   * Register one contribution.
-   * @param contribution - synchronous child-scope installer.
-   * @returns an idempotent registration undo.
-   * @throws after attempting every installation when any disposer fails.
-   */
-  register(contribution: ContinuableSetupContribution): () => void {
-    const registration: Registration = { contribution, removed: false, installations: new Set() }
-    this.registrations.add(registration)
-    return () => {
-      if (registration.removed) return
-      // Close before disposal so a snapshotted apply() cannot install after
-      // revocation reports completion.
-      registration.removed = true
-      this.registrations.delete(registration)
-      this.releaseAll([...registration.installations], 'contribution removal')
-    }
-  }
-
-  /**
-   * Install every live contribution into one unpublished child context.
-   * @param childCtx - the child's unpublished scoped context.
-   * @returns the provisioning commit consumed at Agent publication.
-   */
-  apply(childCtx: Context): AgentSetupCommit {
-    const state: TransactionState = { installations: [], invalidated: false }
-    try {
-      for (const registration of [...this.registrations]) {
-        /* v8 ignore next -- only a synchronous re-entrant revocation of an
-         * already-snapshotted registration reaches this guard. */
-        if (registration.removed) continue
-        const installation: Installation = {
-          registration,
-          childCtx,
-          dispose: registration.contribution(childCtx),
-          released: false,
-          transaction: state,
-        }
-        registration.installations.add(installation)
-        state.installations.push(installation)
-        let indexed = this.byChild.get(childCtx)
-        if (indexed === undefined) {
-          indexed = new Set()
-          this.byChild.set(childCtx, indexed)
-        }
-        indexed.add(installation)
-        // An installer may revoke itself before its installation record exists.
-        // Dispose that escaped record and invalidate the provisioning batch.
-        if (isRemoved(registration)) this.release(installation)
-      }
-    } catch (error: unknown) {
-      // Keep the installer failure authoritative, but attempt every rollback.
-      try {
-        this.releaseAll([...state.installations], 'setup rollback')
-      } catch (releaseFailure: unknown) {
-        /* v8 ignore next -- requires independent installer and rollback faults. */
-        void releaseFailure
-      }
-      throw error
-    }
-    childCtx.effect(() => () => { this.releaseChild(childCtx) }, 'subagents.activationSetup()')
-    return {
-      commit: () => {
-        if (state.invalidated) {
-          throw new SubagentError(
-            'a continuable-subagent setup contribution was revoked while this child was being built; '
-            + 'the child was not established',
-            'ACTIVATION_SETUP_REVOKED',
-          )
-        }
-        for (const installation of state.installations) installation.transaction = undefined
-      },
-    }
-  }
-
-  /** Release every remaining installation owned by one disposed child scope. */
-  private releaseChild(childCtx: Context): void {
-    const indexed = this.byChild.get(childCtx) ?? []
-    this.releaseAll([...indexed], 'child scope disposal')
-  }
-
-  /**
-   * Release a batch completely before reporting disposer failures.
-   * @param installations - records to release.
-   * @param during - operation name for diagnostics.
-   */
-  private releaseAll(installations: readonly Installation[], during: string): void {
-    const failures: unknown[] = []
-    for (const installation of installations) {
-      try {
-        this.release(installation)
-      } catch (error: unknown) {
-        failures.push(error)
-      }
-    }
-    if (failures.length === 0) return
-    throw new SubagentError(
-      `continuable-subagent setup ${during} failed to release ${failures.length} installation(s): `
-      + failures.map(failure => errorChain(failure)).join('; '),
-      'ACTIVATION_SETUP_RELEASE_FAILED',
-    )
-  }
-
-  /** Drop one installation from both indices and dispose it exactly once. */
-  private release(installation: Installation): void {
-    if (installation.released) return
-    installation.released = true
-    installation.registration.installations.delete(installation)
-    const indexed = this.byChild.get(installation.childCtx)
-    /* v8 ignore next 4 -- every live installation is indexed until this method removes it. */
-    if (indexed !== undefined) {
-      indexed.delete(installation)
-      if (indexed.size === 0) this.byChild.delete(installation.childCtx)
-    }
-    if (installation.transaction !== undefined) installation.transaction.invalidated = true
-    installation.dispose()
-  }
-}
-
-export default SubagentActivationSetupRegistry

+ 5 - 15
packages/subagent/subagent/src/continuation.ts

@@ -28,7 +28,6 @@ import type {
   Agent,
   AgentHandle,
   AgentOptions,
-  AgentSetupCommit,
   CreateAgentOptions,
 } from '@deepseek-ai/dsh-agent'
 import { ReasoningEffortId, boundContextSummary, createUserMessage, errorChain } from '@deepseek-ai/dsh-llm'
@@ -53,7 +52,6 @@ import { seedDescriptorTurn } from './descriptor-seed.ts'
 import type { ContinuableCreateRequest, ContinuableCreateSpec, SubagentResult, SubagentStartRequest } from './types.ts'
 import type { ActivationObserver, ActivationTerminal } from './lifecycle.ts'
 import { SubagentError } from './error.ts'
-import type SubagentActivationSetupRegistry from './activation-setup-registry.ts'
 
 /** Durable attribution for one model-authored message between adjacent Agents. */
 export interface AgentMessageSource {
@@ -132,15 +130,10 @@ export interface SubagentSendMessageOptions {
   readonly signal: AbortSignal
 }
 
-/** Private scheduling choice for one direct-child delivery. */
-type ChildDelivery = 'queue' | 'steer'
-
 /** Inputs shared by model steering and the human Queue adapter. */
-interface ChildDeliveryOptions {
-  readonly source: MessageSource
-  readonly signal: AbortSignal
-  readonly delivery: ChildDelivery
-}
+type ChildDeliveryOptions =
+  | { readonly delivery: 'steer'; readonly signal: AbortSignal }
+  | { readonly delivery: 'queue'; readonly source: MessageSource; readonly signal: AbortSignal }
 
 /**
  * The residency state of one continuable child, derived from Agent quiescence
@@ -395,7 +388,6 @@ export class SubagentContinuationManager {
   constructor(
     private readonly ctx: Context,
     private readonly host: ContinuationHost,
-    private readonly setupRegistry: SubagentActivationSetupRegistry,
   ) {
     // Ordinary Cordis owner effects unwind in reverse registration order, which
     // cannot express the dynamic child graph. Register the private scope's
@@ -543,7 +535,6 @@ export class SubagentContinuationManager {
       return this.sendToParent(senderActivation, sender, content)
     }
     return this.deliverToChild(sender, targetId, content, {
-      source: agentMessageSource(sender),
       signal: options.signal,
       delivery: 'steer',
     })
@@ -1069,7 +1060,7 @@ export class SubagentContinuationManager {
     // `AgentRegistry.enter()` is the authoritative collision boundary for an id
     // some other owner holds — a duplicate would reject there with rollback.
     inputs.signal.throwIfAborted()
-    const setup = (childCtx: Context): AgentSetupCommit => {
+    const setup = (childCtx: Context): void => {
       // Only fresh creation seeds the delegation policy onto the child's own
       // log (after any fork seed, so fresh policy wins stale seed state); a
       // cold resume replays those persisted events instead.
@@ -1077,7 +1068,6 @@ export class SubagentContinuationManager {
         appendDelegatedPolicyOverrides((childCtx.agent as Agent).session, create.delegatedPolicies)
       }
       applyChildComposition(childCtx, parent, inputs.composition)
-      return this.setupRegistry.apply(childCtx)
     }
     const observer = this.host.observeActivation(provider, childId, parent)
     // Agent creation owns rollback before handle transfer. A rejection leaves
@@ -1212,7 +1202,7 @@ export class SubagentContinuationManager {
     // Parent-originated delivery keeps the parent live through ownership, so
     // establish it before the message can enter the child's inbox.
     this.acquireOwnership(parent, activation.childId)
-    const message = options.source.kind === 'agent-message'
+    const message = options.delivery === 'steer'
       ? agentMessage(parent, content)
       : createUserMessage({ content, source: options.source })
     const accepted = this.admitWaking(activation, message.id, () => {

+ 1 - 22
packages/subagent/subagent/src/index.ts

@@ -70,8 +70,6 @@ import type {
   SubagentInterruptAuthority,
   SubagentSendMessageOptions,
 } from './continuation.ts'
-import SubagentActivationSetupRegistry from './activation-setup-registry.ts'
-import type { ContinuableSetupContribution } from './activation-setup-registry.ts'
 import { listChildren as listSubagentChildren, listDescendants as listSubagentDescendants } from './list-children.ts'
 import type { SubagentDescendantListEntry, SubagentListEntry } from './list-children.ts'
 import { snapshotSubagentDescriptor } from './descriptor.ts'
@@ -129,7 +127,6 @@ export type {
   SubagentSendMessageOptions,
   SubagentSettledMessageSource,
 } from './continuation.ts'
-export type { ContinuableSetupContribution } from './activation-setup-registry.ts'
 export type * from './control-types.ts'
 export type { SubagentDescendantListEntry } from './list-children.ts'
 export type { SubagentRunEndInfo, SubagentRunInfo } from './types.ts'
@@ -193,8 +190,6 @@ interface BrowserPromptSource {
 export class SubagentRuntime extends TypertRemoteService {
   private providers = new Map<string, SubagentProvider>()
   private continuations: SubagentContinuationManager | undefined
-  /** Deployment contributions composed into unpublished continuable children. */
-  private readonly setupRegistry = new SubagentActivationSetupRegistry()
   /**
    * The contained lifecycle-edge publisher. Built here because scoped dispatch
    * keys its carrier by this exact service instance, whose own context filter
@@ -209,7 +204,7 @@ export class SubagentRuntime extends TypertRemoteService {
       const manager = new SubagentContinuationManager(childCtx, {
         prepareContinuable: (name, request) => this.prepareContinuable(name, request),
         observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent),
-      }, this.setupRegistry)
+      })
       this.continuations = manager
       childCtx.effect(() => () => {
         /* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
@@ -298,22 +293,6 @@ export class SubagentRuntime extends TypertRemoteService {
     this.continuations?.interrupt(targetSessionId, authority)
   }
 
-  /**
-   * Compose one deployment capability into every continuable child's
-   * unpublished creation context on fresh creation and cold resume. Grants wait
-   * for the next Activation; removing the contribution revokes every resident
-   * installation immediately.
-   * @param contribution - synchronous child-scope installer.
-   * @returns the exact Cordis effect disposer.
-   */
-  registerContinuableSetup(contribution: ContinuableSetupContribution): () => void {
-    // oxlint-disable-next-line typescript/no-misused-promises -- synchronous disposer
-    return this.ctx.effect(
-      () => this.setupRegistry.register(contribution),
-      'subagents.registerContinuableSetup()',
-    )
-  }
-
   /**
    * Close continuable admission below exact live parent Agents, stop only their
    * visible descendant Activations synchronously, then await admitted scoped

+ 0 - 163
packages/subagent/subagent/tests/activation-setup-registry.spec.ts

@@ -1,163 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { Context } from '@deepseek-ai/cordis'
-import SubagentActivationSetupRegistry from '../src/activation-setup-registry.ts'
-
-/** A child-like scoped context with observable disposal. */
-function childContext(): { ctx: Context; close: () => Promise<void> } {
-  const root = new Context()
-  const scope = root.plugin(function child() {})
-  return { ctx: scope.ctx, close: async () => { await scope.dispose() } }
-}
-
-describe('SubagentActivationSetupRegistry', () => {
-  it('installs contributions in registration order and commits them', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const order: string[] = []
-    registry.register(() => { order.push('first'); return () => order.push('undo-first') })
-    registry.register(() => { order.push('second'); return () => order.push('undo-second') })
-    const child = childContext()
-
-    const transaction = registry.apply(child.ctx)
-    expect(order).toEqual(['first', 'second'])
-    expect(() => { transaction.commit() }).not.toThrow()
-    expect(order).toEqual(['first', 'second'])
-  })
-
-  it('makes repeated removal and converging ownership idempotent', async () => {
-    const registry = new SubagentActivationSetupRegistry()
-    let disposals = 0
-    const remove = registry.register(() => () => { disposals += 1 })
-    const child = childContext()
-    registry.apply(child.ctx).commit()
-
-    remove()
-    remove()
-    await child.close()
-    expect(disposals).toBe(1)
-  })
-
-  it('makes the opposite ownership convergence idempotent', async () => {
-    const registry = new SubagentActivationSetupRegistry()
-    let disposals = 0
-    const remove = registry.register(() => () => { disposals += 1 })
-    const child = childContext()
-    registry.apply(child.ctx).commit()
-
-    await child.close()
-    remove()
-    expect(disposals).toBe(1)
-  })
-
-  it('skips a contribution removed before a child is applied', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const installed: string[] = []
-    const remove = registry.register(() => { installed.push('gone'); return () => {} })
-    registry.register(() => { installed.push('kept'); return () => {} })
-    remove()
-
-    registry.apply(childContext().ctx).commit()
-    expect(installed).toEqual(['kept'])
-  })
-
-  it('invalidates a provisioning batch revoked before commit', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    let disposals = 0
-    const remove = registry.register(() => () => { disposals += 1 })
-    const transaction = registry.apply(childContext().ctx)
-
-    remove()
-    expect(disposals).toBe(1)
-    expect(() => { transaction.commit() }).toThrow(/revoked while this child was being built/)
-  })
-
-  it('catches a contribution revoked inside its own installer', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    let disposals = 0
-    const self: { remove?: () => void } = {}
-    self.remove = registry.register(() => {
-      self.remove?.()
-      return () => { disposals += 1 }
-    })
-
-    const transaction = registry.apply(childContext().ctx)
-    expect(disposals).toBe(1)
-    expect(() => { transaction.commit() }).toThrow(/revoked/)
-  })
-
-  it('attempts every contribution-removal disposer before reporting failures', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const released: string[] = []
-    let seq = 0
-    const remove = registry.register(() => {
-      const id = `child-${++seq}`
-      return () => {
-        released.push(id)
-        if (id === 'child-1') throw new Error('disposer exploded')
-      }
-    })
-    for (const child of [childContext(), childContext(), childContext()]) {
-      registry.apply(child.ctx).commit()
-    }
-
-    expect(() => { remove() }).toThrow(/failed to release 1 installation\(s\)/)
-    expect(released).toEqual(['child-1', 'child-2', 'child-3'])
-  })
-
-  it('attempts every child-scope disposer before reporting failures', async () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const released: string[] = []
-    registry.register(() => () => {
-      released.push('a')
-      throw new Error('first disposer exploded')
-    })
-    registry.register(() => () => { released.push('b') })
-    const child = childContext()
-    registry.apply(child.ctx).commit()
-
-    await child.close().catch(() => undefined)
-    expect(released).toEqual(['a', 'b'])
-  })
-
-  it('rolls back earlier installations when a later contribution throws', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const undone: string[] = []
-    registry.register(() => () => undone.push('first'))
-    registry.register(() => { throw new Error('boom') })
-    registry.register(() => () => undone.push('third'))
-
-    expect(() => registry.apply(childContext().ctx)).toThrow(/boom/)
-    expect(undone).toEqual(['first'])
-  })
-
-  it('does not dispose twice when revocation precedes setup rollback', () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const disposals: string[] = []
-    const removeFirst = registry.register(() => () => { disposals.push('first') })
-    registry.register(() => {
-      removeFirst()
-      throw new Error('second failed after revoking the first')
-    })
-
-    expect(() => registry.apply(childContext().ctx)).toThrow(/second failed/)
-    expect(disposals).toEqual(['first'])
-  })
-
-  it('does not cross-release independent child scopes', async () => {
-    const registry = new SubagentActivationSetupRegistry()
-    const disposed: string[] = []
-    let seq = 0
-    registry.register(() => {
-      const id = `child-${++seq}`
-      return () => disposed.push(id)
-    })
-    const first = childContext()
-    const second = childContext()
-    registry.apply(first.ctx).commit()
-    registry.apply(second.ctx).commit()
-
-    await first.close()
-    expect(disposed).toEqual(['child-1'])
-    await second.close()
-    expect(disposed).toEqual(['child-1', 'child-2'])
-  })
-})

+ 0 - 10
packages/subagent/subagent/tests/service.spec.ts

@@ -165,16 +165,6 @@ describe('SubagentRuntime', () => {
     )).rejects.toMatchObject({ code: 'CONTINUATION_UNAVAILABLE' })
   })
 
-  it('registers continuable child setup contributions through the service lifetime', async () => {
-    const { subagents } = await service()
-    const contribution = vi.fn(() => vi.fn())
-
-    const dispose = subagents.registerContinuableSetup(contribution)
-
-    expect(contribution).not.toHaveBeenCalled()
-    expect(() => { dispose() }).not.toThrow()
-  })
-
   it.each([
     ['agentOptions', { agentOptions: { model: 'child-model' } }],
     ['outputSchema', { outputSchema: { type: 'object', properties: {} } }],

+ 2 - 2
packages/subagent/tool-subagent-control/README.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 packages/subagent/tool-subagent-control/README.md
-README.md: d61f0ea4d2134f5200e2a4d730b513c465b8f9d6
-README.zh.md: fbfb724d63458daa311f52e3c551c73680170f7d
+README.md: ec03038f6ed7a61538a2dab49c795301c024d3e2
+README.zh.md: 31ae60b6b140574cbad261dd4bc187c148b23c4c

+ 2 - 2
packages/subagent/tool-subagent-control/README.md

@@ -46,7 +46,7 @@ This package takes no configuration: the root plugin provides `send_message` and
 
 ### send_message
 
-Sends a message to an adjacent Agent named by `agent_id`: parent to direct child or continuable child to direct parent. A working target receives the message at its nearest step boundary through Steer; an idle target starts a turn, and a cold direct child resumes through the continuation lifecycle. The call returns only acceptance (the accepted message's stable `messageId`), never a reply. A failure — a non-adjacent target, unavailable parent, unknown child, descriptor-less child that cannot be resumed, or rejected admission — states the message was not delivered.
+Sends a message to an Agent named by `agent_id`: any exact live Agent may target its direct continuable child, while a continuable Agent may also target its direct parent. A working target receives the message at its nearest step boundary through Steer; an idle target starts a turn, and a cold direct child resumes through the continuation lifecycle. The call returns only acceptance (the accepted message's stable `messageId`), never a reply. A failure — an unsupported target, unavailable parent, unknown child, descriptor-less child that cannot be resumed, or rejected admission — states the message was not delivered.
 
 ### interrupt_agent
 
@@ -168,7 +168,7 @@ Append-only; each result follows the reusable request prefix.
 These limits define what the control tools cannot observe or steer; they are current package constraints.
 
 - **A delivered message has no independent result** — acceptance returns only its inbox `messageId`; later target work lands in that target's durable Session and is never collected through this tool. A reply is another explicitly addressed `send_message`, not this call's result.
-- **Only adjacent Agents can communicate** — roots have no parent target, children cannot address siblings or deeper descendants, and a child-to-parent send requires the parent to remain live; only direct-child delivery supports cold activation.
+- **Only supported adjacent Agents can communicate** — every sender may target a direct continuable child, only a continuable sender may target its direct parent, and that parent must remain live; siblings and deeper descendants are not message targets, and only direct-child delivery supports cold activation.
 - **Listing is a snapshot, not a delivery promise** — it may race publication, disposal, or a later message, and another process may activate a child this process reports as `ready`; cross-process accuracy requires a shared lease. `interrupt_agent` performs the authoritative live-lineage check itself, so discovery staleness cannot grant authority.
 - **No pagination or deletion** — the complete stably ordered set is returned, and persisted children remain listed for as long as their sessions remain in persistence; a service-level bound or delete operation is a later product decision.
 

+ 2 - 2
packages/subagent/tool-subagent-control/README.zh.md

@@ -46,7 +46,7 @@ kind: "package-reference"
 
 ### send_message
 
-向 `agent_id` 指定的相邻 Agent 发送消息:parent 到直接 child,或可继续 child 到直接 parent。正在工作的目标通过 Steer 在最近的 step 边界接收消息;空闲目标会启动一个轮次,冷状态的直接 child 会通过继续执行生命周期恢复。调用只返回接受结果(被接受消息的稳定 `messageId`),绝不返回回复。失败——非相邻目标、不可用的 parent、未知 child、缺少描述符而无法恢复的 child,或准入被拒——会明确说明消息未送达。
+向 `agent_id` 指定的 Agent 发送消息:任何确切在线 Agent 都可以指定自己的直接可继续 child,而可继续 Agent 还可以指定自己的直接 parent。正在工作的目标通过 Steer 在最近的 step 边界接收消息;空闲目标会启动一个轮次,冷状态的直接 child 会通过继续执行生命周期恢复。调用只返回接受结果(被接受消息的稳定 `messageId`),绝不返回回复。失败——不受支持的目标、不可用的 parent、未知 child、缺少描述符而无法恢复的 child,或准入被拒——会明确说明消息未送达。
 
 ### interrupt_agent
 
@@ -168,7 +168,7 @@ kind: "package-reference"
 这些限制说明控制工具无法观察或引导什么;它们是当前包约束。
 
 - **已投递消息没有独立结果**——接受时只返回其 inbox `messageId`;目标后续工作会落入该目标的持久化会话,绝不会通过本工具收集。回复是另一条显式指定地址的 `send_message`,而非本次调用的结果。
-- **只有相邻 Agent 可以通信**——根 Agent 没有 parent 目标,child 不能向 sibling 或更深的后代发消息,child 到 parent 的发送要求 parent 仍在线;只有直接 child 投递支持冷激活。
+- **只有受支持的相邻 Agent 可以通信**——每个 sender 都可以指定直接可继续 child,只有可继续 sender 可以指定自己的直接 parent,且该 parent 必须仍在线;sibling 与更深的后代不能作为消息目标,只有直接 child 投递支持冷激活。
 - **列表是快照,而非投递承诺**——它可能与发布、dispose(资源释放)或后续消息发生竞态,另一个进程也可能激活当前进程报告为 `ready` 的子级;跨进程准确性需要共享租约。`interrupt_agent` 自己执行权威的在线 lineage 检查,因此过期的发现结果不会授予权限。
 - **没有分页或删除**——系统返回完整且稳定排序的集合;只要子级会话仍在持久化存储中,它就会继续出现在列表中,服务级上限或删除操作留待后续产品决策。
 

+ 5 - 5
packages/subagent/tool-subagent-control/src/index.ts

@@ -27,15 +27,15 @@ export function apply(ctx: Context): void {
   ctx.tools.register(defineTool({
     name: 'send_message',
     description:
-      'Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct '
-      + 'child. If it is still working, the message steers its nearest step; if it is idle, the message starts '
-      + 'a turn. This call returns no answer from the agent — only confirmation that the message was delivered. '
-      + 'A failure means the message was NOT delivered.',
+      'Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may '
+      + 'also target your direct parent. If the target is still working, the message steers its nearest step; '
+      + 'if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation '
+      + 'that the message was delivered. A failure means the message was NOT delivered.',
     parameters: {
       agent_id: {
         type: 'string',
         required: true,
-        description: 'The agent id of your direct parent or direct child.',
+        description: 'The agent id of your direct continuable child, or your direct parent when you are continuable.',
       },
       message: {
         type: 'string',

+ 5 - 0
packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts

@@ -115,6 +115,11 @@ describe('dsh-tool-subagent-control', () => {
     expect(schemas[0]!.description).not.toContain('job_output')
     expect(schemas[0]!.description).not.toContain('job id')
     expect(schemas[0]!.description).toContain('nearest step')
+    expect(schemas[0]!.description).toContain('direct continuable child')
+    expect(schemas[0]!.description).toContain('If you are a continuable agent')
+    expect(props.agent_id).toMatchObject({
+      description: 'The agent id of your direct continuable child, or your direct parent when you are continuable.',
+    })
   })
 
   it('keeps the send_message definition and ordering byte-identical in a fork child', async () => {

+ 0 - 1
scripts/gen-cordis-catalog.ts

@@ -472,7 +472,6 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
   SpillRef: 'spill.md',
   ContinuableCreateRequest: 'subagent.md',
   ContinuableCreateSpec: 'subagent.md',
-  ContinuableSetupContribution: 'subagent.md',
   ContinuableStart: 'subagent.md',
   ContinuableStartSpec: 'subagent.md',
   AgentMessageSource: 'subagent.md',

+ 2 - 2
snapshots/sdk/bash-tool/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-continuable-inheritance/tool-schemas.1.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-continuable/tool-schemas.1.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json

@@ -339,13 +339,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-list-agents/tool-schemas.1.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/subagent-send-message/tool-schemas.1.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/sdk/text-turn/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 4 - 4
snapshots/session/agent-instructions/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",
@@ -1023,13 +1023,13 @@
       },
       {
         "name": "send_message",
-        "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+        "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
         "parameters": {
           "type": "object",
           "properties": {
             "agent_id": {
               "type": "string",
-              "description": "The agent id of your direct parent or direct child."
+              "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
             },
             "message": {
               "type": "string",

+ 2 - 2
snapshots/session/both-mode-turn/system-prompt.expected.md

@@ -159,9 +159,9 @@ interface ToolArgsMap {
     /** Path to the image file, resolved by the filesystem backend. */
     file_path: string;
   } & Record<string, JsonValue>;
-  /** Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
+  /** Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
   send_message: {
-    /** The agent id of your direct parent or direct child. */
+    /** The agent id of your direct continuable child, or your direct parent when you are continuable. */
     agent_id: string;
     /** The message to deliver to the agent. */
     message: string;

+ 2 - 2
snapshots/session/both-mode-turn/tool-schemas.expected.json

@@ -343,13 +343,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 4 - 4
snapshots/session/compaction-recovery/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",
@@ -1023,13 +1023,13 @@
       },
       {
         "name": "send_message",
-        "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+        "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
         "parameters": {
           "type": "object",
           "properties": {
             "agent_id": {
               "type": "string",
-              "description": "The agent id of your direct parent or direct child."
+              "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
             },
             "message": {
               "type": "string",

+ 2 - 2
snapshots/session/cordis-inspect-jsdoc/system-prompt.expected.md

@@ -326,9 +326,9 @@ interface ToolArgsMap {
     /** Path to the image file, resolved by the filesystem backend. */
     file_path: string;
   } & Record<string, JsonValue>;
-  /** Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
+  /** Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
   send_message: {
-    /** The agent id of your direct parent or direct child. */
+    /** The agent id of your direct continuable child, or your direct parent when you are continuable. */
     agent_id: string;
     /** The message to deliver to the agent. */
     message: string;

+ 2 - 2
snapshots/session/cordis-inspect-jsdoc/tool-schemas.expected.json

@@ -540,13 +540,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/fs-glob-sampling/tool-schemas.expected.json

@@ -242,13 +242,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/lsp-definition/tool-schemas.expected.json

@@ -359,13 +359,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/product-subagent-both/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/product-subagent-codex/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/product-subagent-result-diagnostic/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/ptc-read-image/system-prompt.expected.md

@@ -161,9 +161,9 @@ interface ToolArgsMap {
     /** Path to the image file, resolved by the filesystem backend. */
     file_path: string;
   } & Record<string, JsonValue>;
-  /** Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
+  /** Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
   send_message: {
-    /** The agent id of your direct parent or direct child. */
+    /** The agent id of your direct continuable child, or your direct parent when you are continuable. */
     agent_id: string;
     /** The message to deliver to the agent. */
     message: string;

+ 2 - 2
snapshots/session/ptc-turn/system-prompt.expected.md

@@ -161,9 +161,9 @@ interface ToolArgsMap {
     /** Path to the image file, resolved by the filesystem backend. */
     file_path: string;
   } & Record<string, JsonValue>;
-  /** Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
+  /** Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
   send_message: {
-    /** The agent id of your direct parent or direct child. */
+    /** The agent id of your direct continuable child, or your direct parent when you are continuable. */
     agent_id: string;
     /** The message to deliver to the agent. */
     message: string;

+ 2 - 2
snapshots/session/pty-tools-sandbox-backend/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/ralph-loop/tool-schemas.1.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/ralph-loop/tool-schemas.2.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/session-query-spill/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/subagent-acp-diagnostic/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/subagent-child-question-rejection/tool-schemas.expected.json

@@ -385,13 +385,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/text-turn/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/session/web-fetch/tool-schemas.expected.json

@@ -322,13 +322,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/web/cordis-tool-round/tool-schemas.expected.json

@@ -582,13 +582,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/web/fresh-round-trip/tool-schemas.expected.json

@@ -385,13 +385,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",

+ 2 - 2
snapshots/web/ptc-round/system-prompt.expected.md

@@ -189,9 +189,9 @@ interface ToolArgsMap {
     /** Path to the image file, resolved by the filesystem backend. */
     file_path: string;
   } & Record<string, JsonValue>;
-  /** Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
+  /** Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered. */
   send_message: {
-    /** The agent id of your direct parent or direct child. */
+    /** The agent id of your direct continuable child, or your direct parent when you are continuable. */
     agent_id: string;
     /** The message to deliver to the agent. */
     message: string;

+ 2 - 2
snapshots/web/schedule-catalog/tool-schemas.expected.json

@@ -461,13 +461,13 @@
     },
     {
       "name": "send_message",
-      "description": "Send a message to an adjacent agent by its agent id. The target must be your direct parent or direct child. If it is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
+      "description": "Send a message to a direct continuable child by its agent id. If you are a continuable agent, you may also target your direct parent. If the target is still working, the message steers its nearest step; if it is idle, the message starts a turn. This call returns no answer from the agent — only confirmation that the message was delivered. A failure means the message was NOT delivered.",
       "parameters": {
         "type": "object",
         "properties": {
           "agent_id": {
             "type": "string",
-            "description": "The agent id of your direct parent or direct child."
+            "description": "The agent id of your direct continuable child, or your direct parent when you are continuable."
           },
           "message": {
             "type": "string",