Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts: # .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml # .agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml # .agents/notes/implemented/feature/2026-07-22-docked-web-goal-bar.i18n.yaml # docs/architecture.i18n.yaml # docs/architecture.md # docs/architecture.zh.md # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/persistence-catalog.md # examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl # examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl # examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # examples/acp-agent/tests/snapshots/skill-load/session.jsonl # examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl # examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl # examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl # examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl # examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl # examples/acp-agent/tests/snapshots/workspace-context/session.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl # examples/headless-agent/tests/snapshots/pty-tools/session.jsonl # examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl # examples/jsonrpc-agent/tests/snapshots/subagent-spawn/notifications.expected.jsonl # examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.1.jsonl # packages/acp/acp/README.i18n.yaml # packages/acp/acp/src/index.ts # packages/client/connection/src/client/index.ts # packages/client/runtime/README.i18n.yaml # packages/client/runtime/README.md # packages/client/runtime/README.zh.md # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/runtime/tests/queue-store.spec.ts # packages/client/ui-conversation/src/client/input/contract.ts # packages/client/ui-goal/README.i18n.yaml # packages/compact/compact-basic/src/index.ts # packages/context/time-context/tests/time-context.spec.ts # packages/context/tmux-context/tests/tmux-context.spec.ts # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/README.md # packages/core/agent-loop/README.zh.md # packages/core/agent-loop/src/agent.ts # packages/core/agent-loop/tests/agent.spec.ts # packages/core/agent-loop/tests/loop.spec.ts # packages/core/agent/README.i18n.yaml # packages/core/agent/README.md # packages/core/agent/README.zh.md # packages/core/agent/src/types.ts # packages/core/session/README.i18n.yaml # packages/core/session/README.md # packages/core/session/README.zh.md # packages/goal/command-goal/tests/command-goal.spec.ts # packages/goal/goal/tests/goal.spec.ts # packages/goal/tool-goal/README.i18n.yaml # packages/goal/tool-goal/README.md # packages/goal/tool-goal/README.zh.md # packages/host/apiproxy/README.i18n.yaml # packages/host/apiproxy/README.md # packages/host/apiproxy/README.zh.md # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/events.schema.ts # packages/host/apiproxy/src/api/events.ts # packages/host/apiproxy/src/api/rpc.ts # packages/host/apiproxy/tests/api-proxy-commands.spec.ts # packages/host/apiproxy/tests/api-proxy-projections.spec.ts # packages/host/apiproxy/tests/rpc-schemas.spec.ts # packages/pty/pty-local/tests/index.spec.ts # packages/pty/pty-local/tests/local.spec.ts # packages/pty/tool-pty/tests/loader-composition.spec.ts # packages/pty/tool-pty/tests/tools.spec.ts # packages/session-persistence/session-checkpoint-policy/src/index.ts # packages/session-persistence/session-persistence/tests/coordinator-contract.ts # packages/skill/tool-skill/tests/tool-skill.spec.ts # packages/subagent/subagent-inprocess/README.i18n.yaml # packages/subagent/subagent-inprocess/README.md # packages/subagent/subagent-inprocess/README.zh.md # packages/subagent/subagent-inprocess/src/index.ts # packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts # packages/support/acp-snapshot/src/harness.ts # packages/ui/tui/tests/tui.spec.ts
This commit is contained in:
@@ -132,9 +132,10 @@ flowchart LR
|
||||
pkg_compact["compact"]
|
||||
svc_compact["ctx.compact<br/>Compaction seam"]
|
||||
pkg_subagent["subagent"]
|
||||
svc_subagents["ctx.subagents<br/>Subagent provider registry"]
|
||||
svc_subagents["ctx.subagents<br/>Subagent provider and continuation service"]
|
||||
pkg_subagent_spawn["subagent-spawn"]
|
||||
pkg_subagent_fork["subagent-fork"]
|
||||
pkg_tool_subagent_control["tool-subagent-control"]
|
||||
pkg_tool_ralph["tool-ralph"]
|
||||
pkg_tasks["tasks"]
|
||||
svc_tasks["ctx.tasks<br/>Background task registry"]
|
||||
@@ -313,6 +314,7 @@ flowchart LR
|
||||
svc_storageDomain --> pkg_workspace
|
||||
svc_subagents --> pkg_tool_ralph
|
||||
svc_subagents --> pkg_tool_subagent
|
||||
svc_subagents --> pkg_tool_subagent_control
|
||||
svc_subprocess --> pkg_bash_local
|
||||
svc_subprocess --> pkg_bash_sandbox
|
||||
svc_subprocess --> pkg_lsp_local
|
||||
@@ -389,7 +391,7 @@ flowchart LR
|
||||
| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode). |
|
||||
| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-policy`](../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-policy contributes observed-state checks through the fs/* event gate. |
|
||||
| `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; a model-facing compact tool remains deferred. |
|
||||
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; tool-subagent exposes configured delegation while tool-ralph requires one fresh structured-output route. |
|
||||
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. |
|
||||
| `ctx.tasks` | `seam` | [`tasks`](../packages/tasks/tasks) | [`tasks-local`](../packages/tasks/tasks-local) | [`tool-bash`](../packages/bash/tool-bash), [`tool-pty`](../packages/pty/tool-pty), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-tasks`](../packages/tasks/tool-tasks) | - | Producers (background bash, PTY sends, and subagent delegations) register running work; tool-tasks is the model-facing control surface that reads, lists, and kills it; tasks-local is the process-local registry. |
|
||||
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-local`](../packages/web/web-fetch-local) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. |
|
||||
| `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
responding-to-pr-review-on-a-stack.md: 3fb7eb943eeb8d703303be3f6a844870cc26fd47
|
||||
responding-to-pr-review-on-a-stack.zh.md: d96323b853c093265931904c20996df335f82926
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/responding-to-pr-review-on-a-stack.md
|
||||
responding-to-pr-review-on-a-stack.md: 6bb7be3daf8613666f02f21e96bf7b1ac8cb3606
|
||||
responding-to-pr-review-on-a-stack.zh.md: 94c2bd2cdd03b9eeca1e180b09bb71471db91c0a
|
||||
|
||||
@@ -2,25 +2,31 @@
|
||||
|
||||
English | [中文](responding-to-pr-review-on-a-stack.zh.md)
|
||||
|
||||
Review comments may target several PRs in a dependent stack (`A ← B ← C …`). This guide explains how to resolve them without corrupting the stack. The two invariants it rests on are standing orders in the root [AGENTS.md](../../AGENTS.md) § Conventions: merge commits only, and never rewrite a pushed branch.
|
||||
Review comments may target several PRs in a dependent stack (`A ← B ← C …`). Keep that chain linked through GitHub's official stacked-PR feature. This guide owns review-fix placement and propagation; the [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill owns linkage checks and landing.
|
||||
|
||||
## Ground rules
|
||||
|
||||
1. **One worktree per PR branch.** Each PR's fixes happen in that PR's own worktree; parallel fixes never share a checkout.
|
||||
2. **Bring a child up to date by merging the parent down** (`git merge <parent-branch>` into the child, a new merge commit). Never rebase/amend/force-push a pushed branch: rewriting diverges it from what the parent PR and GitHub recorded, breaks the stacked-merge graph, and erases the review-fix history.
|
||||
3. **A fix lands on the PR that INTRODUCED the issue, then flows down.** When a comment on PR `B` points at code `B` introduced, fix it on `B` and merge `B` into `C` — even if `C` also carries the file. Originating the fix downstream leaves `B` shipping the unfixed code and hides the fix from `B`'s reviewer.
|
||||
4. **Each review fix is a separate commit, never an amend.** The "fix review findings" commit documents what the review caught. Amending is fine only for your own not-yet-pushed, not-yet-reviewed work.
|
||||
2. **GitHub's stack object is authoritative.** Base branches establish the expected dependency order, while `PullRequest.stack` and `stackEntry.position` prove that GitHub recognizes it. Do not treat a matching branch chain as an official stack without checking those fields.
|
||||
3. **A fix lands on the PR that INTRODUCED the issue, then flows up-stack.** When a comment on PR `B` points at code `B` introduced, fix it on `B` and propagate `B` into `C` — even if `C` also carries the file. Originating the fix downstream leaves `B` shipping the unfixed code and hides the fix from `B`'s reviewer.
|
||||
4. **Each review fix remains a distinct commit.** A later rebase may change its OID, but do not amend a reviewed fix out of the branch history. Amend only your own not-yet-pushed, not-yet-reviewed work.
|
||||
5. **Choose merge-forward or rebase deliberately.** Both histories are allowed after review. A rewritten push must be lease-protected and must abort rather than overwrite a concurrently advanced remote head; raw `--force` is forbidden.
|
||||
|
||||
## Resolve comments through the stack
|
||||
|
||||
1. Triage every comment on the merits before acting: verify the claim against the code — a reviewer flagging the right symptom can still mis-diagnose the cause.
|
||||
2. Map each accepted finding to its originating PR, fix it there, then merge down the chain in order.
|
||||
3. Delegated fixes are trust-but-verify: a sub-agent's report describes intent, not necessarily what landed. Re-run the gates yourself on the actual tree, and for a regression guard, prove it FAILS on the unfixed code (introduce the regression, watch red, revert) — a guard that passes both ways guards nothing. A sub-agent that reframes a problem as already-handled is a signal to dig in personally.
|
||||
4. Reply in the review thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level comment, stating the fix and the commit that carries it.
|
||||
5. Before merging the stack, check dependents: deleting a PR's base branch auto-closes the dependent PR — check each branch with `gh pr list --state open --base <branch> --json number --jq length` (non-zero = open dependents), and merge without `--delete-branch` where a child still bases on the branch. The full landing procedure is the [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill.
|
||||
1. Triage every comment on the merits before acting: verify the claim against the code — a reviewer flagging the right symptom can still misdiagnose the cause.
|
||||
2. Map each accepted finding to its originating PR and fix it there.
|
||||
3. Propagate the fixed layer through every affected child in order:
|
||||
- **Merge-forward:** merge the fixed parent branch into its child, validate the child, and continue upward. Preserve each in-progress checkpoint under the [incremental-retargeting decision](../../.agents/notes/implemented/process/2026-07-26-incremental-pr-base-retargeting.md).
|
||||
- **Native cascading rebase:** use `gh stack rebase`, validate the rewritten layers, then publish with `gh stack push`; or use `gh stack sync`, which may publish first and therefore requires immediate post-sync validation under [dsh-pre-push-checks](../../.agents/skills/dsh-pre-push-checks/SKILL.md).
|
||||
4. Treat delegated fixes as trust-but-verify: a sub-agent's report describes intent, not necessarily what landed. Re-run the gates yourself on the actual tree, and for a regression guard, prove it FAILS on the unfixed code (introduce the regression, watch red, revert) — a guard that passes both ways guards nothing. A sub-agent that reframes a problem as already handled is a signal to dig in personally.
|
||||
5. Reply in the review thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level comment, stating the fix and the current commit or head that carries it.
|
||||
6. After any rewritten push, re-read unresolved threads, approvals, mergeability, and checks. A force-pushed commit OID or outdated inline anchor is not current evidence that the finding remains resolved.
|
||||
7. Land only through the official stack procedure. If the PRs are not yet linked, the landing skill automatically links a same-author chain, asks before linking mixed authors, and hard-stops when native stack support is unavailable.
|
||||
|
||||
## Verify
|
||||
|
||||
- Every fixed PR shows a new commit (no force-push icon in the PR timeline).
|
||||
- Each child PR's diff against its parent still shows only its own changes.
|
||||
- The gates pass on every PR in the stack, not just the top.
|
||||
- Every fixed PR's current diff contains the intended correction at the layer that introduced the issue.
|
||||
- GraphQL reports one official stack in the expected order, and each child diff against its parent shows only that child's changes.
|
||||
- Unresolved threads, approvals, mergeability, and checks were re-audited after every rewritten push.
|
||||
- The relevant gates pass on every affected PR in the stack, not just the top.
|
||||
|
||||
@@ -2,25 +2,31 @@
|
||||
|
||||
[English](responding-to-pr-review-on-a-stack.md) | 中文
|
||||
|
||||
评审意见可能同时针对一条依赖堆叠(`A ← B ← C …`)中的多个 PR(Pull Request)。本指南说明如何在不破坏堆叠的前提下解决这些意见。它依赖的两个不变式是根 [AGENTS.md](../../AGENTS.md) § Conventions 中的常设指令:只用 merge commit,以及永远不改写已推送的分支。
|
||||
评审意见可能同时针对一条依赖堆叠(`A ← B ← C …`)中的多个 PR(Pull Request)。请通过 GitHub 官方的堆叠 PR 功能保持这条链的关联。本指南负责评审修复的归属与传播;[dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill(技能)负责检查关联关系和落地。
|
||||
|
||||
## 基本规则
|
||||
|
||||
1. **每个 PR 分支一个 worktree。** 每个 PR 的修复在该 PR 自己的 worktree 中进行;并行修复绝不共享同一个 checkout。
|
||||
2. **通过将父分支向下合并来更新子分支**(在子分支中执行 `git merge <parent-branch>`,产生一个新的 merge commit)。绝不对已推送的分支做 rebase/amend/force-push:改写会使分支与父 PR 及 GitHub 记录的内容产生分歧,破坏堆叠合并图,并抹去评审修复历史。
|
||||
3. **修复落在引入问题的那个 PR 上,然后向下流动。** 当 PR `B` 上的评论指向 `B` 引入的代码时,在 `B` 上修复,再将 `B` 合并到 `C`——即使 `C` 也包含该文件。把修复发起在下游会导致 `B` 带着未修复的代码交付,并对 `B` 的评审者隐藏修复。
|
||||
4. **每个评审修复是一个独立 commit,绝不 amend。** "修复评审发现"的 commit 记录了评审捕获的内容。只有你自己尚未推送、尚未评审的工作才可以 amend。
|
||||
2. **GitHub 的 stack 对象是权威依据。** base 分支确定预期的依赖顺序,`PullRequest.stack` 和 `stackEntry.position` 则证明 GitHub 已识别该堆叠。未经检查这些字段,不得仅凭分支链吻合就将其视为官方堆叠。
|
||||
3. **修复落在引入问题的那个 PR 上,然后沿堆叠向上流动。** 当 PR `B` 上的评论指向 `B` 引入的代码时,在 `B` 上修复,再将 `B` 的变更传播到 `C`,即使 `C` 也包含该文件。把修复发起在下游会导致 `B` 带着未修复的代码交付,并对 `B` 的评审者隐藏修复。
|
||||
4. **每项评审修复都保留为独立 commit。** 后续 rebase 可能改变其 OID,但不得通过 amend 把已经评审的修复从分支历史中抹去。只有你自己尚未推送且尚未评审的工作才可以 amend。
|
||||
5. **明确选择 merge-forward 或 rebase。** 评审后允许采用这两种历史更新方式。改写历史的推送必须受 lease 保护;如果远端 head 在此期间前移,操作必须中止,不得将其覆盖。禁止直接使用 `--force`。
|
||||
|
||||
## 沿堆叠解决评审意见
|
||||
|
||||
1. 在行动之前先就事论事地审视每条评论:对照代码验证其论断——评审者指出了正确的症状,但仍可能误诊原因。
|
||||
2. 将每个被接受的发现映射到其发起 PR,在那里修复,然后按顺序沿链向下合并。
|
||||
3. 委派的修复需要信任但验证:子 agent(智能体)的报告描述的是意图,不一定是实际落地的内容。请亲自在实际代码树上重新运行门禁;对于回归守卫,要证明它在未修复的代码上**失败**(引入回归、观察变红、再还原)——两种情况都通过的守卫什么也守不住。子 agent 将问题重新定性为「已处理」时,这是一个需要亲自深入的信号。
|
||||
4. 在评审线程中回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),而非发顶层评论;说明修复内容及承载修复的 commit。
|
||||
5. 合并堆叠之前,检查依赖方:删除一个 PR 的 base 分支会自动关闭依赖它的 PR。用 `gh pr list --state open --base <branch> --json number --jq length` 检查每个分支(非零 = 有打开的依赖方),当子 PR 仍以该分支为 base 时,合并时不带 `--delete-branch`。完整的落地流程见 [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill(技能)。
|
||||
2. 将每个被接受的发现映射到其发起 PR,并在那里修复。
|
||||
3. 将修复后的层按顺序传播到每个受影响的子 PR:
|
||||
- **Merge-forward:** 将修复后的父分支合并到其子分支,验证子分支,然后继续沿堆叠向上传播。依照[增量更新 base 的决策](../../.agents/notes/implemented/process/2026-07-26-incremental-pr-base-retargeting.md),保留每个正在处理的检查点。
|
||||
- **原生级联 rebase:** 使用 `gh stack rebase`,验证所有已改写的层,然后通过 `gh stack push` 发布;也可以使用 `gh stack sync`,该命令可能先发布,因此必须按照 [dsh-pre-push-checks](../../.agents/skills/dsh-pre-push-checks/SKILL.md) 在同步后立即验证。
|
||||
4. 委派的修复需要信任但验证:子 agent(智能体)的报告描述的是意图,不一定是实际落地的内容。请亲自在实际代码树上重新运行门禁;对于回归守卫,要证明它在未修复的代码上**失败**(引入回归、观察变红、再还原)——两种情况都通过的守卫什么也守不住。子 agent 将问题重新定性为「已处理」时,这是一个需要亲自深入的信号。
|
||||
5. 在评审线程中回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),而非发顶层评论;说明修复内容及当前承载修复的 commit 或 head。
|
||||
6. 每次改写推送后,都要重新读取未解决线程、批准状态、可合并性和检查结果。经 force-push 改写的 commit OID 或已过时的内联锚点,都不足以证明该发现当前仍处于已解决状态。
|
||||
7. 仅可通过官方堆叠流程落地。如果这些 PR 尚未关联,落地 skill 会自动关联作者相同的链;如果作者不同,则先询问用户;如果原生堆叠支持不可用,则硬性停止流程。
|
||||
|
||||
## 验证
|
||||
|
||||
- 每个已修复的 PR 显示一个新 commit(PR 时间线中没有 force-push 图标)。
|
||||
- 每个子 PR 相对其父 PR 的 diff 仍然只包含自身的变更。
|
||||
- 门禁在堆叠中的每个 PR 上都通过,而不仅仅是顶部。
|
||||
- 每个已修复 PR 的当前 diff 都在引入问题的那一层包含预期修正。
|
||||
- GraphQL 报告的官方堆叠只有一个且顺序符合预期;每个子 PR 相对于父 PR 的 diff 只显示该子 PR 自身的变更。
|
||||
- 每次改写推送后,均重新审计了未解决线程、批准状态、可合并性和检查结果。
|
||||
- 相关门禁在堆叠中的每个受影响 PR 上都通过,而不仅仅是顶部。
|
||||
|
||||
@@ -256,8 +256,8 @@ Dispose and immediately reload this plugin with its current config.
|
||||
*
|
||||
* @param config — the new raw config; validated before anything restarts.
|
||||
* @param noSave — hint for persistence hooks not to write the change back.
|
||||
* @returns nothing; the restart runs behind the `internal/update` waterfall.
|
||||
* @throws {ValidationError} when the new config fails validation.
|
||||
* @returns the update waterfall result; the default restart returns a promise.
|
||||
* @throws when validation, an update listener, or the restarted plugin fails.
|
||||
*/
|
||||
update(config: any, noSave = false)
|
||||
```
|
||||
@@ -269,7 +269,7 @@ Runs the `internal/update` waterfall first, so update hooks (and HMR) can veto o
|
||||
- `config` — the new raw config; validated before anything restarts.
|
||||
- `noSave` — hint for persistence hooks not to write the change back.
|
||||
|
||||
**Returns** nothing; the restart runs behind the `internal/update` waterfall.
|
||||
**Returns** the update waterfall result; the default restart returns a promise.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L734)
|
||||
|
||||
|
||||
@@ -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/core-data-structures/persistence.md
|
||||
persistence.md: 0280093883b9fbd4c4bbc788bce7193d59bb7182
|
||||
persistence.zh.md: d32274bbc2e96e3754a8061ba37427e34b6205f6
|
||||
persistence.md: 237ce268691fa6f4b0546c6c1c14c21eaa462612
|
||||
persistence.zh.md: 9cb555f2e157844704896d9b04feb822428540e5
|
||||
|
||||
@@ -66,6 +66,11 @@ interface SessionHeader {
|
||||
* boundary lets resume and replay distinguish parent history from child work.
|
||||
*/
|
||||
readonly seedLength?: number
|
||||
/**
|
||||
* Coarse product classification for a session created as a subagent child.
|
||||
* This is presentation metadata, not proof that the child is continuable.
|
||||
*/
|
||||
readonly origin?: 'subagent'
|
||||
/**
|
||||
* Delegation depth: absent (zero) for a top-level session, parent depth + 1
|
||||
* for a subagent child. Persisted so a recursion budget survives restart and
|
||||
@@ -77,7 +82,7 @@ interface SessionHeader {
|
||||
|
||||
## `CreateSessionOptions` — seeding and metadata
|
||||
|
||||
Creating a `Session` through the store takes a `seed` (initial replay or fork history) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the `delegationDepth`, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
|
||||
Creating a `Session` through the store takes a `seed` (initial replay or fork history) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the optional coarse `origin`, the `delegationDepth`, and — only when reconstructing a persisted session — the original `createdAt` to preserve it. `origin: 'subagent'` lets product navigation hide duplicate child rows; it does not prove that a descriptor is valid or that the child can resume.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -97,6 +102,7 @@ interface CreateSessionOptions {
|
||||
readonly parentSession?: SessionId
|
||||
readonly createdAt?: number
|
||||
readonly seedLength?: number
|
||||
readonly origin?: 'subagent'
|
||||
readonly delegationDepth?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@ interface SessionHeader {
|
||||
* boundary lets resume and replay distinguish parent history from child work.
|
||||
*/
|
||||
readonly seedLength?: number
|
||||
/**
|
||||
* Coarse product classification for a session created as a subagent child.
|
||||
* This is presentation metadata, not proof that the child is continuable.
|
||||
*/
|
||||
readonly origin?: 'subagent'
|
||||
/**
|
||||
* Delegation depth: absent (zero) for a top-level session, parent depth + 1
|
||||
* for a subagent child. Persisted so a recursion budget survives restart and
|
||||
@@ -77,7 +82,7 @@ interface SessionHeader {
|
||||
|
||||
## `CreateSessionOptions`:seed 与元数据
|
||||
|
||||
通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)与 `meta`(store 折叠进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、`delegationDepth`,以及——仅在重建已持久化会话时——需要保留的原始 `createdAt`。
|
||||
通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)与 `meta`(store 折叠进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、可选的粗粒度 `origin`、`delegationDepth`,以及——仅在重建已持久化会话时——需要保留的原始 `createdAt`。`origin: 'subagent'` 让产品导航能够隐藏重复的 child 行;它不证明描述符有效,也不证明 child 可以恢复。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -97,6 +102,7 @@ interface CreateSessionOptions {
|
||||
readonly parentSession?: SessionId
|
||||
readonly createdAt?: number
|
||||
readonly seedLength?: number
|
||||
readonly origin?: 'subagent'
|
||||
readonly delegationDepth?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
subagent.md: 2497dbab9cfc8304eb7aaeba7109404ac614bbff
|
||||
subagent.zh.md: 2d96e9bc635951746e72ed58a7c3638dc2598cc2
|
||||
# pnpm run verify-translation-pairing --write docs/core-data-structures/subagent.md
|
||||
subagent.md: c5fbf80ae71f99606dd86e38f06a4511b4ae4c73
|
||||
subagent.zh.md: 42c1fa7cb10863c1aa4ae975171b901207c08b85
|
||||
|
||||
@@ -4,23 +4,25 @@ English | [中文](subagent.zh.md)
|
||||
|
||||
The subagent seam — an agent delegating work to a child agent. Like [bash](bash.md) it is **one optional capability**, not part of the agent-loop spine, so its vocabulary lives here rather than in [core.md](core.md). But it differs from every other seam on one axis: **multiple provider implementations coexist** in one context, registered by name (`ctx.subagents`), where bash allows only one executor. The registry shape mirrors the [LLM adapter registry](llm-streaming.md), not the single-service bash executor.
|
||||
|
||||
Interface: [dsh-subagent](../../packages/subagent/subagent) (`ctx.subagents` + the vocabulary below). Implementations are sibling packages (`dsh-subagent-spawn`, `-fork`, `-acp`); the model-facing consumer is [dsh-tool-subagent](../../packages/subagent/tool-subagent). The proposal and rationale: [the subagent Agent Note](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md).
|
||||
Interface: [dsh-subagent](../../packages/subagent/subagent) (`ctx.subagents` + the vocabulary below). Implementations are sibling packages (`dsh-subagent-spawn`, `-fork`, `-acp`); the model-facing consumers are [dsh-tool-subagent](../../packages/subagent/tool-subagent) (per-provider delegation), [dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control) (the optional global `send_message` and `list_agents` controls), and [dsh-tool-subagent-report](../../packages/subagent/tool-subagent-report) (the optional child-scoped `report` return channel). The same `ctx.subagents` service owns continuable-child orchestration through an internal activation manager and read-only direct-child discovery through optional session query. The rationale lives in [the subagent Agent Note](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), [the continuable subagents Agent Note](../../.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md), [the report-tool Agent Note](../../.agents/notes/implemented/feature/2026-07-30-continuable-subagent-report-tool.md), [the durable catalog Agent Note](../../.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md), and [the merged-service Agent Note](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md).
|
||||
|
||||
Source: [`packages/subagent/subagent/src/types.ts`](../../packages/subagent/subagent/src/types.ts)
|
||||
Sources: [`packages/subagent/subagent/src/types.ts`](../../packages/subagent/subagent/src/types.ts), [`packages/subagent/subagent/src/index.ts`](../../packages/subagent/subagent/src/index.ts), and [`packages/subagent/subagent/src/continuation.ts`](../../packages/subagent/subagent/src/continuation.ts)
|
||||
|
||||
## Two kinds of capability, discovered two ways
|
||||
|
||||
A provider advertises its **start-time** features on a static descriptor the service checks BEFORE a run exists; a request that needs one the provider lacks is rejected loud (`SubagentError('UNSUPPORTED_CAPABILITY')`), never accepted-then-ignored. **Runtime** features (steering, resume) are instead optional methods on [`SubagentRun`](#a-live-run-subagentrun) — the method's presence IS the capability, and TS narrowing is the discovery mechanism.
|
||||
A provider advertises its **start-time** features on a static descriptor the service checks BEFORE a one-shot run exists; a request that needs one the provider lacks is rejected loud (`SubagentError('UNSUPPORTED_CAPABILITY')`), never accepted-then-ignored. Those flags describe only the one-shot [`start()`](#the-provider-seam-subagentprovider) path, where the provider composes the child. **Continuable** children are composed by the continuation manager itself, so they are gated by one optional method whose presence IS the capability, with TS narrowing as the discovery mechanism: [`SubagentProvider.prepareContinuable`](#the-provider-seam-subagentprovider).
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Which START-TIME features a provider supports. Checked by the service before delegating to
|
||||
* {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks
|
||||
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
|
||||
* degradation" rule). These static flags cover features needed before a run exists; runtime
|
||||
* capabilities such as steering and resume are optional {@link SubagentRun} methods whose presence
|
||||
* is the capability. Each flag corresponds one-to-one to a {@link SubagentStartRequest} option:
|
||||
* `depthLimit` to `maxDepth`; the other names match.
|
||||
* degradation" rule). These flags describe the ONE-SHOT
|
||||
* {@link SubagentProvider.start} path, where the provider composes the child;
|
||||
* continuable children are composed by the continuation manager itself and are
|
||||
* gated by {@link SubagentProvider.prepareContinuable} instead. Each flag
|
||||
* corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit`
|
||||
* to `maxDepth`; the other names match.
|
||||
*/
|
||||
interface SubagentCapabilities {
|
||||
readonly outputSchema: boolean
|
||||
@@ -30,18 +32,21 @@ interface SubagentCapabilities {
|
||||
}
|
||||
```
|
||||
|
||||
## The start request
|
||||
## The one-shot start request
|
||||
|
||||
The tool layer builds this request from the model input and its own config; the service validates it against the named provider before `start`. Required `parent` supplies the session cwd, lineage, and delegation depth. Optional output schema, depth, tool filter, and persona require matching capability flags. Unsupported schemas fail at start; in-process backends scope filters and personas to child creation and implement the supported object-rooted schema with a forced capture tool.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* What a caller asks for when starting a subagent. The tool layer builds this
|
||||
* from the model's `{ description, prompt }` plus its own config; the service
|
||||
* validates {@link SubagentCapabilities} against the named provider, then
|
||||
* passes it to {@link SubagentProvider.start}.
|
||||
* What a caller asks for when starting a ONE-SHOT subagent. The tool layer
|
||||
* builds this from the model's `{ description, prompt }` plus its own config;
|
||||
* the service validates {@link SubagentCapabilities} against the named provider
|
||||
* and resolves the durable descriptor before dispatching to
|
||||
* {@link SubagentProvider.start}.
|
||||
*/
|
||||
interface SubagentStartRequest {
|
||||
/** Optional short display label persisted with a session-backed child. */
|
||||
readonly label?: string
|
||||
/** Content delivered as the child's user message. */
|
||||
readonly prompt: ContentBlock[]
|
||||
/**
|
||||
@@ -54,8 +59,8 @@ interface SubagentStartRequest {
|
||||
* Cancellation signal from the spawning context (the tool's `exec.signal`).
|
||||
* This is the canonical cancellation channel both before and after startup:
|
||||
* a provider rejects `start()` after cleaning partial resources when it
|
||||
* fires before publication, and cancels a published child when it fires
|
||||
* afterward.
|
||||
* fires before the run is published, and cancels the published run's
|
||||
* remaining turn work when it fires afterward.
|
||||
*/
|
||||
readonly signal: AbortSignal
|
||||
readonly agentOptions?: AgentOptions
|
||||
@@ -93,9 +98,161 @@ interface SubagentStartRequest {
|
||||
|
||||
`signal` is the single cancellation channel before and after readiness. The [subagent composition-controls Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md) owns the persona, live global-tool filter, absolute-depth, and visibility-not-authority rationale.
|
||||
|
||||
The caller-facing request does not carry catalog format details or continuation state. `SubagentService.start()` resolves the detached one-shot descriptor after capability checks, then passes this provider-facing request to the selected transport; a continuable child never reaches `SubagentProvider.start()`:
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Provider-facing one-shot request after {@link SubagentService.start} resolves
|
||||
* the durable child descriptor.
|
||||
*/
|
||||
interface ResolvedSubagentStartRequest extends SubagentStartRequest {
|
||||
/** Detached descriptor a session-backed provider persists in the child log. */
|
||||
readonly descriptor: SubagentDescriptorData
|
||||
}
|
||||
```
|
||||
|
||||
## Continuable children and activations
|
||||
|
||||
A **continuable background subagent** is one durable child Session with at most one process-local **Activation** — a residency epoch for a reconstructed child Agent. An Activation is not a request, result, cancellation, or Task boundary: it may execute many FIFO turns and stays resident while descendants it created are still running. The continuation manager owns activation admission, direct-parent authorization, the live ownership graph, cold resume, and child-first disposal; the Agent loop owns all turn ordering and execution. No continuable path creates a Task or an intermediate result-bearing wrapper.
|
||||
|
||||
```text
|
||||
persisted Session
|
||||
-> optional live Activation
|
||||
-> one retained AgentHandle
|
||||
-> Agent inbox as the only turn FIFO
|
||||
-> zero or more owned child Activations
|
||||
```
|
||||
|
||||
`SubagentService.startContinuable()` reserves the stable child id, snapshots the versioned `subagent/descriptor` payload, asks the named provider for its detached `ContinuableCreateSpec`, creates the child Agent through a private activation-owner scope, establishes any continuable-parent ownership, and submits the initial prompt. It resolves with `{ childId, messageId }` when inbox acceptance yields the message id — without waiting for the turn to start or for the message to enter the Session log. Every failure before that acceptance rejects with neither id, disposing any created handle and rolling back the Activation and parent ownership.
|
||||
|
||||
`SubagentService.followup()` is the sole continuation-message operation, and routing depends only on Activation residency:
|
||||
|
||||
| Activation state | `followup` |
|
||||
|---|---|
|
||||
| `running` | enqueue in the same Activation |
|
||||
| `waiting` | wake the same Activation |
|
||||
| no Activation | cold-resume a new Activation |
|
||||
|
||||
`running` means the Agent has an active admission or turn, or waking inbox work; `waiting` means it is quiescent but still owns at least one child Activation that has not completed disposal; `settled` means quiescent with every owned child disposed, at which point the manager disposes the `AgentHandle` and removes the Activation. The manager derives these internal conditions from Agent quiescence and the owned-child set rather than maintaining a second execution state machine.
|
||||
|
||||
The Agent inbox is the only queue. Every continuation message becomes one `Agent.followup()` FIFO turn, so accepted messages have one observable order and a follow-up cannot redirect a turn already underway. Successful delivery returns the accepted `MessageId`; the existing `agent/inbox/enqueue`, `agent/inbox/dequeue`, and `agent/inbox/discard` events remain the message-lifecycle observations, and the continuation layer defines no subagent-specific delivery route.
|
||||
|
||||
Follow-up authority comes from an exact live Agent tool context. The authenticated Agent must be the durable child's direct parent recorded in `SessionHeader.parentSession`. `MessageSource` and `senderSessionId` are durable provenance after admission and grant no authority; the optional model-facing tool uses `CoordinatorMessageSource`.
|
||||
|
||||
For both operations the caller signal owns lookup, materialization, and admission only until inbox acceptance. Afterwards the manager owns the Activation independently: later caller cancellation neither cancels the accepted turn nor disposes the child, and the seam exposes no public subagent cancellation or steering operation.
|
||||
|
||||
Every Activation owns its `AgentHandle` and an `ownedChildren: Set<SessionId>`; because one Session has at most one live Activation, the child Session id identifies the live child without another runtime-incarnation reference. Starting a child or submitting parent-originated work registers the child in a continuation-managed parent's set before the child can run, and that parent cannot settle while the set is non-empty. A top-level or other non-continuation Agent has no Activation and stays outside the waiting graph. Child release happens only after the child Agent is quiescent, every child of that child is disposed, the best-effort final session flush settles, and the child's `AgentHandle` completes disposal.
|
||||
|
||||
Final settlement awaits `ctx.sessions.flush(session)` but ignores its participation boolean because an arbitrary listener cannot prove that a persistence backend stored the state. Rejection is logged without failing the Activation, and the manager still disposes the handle and releases ownership; the persisted child state may then be missing or stale on a later resume. Manager unload invokes an internal manager-wide drain that closes admission and disposes every live forest; `drainContinuableDescendants(parents)` closes admission only below exact live host-owned Agents and disposes their continuable descendants while unrelated forests remain live. Both await already-admitted materializations in their scope, propagate cancellation top-down, release handles child-first, and await every selected branch despite individual failures. Durable child Sessions survive that process-local teardown.
|
||||
|
||||
```ts type-equiv
|
||||
/** Attribution for a model coordinator's follow-up to one of its children. */
|
||||
interface CoordinatorMessageSource {
|
||||
readonly kind: 'coordinator'
|
||||
/** Session id of the agent whose tool call produced the follow-up. */
|
||||
readonly senderSessionId: SessionId
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Options for following up with one continuable child. */
|
||||
interface SubagentFollowupOptions {
|
||||
/** Durable attribution retained on the delivered message; it grants no authority. */
|
||||
readonly source: MessageSource
|
||||
/** Caller cancellation, owning the operation only until inbox acceptance. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Identities returned once a continuable child accepted its initial prompt. */
|
||||
interface ContinuableStart {
|
||||
/** The durable child session id, stable across activations. */
|
||||
readonly childId: SessionId
|
||||
/** The accepted initial prompt's inbox message id. */
|
||||
readonly messageId: MessageId
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
`SubagentService.reportFrom()` uses that extension seam without adding a second queue or a result-bearing child wrapper. The exact live child Agent authorizes the call; callers cannot name a recipient. The manager derives the only recipient from the child's durable `parentSession`, requires that parent Agent to be live, frames the selected content as one `subagent-report` user message, and returns the message's stable `MessageId`. Quiet delivery uses `Agent.inject()` and creates no inbox occurrence or parent turn; waking delivery uses `Agent.followup()` and creates one ordinary later parent turn. Neither mode concludes the child's turn, and no final answer reports implicitly.
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable attribution for a continuable child's explicit parent report. */
|
||||
interface SubagentReportMessageSource {
|
||||
readonly kind: 'subagent-report'
|
||||
/** Session id of the reporting child. */
|
||||
readonly senderSessionId: SessionId
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Deployment scheduling policy for accepted child reports. */
|
||||
type SubagentReportDelivery = 'quiet' | 'wakeup'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Options for one continuable child's report to its direct parent. */
|
||||
interface SubagentReportOptions {
|
||||
/** Already-resolved parent scheduling policy. */
|
||||
readonly delivery: SubagentReportDelivery
|
||||
/** Caller cancellation, owning authorization and admission until acceptance. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
The provider participates only in preparing the initial creation spec, where `spawn` and `fork` differ. Its returned spec carries only detached provider-specific creation inputs — today the optional parent-history seed — and no Agent, `AgentHandle`, prompt delivery, result, disposal, or resume operation. Cold resume does not dispatch through a provider at all: the manager folds the generic descriptor, calls `ctx.agents.resume()` through the same activation-owner scope, and submits the waiting turn.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* What the continuation manager asks a provider for while materializing one
|
||||
* continuable child's FIRST activation. The manager has already reserved the
|
||||
* durable child identity and owns every later operation, so this request
|
||||
* carries only what distinguishes a fresh child from one seeded with parent
|
||||
* history.
|
||||
*/
|
||||
interface ContinuableCreateRequest {
|
||||
/** The reserved durable child session id, for provider diagnostics. */
|
||||
readonly sessionId: SessionId
|
||||
/** The delegating parent agent whose history a seeding provider reads. */
|
||||
readonly parent: Agent
|
||||
/**
|
||||
* Caller cancellation, which owns preparation only until the manager accepts
|
||||
* the initial prompt into the child's inbox.
|
||||
*/
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A provider's detached contribution to one continuable child's creation. This
|
||||
* is DATA, never a capability: it carries no Agent, `AgentHandle`, prompt
|
||||
* delivery, result, disposal, or resume operation, because the continuation
|
||||
* manager owns the child's whole lifecycle after preparation.
|
||||
*/
|
||||
interface ContinuableCreateSpec {
|
||||
/**
|
||||
* Completed-turn prefix of the parent's log to seed the child session with,
|
||||
* or absent for a fresh child. Same durable contract as
|
||||
* `CreateAgentOptions.seed`: contiguous from seq 0, lossless JSON, balanced.
|
||||
*/
|
||||
readonly seed?: readonly SessionEvent[]
|
||||
}
|
||||
```
|
||||
|
||||
The descriptor (`SubagentDescriptorData` in [descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts)) is a mode-discriminated durable identity for every session-backed subagent. Both modes carry the provider name. A `one-shot` descriptor optionally carries a caller-owned display `label`; a `continuable` descriptor requires the delegation `description` as its durable creation label and additionally snapshots resolved child `agentOptions.provider`/`model` and optional `persona`/`toolFilter` for cold resume. It never snapshots the merge-extensible `AgentOptions` object, so an unrelated extension value cannot break continuation and a later composition input is a deliberate version change. It omits `subagentDepth` (cold resume trusts the persisted header's `delegationDepth` as the monotone floor) and `outputSchema` (one run or Activation's result contract, not durable identity).
|
||||
|
||||
A local one-shot provider appends the descriptor inside the child's initial turn before its first request. The continuation manager appends the descriptor after any provider-supplied lineage and before the initial prompt is admitted; `header.seedLength` remains the fork-lineage boundary, so descriptor lookup reads the child's own suffix. The event is log-only: no `surfaceOp`, never in model history, and retained across compaction by the append-only log. Malformed current-version descriptors are corrupt; unsupported versions cannot be classified by this runtime.
|
||||
|
||||
## Durable enumeration: `listChildren()` and `SubagentListEntry`
|
||||
|
||||
`SubagentService.listChildren(parentSessionId)` enumerates the parent's direct session-backed subagents from one `ctx.sessionQuery.traceSession()` observation, without loading or resuming any Agent. Session lineage is broader than subagent identity — ordinary forks share `parentSession` — so exactly one supported `subagent/descriptor` event in the child's own suffix (after `seedLength`, so a fork seed cannot leak an ancestor's descriptor) is the sole subagent discriminator. `SessionHeader.origin: 'subagent'` is only a coarse product-navigation classifier stamped before publication; it can suppress duplicate sidebar rows but cannot establish a valid descriptor, resumability, or authorization. The result is one `SubagentListEntry[]` in the trace's `createdAt`-then-id candidate order: a valid descriptor yields a `child` entry with `mode: 'one-shot' | 'continuable'` and `activity: 'running' | 'inactive'`; continuable entries always carry `label`, while one-shot entries carry it only when the start caller supplied presentation metadata. A per-child inspection failure yields a `diagnostic` entry (`corrupt`, `unsupported`, or `unavailable`) so one damaged sibling cannot hide healthy children; a missing descriptor yields no entry. Activity snapshots only whether the logical record is live in `ctx.sessions`, not outcome or resumability. A service consumer such as a UI can display both modes and choose an unlabeled one-shot fallback, while the model-facing `list_agents` adapter (the separately loadable `/list-agents` plugin of [dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control)) keeps only continuable entries and maps activity to its existing `running`/`complete` vocabulary. A failure while building the initial trace fails the whole call — per-child isolation begins only after a trustworthy candidate set exists. The service keeps `sessionQuery` optional for by-id continuation: `listChildren()` throws `SubagentError` with code `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` when it is absent, while the list tool requires `ctx.subagents` and `ctx.sessionQuery` at plugin load. Listing does not consult the continuation manager's Activation map, Agent registry, or provider availability; `send_message` remains the authoritative delivery-time operation, and a listed running continuable child may still reject delivery as an ownership conflict.
|
||||
|
||||
## The terminal result: `SubagentResult`
|
||||
|
||||
The outcome of a run, resolved by `SubagentRun.result`. `structured` is present only after a requested `outputSchema` was successfully satisfied; requesting a schema does not guarantee it, and a provider may return `stopReason: 'error'` when the child fails or finishes without a valid capture. A non-`completed` `stopReason` means `output` may be partial — the consumer maps it to an `isError` tool result rather than reporting partial output as success.
|
||||
The outcome of a one-shot run, resolved by `SubagentRun.result`. `structured` is present only after a requested `outputSchema` was successfully satisfied; requesting a schema does not guarantee it, and a provider may return `stopReason: 'error'` when the child fails or finishes without a valid capture. A non-`completed` `stopReason` means `output` may be partial — the consumer maps it to an `isError` tool result rather than reporting partial output as success.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -140,15 +297,19 @@ interface SubagentStopReasonMap {
|
||||
}
|
||||
```
|
||||
|
||||
## A live run: `SubagentRun`
|
||||
## A one-shot run: `SubagentRun`
|
||||
|
||||
`SubagentRun` is the consumer-owned handle for a ready child. Consumers await `result` and always dispose the run to reach quiescence. Child failures resolve with a non-completed stop reason; only unrepresentable infrastructure faults reject. Optional `sendMessage` and `resume` methods advertise their runtime capabilities by presence.
|
||||
`SubagentRun` is the consumer-owned handle for a published one-shot child — one disposable foreground delegation with one result, never a durable child handle. Prompt submission, turn work, and infrastructure faults after publication belong to `result`. Consumers await that result and always dispose the run to reach quiescence. Child failures resolve with a non-completed stop reason; only unrepresentable infrastructure faults reject. A run has no steering and no resume: continuable conversations have no run at all, because the continuation manager holds their `AgentHandle` directly and orders every turn through the child's own inbox.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Child handle returned only after readiness. Consumers await {@link result} and must always
|
||||
* {@link dispose} to cancel remaining work and reach quiescence. Optional methods are runtime
|
||||
* capability discovery; narrow their presence before calling.
|
||||
* ONE-SHOT child handle returned after publication. Prompt submission, turn
|
||||
* work, and infrastructure faults after that boundary belong to {@link result}.
|
||||
* Consumers await that result and must always {@link dispose} to cancel
|
||||
* remaining work and reach quiescence. A run is one disposable foreground
|
||||
* delegation with one result; continuable conversations have no run — the
|
||||
* continuation manager holds their `AgentHandle` directly and orders every
|
||||
* turn through the child's own inbox.
|
||||
*/
|
||||
interface SubagentRun {
|
||||
/**
|
||||
@@ -167,8 +328,8 @@ interface SubagentRun {
|
||||
* Resolves with the child's terminal {@link SubagentResult} when the run
|
||||
* settles. Does NOT reject on a child-level failure — a model/transport
|
||||
* failure resolves with `stopReason: 'error'` so the consumer maps it to an
|
||||
* `isError` tool result. Rejects only on an infrastructure fault the seam
|
||||
* cannot represent as a stop reason.
|
||||
* `isError` tool result. Rejects on an infrastructure fault the seam cannot
|
||||
* represent as a stop reason.
|
||||
*/
|
||||
readonly result: Promise<SubagentResult>
|
||||
/**
|
||||
@@ -176,24 +337,14 @@ interface SubagentRun {
|
||||
* Idempotent.
|
||||
*/
|
||||
dispose(): Promise<void>
|
||||
/**
|
||||
* OPTIONAL (steering capability): send additional content to the running
|
||||
* child between steps. Present only on providers that support live steering.
|
||||
*/
|
||||
sendMessage?(content: ContentBlock[]): void
|
||||
/**
|
||||
* OPTIONAL (resume capability): send a follow-up task to a settled child,
|
||||
* continuing its session, and return a fresh run for the continuation.
|
||||
*/
|
||||
resume?(content: ContentBlock[]): Promise<SubagentRun>
|
||||
}
|
||||
```
|
||||
|
||||
A local run MUST publish an ordinary child agent/session before `start()` fulfills, return that child session id as `SubagentRun.id`, expose the exact child as `localAgent`, and record `request.parent.session.id` in the child's `parentSession` header. Runtime ownership may place the child under the parent, provider, or root scope. A remote provider instead returns a parent-scoped lifecycle id and `localAgent: undefined`.
|
||||
A local one-shot run MUST publish an ordinary child agent/session before `start()` fulfills, return that child session id as `SubagentRun.id`, expose the exact child as `localAgent`, record `request.parent.session.id` in the child's `parentSession` header, and append the resolved descriptor inside the child's initial turn before its first request. Runtime ownership may place the child under the parent, provider, or root scope. A remote provider instead returns a parent-scoped lifecycle id and `localAgent: undefined`; without a local child Session, it is absent from trace-backed enumeration.
|
||||
|
||||
## The provider seam: `SubagentProvider`
|
||||
|
||||
Each provider is a named child-agent transport, and multiple providers may coexist. The service validates requested start-time capabilities before `start()`. `inheritsParentContext` describes only conversation seeding (`fork`: true; `spawn` and `acp`: false), allowing consumers to generate accurate model-facing wording without implying inherited tools, services, or authority.
|
||||
Each provider is a named child-agent transport, and multiple providers may coexist. The service validates requested start-time capabilities before `start()`, and rejects a continuable start on a provider without `prepareContinuable`. `inheritsParentContext` describes only conversation seeding (`fork`: true; `spawn` and `acp`: false), allowing consumers to generate accurate model-facing wording without implying inherited tools, services, or authority.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -213,22 +364,38 @@ interface SubagentProvider {
|
||||
*/
|
||||
readonly inheritsParentContext: boolean
|
||||
/**
|
||||
* Establish a child and return its handle only after publication. The
|
||||
* service has already validated that every requested start-time capability
|
||||
* is supported, so an implementation may assume e.g. `request.maxDepth` is
|
||||
* honorable when present. If setup fails or `request.signal` aborts before
|
||||
* fulfillment, the provider owns and cleans all partial resources before this
|
||||
* promise rejects. Ownership transfers to the caller only on fulfillment.
|
||||
* Establish a ONE-SHOT child and return its handle after publication.
|
||||
* The service has already validated that every requested start-time
|
||||
* capability is supported and resolved `request.descriptor`, so a
|
||||
* session-backed implementation appends that descriptor inside the child's
|
||||
* initial turn. Before fulfillment, the provider owns setup and cleans any
|
||||
* unpublished partial resources before rejecting. Ownership transfers on
|
||||
* fulfillment; subsequent turn or infrastructure failure settles through
|
||||
* the returned run.
|
||||
*/
|
||||
start(request: SubagentStartRequest): Promise<SubagentRun>
|
||||
start(request: ResolvedSubagentStartRequest): Promise<SubagentRun>
|
||||
/**
|
||||
* OPTIONAL (continuable-creation capability): contribute the detached
|
||||
* creation inputs that distinguish this provider's continuable children —
|
||||
* today only whether the child session is seeded with parent history. Method
|
||||
* presence IS the capability: the service rejects continuable starts on
|
||||
* providers without it, while a provider that has it may still serve
|
||||
* ordinary one-shot delegations.
|
||||
*
|
||||
* This is the provider's ONLY participation in a continuable child. The
|
||||
* continuation manager owns identity reservation, composition, Agent
|
||||
* creation, prompt delivery, cold resume, ownership, and disposal, so a
|
||||
* provider never sees the child's Agent, handle, turns, or teardown.
|
||||
*/
|
||||
prepareContinuable?(request: ContinuableCreateRequest): Promise<ContinuableCreateSpec>
|
||||
}
|
||||
```
|
||||
|
||||
`start()` fulfills only with a ready run. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; rejection implies provider cleanup and emits no lifecycle pair. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions.
|
||||
Provider `start()` fulfills with a published run. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; a `start()` rejection implies cleanup of unpublished resources and emits no lifecycle pair, while a post-publication result rejection closes the emitted pair. Each continuable Activation emits the same observe-only pair for its residency epoch, so a cold resume is a new epoch with its own `runId`. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions. Their `provider` field is provenance for the run or Activation epoch, not a claim that the provider remains registered when the edge is emitted.
|
||||
|
||||
## In-process backends: depth and seed
|
||||
|
||||
The spawn and fork backends create an ordinary agent through `parent.ctx`, pass cancellation into core creation, and dispose through `AgentHandle`. Provider removal blocks new starts without revoking accepted runs. Each child gets a new flat scope rather than inheriting parent registrations. Depth and fork seeding reuse existing agent and session vocabulary:
|
||||
The spawn and fork backends create an ordinary one-shot agent through `parent.ctx`, pass cancellation into core creation, and dispose through `AgentHandle`; a continuable child is instead created by the continuation manager through its own activation-owner scope. Provider removal blocks new starts without revoking accepted runs. Each child gets a new flat scope rather than inheriting parent registrations. Depth and fork seeding reuse existing agent and session vocabulary:
|
||||
|
||||
- **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap.
|
||||
- **Fork seeding** uses `CreateAgentOptions.seed` (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `resume` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/support/invariants) replay accepts it (the in-flight, unbalanced turn is excluded).
|
||||
- **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, cold resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap.
|
||||
- **Fork seeding** uses `CreateAgentOptions.seed` (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/support/invariants) replay accepts it (the in-flight, unbalanced turn is excluded).
|
||||
|
||||
@@ -4,23 +4,25 @@
|
||||
|
||||
subagent seam:一个 agent(智能体)将工作委派给子 agent。与 [bash](bash.md) 一样,它是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.md) 中。但它在一个维度上与其他所有 seam 不同:**同一上下文中可共存多个提供方实现**,按名称注册(`ctx.subagents`),而 bash 只允许一个执行器。注册表的形状参照 [LLM(大语言模型)适配器注册表](llm-streaming.md),而非单服务的 bash 执行器。
|
||||
|
||||
接口:[dsh-subagent](../../packages/subagent/subagent)(`ctx.subagents` + 下文词汇)。实现为三个兄弟包(package):`dsh-subagent-spawn`、`-fork`、`-acp`;面向模型的消费方是 [dsh-tool-subagent](../../packages/subagent/tool-subagent)。提案与设计理由见 [subagent Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)。
|
||||
接口:[dsh-subagent](../../packages/subagent/subagent)(`ctx.subagents` + 下文词汇)。实现为三个兄弟包(package):`dsh-subagent-spawn`、`-fork`、`-acp`;面向模型的消费方包括 [dsh-tool-subagent](../../packages/subagent/tool-subagent)(按提供方委派)、[dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control)(可选的全局 `send_message` 与 `list_agents` 控制工具)和 [dsh-tool-subagent-report](../../packages/subagent/tool-subagent-report)(可选的 child 作用域 `report` 返回通道)。同一个 `ctx.subagents` 服务通过内部激活管理器负责可继续子 agent 编排,并通过可选的会话查询负责只读的直接 child 发现。设计理由见 [subagent Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)、[可继续 subagent Agent Note](../../.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md)、[report 工具 Agent Note](../../.agents/notes/implemented/feature/2026-07-30-continuable-subagent-report-tool.md)、[持久化目录 Agent Note](../../.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md)和[服务合并 Agent Note](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md)。
|
||||
|
||||
源码:[`packages/subagent/subagent/src/types.ts`](../../packages/subagent/subagent/src/types.ts)
|
||||
源码:[`packages/subagent/subagent/src/types.ts`](../../packages/subagent/subagent/src/types.ts)、[`packages/subagent/subagent/src/index.ts`](../../packages/subagent/subagent/src/index.ts)和 [`packages/subagent/subagent/src/continuation.ts`](../../packages/subagent/subagent/src/continuation.ts)
|
||||
|
||||
## 两类能力,两种发现方式
|
||||
|
||||
提供方通过一个静态描述符公布其**启动时**特性,服务在 run 存在之前即行检查;如果请求依赖提供方不具备的特性,会被大声拒绝(`SubagentError('UNSUPPORTED_CAPABILITY')`),绝不会被接受后静默忽略。**运行时**特性(steering(中途引导)、恢复)则是 [`SubagentRun`](#a-live-run-subagentrun) 上的可选方法——方法的存在即为能力,TypeScript 的类型收窄即为发现机制。
|
||||
提供方通过一个静态描述符公布其**启动时**特性,服务会在单次 run 存在之前即行检查;如果请求依赖提供方不具备的特性,会被大声拒绝(`SubagentError('UNSUPPORTED_CAPABILITY')`),绝不会被接受后静默忽略。这些 flag 仅描述单次 [`start()`](#the-provider-seam-subagentprovider) 路径,即由提供方组合子 agent 的路径。**可继续**子 agent 由继续执行管理器自行组合,因此它们由唯一一个可选方法把关,方法存在即为能力,并以 TypeScript 的类型收窄作为发现机制:[`SubagentProvider.prepareContinuable`](#the-provider-seam-subagentprovider)。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Which START-TIME features a provider supports. Checked by the service before delegating to
|
||||
* {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks
|
||||
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
|
||||
* degradation" rule). These static flags cover features needed before a run exists; runtime
|
||||
* capabilities such as steering and resume are optional {@link SubagentRun} methods whose presence
|
||||
* is the capability. Each flag corresponds one-to-one to a {@link SubagentStartRequest} option:
|
||||
* `depthLimit` to `maxDepth`; the other names match.
|
||||
* degradation" rule). These flags describe the ONE-SHOT
|
||||
* {@link SubagentProvider.start} path, where the provider composes the child;
|
||||
* continuable children are composed by the continuation manager itself and are
|
||||
* gated by {@link SubagentProvider.prepareContinuable} instead. Each flag
|
||||
* corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit`
|
||||
* to `maxDepth`; the other names match.
|
||||
*/
|
||||
interface SubagentCapabilities {
|
||||
readonly outputSchema: boolean
|
||||
@@ -30,18 +32,21 @@ interface SubagentCapabilities {
|
||||
}
|
||||
```
|
||||
|
||||
## 启动请求
|
||||
## 单次启动请求
|
||||
|
||||
工具层根据模型输入和自身配置构建此请求;服务在 `start` 之前针对指定提供方进行校验。必填的 `parent` 提供会话 cwd、谱系与委派深度。可选的 output schema、depth、工具过滤器和 persona 需要对应的能力 flag 匹配。不支持的 schema 在启动时即失败;进程内后端将 filter 和 persona 的作用域限定在子 agent 创建阶段,并通过强制 capture 工具实现所支持的 object-rooted schema。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* What a caller asks for when starting a subagent. The tool layer builds this
|
||||
* from the model's `{ description, prompt }` plus its own config; the service
|
||||
* validates {@link SubagentCapabilities} against the named provider, then
|
||||
* passes it to {@link SubagentProvider.start}.
|
||||
* What a caller asks for when starting a ONE-SHOT subagent. The tool layer
|
||||
* builds this from the model's `{ description, prompt }` plus its own config;
|
||||
* the service validates {@link SubagentCapabilities} against the named provider
|
||||
* and resolves the durable descriptor before dispatching to
|
||||
* {@link SubagentProvider.start}.
|
||||
*/
|
||||
interface SubagentStartRequest {
|
||||
/** Optional short display label persisted with a session-backed child. */
|
||||
readonly label?: string
|
||||
/** Content delivered as the child's user message. */
|
||||
readonly prompt: ContentBlock[]
|
||||
/**
|
||||
@@ -54,8 +59,8 @@ interface SubagentStartRequest {
|
||||
* Cancellation signal from the spawning context (the tool's `exec.signal`).
|
||||
* This is the canonical cancellation channel both before and after startup:
|
||||
* a provider rejects `start()` after cleaning partial resources when it
|
||||
* fires before publication, and cancels a published child when it fires
|
||||
* afterward.
|
||||
* fires before the run is published, and cancels the published run's
|
||||
* remaining turn work when it fires afterward.
|
||||
*/
|
||||
readonly signal: AbortSignal
|
||||
readonly agentOptions?: AgentOptions
|
||||
@@ -93,6 +98,158 @@ interface SubagentStartRequest {
|
||||
|
||||
`signal` 是就绪前后唯一的取消通道。[subagent 组合控制 Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md)规定 persona、live 全局工具过滤、绝对深度以及「可见性而非权限」的设计理由。
|
||||
|
||||
面向调用方的请求不携带目录格式细节或继续执行状态。`SubagentService.start()` 会在功能检查后解析分离的一次性描述符,再将以下面向提供方的请求传给所选传输;可继续子 agent 绝不会到达 `SubagentProvider.start()`:
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Provider-facing one-shot request after {@link SubagentService.start} resolves
|
||||
* the durable child descriptor.
|
||||
*/
|
||||
interface ResolvedSubagentStartRequest extends SubagentStartRequest {
|
||||
/** Detached descriptor a session-backed provider persists in the child log. */
|
||||
readonly descriptor: SubagentDescriptorData
|
||||
}
|
||||
```
|
||||
|
||||
## 可继续子 agent 与激活
|
||||
|
||||
**可继续后台 subagent** 是一份持久化子 agent 会话(Session),至多关联一个进程内的 **Activation(激活)**——即被重建的子 Agent 的一段驻留纪元(residency epoch)。Activation 不是请求、结果、取消或 Task 边界:它可以执行多个 FIFO 轮次,并在其创建的后代仍在运行期间保持驻留。继续执行管理器负责 activation 准入、直接父级鉴权、实时所有权图、冷恢复(cold resume)与子级优先释放;agent loop 负责一切轮次排序与执行。任何可继续路径都不会创建 Task,也不会创建承载中间结果的包装层。
|
||||
|
||||
```text
|
||||
persisted Session
|
||||
-> optional live Activation
|
||||
-> one retained AgentHandle
|
||||
-> Agent inbox as the only turn FIFO
|
||||
-> zero or more owned child Activations
|
||||
```
|
||||
|
||||
`SubagentService.startContinuable()` 会预留稳定的子 agent id,对版本化的 `subagent/descriptor` payload 建立快照,向指定提供方索取其分离的 `ContinuableCreateSpec`,通过私有的 activation-owner 作用域创建子 Agent,建立任何可继续父级的所有权,并提交初始 prompt。当收件箱(inbox)准入产出消息 id 时,它以 `{ childId, messageId }` resolve——无需等待轮次开始,也无需等待消息进入会话日志。在该准入之前的任何失败都会以两个 id 都不返回的方式 reject,并 dispose 任何已创建的 handle,回滚 Activation 与父级所有权。
|
||||
|
||||
`SubagentService.followup()` 是唯一的继续执行消息操作,其路由仅取决于 Activation 的驻留状态:
|
||||
|
||||
| Activation 状态 | `followup` |
|
||||
|---|---|
|
||||
| `running` | 在同一 Activation 中入队 |
|
||||
| `waiting` | 唤醒同一 Activation |
|
||||
| 无 Activation | 冷恢复一个新的 Activation |
|
||||
|
||||
`running` 表示 Agent 拥有活跃的准入或轮次,或正在唤醒收件箱工作;`waiting` 表示它已停稳,但仍拥有至少一个尚未完成 dispose 的子 Activation;`settled` 表示已停稳且其拥有的每个子级都已 dispose,此时管理器会 dispose `AgentHandle` 并移除该 Activation。管理器根据 Agent 的完全停稳状态与其拥有的子级集合推导这些内部条件,而非维护第二套执行状态机。
|
||||
|
||||
Agent 收件箱是唯一的队列。每条继续执行消息都会成为一个 `Agent.followup()` FIFO 轮次,因此已接受的消息共享同一个可观测顺序,且后续消息无法改变已在进行中的轮次。投递成功会返回被接受的 `MessageId`;既有的 `agent/inbox/enqueue`、`agent/inbox/dequeue` 与 `agent/inbox/discard` 事件仍是消息生命周期的观测点,继续执行层不定义任何 subagent 专属的投递路由。
|
||||
|
||||
后续操作的权限来自确切的在线 Agent 工具上下文。已认证的 Agent 必须是持久化子 agent 在 `SessionHeader.parentSession` 中记录的直接父级。`MessageSource` 与 `senderSessionId` 在准入之后是持久的来源凭据,不授予任何权限;可选的面向模型工具使用 `CoordinatorMessageSource`。
|
||||
|
||||
对于这两种操作,调用方 signal 仅在收件箱接受之前掌管查找、物化与准入。此后管理器独立掌管该 Activation:之后的调用方取消既不会取消已接受的轮次,也不会 dispose 子 agent,并且该 seam 不对外暴露任何 subagent 取消或 steering(中途引导)操作。
|
||||
|
||||
每个 Activation 都拥有自己的 `AgentHandle` 和一个 `ownedChildren: Set<SessionId>`;由于一份会话至多有一个存活 Activation,子会话 id 无需另一个运行时化身引用即可标识存活的子 agent。启动子 agent 或提交源自 parent 的工作,会在子 agent 能够运行之前将其注册到受继续执行管理的父级集合中;只要该集合非空,该父级就无法 settle。顶层或其他非继续执行的 Agent 没有 Activation,处于 waiting 图之外。只有当子 Agent 已停稳、该子 agent 的每个子级都已 dispose、best-effort 的最终会话 flush 结算完毕,且子 agent 的 `AgentHandle` 完成 dispose 之后,才会释放子 agent。
|
||||
|
||||
最终结算会等待 `ctx.sessions.flush(session)`,但会忽略其参与布尔值,因为任意 listener 都无法证明某个持久化后端已存储该状态。rejection 会被记录,但不会使 Activation 失败;管理器仍会 dispose 该 handle 并释放所有权,此后持久化的子 agent 状态在后续恢复时可能缺失或陈旧。管理器卸载会调用内部的管理器全局 drain,关闭准入并 dispose 每片在线森林;`drainContinuableDescendants(parents)` 只关闭由 host 确切拥有的在线 Agent 之下的准入,并 dispose 其可继续后代,而无关森林保持在线。两者都会等待各自作用域内已获准的物化过程,自顶向下传播取消,按 child-first 顺序释放 handle,并且即使个别分支失败也会等待所有选中分支。持久化子会话不受该进程内拆卸的影响。
|
||||
|
||||
```ts type-equiv
|
||||
/** Attribution for a model coordinator's follow-up to one of its children. */
|
||||
interface CoordinatorMessageSource {
|
||||
readonly kind: 'coordinator'
|
||||
/** Session id of the agent whose tool call produced the follow-up. */
|
||||
readonly senderSessionId: SessionId
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Options for following up with one continuable child. */
|
||||
interface SubagentFollowupOptions {
|
||||
/** Durable attribution retained on the delivered message; it grants no authority. */
|
||||
readonly source: MessageSource
|
||||
/** Caller cancellation, owning the operation only until inbox acceptance. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Identities returned once a continuable child accepted its initial prompt. */
|
||||
interface ContinuableStart {
|
||||
/** The durable child session id, stable across activations. */
|
||||
readonly childId: SessionId
|
||||
/** The accepted initial prompt's inbox message id. */
|
||||
readonly messageId: MessageId
|
||||
}
|
||||
```
|
||||
|
||||
可选的可继续 child 设置贡献可以在 child 基础组合完成后、Activation 发布前安装限定在作用域内的能力。该注册表按顺序执行且具有事务性:设置失败或被撤销时会回滚未发布的 Activation;child 作用域 dispose 时会释放所有安装;新注册项在下一个 Activation 生效;移除注册项时则会立即撤销每个驻留中的安装。
|
||||
|
||||
`SubagentService.reportFrom()` 通过该扩展 seam 实现报告,无需新增第二条队列或承载结果的 child 包装层。调用由确切的在线 child Agent 授权,调用方不能指定接收方。管理器从 child 的持久化 `parentSession` 中推导唯一接收方,要求该 parent Agent 必须在线,将选中内容封装为一条 `subagent-report` 用户消息,并返回该消息的稳定 `MessageId`。静默投递使用 `Agent.inject()`,不产生 inbox 条目实例或 parent 轮次;唤醒投递使用 `Agent.followup()`,会产生一个普通的后续 parent 轮次。两种模式都不会结束 child 轮次,最终回答也不会隐式报告。
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable attribution for a continuable child's explicit parent report. */
|
||||
interface SubagentReportMessageSource {
|
||||
readonly kind: 'subagent-report'
|
||||
/** Session id of the reporting child. */
|
||||
readonly senderSessionId: SessionId
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Deployment scheduling policy for accepted child reports. */
|
||||
type SubagentReportDelivery = 'quiet' | 'wakeup'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Options for one continuable child's report to its direct parent. */
|
||||
interface SubagentReportOptions {
|
||||
/** Already-resolved parent scheduling policy. */
|
||||
readonly delivery: SubagentReportDelivery
|
||||
/** Caller cancellation, owning authorization and admission until acceptance. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
提供方只参与准备初始创建 spec,`spawn` 与 `fork` 在此有所不同。其返回的 spec 只携带分离的、提供方专属的创建输入——目前是可选的父级历史种子——不含 Agent、`AgentHandle`、prompt 投递、结果、dispose 或 resume 操作。冷恢复根本不经由提供方分发:管理器折叠通用描述符,通过同一个 activation-owner 作用域调用 `ctx.agents.resume()`,并提交等待中的轮次。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* What the continuation manager asks a provider for while materializing one
|
||||
* continuable child's FIRST activation. The manager has already reserved the
|
||||
* durable child identity and owns every later operation, so this request
|
||||
* carries only what distinguishes a fresh child from one seeded with parent
|
||||
* history.
|
||||
*/
|
||||
interface ContinuableCreateRequest {
|
||||
/** The reserved durable child session id, for provider diagnostics. */
|
||||
readonly sessionId: SessionId
|
||||
/** The delegating parent agent whose history a seeding provider reads. */
|
||||
readonly parent: Agent
|
||||
/**
|
||||
* Caller cancellation, which owns preparation only until the manager accepts
|
||||
* the initial prompt into the child's inbox.
|
||||
*/
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A provider's detached contribution to one continuable child's creation. This
|
||||
* is DATA, never a capability: it carries no Agent, `AgentHandle`, prompt
|
||||
* delivery, result, disposal, or resume operation, because the continuation
|
||||
* manager owns the child's whole lifecycle after preparation.
|
||||
*/
|
||||
interface ContinuableCreateSpec {
|
||||
/**
|
||||
* Completed-turn prefix of the parent's log to seed the child session with,
|
||||
* or absent for a fresh child. Same durable contract as
|
||||
* `CreateAgentOptions.seed`: contiguous from seq 0, lossless JSON, balanced.
|
||||
*/
|
||||
readonly seed?: readonly SessionEvent[]
|
||||
}
|
||||
```
|
||||
|
||||
描述符([descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts) 中的 `SubagentDescriptorData`)是每个由会话支撑的 subagent 所使用、按模式判别的持久化身份。两种模式都携带提供方名称。`one-shot` 描述符可以携带调用方拥有的可选显示 `label`;`continuable` 描述符要求以委派 `description` 作为持久化创建标签,并另外对已解析的子 agent `agentOptions.provider`/`model` 与可选的 `persona`/`toolFilter` 建立快照,用于冷恢复。它绝不会对可合并扩展的 `AgentOptions` 对象建立快照,因此无关的扩展值不会破坏继续执行,后续新增组合配置输入则是一次有意的版本更改。描述符省略 `subagentDepth`(冷恢复以持久化 header 中的 `delegationDepth` 作为单调下界)和 `outputSchema`(单次运行或 Activation 的结果契约,而非持久化身份)。
|
||||
|
||||
本地一次性提供方会在子 agent 的初始轮次内、首次请求前追加描述符。继续执行管理器会在任何提供方提供的谱系之后、初始 prompt 获准之前追加描述符;`header.seedLength` 仍是 fork 谱系边界,因此描述符查找会读取子 agent 自身的后缀。该事件只进入日志:不含 `surfaceOp`,绝不进入模型历史,并由仅追加日志跨压缩保留。格式错误的当前版本描述符属于损坏;本运行时无法对不受支持的版本进行分类。
|
||||
|
||||
## 持久化枚举:`listChildren()` 与 `SubagentListEntry`
|
||||
|
||||
`SubagentService.listChildren(parentSessionId)` 从一次 `ctx.sessionQuery.traceSession()` 观测中枚举 parent 直接且由会话支撑的 subagent,而不会加载或恢复任何 Agent。会话谱系的范围比 subagent 身份更广——普通 fork 也会共享 `parentSession`——因此,child 自身后缀中恰好一个受支持的 `subagent/descriptor` 事件(位于 `seedLength` 之后,避免 fork seed 泄漏祖先描述符)是唯一的 subagent 判别信息。`SessionHeader.origin: 'subagent'` 只是在发布前写入的粗粒度产品导航分类器;它可以隐藏重复的侧边栏行,却不能证明描述符有效、child 可恢复或操作已获授权。结果是一个按追踪结果中 `createdAt`、再按 id 排列候选顺序的 `SubagentListEntry[]`:有效描述符生成带有 `mode: 'one-shot' | 'continuable'` 和 `activity: 'running' | 'inactive'` 的 `child` 条目;可继续条目始终携带 `label`,一次性条目则只在启动调用方提供展示元数据时携带该字段。逐 child 检查失败生成 `diagnostic` 条目(`corrupt`、`unsupported` 或 `unavailable`),因此一个损坏的 sibling 不会隐藏健康 child;缺少描述符则不生成条目。活动状态只表示逻辑记录是否在 `ctx.sessions` 中存活,而不表示结果或可恢复性。UI 等服务消费方可以展示两种模式,并为无标签的一次性 child 选择回退展示;面向模型的 `list_agents` 适配器([dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control) 中可单独加载的 `/list-agents` 插件)则只保留可继续条目,并将活动状态映射到现有的 `running`/`complete` 词汇。构建初始追踪时的失败会让整个调用失败——只有得到可信候选集后才开始逐 child 隔离。服务将 `sessionQuery` 保持为按 id 继续执行时的可选依赖:缺少该服务时,`listChildren()` 抛出 `SubagentError`,并携带错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`;列表工具则在插件加载时要求 `ctx.subagents` 与 `ctx.sessionQuery`。枚举不会查询继续执行管理器的 Activation map、Agent 注册表或提供方可用性;`send_message` 仍是消息送达时的权威操作,列表中的运行中可继续 child 仍可能因所有权冲突而拒绝投递。
|
||||
|
||||
## 终态结果:`SubagentResult`
|
||||
|
||||
一次 run 的最终产出,由 `SubagentRun.result` resolve。`structured` 仅在请求了 `outputSchema` 且成功满足时才存在;请求 schema 不保证一定能得到它,当子 agent 失败或结束时未产出有效 capture 时,提供方可能返回 `stopReason: 'error'`。非 `completed` 的 `stopReason` 意味着 `output` 可能不完整——消费方将其映射为 `isError` 的工具结果,而非将部分输出报告为成功。
|
||||
@@ -140,17 +297,19 @@ interface SubagentStopReasonMap {
|
||||
}
|
||||
```
|
||||
|
||||
<a id="a-live-run-subagentrun"></a>
|
||||
## 单次 run:`SubagentRun`
|
||||
|
||||
## 活跃 run:`SubagentRun`
|
||||
|
||||
`SubagentRun` 是消费方持有的、指向一个就绪子 agent 的句柄。消费方 await `result` 并始终 dispose(资源释放)该 run,直至其完全停稳。子 agent 失败时以非 completed 的 stop reason resolve;只有不可表示的基础设施故障才会 reject。可选的 `sendMessage` 和 `resume` 方法通过自身的存在来公布运行时能力。
|
||||
`SubagentRun` 是消费方持有的、指向一个已发布单次子 agent 的句柄——一次可 dispose 的前台委派,只有一个结果,绝不是持久化子 agent handle。发布后的提示词提交、轮次工作与基础设施故障归 `result` 所有。消费方 await 该结果并始终 dispose 该 run,直至完全停稳。子 agent 失败时以非 completed 的 stop reason resolve;只有无法表示的基础设施故障才会 reject。run 没有 steering,也没有 resume:可继续对话根本没有 run,因为继续执行管理器直接持有它们的 `AgentHandle`,并通过子 agent 自己的收件箱为每个轮次排序。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Child handle returned only after readiness. Consumers await {@link result} and must always
|
||||
* {@link dispose} to cancel remaining work and reach quiescence. Optional methods are runtime
|
||||
* capability discovery; narrow their presence before calling.
|
||||
* ONE-SHOT child handle returned after publication. Prompt submission, turn
|
||||
* work, and infrastructure faults after that boundary belong to {@link result}.
|
||||
* Consumers await that result and must always {@link dispose} to cancel
|
||||
* remaining work and reach quiescence. A run is one disposable foreground
|
||||
* delegation with one result; continuable conversations have no run — the
|
||||
* continuation manager holds their `AgentHandle` directly and orders every
|
||||
* turn through the child's own inbox.
|
||||
*/
|
||||
interface SubagentRun {
|
||||
/**
|
||||
@@ -169,8 +328,8 @@ interface SubagentRun {
|
||||
* Resolves with the child's terminal {@link SubagentResult} when the run
|
||||
* settles. Does NOT reject on a child-level failure — a model/transport
|
||||
* failure resolves with `stopReason: 'error'` so the consumer maps it to an
|
||||
* `isError` tool result. Rejects only on an infrastructure fault the seam
|
||||
* cannot represent as a stop reason.
|
||||
* `isError` tool result. Rejects on an infrastructure fault the seam cannot
|
||||
* represent as a stop reason.
|
||||
*/
|
||||
readonly result: Promise<SubagentResult>
|
||||
/**
|
||||
@@ -178,24 +337,16 @@ interface SubagentRun {
|
||||
* Idempotent.
|
||||
*/
|
||||
dispose(): Promise<void>
|
||||
/**
|
||||
* OPTIONAL (steering capability): send additional content to the running
|
||||
* child between steps. Present only on providers that support live steering.
|
||||
*/
|
||||
sendMessage?(content: ContentBlock[]): void
|
||||
/**
|
||||
* OPTIONAL (resume capability): send a follow-up task to a settled child,
|
||||
* continuing its session, and return a fresh run for the continuation.
|
||||
*/
|
||||
resume?(content: ContentBlock[]): Promise<SubagentRun>
|
||||
}
|
||||
```
|
||||
|
||||
本地 run 必须在 `start()` fulfill 前发布一个普通子 agent/会话,将该子会话 id 作为 `SubagentRun.id` 返回,以 `localAgent` 暴露确切子 agent,并在子 agent 的 `parentSession` header 中记录 `request.parent.session.id`。运行时所有权可以把子 agent 放在 parent、提供方或 root 作用域下。远程提供方则返回 parent 作用域的生命周期 id 与 `localAgent: undefined`。
|
||||
本地单次 run 必须在 `start()` fulfill 之前发布一个普通子 agent/会话,将该子会话 id 作为 `SubagentRun.id` 返回,以 `localAgent` 暴露确切的子 agent,在子 agent 的 `parentSession` header 中记录 `request.parent.session.id`,并在子 agent 的初始轮次内、首次请求前追加已解析的描述符。运行时所有权可以把子 agent 放在 parent、提供方或 root 作用域下。远程提供方则返回 parent 作用域的生命周期 id 与 `localAgent: undefined`;由于没有本地 child Session,它不会出现在基于追踪的枚举结果中。
|
||||
|
||||
<a id="the-provider-seam-subagentprovider"></a>
|
||||
|
||||
## 提供方 seam:`SubagentProvider`
|
||||
|
||||
每个提供方是一个具名的子 agent 传输层,多个提供方可以共存。服务在 `start()` 之前校验请求的启动时能力。`inheritsParentContext` 仅描述对话种子注入(`fork`:true;`spawn` 和 `acp`:false),使消费方能生成准确的面向模型的措辞,而不暗示继承了工具、服务或权限。
|
||||
每个提供方都是一个具名的子 agent 传输层,多个提供方可以共存。服务在 `start()` 之前校验请求的启动时能力,并拒绝在没有 `prepareContinuable` 的提供方上发起可继续 start。`inheritsParentContext` 仅描述对话种子注入(`fork`:true;`spawn` 和 `acp`:false),使消费方能生成准确的面向模型措辞,而不暗示继承了工具、服务或权限。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -215,22 +366,38 @@ interface SubagentProvider {
|
||||
*/
|
||||
readonly inheritsParentContext: boolean
|
||||
/**
|
||||
* Establish a child and return its handle only after publication. The
|
||||
* service has already validated that every requested start-time capability
|
||||
* is supported, so an implementation may assume e.g. `request.maxDepth` is
|
||||
* honorable when present. If setup fails or `request.signal` aborts before
|
||||
* fulfillment, the provider owns and cleans all partial resources before this
|
||||
* promise rejects. Ownership transfers to the caller only on fulfillment.
|
||||
* Establish a ONE-SHOT child and return its handle after publication.
|
||||
* The service has already validated that every requested start-time
|
||||
* capability is supported and resolved `request.descriptor`, so a
|
||||
* session-backed implementation appends that descriptor inside the child's
|
||||
* initial turn. Before fulfillment, the provider owns setup and cleans any
|
||||
* unpublished partial resources before rejecting. Ownership transfers on
|
||||
* fulfillment; subsequent turn or infrastructure failure settles through
|
||||
* the returned run.
|
||||
*/
|
||||
start(request: SubagentStartRequest): Promise<SubagentRun>
|
||||
start(request: ResolvedSubagentStartRequest): Promise<SubagentRun>
|
||||
/**
|
||||
* OPTIONAL (continuable-creation capability): contribute the detached
|
||||
* creation inputs that distinguish this provider's continuable children —
|
||||
* today only whether the child session is seeded with parent history. Method
|
||||
* presence IS the capability: the service rejects continuable starts on
|
||||
* providers without it, while a provider that has it may still serve
|
||||
* ordinary one-shot delegations.
|
||||
*
|
||||
* This is the provider's ONLY participation in a continuable child. The
|
||||
* continuation manager owns identity reservation, composition, Agent
|
||||
* creation, prompt delivery, cold resume, ownership, and disposal, so a
|
||||
* provider never sees the child's Agent, handle, turns, or teardown.
|
||||
*/
|
||||
prepareContinuable?(request: ContinuableCreateRequest): Promise<ContinuableCreateSpec>
|
||||
}
|
||||
```
|
||||
|
||||
`start()` 仅在 run 就绪时 fulfill。服务铸造唯一 `runId`,从提供方的确切 `localAgent` 快照 `local`,观察结果,emit `subagent/start`,并返回同一个 run;rejection 意味着提供方已清理,且不会 emit 生命周期事件对。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,每个 listener 异常都会被独立隔离。
|
||||
提供方的 `start()` 会以已发布的 run fulfill。服务铸造唯一的 `runId`,从提供方确切的 `localAgent` 快照 `local`,观察结果,emit `subagent/start`,并返回同一个 run;`start()` rejection 意味着未发布资源已清理,且不会 emit 生命周期事件对,而发布后的结果 rejection 会结束已经 emit 的事件对。每个可继续 Activation 都会为其驻留纪元 emit 相同的仅观察事件对,因此一次冷恢复就是一段拥有自己 `runId` 的新纪元。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,且会隔离各自的 listener 异常。其中的 `provider` 字段是 run 或 Activation 时段的来源信息,并不声明该 edge 发出时提供方仍处于注册状态。
|
||||
|
||||
## 进程内后端:深度与种子
|
||||
|
||||
spawn 和 fork 后端通过 `parent.ctx` 创建一个普通 agent,将取消信号传入核心创建流程,并通过 `AgentHandle` 进行 dispose。移除提供方会阻止新的 start,但不会撤销已接受的 run。每个子 agent 获得一个新的扁平作用域,而非继承父级注册。深度与 fork 种子注入复用既有的 agent 和会话词汇:
|
||||
spawn 和 fork 后端通过 `parent.ctx` 创建一个普通的单次 agent,将取消信号传入核心创建流程,并通过 `AgentHandle` 进行 dispose;而可继续子 agent 则由继续执行管理器通过其自己的 activation-owner 作用域创建。移除提供方会阻止新的 start,但不会撤销已接受的 run。每个子 agent 获得一个新的扁平作用域,而非继承父级注册。深度与 fork 种子注入复用既有的 agent 和会话词汇:
|
||||
|
||||
- **委派深度**由持久 `SessionHeader.delegationDepth` 与可合并扩展的运行时字段 `AgentOptions.subagentDepth` 共同表示;缺失表示顶层深度为零,存在的较大值具有权威性。两个字段都归该 seam 所有——循环既不设置也不读取它们——因此进程内子 agent 会持久保存 parent 深度 + 1,恢复无法降低深度,而且每次 start 都会拒绝超出安全整数域、或高于已定义绝对 `request.maxDepth` 上限的派生深度。
|
||||
- **Fork 种子注入**使用 `CreateAgentOptions.seed`(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `resume` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/support/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。
|
||||
- **委派深度**由持久 `SessionHeader.delegationDepth` 与可合并扩展的运行时字段 `AgentOptions.subagentDepth` 共同表示;缺失表示顶层深度为零,存在的较大值具有权威性。两个字段都归该 seam 所有——循环既不设置也不读取它们——因此进程内子 agent 会持久保存 parent 深度 + 1,冷恢复无法降低深度,而且每次 start 都会拒绝超出安全整数域、或高于已定义绝对 `request.maxDepth` 上限的派生深度。
|
||||
- **Fork 种子注入**使用 `CreateAgentOptions.seed`(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/support/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。
|
||||
|
||||
@@ -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/core-data-structures/tools.md
|
||||
tools.md: 98b642b846b23e2b29e6c6d800fe4106235eda85
|
||||
tools.zh.md: 1ef90c1e76ace7485ed6267de5ee82cbb4de6aa6
|
||||
tools.md: acaf5d32dd5481aec495ac49f727c9b64f25211e
|
||||
tools.zh.md: 5c39226fdf5d1406dab7383d40227c26ff1e447c
|
||||
|
||||
@@ -200,20 +200,23 @@ interface ToolExecutionInput {
|
||||
}
|
||||
```
|
||||
|
||||
A tool body receives the runtime extension. `deferContext()` is the composite-tool channel: it records nested-dispatch context without injecting inside the still-open outer call.
|
||||
A tool body receives the runtime extension. `deferContext()` attaches context to the execution's own result — the composite-tool nested-dispatch channel, also usable by a leaf tool minting a plugin-sourced instruction — without injecting inside the still-open outer call.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Runtime context handed to a tool implementation after the registry has
|
||||
* accepted a {@link ToolExecution}. A composite tool uses
|
||||
* {@link deferContext} to ferry context produced by nested dispatches back to
|
||||
* the outer result; the loop appends it only after the outer `tool/result`.
|
||||
* accepted a {@link ToolExecution}. {@link deferContext} attaches context to
|
||||
* this execution's own result — a composite tool ferries nested-dispatch
|
||||
* context back to the outer result, and a leaf tool may mint a fresh
|
||||
* plugin-sourced instruction; the loop appends it only after the
|
||||
* `tool/result`.
|
||||
*/
|
||||
interface ToolRunContext extends ToolExecution {
|
||||
/**
|
||||
* Defer one nested-dispatch context until this tool's final result reaches
|
||||
* the agent loop. Contexts retain their individual source and metadata and
|
||||
* are emitted in call order.
|
||||
* Defer one context — typically a nested-dispatch context ferried by a
|
||||
* composite tool, or a fresh plugin-sourced instruction — until this tool's
|
||||
* final result reaches the agent loop. Contexts retain their individual
|
||||
* source and metadata and are emitted in call order.
|
||||
*/
|
||||
deferContext(context: UserMessage): void
|
||||
/**
|
||||
|
||||
@@ -200,20 +200,23 @@ interface ToolExecutionInput {
|
||||
}
|
||||
```
|
||||
|
||||
工具函数体接收运行时扩展。`deferContext()` 是组合工具的通道:它记录嵌套分派产生的上下文,而不会在外层调用尚未结束时注入这些上下文。
|
||||
工具函数体接收运行时扩展。`deferContext()` 把上下文附着到本次执行自己的结果上——既是组合工具转运嵌套分派上下文的通道,也可供叶子工具铸造插件来源指令——而不会在外层调用尚未结束时注入这些上下文。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Runtime context handed to a tool implementation after the registry has
|
||||
* accepted a {@link ToolExecution}. A composite tool uses
|
||||
* {@link deferContext} to ferry context produced by nested dispatches back to
|
||||
* the outer result; the loop appends it only after the outer `tool/result`.
|
||||
* accepted a {@link ToolExecution}. {@link deferContext} attaches context to
|
||||
* this execution's own result — a composite tool ferries nested-dispatch
|
||||
* context back to the outer result, and a leaf tool may mint a fresh
|
||||
* plugin-sourced instruction; the loop appends it only after the
|
||||
* `tool/result`.
|
||||
*/
|
||||
interface ToolRunContext extends ToolExecution {
|
||||
/**
|
||||
* Defer one nested-dispatch context until this tool's final result reaches
|
||||
* the agent loop. Contexts retain their individual source and metadata and
|
||||
* are emitted in call order.
|
||||
* Defer one context — typically a nested-dispatch context ferried by a
|
||||
* composite tool, or a fresh plugin-sourced instruction — until this tool's
|
||||
* final result reaches the agent loop. Contexts retain their individual
|
||||
* source and metadata and are emitted in call order.
|
||||
*/
|
||||
deferContext(context: UserMessage): void
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/postmortem/0003-web-agent-gui-feedback-loop.md
|
||||
0003-web-agent-gui-feedback-loop.md: 13d13a607babfe7f5ddfdb6773c94f973bbef0db
|
||||
0003-web-agent-gui-feedback-loop.zh.md: f80530605ef9274638c6848bf5a256f76057915c
|
||||
53
docs/postmortem/0003-web-agent-gui-feedback-loop.md
Normal file
53
docs/postmortem/0003-web-agent-gui-feedback-loop.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Post-mortem 0003: Web agent validated a replacement server instead of its current GUI
|
||||
|
||||
English | [中文](0003-web-agent-gui-feedback-loop.zh.md)
|
||||
|
||||
Status: resolved
|
||||
|
||||
## Executive summary
|
||||
|
||||
A Web agent changed the GUI source but did not know which URL and process hosted its session. It delegated acceptance to the user, then treated a bare Vite HTTP 200 as success despite a missing `window.__DSH_BOOT__` white screen, and finally validated a replacement `dsh web` server on another port while the original page had already picked up rebuilt artifacts. The fix makes the current URL and runtime mode model-visible and shell-queryable, rejects standalone Vite before listen, and verifies production refresh and development HMR against external state.
|
||||
|
||||
## Summary
|
||||
|
||||
The session ran inside the DeepSeek Harness Web GUI at port 3081 while its selected Workspace was an empty `test/` directory. The model request named neither the GUI nor its source checkout, URL, process, or update mode. Repository affordances exposed `apps/web` with a Vite development script, while the full browser composition lived behind `dsh web`.
|
||||
|
||||
The resulting actions were individually plausible but did not share one acceptance target. A source edit, a successful build, an HTTP 200, an injected boot manifest, and the user's existing page were treated as interchangeable facts.
|
||||
|
||||
The evidence source is the persisted event log for `session-3eb796c2-5159-4686-affe-df8719f6f987`, whose header records cwd `/Users/tn.shen/Documents/deepseek-harness-gui-master/test`. Its initial request header is sequence 6; the user-facing handoff, bare-Vite launch, replacement-host launch, boot-manifest probe, and first 3081 process probe are sequences 30939, 31865, 34309, 34441, and 34681 respectively. The timeline below follows those events rather than reconstructing intent from the later report.
|
||||
|
||||
## Impact
|
||||
|
||||
The user had to identify three consecutive mistakes: acceptance was delegated back to them; the proposed preview was a blank page; and the reported successful URL was not the page they were using. An unmanaged replacement server also outlived the turn until the user challenged it.
|
||||
|
||||
No change in this investigation restarted or modified the read-only 3081 and 3082 trial services.
|
||||
|
||||
## Timeline
|
||||
|
||||
- In turn 2, after editing the theme, the agent's sequence-30939 message told the user to run `pnpm run demo:tui` or open an unspecified Web application. It ran no assembled Web acceptance.
|
||||
- In turn 3, the agent read `apps/web/package.json`, launched bare Vite on port 5173 at sequence 31865, observed HTTP 200, and declared success. The browser instead threw `client-modules: window.__DSH_BOOT__ is missing or not an object` and rendered a white page.
|
||||
- In turn 4, the agent found the full `dsh web` path, rebuilt the shell, launched an unmanaged process on port 3334 at sequence 34309, and checked only that this replacement returned 200 with a boot manifest at sequence 34441. It never probed port 3081.
|
||||
- In turn 5, the user reported at sequence 34556 that 3081 already showed the new theme. Only then, at sequence 34681, did the agent inspect the existing process and remove the redundant server.
|
||||
|
||||
## Root cause
|
||||
|
||||
The Web assembly had no model-visible identity for the current GUI, canonical URL, or runtime mode. The session cwd correctly represented the user's selected Workspace, but the model mistook that project boundary for the application boundary. No durable contract related the GUI source checkout, built artifacts, serving process, target origin, and browser acceptance.
|
||||
|
||||
The wrong startup path looked legitimate because bare Vite returned HTTP 200. `window.__DSH_BOOT__` is injected only by the full host, so transport readiness did not imply application readiness. The first regression test repeated this mistake in another form: a timeout killed Vite and satisfied a nonzero-exit assertion. Live reproduction exposed that false positive.
|
||||
|
||||
Background process semantics were also bypassed with shell `&`, so task identity, completion notices, collection, and cleanup did not apply. Verifying port 3334 therefore proved only that a second service worked.
|
||||
|
||||
## Guardrails added
|
||||
|
||||
- The Web launcher publishes the canonical loopback URL and actual production/development mode in the logged `app:web-surface` prompt section and managed `$DSH_WEB_URL`/`$DSH_WEB_MODE` environment.
|
||||
- Production guidance requires rebuilding artifacts and verifying the existing URL after refresh. Development guidance explains that `dsh web --dev` mounts only the HMR receiver; `pnpm run dev:web` in the same checkout must also rebuild client-plugin bundles, while shell and plain-package changes still require refresh.
|
||||
- `apps/web` standalone Vite serve mode rejects during configuration. Its subprocess test proves natural exit and instruments `Server.listen()` so a transient bind cannot pass unnoticed.
|
||||
- Layered real-path tests cover the CLI request, exact production/development prompts, shell runtime facts, same-port static replacement, source watcher rebuild, host stat polling, and browser HMR under an unchanged page identity.
|
||||
- PR evidence preserves screenshots from the original 3081 session and a real-model before/after GUI run; external browser, HTTP, process, and session-log observations carry acceptance.
|
||||
|
||||
## Lessons
|
||||
|
||||
- The agent must know hidden runtime prerequisites before it can guide the user; startup mode is application context, not tribal knowledge.
|
||||
- HTTP readiness, build success, and a boot manifest are different facts. Acceptance names the exact origin and externally observes the requested change there.
|
||||
- A replacement service cannot prove that an existing page changed. Long-running processes use managed task lifecycles when they are actually requested.
|
||||
- A regression test must be able to fail for the reported mechanism. Process timeout is not equivalent to fail-fast, and post-exit port availability does not prove the port was never bound.
|
||||
53
docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md
Normal file
53
docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 事故复盘(postmortem) 0003:Web agent(智能体)验收了替代服务器,而非其当前 GUI
|
||||
|
||||
[English](0003-web-agent-gui-feedback-loop.md) | 中文
|
||||
|
||||
Status: resolved
|
||||
|
||||
## 摘要
|
||||
|
||||
Web agent 修改了 GUI 源码,却不知道由哪个 URL 和进程承载当前会话。它把验收交还给用户,随后在 `window.__DSH_BOOT__` 缺失导致白屏的情况下,仍把裸 Vite 返回的 HTTP 200 当作成功;最后,原页面其实已经加载了重建产物,它却去验收另一个端口上的替代 `dsh web` 服务器。修复让当前 URL 和运行模式对模型可见且可由 shell 查询,在独立 Vite 开始监听前拒绝启动,并依据外部状态验收生产模式刷新与开发模式 HMR(热模块替换)。
|
||||
|
||||
## 概述
|
||||
|
||||
该会话运行在端口 3081 的 DeepSeek Harness Web GUI 中,而用户选择的 Workspace 是空的 `test/` 目录。模型请求既未指明该 GUI,也未提供它的源码检出目录、URL、进程或更新模式。仓库在 `apps/web` 中提供了 Vite 开发脚本,完整的浏览器组合则由 `dsh web` 提供。
|
||||
|
||||
由此产生的各个动作单看都合理,却没有指向同一个验收目标。源码修改、成功构建、HTTP 200、注入的启动 manifest(元数据清单)和用户原本打开的页面,被当成了可以相互替代的事实。
|
||||
|
||||
证据源是 `session-3eb796c2-5159-4686-affe-df8719f6f987` 的持久化事件日志,其头部记录的 cwd 为 `/Users/tn.shen/Documents/deepseek-harness-gui-master/test`。初始请求头位于序列 6;面向用户的交接、裸 Vite 启动、替代宿主启动、启动 manifest 探测,以及首次探测 3081 进程,分别位于序列 30939、31865、34309、34441 和 34681。下方时间线以这些事件为依据,而不是根据后续报告反推意图。
|
||||
|
||||
## 影响
|
||||
|
||||
用户不得不连续指出三个错误:agent 把验收交还给用户;建议预览的页面一片空白;报告成功的 URL 并不是用户正在使用的页面。一个不受管理的替代服务器还持续运行到下一轮,直到用户提出质疑。
|
||||
|
||||
本次调查没有重启或修改只读的 3081 和 3082 试验服务。
|
||||
|
||||
## 时间线
|
||||
|
||||
- 在第 2 轮中,agent 修改主题后,在序列 30939 的消息中让用户运行 `pnpm run demo:tui` 或打开一个未明确指定的 Web 应用。它没有对组装后的 Web 应用执行任何验收。
|
||||
- 在第 3 轮中,agent 读取 `apps/web/package.json`,在序列 31865 于端口 5173 上启动裸 Vite,观察到 HTTP 200 后便宣布成功。浏览器却抛出 `client-modules: window.__DSH_BOOT__ is missing or not an object`,并显示白屏。
|
||||
- 在第 4 轮中,agent 找到了完整的 `dsh web` 启动路径,重新构建 shell,在序列 34309 于端口 3334 上启动一个不受管理的进程,并且只在序列 34441 检查了这个替代服务是否返回 200 和启动 manifest。它从未探测端口 3081。
|
||||
- 在第 5 轮中,用户在序列 34556 报告 3081 已经显示新主题。直到序列 34681,agent 才检查既有进程并移除冗余服务器。
|
||||
|
||||
## 根因
|
||||
|
||||
Web 组合没有向模型提供当前 GUI、规范 URL 或运行模式的身份信息。会话 cwd 正确表示了用户选择的 Workspace,但模型误把这个项目边界当成了应用边界。系统也没有持久契约将 GUI 源码检出目录、构建产物、服务进程、目标 origin 和浏览器验收关联起来。
|
||||
|
||||
裸 Vite 返回 HTTP 200,使错误的启动路径看似合理。`window.__DSH_BOOT__` 只由完整宿主注入,因此传输层就绪不代表应用已就绪。首个回归测试以另一种方式重复了同样的错误:超时机制终止 Vite 后,非零退出断言仍会通过。真实复现暴露了这一误报。
|
||||
|
||||
agent 还通过 shell `&` 绕过了后台进程语义,因此任务身份、完成通知、结果收集和清理机制均未生效。验证端口 3334 只能证明第二个服务可以工作。
|
||||
|
||||
## 已添加的防护措施
|
||||
|
||||
- Web 启动器在记录到日志的 `app:web-surface` 提示词区段,以及受管的 `$DSH_WEB_URL`/`$DSH_WEB_MODE` 环境变量中,发布规范环回 URL 和实际的生产/开发模式。
|
||||
- 生产模式指南要求重新构建产物,并在刷新后验证既有 URL。开发模式指南说明,`dsh web --dev` 只挂载 HMR 接收端;同一源码检出目录中的 `pnpm run dev:web` 还必须重新构建客户端插件 bundle,而 Web shell 和普通包的改动仍然需要刷新页面。
|
||||
- `apps/web` 的独立 Vite 服务模式会在配置阶段拒绝启动。其子进程测试验证进程自然退出,并插桩 `Server.listen()`,确保短暂绑定端口也不会漏检。
|
||||
- 分层的真实路径测试覆盖 CLI(命令行界面)请求、精确的生产/开发模式提示词、shell 运行时事实、同端口静态产物替换、源码 watcher 重建、宿主 stat 轮询,以及页面 identity 不变的浏览器 HMR。
|
||||
- PR(Pull Request)证据保留了原始 3081 会话的截图,以及真实模型驱动的 GUI 修改前后对比;验收以外部浏览器、HTTP、进程和会话日志的观测结果为准。
|
||||
|
||||
## 教训
|
||||
|
||||
- agent 必须先知道隐藏的运行时前置条件,才能指导用户;启动模式属于应用上下文,不应依赖团队口口相传。
|
||||
- HTTP 就绪、构建成功和启动 manifest 是不同的事实。验收必须明确指定确切的 origin,并从外部观察所请求的改动是否在该 origin 生效。
|
||||
- 替代服务无法证明既有页面已经改变。确实收到启动长时间运行进程的请求时,应使用受管任务生命周期。
|
||||
- 回归测试必须能够针对所报告的机制失败。进程超时不等同于快速失败,进程退出后端口可用也不能证明该端口从未被绑定。
|
||||
@@ -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/postmortem/README.md
|
||||
README.md: df0e2fcb8540aeed005153dbecc451d781ca5ff1
|
||||
README.zh.md: e364ef30e342f8484a40f35e3970dea0f2f86ef3
|
||||
README.md: 4858f8841e92a895f2d1a840b59b42758e83d952
|
||||
README.zh.md: 127eb19422f1eb6738c1d246791064096f4382f9
|
||||
|
||||
@@ -14,3 +14,4 @@ Every post-mortem opens with an **Executive summary**: one short paragraph a bus
|
||||
|---|---|
|
||||
| [0001](0001-acp-default-export-drops-inject.md) | ACP server crashed on connect: `export default` dropped the plugin's `inject` |
|
||||
| [0002](0002-js-expression-disabled-filesystem-tools.md) | Filesystem snapshot tools were permanently disabled by a literal `!!js` object |
|
||||
| [0003](0003-web-agent-gui-feedback-loop.md) | Web agent validated a replacement server instead of the GUI hosting its session |
|
||||
|
||||
@@ -14,3 +14,4 @@
|
||||
|---|---|
|
||||
| [0001](0001-acp-default-export-drops-inject.md) | ACP(Agent Client Protocol)服务器在连接时崩溃:`export default` 丢失了插件的 `inject` |
|
||||
| [0002](0002-js-expression-disabled-filesystem-tools.md) | 文件系统快照工具被一个字面量 `!!js` 对象永久禁用 |
|
||||
| [0003](0003-web-agent-gui-feedback-loop.md) | Web agent 验证了替代服务器,而非承载其会话的 GUI |
|
||||
|
||||
@@ -31,6 +31,8 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.agents`, `ctx.skills` | `tool/call`, `tool/result`, `user/message replacement catalogs via agent.inject()` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-session-query` | `session_event_read`, `session_event_search`, `session_event_trace`, `session_search`, `session_trace` | `ctx.tools`, `ctx.systemPrompt`, `ctx.sessionQuery`, `a calling Agent for workspace authority` | `tool/call`, `tool/result` | - | The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies. |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `apps/cli/config/base.cordis.yml` and `examples/acp-agent/cordis.yml`. |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.sessionQuery (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` once, plus `list_agents` from its separately loaded `/list-agents` plugin (which additionally requires session query). |
|
||||
| `@deepseek-ai/dsh-tool-subagent-report` | `report` | `ctx.subagents`, `a live continuable in-process child Agent` | `tool/call`, `tool/result`, `a user-role message in the direct parent session` | - | Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global `toolFilter`. The parent-facing `send_message` tool is installed independently. |
|
||||
| `@deepseek-ai/dsh-tool-tasks` | `task_kill`, `task_list`, `task_output` | `ctx.tools`, `ctx.tasks`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`. |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. |
|
||||
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflows`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
|
||||
@@ -1146,6 +1148,74 @@ Source: [`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/to
|
||||
|
||||
The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `apps/cli/config/base.cordis.yml` and `examples/acp-agent/cordis.yml`.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-subagent-control`
|
||||
|
||||
### `list_agents`
|
||||
|
||||
List your continuable background subagents by durable id and label. Status is a snapshot of the stored record: running means the subagent session is currently live in this process, complete means it exists only in storage and a `send_message` starts a new turn on the same conversation. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/tool-subagent-control/src/list-agents.ts`](../packages/subagent/tool-subagent-control/src/list-agents.ts)
|
||||
|
||||
### `send_message`
|
||||
|
||||
Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subagent_id": {
|
||||
"type": "string",
|
||||
"description": "The subagent id returned when the background subagent was started."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The message to deliver to the subagent."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"subagent_id",
|
||||
"message"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts)
|
||||
|
||||
The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` once, plus `list_agents` from its separately loaded `/list-agents` plugin (which additionally requires session query).
|
||||
|
||||
## `@deepseek-ai/dsh-tool-subagent-report`
|
||||
|
||||
### `report`
|
||||
|
||||
Report selected content to the agent that started you. Call this zero or more times for progress, findings, or a final answer. Reporting does not end your turn or finish your work, and only your direct parent receives it. A failed call may still have arrived, so do not blindly repeat it.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Self-contained content for your parent; it does not see your private work."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"output"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/tool-subagent-report/src/index.ts`](../packages/subagent/tool-subagent-report/src/index.ts)
|
||||
|
||||
Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global `toolFilter`. The parent-facing `send_message` tool is installed independently.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-tasks`
|
||||
|
||||
### `task_kill`
|
||||
|
||||
Reference in New Issue
Block a user