# Agent Note: 产品 one-shot subagent 使用通用后台 Job

Status: implemented

[English](2026-08-12-product-subagent-one-shot-background-tasks.md) | 中文

## 问题

Codex 与 Claude Code 提供方已经能够运行一项自包含任务并返回一个最终回答，而 `dsh-tool-subagent` 也已经能够把任意 one-shot 提供方接入通用后台 Job 运行时。随附产品工具行禁用了这条路径，因此即使委托与 agent 的下一步操作彼此独立，agent 也只能等待产品回答。

公开后台执行不得增加产品会话、产品专属作业状态、另一取消责任方或另一结果协议。同一个提供方运行必须继续负责一个原生进程或 query 和一个最终回答，而现有作业注册表继续负责 id、收集、取消、owner 清理与完成通知。

## 决策

生产 `dsh` 不安装可选产品提供方。选择启用产品集成的 Profile 会安装 `dsh-subagent-codex`、`dsh-subagent-claude-code` 或两者，并在 host plane（宿主平面）各挂载一次。`standard`、`code` 与 `cordis` Agent Preset 使用 `backgroundMode: one-shot` 配置相应的休眠工具行；删除某一行的 `disabled` 字段后，现有可选参数 `run_in_background` 会向由该 preset 组装的 agent 公开。省略该参数或传入 `false` 时会在前台等待；显式传入 `true` 时会在同步完成 Job 预检与登记后返回由父级拥有的 Job id，而不会等待提供方启动或完成。

[通用 one-shot 后台适配器](2026-07-08-background-subagent-tasks.md)负责后台登记与结算。它会启动同一个 [`SubagentRun`](2026-06-21-subagent-capability-seam.md)，让 Job 自有的取消信号覆盖提供方启动与执行，等待 `run.result` 和 `run.dispose()`，把终态结果映射进 Job，并由 `job_output`、`job_list`、`job_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: Job preflight + owner cleanup
           -> starter begins provider startup under Job-owned signal
           -> Job record/id published and returned (startup remains pending)
           -> provider result + run disposal -> Job settlement + notice
                                              -> job_output reads / job_kill cancels
  -> parent disposal: Job owner cleanup cancels -> run disposal -> process exit
```

| 事实或资源 | 责任方 | 产品工具职责 | 可观察结果 |
| --- | --- | --- | --- |
| 产品提供方安装与登记 | 显式 Profile | 安装可选提供方包，并在 host plane 挂载一次 | 提供方名称可用，但不会让每次生产 `dsh` 安装都包含该包 |
| 产品选择与公开 | Agent Preset | 把一个固定工具名绑定到一个固定提供方 | 启用一行只会公开对应产品工具 |
| 前台或后台选择 | `dsh-tool-subagent` | 按 `one-shot` 策略解析 `run_in_background` | 省略参数时在前台运行；显式传入 `true` 时返回 Job id |
| Job id、状态、输出、取消与通知 | `ctx.jobs` 与 `dsh-tool-jobs` | 登记并展示现有 one-shot 运行 | 通用作业工具为准确父级收集或停止运行 |
| 原生答案与进程完全停稳 | 产品提供方与 `dsh-subprocess` | 产生一个最终结果并释放一棵进程树 | Job 结算与前台返回都会等待资源释放 |

## 发布组装

生产 base 不让两个可选产品提供方进入依赖闭包。选择启用产品集成的 Profile 会在 host plane 安装并挂载任一或两个提供方。每个完整 preset 让两个产品工具行保持禁用，并把通用 Job 控制工具贡献到自身 agent 作用域；base host 负责共享 Job 注册表。Profile 提供方存在后，用户复制一个 preset，再从对应产品行删除 `disabled`；组装期间不会启动产品进程。

独立自定义组装若启用 one-shot 后台执行，就必须同时提供产品提供方与完整通用 Job 能力：由 `dsh-jobs-local` 充当 Job 提供方，由 `dsh-tool-jobs` 充当面向模型的消费方。基于 `dsh-base` 的 Profile 已具备 Job 能力，只需在启用 preset 工具行前新增可选产品提供方。没有 Job 运行时的产品工具仍可在前台执行，但显式后台请求会在现有 Job 预检中失败，不会发布无法收集的 id。

ACP 产品组装使用相同的固定产品行与通用作业控制工具。其无密钥 schema 快照会为每个已启用产品工具公开 `description`、`prompt` 和可选的 `run_in_background`，而不会调用 Codex、Claude Code 或外部模型。

## 验证

Web 组装测试会从仓库 examples 依赖锚点显式挂载两个可选提供方，再启动四种用户 preset 变体——不启用产品、只启用 Codex、只启用 Claude Code，以及同时启用两者——并检查每个已启用产品工具都会与 `job_output`、`job_list` 和 `job_kill` 一起公开 `run_in_background`。两个由包负责的 Loader 组装会在空 `PATH` 下运行，检查相同 schema 与控制工具，并证明显式加载提供方不会启动产品进程。ACP 无密钥快照会固定显式组装后的产品 schema，而现有 `dsh-tool-subagent` 与作业测试套件会固定前台默认值、Job 登记、最终输出收集、取消、完成通知、owner 资源释放与提供方资源释放。

## 曾考虑的替代方案

**让产品工具继续只支持前台运行。** 这种方案保留最小 schema，却会阻止 agent 调度独立产品工作，即使通用 one-shot Job 适配器已经负责所需生命周期。

**让产品委托默认在后台运行。** one-shot Job 需要后续收集，这不同于拥有自身持久会话 id 与结算交付的可续接子级。前台继续作为兼容默认值，后台继续作为显式调度选择。

**让 Codex 或 Claude Code 原生会话状态负责后台生命周期。** 这会在通用作业注册表之外建立提供方专属 id、状态、取消与恢复语义。提供方继续只产生 one-shot 结果，并把原生 id 保持为私有事实。

**增加产品专属 output、wait 或 kill 工具。** 独立控制工具会复制通用作业协议，并为每个提供方教授不同的收集工作流。现有 `job_*` 工具已经覆盖所需操作。

**同时增加可续接产品会话。** 恢复、后续交互、进度与持久化产品会话需要新的产品约定和生命周期归属。本决策只公开已经实现的 one-shot 后台路径。

## 后果

agent 可以在 Codex 或 Claude Code 处理独立 one-shot 任务时继续推进其他工作，随后通过其他后台 producer 共用的 Job 控制工具收集最终回答或取消运行。前台调用方继续获得既有结果与错误行为。

每次产品委托仍会启动一个全新的原生进程或 query，把最终文本作为唯一产品载荷，并以提供方资源释放和整棵进程树退出结束。后台调用还会额外公开通用 Job id、状态、完成通知以及收集或取消结果。后台 Job 仅存在于当前进程且由父级拥有：它不会在父级资源释放后继续存活，不会公开产品中间活动，也不会让产品对话变得可恢复。只有 Profile 显式安装产品集成时，生产安装才承担对应成本；公开后台参数的任何组装还必须让通用 Job 提供方与控制工具保持可用。
