子代理、待办与提问

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

作者 David更新于 第 16 篇(共 38 篇)

除了读写文件和跑命令,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。

图表加载中…

深度:沿 parentID 数祖先层数,达到配置 subagent_depth(默认 1)就报错,也就是默认不允许子代理再派子代理。

权限询问:以子代理名为模式询问 task。用户通过命令触发的子任务由主循环的 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,流处理器把它与“拒绝授权”同等对待:本轮循环停止。

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。

下一篇:联网工具与 Code Mode——webfetch 与 websearch 的实现细节,以及让模型写一段程序来编排 MCP 工具的 Code Mode。

本页目录