Skip to content

第 9 章 · Subagents 子代理委派

本章目标:掌握 useSubagent() 定义专家子代理,理解"新鲜上下文委派"的机制与继承规则,学会设计主从协作的多 Agent 架构。

9.1 为什么需要子代理

单一 agent 的上下文窗口是稀缺资源:让它同时做调研、写代码、跑测试,中间产物会把上下文撑爆,注意力也被稀释。

子代理(Subagent)= 有名字的专家委托对象。父 agent 把聚焦任务交给它,子代理在全新的上下文里独立完成,只把最终答案交回来——中间推理、工具调用、文件读取统统不进父会话:

text
父 Agent(主会话)
   │ task(name='reproducer', prompt='复现 issue #42…')

子代理 Reproducer(全新上下文)── 独立读文件、跑命令 ──┐
   │                                                  │
   └── ◀──────── 只有最终结论回传 ──────────────────────┘

9.2 声明一个子代理

typescript
// src/agents/case-agent.ts
'use agent';
import { useModel, useSubagent } from '@flue/runtime';

// 注意:Summarizer 故意【不导出】!
// 导出的大写函数会被注册为顶层 agent,
// 而子代理只是父 agent 挂载的一种能力
function Summarizer() {
  return '你将支持案例总结成三句话,突出问题与影响。'; // 返回值 = 子代理指令
}

export function CaseAgent() {
  useModel('anthropic/claude-sonnet-4-6');

  // 三要素:name、description、agent 函数
  useSubagent({
    name: 'summarizer',
    // description 是父模型决定是否委派的唯一依据——像工具描述一样认真写
    description: '总结单个支持案例。当需要压缩长对话或案例细节时使用。',
    agent: Summarizer,
  });

  return `
调查支持案例并给出处理建议。
完成调查后,把案例摘要工作委派给 summarizer 子代理。`;
}

子代理的 agent 函数就是普通 agent 函数:返回指令、可用 Hook 组合能力。但它是能力而非实体——没有自己的会话 ID、没有 HTTP 路由。

9.3 委派的运行机制

委派由模型驱动。每个 agent 的工具集里都有一个框架自带的 task 工具;已声明的子代理以"Available Agents"目录形式进入系统提示。流程:

  1. 父模型判断某块工作匹配某子代理 → 调用 task 工具(带 name + 任务 prompt);
  2. 运行时在委派时刻全新渲染子代理函数,生成其指令与工具集;
  3. 子代理作为分离会话在父环境中运行,用自己的上下文窗口干到完成;
  4. 只有最终消息作为 task 结果回到父会话。

两个推论值得牢记:

  • 任务 prompt 就是全部简报。子代理看不到父对话,所以父指令必须要求"用完整、自包含的 prompt 委派";
  • 任务天然可并行。同一批工具调用并发执行,五个独立检查 = 五个并发子会话,各用各的窗口。

9.4 继承规则:给什么用什么

typescript
'use agent';
import { useModel, useSkill, useSubagent, useTool } from '@flue/runtime';
import { searchIssues } from '../tools/search-issues.ts';
import reproduce from '../skills/reproduce/SKILL.md';

function Reproducer() {
  // 子代理自己的能力组合——只有这里声明的才属于它
  useSkill(reproduce);
  return '按复现技能中的流程验证 issue 是否可复现。';
}

export function Triage() {
  useModel('anthropic/claude-sonnet-4-6');
  useTool(searchIssues);      // ← 父的工具,子代理【不】继承
  useSubagent({
    name: 'reproducer',
    description: '验证 bug 报告是否可复现。',
    agent: Reproducer,
    // 可选:覆盖继承来的模型(不设则用父的模型)
    model: 'anthropic/claude-haiku-4-5',
  });
  return '分诊 issue。需要确认可复现性时委派给 reproducer。';
}
继承 ✅不继承 ❌
沙箱及其内置工具父的对话历史
工作区上下文(AGENTS.md 等)父的指令、自有工具
父的模型与推理档位*父的技能与其他子代理
(*除非定义时覆盖)父的持久状态与初始数据

一句话:环境继承,对话隔离。子代理的世界 = 你在它的渲染中组合的一切。

9.5 协作模式与限制

typescript
'use agent';
import { useModel, useSubagent } from '@flue/runtime';

function SecurityCheck() { return '对给定模块做安全审计,输出风险清单。'; }
function PerfCheck()     { return '分析给定模块的性能瓶颈并量化。'; }

export function ReleaseManager() {
  useModel('anthropic/claude-opus-4-6');

  // 发布前的并行体检团:三个独立检查互不干扰
  useSubagent({ name: 'security', description: '安全审计。发布前必做。', agent: SecurityCheck });
  useSubagent({ name: 'perf',     description: '性能分析。发现回归时使用。', agent: PerfCheck });

  return `
执行发布前检查:
1. 并行委派 security 与 perf 两项检查;
2. 收集双方结论后汇总为放行/拦截建议。
委派时请使用包含目标模块路径的完整 prompt。`; // ← 明确要求自包含简报
}

关键约束:

typescript
// 子代理定义的类型签名——理解三要素背后的契约
interface SubagentDefinition {
  name: string;          // 目录中的唯一标识,同名重复声明会抛错
  description: string;   // 父模型的委派依据,务必写清"做什么+何时用"
  agent: () => string;   // 普通 agent 函数,可组合 Hook,但不要导出
  model?: string;        // 可选:覆盖继承的模型,如 'anthropic/claude-haiku-4-5'
}
// 委派深度上限 4 层:父 → 子 → 孙 → 曾孙,再往下会被运行时拒绝
  • 同名子代理一次渲染只能声明一个,重复抛错;
  • 委派深度上限 4 层(子代理还可以再声明自己的子代理);
  • 支持条件声明——阶段翻转后才出现在目录里,运行时会播报变化;
  • 应用代码也能委派:harness.prompt() 的指令中点名某个子代理即可强制路由。

本章小结

  • 子代理 = 具名的专家委托:新上下文干活,只回传最终答案;
  • useSubagent({ name, description, agent }) 三要素;delegate 函数不要导出;
  • 委派走框架保留的 task 工具;prompt 是全部简报,务必自包含;
  • 继承沙箱/工作区/模型,不继承对话与父级资源;深度上限 4 层;
  • 多个子代理 + 并行 task = 天然的多专家流水线。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 子代理完成后,哪些内容会回到父 agent 的会话?

2. 为什么 Summarizer 这类子代理函数故意不导出?

3. 关于委派时的任务 prompt,正确理解是?

4. 子代理默认会继承父 agent 的哪一项?

🛠️ 动手实践

  1. 给第 6 章的客服 agent 加一个"退款专员"子代理,对比委派前后主会话的 token 消耗。
  2. 设计两个可并行的检查子代理,让模型同批发起两个 task,观察耗时是否接近单项耗时。
  3. 验证隔离性:让子代理尝试访问父会话中提到的临时变量信息,确认它确实看不到。

至此 Flue 前 9 章的核心编程模型完整了:Agent 函数 + Hook 组合出模型、工具、技能、MCP、子代理五大能力面。后续章节将走向沙箱、持久化与生产部署。