任务工具
你已经拥有大部分任务工具了。第6.2和6.3课构建了探索器,执行器分支纳入其execute功能。
本课讲的是把工具当作它实际的布线层来对待。父 Agent 调用 task。工具会选择正确的子 Agent类型,验证父 Agent生成该类型,并返回结果。以后增加更多角色(审查员、架构师、验证员)应该只是添加一个分支,而不是重新设计工具。
学习成果
task工具结构为一个明确的路由器,包含一个整合的描述、角色特定模型,以及需要时的派生权限检查清晰位置。
快速路径
- 收紧
task工具描述,让父 Agent知道什么时候该选择哪个角色 - 把子 Agent结构提取成一个小助手,这样以后添加角色就只剩一个区块
- 勾勒出生成权限检查的形状,即使你还没强制执行
动手练习 6.4
重构createTaskTool让路由成为你首先看到的内容,下面是针对特定角色的构造。
要求:
- 任务工具的描述会列出这两个角色,说明各自的用途,并指向非委派场景的父 Agent
askUser和直接工作 execute体是一台精简路由器。每个角色都由同一文件内的独立辅助函数构建- 每个角色助手会拿沙箱和父 Agent工具,返回
ToolLoopAgent,并在定义顶部展示模型和步骤预算 - 保持错误处理作为字符串返回,而不是抛出的异常
实现提示:
- 这两个助手可以共享生成和格式化功能,使
[Role: N steps]格式化集中在一个地方 - 不要过于抽象。两个助手和一个路由器就够了。当你有五个角色,而不是两个角色时,采用注册表与工厂系统才是正确的选择
- 描述就是父 Agent里读的。WHEN TO USE和WHEN NOT TO USE 也适用于路由层,而不仅仅是单个工具
路由器结构
function buildExplorer(sandbox: Sandbox, parentTools: { read: any; grep: any }) {
return new ToolLoopAgent({
model: "anthropic/claude-haiku-4-5",
instructions: `You are an explorer agent. Investigate and report back concisely.
Working directory: ${sandbox.workingDirectory}`,
tools: { read: parentTools.read, grep: parentTools.grep },
stopWhen: stepCountIs(5),
});
}
function buildExecutor(sandbox: Sandbox, parentTools: { read: any; grep: any }) {
const executorBash = createBashTool(
sandbox,
createApproval({
mode: "delegated",
trust: ["npm test", "npm run build", "npx tsc"],
}),
);
return new ToolLoopAgent({
model: "anthropic/claude-sonnet-4-6",
instructions: `You are an executor agent. Follow instructions precisely.
Working directory: ${sandbox.workingDirectory}
Do NOT ask questions. Do NOT explore beyond what's needed. Execute the task.`,
tools: { read: parentTools.read, grep: parentTools.grep, bash: executorBash },
stopWhen: stepCountIs(15),
});
}
async function runSubagent(role: string, agent: ToolLoopAgent, description: string) {
try {
const { text, steps } = await agent.generate({ prompt: description });
return text ? `[${role}: ${steps.length} steps]\n${text}` : `(no response from ${role})`;
} catch (e: any) {
return `${role} error: ${e.message}`;
}
}
export function createTaskTool(
sandbox: Sandbox,
parentTools: { read: any; grep: any },
) {
return tool({
description: `Delegate work to a subagent.
Explorer (default): read-only research with Haiku. Use for searching across files,
understanding patterns, and gathering context.
Executor: implementation with Sonnet and delegated bash. Use for focused
changes with explicit instructions and a known verification step.
WHEN TO USE: research across many files (explorer), bulk implementation (executor).
WHEN NOT TO USE: ambiguous requirements (use askUser), architectural decisions
(the parent decides).
DO NOT USE FOR: single-step tasks the parent can do directly.`,
inputSchema: z.object({
description: z.string().describe("Task instructions for the subagent"),
subagentType: z
.enum(["explorer", "executor"])
.default("explorer")
.describe("Subagent role"),
}),
execute: async ({ description, subagentType }) => {
const agent =
subagentType === "executor"
? buildExecutor(sandbox, parentTools)
: buildExplorer(sandbox, parentTools);
return runSubagent(subagentType, agent, description);
},
});
}路由器现在有五条线路。其他都是按角色构建的。
生成权限的去向
现在任何Agent都可以调用 task任何subagentType。这对入门Harness来说没问题。在更分层的设置中,你需要一个按角色设置权限映射:
const SPAWN_PERMISSIONS: Record<string, string[]> = {
orchestrator: ["explorer", "executor", "reviewer"],
executor: ["explorer"],
explorer: [],
};
function canSpawn(parentRole: string, subagentType: string): boolean {
return SPAWN_PERMISSIONS[parentRole]?.includes(subagentType) ?? false;
}支票放在execute顶。如果不允许生成,返回错误字符串并不要构建子 Agent。
我们还没把它加到工作Harness里,因为父 Agent目前还没有角色。当你开始使用子 Agent本身调用 task时,权限表就是你接下来需要的。在此之前,缺席没问题,形状也有记录。
按角色模型,而非按会话模型
模型是角色定义的一部分,而非全局环境:
| 职责 | 模型 | 为什么 |
|---|---|---|
| 探索器 | Haiku | 快速、便宜、只读 |
| 执行器 | Sonnet | 实现可靠 |
| 评论员(后期) | Opus | 代码审查的深层推理 |
| 编曲者(后期) | Sonnet | 多工具布线 |
不同的角色,不同的模式。不要只选一个模型到处用。成本差异在长期任务中会叠加,失败模式也不同。
**注意:两个角色是正确的起点**
你可以建立更复杂的层级结构:架构师、规划师、审查员、整合者。我们做这件事并不是因为两个岗位涵盖了大多数人在意的工作。当有真正需要的任务时,再加多一些。不要随意添加。每个角色都是指令漂移的新场所,也是新的模型账单需要跟踪。
动手试试
请让父 Agent按顺序分配两项工作:先做调研,然后是实施:
bun run index.ts . "First, delegate to an explorer: find every file that uses the zod schema for tools. Then delegate to an executor: in those files, add a comment above each tool() call saying which lesson introduced it."你应该会看到父 Agent有两个任务调用。第一个返回文件列表。第二位负责编辑并反馈。
npx tsc --noEmit提交
git add src/tools.ts
git commit -m "refactor(subagents): split task tool into router and role helpers"完成标准
- [ ]
createTaskTool是一个按subagentType调度的精简路由器 - [ ] 每个角色都存在于一个独立的助手中(
buildExplorer,buildExecutor) - [ ] 任务工具描述中列出了两个角色以及使用它们的合适时间
- [ ] 错误以字符串形式返回,而非例外
- [ ] 增加第三个角色只是一个新的助手和一个新分支,仅此而已
- [ ]
npx tsc --noEmit
**注意:添加审核员角色**
试着添加一个reviewer 子 Agent:只读工具、Opus级模型,以及一个verdict工具,能返回带有反馈的pass或fail。执行器结束后,会自动生成一个审核员,显示原始任务和执行器的差异。如果评审失败,请重新运行执行器并附上反馈。重试次数上限为两次。哪种模型组合能产生最佳的评测质量? 评审什么时候会草率放行,而不是抓住真正的问题?
参考实现
请看上面的路由器形状。练习解法是同样的代码,应用到你的src/tools.ts上。