feat(agent-presets): enable background Codex and Claude Code subagent tasks

This commit is contained in:
pku-xht
2026-08-12 17:07:34 +08:00
parent b423ed15d7
commit 28fcda2751
35 changed files with 318 additions and 102 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md
2026-08-04-claude-code-and-codex-subagent-backends.md: ccc96d6c998c4ab958a7eea1e502d036d16ec90d
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 740eeb633e336d5b01cb0b84fb656612e690959d
2026-08-04-claude-code-and-codex-subagent-backends.md: 99801d6bbd9c67e6c9215d4e10c77fb70d5e7525
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 0944f1356ff334cc284def91d5ce0924ce950b83

View File

@@ -12,24 +12,25 @@ The product integrations must not become second owners for task text, cwd, cance
## Decision
The harness publishes two sibling one-shot provider packages: `codex` and `claude-code`. This note owns their product protocols, result mapping, and process lifecycle; the [shared-profile-host placement decision](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md) supersedes the original opt-in composition placement. Loading either provider starts no product process, and each tool accepts only a standalone text task; product selection and background execution are not model arguments.
The harness publishes two sibling one-shot provider packages: `codex` and `claude-code`. This note owns their product protocols, result mapping, and process lifecycle; the [shared-profile-host placement decision](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md) owns provider placement, and the [product one-shot background decision](2026-08-12-product-subagent-one-shot-background-tasks.md) owns the model-visible scheduling choice. Loading either provider starts no product process, and each tool accepts only a standalone text task; product selection remains deployment configuration.
Both providers report `inheritsParentContext: false`, advertise no optional start capabilities, and pass the parent Session cwd without copying the parent conversation. Their documented tools disable background execution and use `maxDepth: 'provider-managed'`, leaving recursion policy with the out-of-process product instead of sending a limit the provider cannot enforce. Every call creates a fresh product process and a non-resumable product conversation. The shared subagent service continues to own request resolution, lifecycle events, result settlement, and foreground collection; the shared subprocess service owns credential scrubbing, process-tree termination, and whole-tree exit observation.
Both providers report `inheritsParentContext: false`, advertise no optional start capabilities, and pass the parent Session cwd without copying the parent conversation. Their documented tools use `backgroundMode: 'one-shot'` and `maxDepth: 'provider-managed'`: the consumer keeps foreground collection as the default and may place the same run in the generic Task runtime, while recursion policy stays with the out-of-process product. Every call creates a fresh product process and a non-resumable product conversation. `ctx.subagents` owns named-request resolution and paired lifecycle events; `dsh-tool-subagent` owns model-visible scheduling and foreground-versus-Task adaptation; `ctx.tasks` and `dsh-tool-tasks` own Task ids, state, output, controls, notices, and parent-owner cancellation; each product provider owns native result mapping, while `dsh-subprocess` owns credential scrubbing, process-tree termination, and whole-tree exit observation.
```text
fixed tool → shared subagent service → product provider → official product process
← final answer / explicit error / cancellation ← terminal product fact
→ foreground disposal → shared process-tree termination → whole-tree exit
fixed tool -> dsh-tool-subagent -> ctx.subagents -> product provider -> product process
foreground <- final product outcome
background -> ctx.tasks / dsh-tool-tasks -> Task id / state / notice / controls
both -> provider disposal -> dsh-subprocess -> whole-tree exit
```
### Ownership and lifecycle
| Phase | Shared owner | Product-specific responsibility | Observable result |
| Layer | Owner | Responsibility | Observable result |
| --- | --- | --- | --- |
| Resolve | `dsh-tool-subagent` and `ctx.subagents` | Validate the product's text-only input and derive native startup parameters | Unsupported context or malformed input fails before a run is published |
| Start | `dsh-subprocess` owns every acquired process tree | Reach the smallest native point at which the product conversation and process can both be controlled | `start()` publishes one existing `SubagentRun`, or cleans up and rejects |
| Run | The product owns its native protocol facts; the holder owns their mapping | Submit exactly one task and derive an existing shared stop reason; Codex uses `max-tokens` only for explicit context exhaustion | The parent receives only a final answer or an explicit failure |
| Dispose | The foreground consumer requests release; `dsh-subprocess` proves exit | Close the native protocol and express any best-effort native cancellation | Disposal is idempotent and returns only after the whole process tree exits |
| Delegation lifecycle | `ctx.subagents` | Resolve the named provider request and pair lifecycle events around the published `SubagentRun` | Unsupported context or malformed input fails before a run is published; start and terminal events remain paired |
| Scheduling and adaptation | `dsh-tool-subagent` | Interpret `run_in_background`, choose foreground collection or one-shot Task registration, and map the shared stop reason | Foreground returns the product outcome; background returns a Task id after registration |
| Task state and control | `ctx.tasks` and `dsh-tool-tasks` | Own Task state, output, cancellation, owner cleanup, completion notices, and model-facing controls | The exact parent can collect, list, or stop background work and receives its completion notice |
| Native run and teardown | Product provider and `dsh-subprocess` | Produce one native result, close the product protocol, request best-effort native cancellation, and prove process-tree exit | Foreground return and Task settlement both wait for idempotent disposal and whole-tree exit |
## Codex provider
@@ -59,7 +60,7 @@ The credentialed Claude Code e2e uses the official DeepSeek Claude Code contract
## Distribution and evidence
Each product owns branch-complete package tests, a required keyless real-product spec, a Loader composition e2e, and a credentialed DeepSeek e2e. The keyless product tier uses the exact official distribution under test, a non-empty fake product key, an isolated temporary workspace and product home, and a loopback fixed-answer model. Missing product requests, wrong authentication, altered task text, a non-exact answer, a skipped real product, or a surviving managed handle fails the required test. The Loader tier boots the README-shaped user configuration, verifies both fixed foreground-only tools in one context, and starts neither product process. The credentialed tier starts the same production provider and real product with a runtime-only key, requires a unique nonce from the fixed official DeepSeek service, and proves quiescence again; it self-skips only when a local operator supplied no key, while trusted CI preflights the secret.
Each product owns branch-complete package tests, a required keyless real-product spec, a Loader composition e2e, and a credentialed DeepSeek e2e. The keyless product tier uses the exact official distribution under test, a non-empty fake product key, an isolated temporary workspace and product home, and a loopback fixed-answer model. Missing product requests, wrong authentication, altered task text, a non-exact answer, a skipped real product, or a surviving managed handle fails the required test. The Loader tier boots the README-shaped user configuration, verifies both fixed one-shot tools expose optional background scheduling alongside generic task controls, and starts neither product process. The credentialed tier starts the same production provider and real product with a runtime-only key, requires a unique nonce from the fixed official DeepSeek service, and proves quiescence again; it self-skips only when a local operator supplied no key, while trusted CI preflights the secret.
The Codex evidence pins `@openai/codex@0.147.0` and `codex-cli 0.147.0`. Its real-product spec observes the exact Bearer key, original task, byte-exact final answer, unattended command rejection with no file side effect, local cancellation, and whole-tree exit. Production still supplies `codex` on `PATH`.
@@ -83,12 +84,12 @@ The project owner's distribution authorization is scoped to the official `@anthr
**Plugin-managed login, product home, models, settings, or permissions.** Those choices would create another authority beside each product's native configuration and enlarge a one-shot provider into account management. The providers expose only an explicit environment overlay and teardown grace; unattended interaction fails closed.
**Continuation, progress, background collection, and shared parent context.** The delivered user result is one self-contained task and one final answer. Product sessions, resume, follow-up, intermediate messages, parent transcript transfer, structured output, and background collection need separate user contracts and are not prebuilt.
**Continuation, progress, product-native background state, and shared parent context.** The provider payload remains one final answer for one self-contained task. The generic Task layer may add its id, status, notice, collection, and cancellation results, but product sessions, resume, follow-up, intermediate messages, parent transcript transfer, structured output, and provider-specific background state need separate user contracts and are not prebuilt.
## Consequences
Users delegate through two stable foreground tools backed by the official product integrations. Their Profile placement and per-Preset exposure are owned by the [shared-host placement decision](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md); this note's provider lifecycle keeps native settings and behavior while shared services retain the sole ownership of task settlement and process-tree quiescence.
Users delegate through two stable one-shot tools backed by the official product integrations. Their Profile placement and per-Preset exposure are owned by the [shared-host placement decision](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md), and their foreground-default optional Task scheduling is owned by the [product one-shot background decision](2026-08-12-product-subagent-one-shot-background-tasks.md). This note's provider lifecycle keeps native settings and behavior while shared services retain the sole ownership of task settlement and process-tree quiescence.
Every delegation pays for a fresh product process and independent model context, and only final text reaches the parent. Product-native configuration makes behavior depend on the deployment's installed product, account state, and workspace settings. Credentialed e2e runs also spend external API quota and depend on the official DeepSeek endpoint; deterministic protocol, failure, cancellation, and approval coverage remains in the keyless tier. The providers do not resume sessions, stream progress, accept new human interaction, roll back tool or file side effects, or impose a wall-clock timeout.
Every delegation pays for a fresh product process and independent model context. The product payload reaching the parent is final text only; background scheduling additionally exposes generic Task ids, status, completion notices, and collection or cancellation results. Product-native configuration makes behavior depend on the deployment's installed product, account state, and workspace settings. Credentialed e2e runs also spend external API quota and depend on the official DeepSeek endpoint; deterministic protocol, failure, cancellation, and approval coverage remains in the keyless tier. The providers do not resume sessions, stream progress, accept new human interaction, roll back tool or file side effects, or impose a wall-clock timeout.
Compatibility is pinned by package-level unit coverage, keyless real-product loopback tests, credentialed DeepSeek nonce tests, public Loader composition, built-package and NodeNext consumer checks, generated documentation and notices, and the repository CI matrix. A supported product or DeepSeek endpoint/model baseline change must refresh those facts; production performs no separate runtime version probe.

