31.2 子代理

生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~300k tokens,输出 ~8k tokens(本节)


上一节介绍了多 Agent 的静态配置与解析。本节深入探讨子代理(Sub-agent) 机制——即一个 Agent 在运行时动态派生另一个 Agent 来执行特定任务的能力。这是 OpenClaw 多代理架构中最具动态性的部分。

衍生解释:子代理模式 子代理模式源自分布式计算中的 Actor 模型——每个 Actor(代理)是独立的计算单元,通过消息传递进行通信。子代理模式将其应用于 AI Agent 领域:主 Agent 像"经理"一样分配任务给专门的子 Agent,子 Agent 完成后汇报结果。这类似于操作系统中的 fork + wait 语义,但在更高的语义层面运作。

31.2.1 子代理注册表

子代理的生命周期管理由 subagent-registry.ts 实现。它维护了所有活跃和已归档的子代理运行记录。

SubagentRunRecord 数据结构

// src/agents/subagent-registry.ts(简化)

type SubagentRunRecord = {
  runId: string;              // 全局唯一运行 ID(UUID)
  childSessionKey: string;    // 子代理会话键
  requesterSessionKey: string; // 发起者会话键
  task: string;               // 任务描述
  cleanup: "delete" | "keep"; // 完成后的清理策略
  label?: string;             // 人类可读标签
  createdAt: number;          // 创建时间戳(ms)
  startedAt?: number;         // 实际开始时间
  endedAt?: number;           // 结束时间
  outcome?: "ok" | "error" | "timeout"; // 最终状态
  archiveAtMs?: number;       // 归档时间点
};

每个子代理运行都有完整的生命周期追踪。cleanup 字段决定了任务完成后的行为:"delete" 会自动清除子代理的会话记录(包括转录文件),"keep" 则保留以供后续审查。

注册与生命周期监听

注册后,系统立即将记录持久化到磁盘,并确保生命周期监听器处于活跃状态。

生命周期事件流

子代理的状态变更通过 Gateway 的事件流(Server-Sent Events)进行追踪:

生命周期事件通过 Gateway 的 WebSocket 事件系统广播,包含三个阶段:start(Agent 开始处理)、end(正常完成)、error(异常终止)。

清扫器(Sweeper)

后台清扫器以 60 秒间隔运行,负责归档和清理过期的子代理记录:

archiveAtMs 默认设置为 endedAt + archiveAfterMinutes * 60_000,其中 archiveAfterMinutes 默认为 60 分钟。这意味着子代理完成后,其记录会在内存中保留一小时(供查询和调试),然后被归档。

等待子代理完成

主 Agent 可以同步等待子代理完成:

agent.wait 是一个阻塞式 RPC 调用——Gateway 会挂起这个请求,直到目标子代理完成或超时。注意 timeoutMs + 2000 的设计:Gateway 侧的超时比请求参数多 2 秒,避免请求本身因网络延迟而提前超时。

磁盘持久化与恢复

子代理记录持久化到磁盘,确保 Gateway 重启后能恢复:

restoreSubagentRunsOnce() 使用模块级 restored 标志确保只恢复一次。归档记录只保留最近 50 条,防止磁盘文件无限增长。

31.2.2 sessions_spawn 工具

子代理的创建通过 sessions_spawn 工具完成。这是一个暴露给 AI Agent 的工具,允许 Agent 在对话中动态派生子代理。

工具参数

跨代理访问控制

并非所有 Agent 都可以派生任意其他 Agent。访问控制通过 subagents.allowAgents 配置实现:

安全规则:

配置
行为

allowAgents: ["*"]

允许派生任何 Agent

allowAgents: ["coder", "researcher"]

只允许派生指定的 Agent

allowAgents 未设置

使用全局默认策略

此外,系统通过 isSubagentSessionKey() 检查防止子代理嵌套——子代理不能再派生自己的子代理,避免无限递归:

会话键生成

每个子代理获得一个唯一的会话键:

格式为 agent:{targetAgentId}:subagent:{uuid}。这个键编码了两个关键信息:目标 Agent 的 ID 和一个全局唯一标识符。Gateway 通过解析这个键来路由子代理的请求到正确的 Agent 配置。

模型解析链

子代理的模型通过三级优先级链解析:

解析后通过 sessions.patch RPC 应用到子代理会话:

思维级别标准化

thinking 参数支持多种输入格式并统一标准化:

子代理系统提示

每个子代理在启动时注入一段特殊的系统提示词,明确其角色和约束:

