# 子代理、待办与提问

> task 工具如何派生子会话、继承权限并回传结果，前台与后台子代理，以及 todowrite、question、plan_exit、skill 这几个组织与交互型工具。

- 作者：David（道雾轩）
- 专栏：OpenCode 源码解读（https://daiw.net/manual/opencode.md）
- 最后更新：2026-09-15
- 原文：https://daiw.net/manual/opencode/task-and-todo
- 转载与引用：请注明出处并附原文链接（https://daiw.net/about/copyright）

除了读写文件和跑命令，agent 还需要“组织工作”的能力：把活派给子代理、维护待办、向用户提问、退出计划模式、加载技能。这些工具都在 `packages/opencode/src/tool/` 下，本篇逐个来看。

## task：派生子会话

参数是 `description`（3～5 个词）、`prompt`、`subagent_type`，以及可选的 `task_id`（续用之前的子会话）和 `command`。模型看到的说明末尾会追加一份子代理清单：注册表列出所有非 primary 的 agent，再滤掉当前 agent 的 `task` 权限所拒绝的那些。内置子代理有 `general`（通用，适合并行干多件事）和 `explore`（只放行读取、搜索、Shell 与联网），自定义 agent 见 [Agent、命令与 Skills](https://daiw.net/manual/opencode/agents-and-commands)。

```mermaid
sequenceDiagram
  participant T as 父会话的 task 工具
  participant S as Session 服务
  participant C as 子会话主循环
  T->>T: 深度检查与 task 权限询问
  T->>S: 创建子会话（parentID 指向父会话）
  T->>C: promptOps.prompt 跑完整循环
  C-->>T: 最后一条文本
  T-->>T: 包成 task 标签返回模型
```

**深度**：沿 `parentID` 数祖先层数，达到配置 `subagent_depth`（默认 1）就报错，也就是默认不允许子代理再派子代理。

**权限询问**：以子代理名为模式询问 `task`。用户通过命令触发的子任务由[主循环](https://daiw.net/manual/opencode/prompt-loop)的 `handleSubtask` 直接调用 task 的 `execute`，并在 `extra` 里设 `bypassAgentCheck: true` 跳过这一步——用户亲手发起的委派不必再问一次。

### 子会话的权限

子会话的 `permission` 由 `deriveSubagentSessionPermission` 生成，注释讲明了原则：

> Parent agent restrictions only govern that agent; the subagent's own permissions determine its capabilities.

（`packages/opencode/src/agent/subagent-permissions.ts:9`）

从父**会话**传下来的只有两类规则：拒绝规则和 `external_directory` 规则。父 agent 的限制不下传，子代理能做什么由它自己的 agent 规则决定。另外，除非子代理自己的规则提到过，`todowrite` 与 `task` 一律拒绝；配置 `experimental.primary_tools` 列出的工具同样对子代理拒绝。

### 运行与回传

子会话的模型取子代理配置的 `model`，没配就沿用父消息的模型。真正执行靠 `ctx.extra.promptOps`，这是会话层注入的一组操作，其中 `prompt()` 会在子会话里跑一遍完整的主循环。结束后取最后一条文本部件作为结果；子会话的最终消息带错误、或其中有失败的工具调用时，task 报 `Subagent failed (task_id: …)`。

结果用 `<task id="…" state="completed">` 包住 `<task_result>` 正文返回给模型。`task_id` 就是子会话 ID，传回它即可让同一个子代理带着历史继续干。父会话被中止时，abort 事件会连带取消子会话。

### 前台与后台

每个子代理其实都挂在 `BackgroundJob` 上运行，前台调用只是原地等它结束。开启 `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` 后：

- 参数里多出 `background`，为 true 时立刻返回 `state="running"`，并叮嘱模型别轮询、别重复它的工作；
- 任务结束后，结果以合成（synthetic）用户消息注入父会话，父会话随之继续；
- 正在前台运行的子代理可以被“提升”为后台（服务端有个实验接口，把某会话的前台 task 全部提升），父会话里的 task 调用随即返回。

未开启时，`background` 不出现在给模型的 JSON Schema 里，硬传也会报错。另外，对仍在运行的任务再传同一个 `task_id`，新指令会排在它后面执行。

## todowrite：整表覆盖的待办

参数是完整的 `todos` 数组，每项有 `content`、`status`（pending、in_progress、completed、cancelled）和 `priority`（high、medium、low）。每次调用都整表替换：`packages/opencode/src/session/todo.ts` 在一个事务里删掉该会话的全部待办，再按顺序插入，然后发布 `todo.updated` 事件供界面渲染。工具输出就是这张表的 JSON，标题是未完成项的数量。

todowrite.txt 的规则很具体：3 步以上才用，同一时刻只能有一个 `in_progress`，做完一项立即标记，并且只有真正做完（含验证）才能标 completed。待办属于主会话：子代理默认拒绝 `todowrite`，内置的 general 也显式拒绝。

## question：向用户提问

question 只在 `OPENCODE_CLIENT` 为 app、cli、desktop，或设置了 `OPENCODE_ENABLE_QUESTION_TOOL` 时注册。默认规则对 `question` 是 deny，只有 build 与 plan 两个主 agent 放行，所以子代理根本看不到它；非交互的 `opencode run` 也会给会话加上拒绝规则。

每个问题包含 `question`、`header`（不超过 30 字符）、`options`（label 加 description）与可选的 `multiple`。模型侧参数没有 `custom` 字段，而它默认为 true，所以用户总能自由输入答案。

`Question` 服务的结构与权限服务如出一辙：生成请求、发布 `question.asked` 事件、用 `Deferred` 把工具挂起，等客户端 reply 或 reject。回答是按问题排列的标签数组，工具把它格式化成 `"问题"="答案"` 返回。用户关掉提问会抛出 `Question.RejectedError`，[流处理器](https://daiw.net/manual/opencode/processor)把它与“拒绝授权”同等对待：本轮循环停止。

## plan_exit：计划模式的出口

计划模式仍是实验功能：设置 `OPENCODE_EXPERIMENTAL_PLAN_MODE` 且客户端为 cli 时，注册表才放出 `plan_exit`。plan agent 禁止一切编辑，只允许写计划文件：有版本控制时是 `.opencode/plans/时间戳-slug.md`，否则放在数据目录的 `plans` 下。

模型写完计划后调用 `plan_exit`，工具借 `Question` 服务问用户是否切换到 build agent 开始实现（这个提问把 `custom` 设为 false，只能选 Yes 或 No）。选 Yes 就插入一条 agent 为 `build` 的合成用户消息，内容是“计划已批准，现在可以编辑文件，执行计划”；选 No 则抛出 `Question.RejectedError`。TUI 看到 `plan_exit` 完成后，会把当前 agent 切回 build。

目录里还有一份 `plan-enter.txt`，但在 v1.18.30 中没有任何 V1 工具引用它，只剩权限键 `plan_enter` 与 TUI 里的切换逻辑。

## skill：加载技能

skill 工具只做一件事：按名字取出技能，询问 `skill` 权限（模式和 always 都是技能名），然后把 SKILL.md 正文、技能所在目录，以及用 ripgrep 抽样的至多 10 个附属文件路径包进 `<skill_content>` 返回。模型由此知道目录里有哪些脚本和参考资料，需要时再用 read 打开。技能如何被发现、如何列入系统提示，见 [Agent、命令与 Skills](https://daiw.net/manual/opencode/agents-and-commands)。

下一篇：[联网工具与 Code Mode](https://daiw.net/manual/opencode/web-and-codemode)——webfetch 与 websearch 的实现细节，以及让模型写一段程序来编排 MCP 工具的 Code Mode。