View File

@@ -12,24 +12,25 @@ Status: implemented
## 决策
harness 交付两个同级的一次性提供方包:`codex` 与 `claude-code`。本说明负责它们的产品协议、结果映射和进程生命周期;[共享 profile 宿主归属决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)取代原先由用户选择启用的组装位置。加载任一提供方都不会启动产品进程,而且每个工具只接受独立文本任务;产品选择与后台执行都不作为模型参数。
harness 交付两个同级的一次性提供方包:`codex` 与 `claude-code`。本说明负责它们的产品协议、结果映射和进程生命周期;[共享 profile 宿主归属决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)负责提供方位置,[产品一次性后台任务决策](2026-08-12-product-subagent-one-shot-background-tasks.md)负责模型可见的调度选择。加载任一提供方都不会启动产品进程,而且每个工具只接受独立文本任务;产品选择仍属于部署配置。
这两个提供方都报告 `inheritsParentContext: false`,不声明任何可选的启动能力,并传递父会话 cwd,但不会复制父级对话。文档所示的工具会禁用后台执行,并使用 `maxDepth: 'provider-managed'`,将递归策略留给进程外产品,而不是发送提供方无法强制执行的限制。每次调用都会创建一个全新的产品进程和一次不可续接的产品对话。共享 subagent 服务继续负责请求解析、生命周期事件、结果结算和前台收集;共享子进程服务负责凭证清洗、进程树终止以及整棵进程树的退出观测。
这两个提供方都报告 `inheritsParentContext: false`,不声明任何可选的启动能力,并传递父会话 cwd,但不会复制父级对话。文档所示的工具使用 `backgroundMode: 'one-shot'` 与 `maxDepth: 'provider-managed'`:消费方默认在前台收集结果,也可把同一次运行放入通用 Task 运行时,而递归策略仍由进程外产品负责。每次调用都会创建一个全新的产品进程和一次不可续接的产品对话。`ctx.subagents` 负责具名请求解析与成对生命周期事件;`dsh-tool-subagent` 负责模型可见的调度以及前台与 Task 适配;`ctx.tasks` 和 `dsh-tool-tasks` 负责 Task id、状态、输出、控制、通知与父级 owner 取消;各产品提供方负责原生结果映射,`dsh-subprocess` 则负责凭证清洗、进程树终止以及整棵进程树的退出观测。
```text
fixed tool → shared subagent service → product provider → official product process
← final answer / explicit error / cancellation ← terminal product fact
→ foreground disposal → shared process-tree termination → whole-tree exit
fixed tool -> dsh-tool-subagent -> ctx.subagents -> product provider -> product process
foreground <- final product outcome
background -> ctx.tasks / dsh-tool-tasks -> Task id / state / notice / controls
both -> provider disposal -> dsh-subprocess -> whole-tree exit
```
### 归属与生命周期
| 阶段 | 共享责任方 | 产品特定职责 | 可观察结果 |
| 层级 | 责任方 | 职责 | 可观察结果 |
| --- | --- | --- | --- |
| 解析 | `dsh-tool-subagent` 与 `ctx.subagents` | 验证产品的纯文本输入并推导原生启动参数 | 不受支持的上下文或格式错误的输入会在发布运行前报错 |
| 启动 | `dsh-subprocess` 负责每棵已获取的进程树 | 到达能够同时控制产品对话与进程的最小原生控制点 | `start()` 发布一个已存在的 `SubagentRun`,否则清理后拒绝调用 |
| 运行 | 产品负责其原生协议事实;持有方负责映射这些事实 | 只提交一项任务,并推导出一种现有的共享停止原因;Codex 仅在明确发生上下文耗尽时使用 `max-tokens` | 父级只会收到最终回答或明确失败 |
| dispose(资源释放) | 前台消费方请求释放;`dsh-subprocess` 证明进程已退出 | 关闭原生协议,并发出尽力而为的原生取消请求 | 释放操作具有幂等性,且仅在整棵进程树退出后才返回 |
| 委派生命周期 | `ctx.subagents` | 解析具名提供方请求,并为已发布的 `SubagentRun` 配对生命周期事件 | 不受支持的上下文或格式错误的输入会在发布运行前报错;启动与终态事件保持成对 |
| 调度与适配 | `dsh-tool-subagent` | 解释 `run_in_background`,选择前台收集或 one-shot Task 登记,并映射共享停止原因 | 前台返回产品结果;后台在登记完成后返回 Task id |
| Task 状态与控制 | `ctx.tasks` 与 `dsh-tool-tasks` | 负责 Task 状态、输出、取消、owner 清理、完成通知与面向模型的控制工具 | 准确父级可以收集、列出或停止后台工作,并收到完成通知 |
| 原生运行与清理 | 产品提供方与 `dsh-subprocess` | 产生一个原生结果、关闭产品协议、请求尽力而为的原生取消,并证明进程树退出 | 前台返回与 Task 结算都会等待幂等资源释放和整棵进程树退出 |
## Codex 提供方
@@ -59,7 +60,7 @@ Codex 0.147.0 使用 Responses 协议,而 DeepSeek 的公开 OpenAI 兼容端
## 分发与证据
每个产品都负责覆盖所有分支的包测试、一项必跑的无密钥真实产品测试、一项 Loader 组合 e2e 和一项带密钥 DeepSeek e2e。无密钥产品层级使用被测的确切官方发行版、非空的伪产品密钥、隔离的临时工作区与产品主目录,以及能返回固定答案的回环模型。产品请求缺失、身份验证错误、任务文本被改动、答案不完全一致、真实产品被跳过或受管句柄仍存活,都会使这项必跑测试失败。Loader 层级会启动 README 所示形态的用户配置,在同一个上下文中验证两个固定且只支持前台执行的工具,并且不会启动任何产品进程。带密钥层级会使用仅在运行时提供的密钥启动同一生产提供方与真实产品,要求从固定的 DeepSeek 官方服务取得唯一随机数,并再次证明完全停稳;仅当本地操作者未提供密钥时才会自行跳过,而受信任的 CI 会预检该 secret。
每个产品都负责覆盖所有分支的包测试、一项必跑的无密钥真实产品测试、一项 Loader 组合 e2e 和一项带密钥 DeepSeek e2e。无密钥产品层级使用被测的确切官方发行版、非空的伪产品密钥、隔离的临时工作区与产品主目录,以及能返回固定答案的回环模型。产品请求缺失、身份验证错误、任务文本被改动、答案不完全一致、真实产品被跳过或受管句柄仍存活,都会使这项必跑测试失败。Loader 层级会启动 README 所示形态的用户配置,在同一个上下文中验证两个固定一次性工具会与通用任务控制工具一起公开可选后台调度,而且不会启动任何产品进程。带密钥层级会使用仅在运行时提供的密钥启动同一生产提供方与真实产品,要求从固定的 DeepSeek 官方服务取得唯一随机数,并再次证明完全停稳;仅当本地操作者未提供密钥时才会自行跳过,而受信任的 CI 会预检该 secret。
Codex 证据锁定 `@openai/codex@0.147.0` 与 `codex-cli 0.147.0`。其真实产品测试会观测确切的 Bearer 密钥、原始任务、逐字节完全一致的最终回答、不会产生文件副作用的无人值守命令拒绝、本地取消以及整棵进程树退出。生产环境仍提供 `codex`,并通过 `PATH` 解析。
@@ -83,12 +84,12 @@ Claude Code 证据锁定 Agent SDK 0.3.220,并使用 SDK 按平台分发的 Cl
**由插件管理登录、产品主目录、模型、设置或权限。** 这些选择会在每个产品的原生配置之外建立另一套权威来源,并将一次性提供方扩张为账户管理功能。提供方只公开显式环境覆盖项和清理宽限期;无人值守交互会以默认拒绝方式失败。
**续接、进度、后台收集和共享父级上下文。** 已交付的用户结果是一项自包含任务和一个最终回答。产品会话、恢复、后续交互、中间消息、父级 transcript(文本记录)传递、结构化输出和后台收集都需要独立的用户约定,当前实现不会预先构建这些功能。
**续接、进度、产品原生后台状态和共享父级上下文。** 提供方载荷仍是一项自包含任务的一个最终回答。通用 Task 层可以额外提供 id、状态、通知、收集与取消结果,但产品会话、恢复、后续交互、中间消息、父级 transcript(文本记录)传递、结构化输出和提供方专属后台状态都需要独立的用户约定,当前实现不会预先构建这些功能。
## 后果
用户通过官方产品集成支持的两个稳定前台工具进行委派。它们在 Profile 中的归属和按 Preset 暴露方式由[共享宿主归属决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)负责;本说明规定的提供方生命周期会保留原生设置与行为,而共享服务继续独占任务结算与进程树完全停稳的责任。
用户通过官方产品集成支持的两个稳定一次性工具进行委派。它们在 Profile 中的归属和按 Preset 暴露方式由[共享宿主归属决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)负责,默认前台且可选通用 Task 的调度方式由[产品一次性后台任务决策](2026-08-12-product-subagent-one-shot-background-tasks.md)负责。本说明规定的提供方生命周期会保留原生设置与行为,而共享服务继续独占任务结算与进程树完全停稳的责任。
每次委派都要承担新建产品进程和独立模型上下文的开销,且只有最终文本会到达父级。产品原生配置使行为取决于部署环境中安装的产品、账户状态和工作区设置。带密钥 e2e 运行还会消耗外部 API 配额,并依赖 DeepSeek 官方端点;对协议、失败、取消与审批的确定性覆盖仍由无密钥层级承担。提供方不会恢复会话、以流式方式传送进度、接受新的人工交互、回滚工具或文件副作用,也不会施加按实际经过时间触发的超时。
每次委派都要承担新建产品进程和独立模型上下文的开销。到达父级的产品载荷仍只有最终文本;后台调度还会额外公开通用 Task id、状态、完成通知以及收集或取消结果。产品原生配置使行为取决于部署环境中安装的产品、账户状态和工作区设置。带密钥 e2e 运行还会消耗外部 API 配额,并依赖 DeepSeek 官方端点;对协议、失败、取消与审批的确定性覆盖仍由无密钥层级承担。提供方不会恢复会话、以流式方式传送进度、接受新的人工交互、回滚工具或文件副作用,也不会施加按实际经过时间触发的超时。
兼容性由包级单元测试覆盖率、无密钥真实产品回环测试、带密钥 DeepSeek 随机数测试、公开 Loader 组合、已构建包与 NodeNext 消费方检查、生成的文档与声明以及仓库 CI 矩阵共同锁定。更改受支持的产品基线或 DeepSeek 端点/模型基线时必须刷新这些事实;生产环境不会另行执行运行时版本探测。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-12-product-subagent-one-shot-background-tasks.md
2026-08-12-product-subagent-one-shot-background-tasks.md: bcc4d9b947e5e666161fd2c2b447fa2edf86bd69
2026-08-12-product-subagent-one-shot-background-tasks.zh.md: 3aa8266788c48cd5301dde65dab2cb9d58b4145b