这段系统提示确保子代理:

  • 专注于分配的任务

  • 不会擅自与用户交互

  • 不会创建持久化副作用(如定时任务)

  • 清楚自己是临时的、可被销毁的

完整的 spawn 流程

将上述组件串联起来,sessions_spawn 的完整执行流程如下:

lane=AGENT_LANE_SUBAGENT 告诉 Gateway 这是一个子代理运行,deliver=false 表示子代理的输出不直接投递给用户通道——而是通过公告流程(下一小节)由主 Agent 转述。

31.2.3 子代理公告

子代理完成任务后,其结果需要公告给主 Agent,再由主 Agent 以自然的方式告知用户。subagent-announce.ts 实现了这一桥接机制。

公告流程概览

等待用量统计

子代理完成后,token 使用量可能尚未写入会话存储(写入是异步的)。waitForSessionUsage() 通过轮询等待:

4 次重试、每次 200ms 间隔——总共最多等待 800ms。这是一个典型的最终一致性等待模式:系统不保证用量数据立即可用,但在合理时间内会收敛。

统计行构建

统计行包含运行时长、token 消耗、估算成本、会话键和转录文件路径。成本估算基于配置中的模型定价信息(models.providers[provider].models[].cost)。

格式化工具函数遵循人类可读原则:

formatTokenCount

formatDurationShort

formatUsd

1,500,000

"1.5m"

42,300

"42.3k"

185 秒

"3m5s"

0.0042

"$0.0042"

1.23

"$1.23"

触发消息构造

公告的核心是构造一条触发消息发送给主 Agent,让主 Agent 以自然语言转述结果:

这条消息包含了子代理的完整输出,但最后的指令要求主 Agent 转译而非直接转发。用户看到的是主 Agent 自然流畅的总结,而不是机械的技术报告。

投递策略

maybeQueueSubagentAnnounce() 根据当前主 Agent 的状态决定如何投递:

三种投递模式:

模式
条件
行为

steer

主 Agent 正在运行 + steer 模式

将消息注入到当前 Agent 轮次中,Agent 可以在回复中自然提及

queued

主 Agent 正在运行 + followup/collect 模式

放入公告队列,等待 Agent 空闲时批量投递

none / 直接发送

主 Agent 空闲

立即发送触发消息,启动新的 Agent 轮次

清理与善后

公告流程的 finally 块处理善后工作:

31.2.4 公告队列

当多个子代理同时完成时,它们的公告需要有序地投递给主 Agent,避免消息洪泛。subagent-announce-queue.ts 实现了这一队列机制。

队列状态

防抖与批量投递

队列采用防抖(debounce)策略:入队后等待 debounceMs(默认 1 秒),如果期间有新消息入队则重置计时器。这样,短时间内完成的多个子代理的公告会被合并处理。

当队列模式为 "collect" 时,所有待投递的消息会被合并成一条:

注意跨通道安全:如果待投递的消息来自不同通道(如一个来自 Telegram、一个来自 Discord),系统会切换到逐条发送模式,避免将不同通道的上下文混合。

溢出策略

当队列达到上限(默认 20 条)时,dropPolicy 决定如何处理新消息:

策略
行为

"summarize"

丢弃消息但记录摘要行,最终以汇总形式告知主 Agent

"new"

丢弃新入队的消息(旧消息保留)

"old"

丢弃最旧的消息(新消息入队)

"summarize" 是默认策略——即使消息被丢弃,主 Agent 仍会收到类似"另有 N 条公告被汇总"的通知,确保不会完全丢失信息。


本节小结

  1. SubagentRunRecord 追踪子代理完整生命周期:从创建到归档,包含时间戳、状态、清理策略等元数据。

  2. 生命周期监听 通过 Gateway 的 lifecycle 事件流(start/end/error)实时更新子代理状态。

  3. 清扫器 以 60 秒间隔运行,归档超时记录(默认 60 分钟),限制归档列表为 100 条。

  4. sessions_spawn 工具实现了完整的 spawn 流程:权限检查 → 会话键生成 → 模型解析 → 系统提示注入 → Gateway 调用 → 注册表记录。

  5. 跨代理安全allowAgents 配置控制哪些 Agent 可以被派生,子代理不能嵌套(防止无限递归)。

  6. 子代理系统提示 明确了子代理的角色约束:专注任务、不与用户交互、不创建持久副作用。

  7. 公告流程 将子代理的结果转化为自然语言,通过 steer/queue/direct 三种策略投递给主 Agent。

  8. 公告队列 使用防抖、批量合并和跨通道安全检测,防止消息洪泛,确保用户体验流畅。

Last updated