跳到正文

任务工具

你已经拥有大部分任务工具了。第6.2和6.3课构建了探索器,执行器分支纳入其execute功能。

本课讲的是把工具当作它实际的布线层来对待。父 Agent 调用 task。工具会选择正确的子 Agent类型,验证父 Agent生成该类型,并返回结果。以后增加更多角色(审查员、架构师、验证员)应该只是添加一个分支,而不是重新设计工具。

学习成果

task工具结构为一个明确的路由器,包含一个整合的描述、角色特定模型,以及需要时的派生权限检查清晰位置。

快速路径

  1. 收紧task工具描述,让父 Agent知道什么时候该选择哪个角色
  2. 把子 Agent结构提取成一个小助手,这样以后添加角色就只剩一个区块
  3. 勾勒出生成权限检查的形状,即使你还没强制执行

动手练习 6.4

重构createTaskTool让路由成为你首先看到的内容,下面是针对特定角色的构造。

要求:

  1. 任务工具的描述会列出这两个角色,说明各自的用途,并指向非委派场景的父 AgentaskUser 和直接工作
  2. execute体是一台精简路由器。每个角色都由同一文件内的独立辅助函数构建
  3. 每个角色助手会拿沙箱和父 Agent工具,返回ToolLoopAgent,并在定义顶部展示模型和步骤预算
  4. 保持错误处理作为字符串返回,而不是抛出的异常

实现提示:

  • 这两个助手可以共享生成和格式化功能,使[Role: N steps]格式化集中在一个地方
  • 不要过于抽象。两个助手和一个路由器就够了。当你有五个角色,而不是两个角色时,采用注册表与工厂系统才是正确的选择
  • 描述就是父 Agent里读的。WHEN TO USE和WHEN NOT TO USE 也适用于路由层,而不仅仅是单个工具

路由器结构

ts
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来说没问题。在更分层的设置中,你需要一个按角色设置权限映射:

ts
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按顺序分配两项工作:先做调研,然后是实施:

bash
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有两个任务调用。第一个返回文件列表。第二位负责编辑并反馈。

bash
npx tsc --noEmit

提交

bash
git add src/tools.ts
git commit -m "refactor(subagents): split task tool into router and role helpers"

完成标准

  • [ ] createTaskTool 是一个按subagentType调度的精简路由器
  • [ ] 每个角色都存在于一个独立的助手中(buildExplorerbuildExecutor
  • [ ] 任务工具描述中列出了两个角色以及使用它们的合适时间
  • [ ] 错误以字符串形式返回,而非例外
  • [ ] 增加第三个角色只是一个新的助手和一个新分支,仅此而已
  • [ ] npx tsc --noEmit

**注意:添加审核员角色**

试着添加一个reviewer 子 Agent:只读工具、Opus级模型,以及一个verdict工具,能返回带有反馈的passfail。执行器结束后,会自动生成一个审核员,显示原始任务和执行器的差异。如果评审失败,请重新运行执行器并附上反馈。重试次数上限为两次。哪种模型组合能产生最佳的评测质量? 评审什么时候会草率放行,而不是抓住真正的问题?

参考实现

请看上面的路由器形状。练习解法是同样的代码,应用到你的src/tools.ts上。

非官方简体中文翻译 · 原课程来自 Vercel Academy