View File

@@ -0,0 +1,69 @@
# Agent Note: Product one-shot subagents use generic background Tasks
Status: implemented
English | [中文](2026-08-12-product-subagent-one-shot-background-tasks.zh.md)
## Problem
The Codex and Claude Code providers already run one self-contained task and return one final answer, while `dsh-tool-subagent` already adapts any one-shot provider to the generic background Task runtime. The shipped product-tool rows disabled that route, so an agent could only wait for the product answer even when the delegation was independent of its next action.
Exposing background execution must not add a product session, product-specific task state, another cancellation owner, or another result protocol. The same provider run must remain responsible for one native process or query and one final answer, while the existing Task registry remains responsible for ids, collection, cancellation, owner cleanup, and completion notices.
## Decision
The `standard`, `code`, and `cordis` Agent Presets configure the dormant `subagent_codex` and `subagent_claude_code` rows with `backgroundMode: one-shot`. Removing a row's `disabled` field exposes the existing optional `run_in_background` argument. Omission or `false` waits in the foreground; explicit `true` returns a parent-owned Task id after synchronous Task preflight and registration, without waiting for provider startup or completion.
The [generic one-shot background adapter](2026-07-08-background-subagent-tasks.md) owns background registration and settlement. It starts the same [`SubagentRun`](2026-06-21-subagent-capability-seam.md), uses a Task-owned cancellation signal across provider startup and execution, waits for `run.result` and `run.dispose()`, maps the terminal result into the Task, and lets `task_output`, `task_list`, `task_kill`, and the existing completion notice expose that state. The [product provider decision](2026-08-04-claude-code-and-codex-subagent-backends.md) continues to own native protocols, answer selection, local cancellation, and process-tree quiescence.
No provider configuration, service interface, event, wire field, persistence format, or product identifier is added. Foreground and background differ only in which existing consumer waits for the same one-shot run.
### Ownership and lifecycle
```text
product tool call
-> omitted / false: tool call waits -> final answer or error -> run disposal
-> true: Task preflight + owner cleanup
-> starter begins provider startup under Task-owned signal
-> Task record/id published and returned (startup remains pending)
-> provider result + run disposal -> Task settlement + notice
-> task_output reads / task_kill cancels
-> parent disposal: Task owner cleanup cancels -> run disposal -> process exit
```
| Fact or resource | Owner | Product-tool responsibility | Observable result |
| --- | --- | --- | --- |
| Product selection and exposure | Agent Preset | Bind one fixed tool name to one fixed provider | Enabling one row exposes only that product tool |
| Foreground or background choice | `dsh-tool-subagent` | Resolve `run_in_background` under `one-shot` policy | Omission is foreground; explicit `true` returns a Task id |
| Task id, state, output, cancellation, and notice | `ctx.tasks` and `dsh-tool-tasks` | Register and present the existing one-shot run | Generic task tools collect or stop the run for the exact parent |
| Native answer and process quiescence | Product provider and `dsh-subprocess` | Produce one final result and release one process tree | Task settlement and foreground return both wait for disposal |
## Published composition
Full profiles keep both product providers on the host and keep both product-tool rows disabled in each full preset. The host task registry is shared across sessions, while each preset contributes the generic task controls to its own agent scope. A user copies a preset and removes `disabled` from either or both product rows; no product process starts during composition.
A custom composition that enables one-shot background execution must provide the complete generic Task capability: `dsh-tasks-local` as the provider and `dsh-tool-tasks` as the model-facing consumer. A product tool without that runtime can still execute in the foreground, but an explicit background request fails the existing Task preflight instead of publishing an uncollectable id.
The ACP product compositions use the same fixed product rows and generic task controls. Their keyless schema snapshots expose `description`, `prompt`, and optional `run_in_background` for each enabled product tool without invoking Codex, Claude Code, or an external model.
## Verification
The shipped Web composition boots four user-preset variants—neither product, Codex, Claude Code, and both—and checks that each enabled product tool exposes `run_in_background` alongside `task_output`, `task_list`, and `task_kill`. The two package-owned Loader compositions run with an empty `PATH`, inspect the same schemas and controls, and prove that loading the providers starts no product process. ACP keyless snapshots pin the assembled product schemas, while the existing `dsh-tool-subagent` and task suites pin foreground defaulting, Task registration, final-output collection, cancellation, completion notices, owner disposal, and provider disposal.
## Alternatives considered
**Keep the product tools foreground-only.** This preserves the smallest schema but prevents agents from scheduling independent product work even though the generic one-shot Task adapter already owns the required lifecycle.
**Make product delegations background by default.** A one-shot Task requires later collection, unlike a continuable child with its own durable conversation id and settlement delivery. Foreground remains the compatible default, and background remains an explicit scheduling choice.
**Use Codex or Claude Code native session state as the background owner.** That would create provider-specific ids, status, cancellation, and recovery semantics beside the generic Task registry. The providers remain one-shot result producers and keep native ids private.
**Add product-specific output, wait, or kill tools.** Separate controls would duplicate the generic task protocol and teach a different collection workflow for each provider. The existing `task_*` tools already cover the required operations.
**Add continuable product sessions at the same time.** Resume, follow-up, progress, and persisted product sessions require new product contracts and lifecycle ownership. This decision exposes only the already implemented one-shot background route.
## Consequences
Agents can continue useful work while Codex or Claude Code handles an independent one-shot task, then collect the final answer or cancel it through the same Task controls used by other background producers. Foreground callers retain their existing result and error behavior.
Every product delegation still starts a fresh native process or query, produces final text as its only product payload, and ends with provider disposal and whole-tree exit. A background call additionally exposes the generic Task id, status, completion notice, and collection or cancellation results. Background Tasks are process-local and parent-owned: they do not survive parent disposal, do not expose intermediate product activity, and do not make a product conversation resumable. Custom compositions that expose the background argument must also keep the generic Task provider and controls available.

View File

@@ -0,0 +1,69 @@
# Agent Note: 产品 one-shot subagent 使用通用后台 Task
Status: implemented
[English](2026-08-12-product-subagent-one-shot-background-tasks.md) | 中文
## 问题
Codex 与 Claude Code 提供方已经能够运行一项自包含任务并返回一个最终回答,而 `dsh-tool-subagent` 也已经能够把任意 one-shot 提供方接入通用后台 Task 运行时。随附产品工具行禁用了这条路径,因此即使委托与 agent 的下一步操作彼此独立,agent 也只能等待产品回答。
公开后台执行不得增加产品会话、产品专属任务状态、另一取消责任方或另一结果协议。同一个提供方运行必须继续负责一个原生进程或 query 和一个最终回答,而现有任务注册表继续负责 id、收集、取消、owner 清理与完成通知。
## 决策
`standard`、`code` 与 `cordis` Agent Preset 使用 `backgroundMode: one-shot` 配置休眠的 `subagent_codex` 与 `subagent_claude_code` 行。删除某一行的 `disabled` 字段后,现有可选参数 `run_in_background` 就会公开。省略该参数或传入 `false` 时会在前台等待;显式传入 `true` 时会在同步完成 Task 预检与登记后返回由父级拥有的 Task id,而不会等待提供方启动或完成。
[通用 one-shot 后台适配器](2026-07-08-background-subagent-tasks.md)负责后台登记与结算。它会启动同一个 [`SubagentRun`](2026-06-21-subagent-capability-seam.md),让 Task 自有的取消信号覆盖提供方启动与执行,等待 `run.result` 和 `run.dispose()`,把终态结果映射进 Task,并由 `task_output`、`task_list`、`task_kill` 与现有完成通知公开该状态。[产品提供方决策](2026-08-04-claude-code-and-codex-subagent-backends.md)继续负责原生协议、答案选择、本地取消与进程树完全停稳。
本决策不新增提供方配置、服务接口、事件、协议字段、持久化格式或产品标识符。前台与后台的区别仅在于由哪个现有消费方等待同一个 one-shot 运行。
### 归属与生命周期
```text
product tool call
-> omitted / false: tool call waits -> final answer or error -> run disposal
-> true: Task preflight + owner cleanup
-> starter begins provider startup under Task-owned signal
-> Task record/id published and returned (startup remains pending)
-> provider result + run disposal -> Task settlement + notice
-> task_output reads / task_kill cancels
-> parent disposal: Task owner cleanup cancels -> run disposal -> process exit
```
| 事实或资源 | 责任方 | 产品工具职责 | 可观察结果 |
| --- | --- | --- | --- |
| 产品选择与公开 | Agent Preset | 把一个固定工具名绑定到一个固定提供方 | 启用一行只会公开对应产品工具 |
| 前台或后台选择 | `dsh-tool-subagent` | 按 `one-shot` 策略解析 `run_in_background` | 省略参数时在前台运行;显式传入 `true` 时返回 task id |
| task id、状态、输出、取消与通知 | `ctx.tasks` 与 `dsh-tool-tasks` | 登记并展示现有 one-shot 运行 | 通用任务工具为准确父级收集或停止运行 |
| 原生答案与进程完全停稳 | 产品提供方与 `dsh-subprocess` | 产生一个最终结果并释放一棵进程树 | Task 结算与前台返回都会等待资源释放 |
## 发布组装
完整 profile 把两个产品提供方保留在宿主,并在每个完整 preset 中让两个产品工具行保持禁用。宿主任务注册表由各会话共享,而每个 preset 会把通用任务控制工具贡献到自身 agent 作用域。用户复制一个 preset,再从任一或两个产品行删除 `disabled`;组装期间不会启动产品进程。
自定义组装若启用 one-shot 后台执行,就必须提供完整的通用 Task 能力:由 `dsh-tasks-local` 充当提供方,由 `dsh-tool-tasks` 充当面向模型的消费方。没有该运行时的产品工具仍可在前台执行,但显式后台请求会在现有 Task 预检中失败,不会发布无法收集的 id。
ACP 产品组装使用相同的固定产品行与通用任务控制工具。其无密钥 schema 快照会为每个已启用产品工具公开 `description`、`prompt` 和可选的 `run_in_background`,而不会调用 Codex、Claude Code 或外部模型。
## 验证
随附 Web 组装会启动四种用户 preset 变体——不启用产品、只启用 Codex、只启用 Claude Code,以及同时启用两者——并检查每个已启用产品工具都会与 `task_output`、`task_list` 和 `task_kill` 一起公开 `run_in_background`。两个由包负责的 Loader 组装会在空 `PATH` 下运行,检查相同 schema 与控制工具,并证明加载提供方不会启动产品进程。ACP 无密钥快照会固定组装后的产品 schema,而现有 `dsh-tool-subagent` 与任务测试套件会固定前台默认值、Task 登记、最终输出收集、取消、完成通知、owner 资源释放与提供方资源释放。
## 曾考虑的替代方案
**让产品工具继续只支持前台运行。** 这种方案保留最小 schema,却会阻止 agent 调度独立产品工作,即使通用 one-shot Task 适配器已经负责所需生命周期。
**让产品委托默认在后台运行。** one-shot Task 需要后续收集,这不同于拥有自身持久会话 id 与结算交付的可续接子级。前台继续作为兼容默认值,后台继续作为显式调度选择。
**让 Codex 或 Claude Code 原生会话状态负责后台生命周期。** 这会在通用任务注册表之外建立提供方专属 id、状态、取消与恢复语义。提供方继续只产生 one-shot 结果,并把原生 id 保持为私有事实。
**增加产品专属 output、wait 或 kill 工具。** 独立控制工具会复制通用任务协议,并为每个提供方教授不同的收集工作流。现有 `task_*` 工具已经覆盖所需操作。
**同时增加可续接产品会话。** 恢复、后续交互、进度与持久化产品会话需要新的产品约定和生命周期归属。本决策只公开已经实现的 one-shot 后台路径。
## 后果
agent 可以在 Codex 或 Claude Code 处理独立 one-shot 任务时继续推进其他工作,随后通过其他后台 producer 共用的 Task 控制工具收集最终回答或取消运行。前台调用方继续获得既有结果与错误行为。
每次产品委托仍会启动一个全新的原生进程或 query,把最终文本作为唯一产品载荷,并以提供方资源释放和整棵进程树退出结束。后台调用还会额外公开通用 Task id、状态、完成通知以及收集或取消结果。后台 Task 仅存在于当前进程且由父级拥有:它不会在父级资源释放后继续存活,不会公开产品中间活动,也不会让产品对话变得可恢复。公开后台参数的自定义组装还必须让通用 Task 提供方与控制工具保持可用。