Skip to content

第6章 Agent 系统

"聊天机器人和 Agent 的分界线只有一条:聊天机器人在你说话时才存在,Agent 在你不说话时依然在工作。"

本章要点

  • 理解 Agent 的声明式定义:身份、工具、权限边界的配置化表达
  • 掌握系统提示组装管线:从静态模板到动态上下文注入
  • 深入 Sub-agent 编排:树形协作模型与推送式完成通知
  • 解析 ACP 协议:跨进程 Agent 通信的设计与实现

想象你是一家公司的 CEO。你有一支核心管理团队:COO 负责日常运营,CTO 负责技术决策,CFO 负责财务管控。新项目启动时,你不会亲自写代码、做账、谈客户——你把任务分配给合适的人,给他们必要的权限和资源,然后等待汇报。你的价值不在于亲力亲为,而在于知人善任、运筹帷幄

OpenClaw 的 Agent 系统,就是这支管理团队的数字孪生。

主 Agent 扮演 CEO 的角色——理解用户意图,决定任务分配。它可以生成子 Agent 来处理特定任务:一个负责搜索信息,一个负责写代码,一个负责监控服务器。每个子 Agent 有自己的身份(名字和人格)、工具箱(能调用哪些工具)、安全边界(能执行什么命令),以及与父 Agent 沟通的通信协议。分工明确,各司其职,协同运转。

🔥 深度洞察:Agent 系统的本质不是让 AI 更聪明,而是让 AI 的错误可控

这是理解 Agent 系统设计的关键认知跃迁。LLM 会犯错——这不是缺陷,而是概率系统的固有属性。单个 Agent 犯错时,整个任务失败。但在多 Agent 编排中,一个子 Agent 的错误被限制在它的沙箱内:搜索 Agent 返回了错误的结果,编码 Agent 可以验证;编码 Agent 写了有 bug 的代码,测试 Agent 可以发现。这与生物系统中细胞分裂的策略惊人相似——单细胞生物的一次 DNA 复制错误就是致命的,但多细胞生物有免疫系统来检测和清除异常细胞。Agent 系统的多层编排,本质上是 AI 的"免疫系统"——不是消除错误,而是让错误可检测、可隔离、可恢复。

📖 历史小故事:从 ELIZA 到 ReAct——Agent 系统 60 年的关键转折

Agent 系统的历史比大多数人想象的长得多。1966 年,MIT 的 Joseph Weizenbaum 创建了 ELIZA——一个通过模式匹配模拟心理治疗师的程序。ELIZA 没有"理解"任何东西,但它的用户(包括 Weizenbaum 自己的秘书)深信不疑地向它倾诉心事。这个意外的发现揭示了 Agent 系统的第一个深刻真理:用户对 Agent 的信任与 Agent 的实际能力几乎无关

快进到 2022 年,Yao 等人发表了 ReAct 论文("Reasoning and Acting"),这是现代 Agent 系统的真正转折点。ReAct 的核心洞察惊人地简单:让 LLM 交替进行推理(Thought)和行动(Action),而不是一次性生成最终答案。这个"想一步、做一步、看结果、再想"的循环,就是 OpenClaw Agent 运行时中工具调用循环的理论基础。从 ELIZA 到 ReAct,Agent 系统经历了从"假装理解"到"真正行动"的质变——而 OpenClaw 站在这段 60 年演化的最新节点上,解决的是 ReAct 论文没有回答的工程问题:如何让 Agent 在生产环境中安全、可靠、持久地运行。

前五章,我们搭建了 OpenClaw 的完整基础设施——Gateway 是办公大楼,Provider 是外部供应商网络,Session 是每个项目的文档档案,配置系统是行政规章制度。既然大楼已建成、制度已就位、档案柜已备好,接下来的问题自然是:坐在办公室里做决策的人是谁? 本章走进 Agent 系统的核心,深入剖析 Agent 的定义与配置、工具注册与安全策略、系统提示词的精密组装、运行时执行循环,以及多 Agent 编排的完整生命周期。

6.1 Agent 定义与配置

6.1.1 AgentConfig:Agent 的 DNA

每个 Agent 的全部特性浓缩在一个 AgentConfig 类型中。src/config/types.agents.ts 刻画了这个核心类型:

typescript
// src/config/types.agents.ts — Agent 的完整配置(声明式,非代码继承)
type AgentConfig = {
  id: string;                        // 唯一标识符
  default?: boolean;                 // 是否为默认 Agent
  name?: string;                     // 显示名称
  workspace?: string;                // 工作目录
  model?: AgentModelConfig;          // 模型配置(主模型 + 降级链)
  skills?: string[];                 // 技能白名单
  identity?: IdentityConfig;         // 身份/人格
  subagents?: { allowAgents?: string[]; model?: AgentModelConfig };
  sandbox?: AgentSandboxConfig;      // 沙箱配置
  tools?: AgentToolsConfig;          // 工具策略覆盖
  runtime?: AgentRuntimeConfig;      // 运行时类型(embedded / acp)
  // ... 更多可选字段:heartbeat, groupChat, humanDelay, memorySearch
};

这个类型定义体现了 OpenClaw Agent 系统的核心设计哲学——一个 Agent 就是一组配置的组合。它不是一个需要继承的基类,不是一个需要实现的接口,而是一个声明式的配置对象。

关键概念:声明式 Agent 定义 OpenClaw 的 Agent 不通过代码继承定义,而是通过纯配置声明。一个 AgentConfig 对象完整描述了 Agent 的身份(名称、人格)、能力(工具、技能)、约束(安全策略、沙箱)和运行时参数(模型、降级链)。这种声明式设计意味着创建新 Agent 不需要写任何代码——只需要编写配置文件。

图 6-1:AgentConfig 结构与关联关系

下图展示了 AgentConfig 的完整字段结构及其与 ToolConfigSessionOverridesModelRef 等关联类型的关系。注意 AgentConfig 是纯声明式配置对象——Agent 的身份、模型选择、工具策略和安全约束全部通过配置字段表达,无需编写任何代码。

图 6-1:AgentConfig 结构与关联关系

一个典型的 Agent 配置(YAML 格式)如下:

yaml
# openclaw.yaml — 典型 Agent 配置示例
agents:
  defaults:
    model: "claude-3-5-sonnet"            # 全局默认模型
    heartbeat: { schedule: "0 9 * * *" }  # 每天 9 点心跳

  list:
    - id: "main"
      default: true
      name: "Main Assistant"
      workspace: "~/my-workspace"
      model:
        primary: "claude-3-5-sonnet"                   # 主模型
        fallbacks: ["claude-3-opus", "claude-3-haiku"]  # 降级链
      skills: ["product-scout", "aeo-content-free"]
      subagents: { allowAgents: ["*"], model: { primary: "claude-3-haiku" } }
      sandbox: { enabled: true, workspaceAccess: "rw" }
      runtime: { type: "embedded" }

⚠️ 常见陷阱:Agent 配置中的 id 字段必须唯一

agents.list 中的每个 Agent 必须有唯一的 id。如果两个 Agent 使用相同的 id,后者会静默覆盖前者,导致难以排查的行为异常:

yaml
# ❌ 错误:两个 Agent 使用相同 id
agents:
  list:
    - id: "main"
      model: { primary: "claude-sonnet-4-20250514" }
    - id: "main"   # 静默覆盖上面的配置!
      model: { primary: "gpt-4.1" }

# ✅ 正确:每个 Agent 有唯一 id
agents:
  list:
    - id: "main"
      model: { primary: "claude-sonnet-4-20250514" }
    - id: "coder"
      model: { primary: "gpt-4.1" }

⚠️ 常见陷阱:workspace 路径中的 ~ 展开

Agent 配置中的 workspace 字段支持 ~ 表示用户主目录,但在 Docker 容器中 ~ 可能指向 /root 而非你期望的用户目录。在容器化部署中,始终使用绝对路径:

yaml
# ❌ 可能出问题:Docker 中 ~ 展开不确定
workspace: "~/my-workspace"

# ✅ 容器中使用绝对路径
workspace: "/home/openclaw/workspace"

⚠️ 常见陷阱:subagents.allowAgents: ["*"] 的安全风险

通配符 "*" 允许子 Agent 使用任意 Agent 配置。在面向公众的 Agent 上,这意味着用户可以通过提示注入让 Agent 以高权限子 Agent 身份执行操作。生产环境中应显式列出允许的子 Agent ID:

yaml
# ⚠️ 开发环境可用,生产环境谨慎
subagents: { allowAgents: ["*"] }

# ✅ 生产环境:显式白名单
subagents: { allowAgents: ["coder", "researcher"] }

6.1.2 Agent 发现与解析

src/agents/agent-scope.ts 承载着 Agent 的发现、解析与配置合并逻辑:

typescript
export function listAgentIds(cfg: OpenClawConfig): string[] {
  // 从配置中提取所有 Agent ID
}

export function resolveDefaultAgentId(cfg: OpenClawConfig): string {
  // 第一个标记 default: true 的 Agent,或列表中的第一个
}

export function resolveSessionAgentIds(params: {
  cfg: OpenClawConfig;
  sessionKey?: string;
}): string[] {
  // 从 Session Key 解析 Agent ID,或回退到默认
}

Agent 解析的核心函数 resolveAgentConfig 执行配置的层级合并:

typescript
export function resolveAgentConfig(
  cfg: OpenClawConfig,
  agentId: string
): ResolvedAgentConfig {
  // 1. 加载全局默认值 (agents.defaults)
  // 2. 加载 Agent 特定配置 (agents.list[id])
  // 3. 合并:Agent 配置覆盖全局默认
}

ResolvedAgentConfig 是合并后的最终结果:

typescript
type ResolvedAgentConfig = {
  name?: string;
  workspace?: string;
  agentDir?: string;
  model?: AgentEntry["model"];
  skills?: AgentEntry["skills"];
  memorySearch?: AgentEntry["memorySearch"];
  humanDelay?: AgentEntry["humanDelay"];
  heartbeat?: AgentEntry["heartbeat"];
  identity?: AgentEntry["identity"];
  groupChat?: AgentEntry["groupChat"];
  subagents?: AgentEntry["subagents"];
  sandbox?: AgentEntry["sandbox"];
  tools?: AgentEntry["tools"];
};

这种分离全局默认和 Agent 特定配置的设计带来了几个好处:

  • 通用参数(如默认模型)只需定义一次
  • 特定 Agent 可以选择性覆盖任何参数
  • 新增 Agent 时只需声明差异部分

6.1.3 Agent 路由绑定

消息如何找到正确的 Agent?AgentRouteBinding 刻画了路由规则:

typescript
type AgentRouteBinding = {
  agentId: string;
  match: AgentBindingMatch;
};

type AgentBindingMatch = {
  channel?: string;         // 通道类型(telegram/discord/...)
  accountId?: string;       // 账户 ID
  peer?: string;            // 对端标识
  guildId?: string;         // Discord 服务器 ID
  teamId?: string;          // Slack 团队 ID
  roles?: string[];         // Discord 角色过滤
};

路由绑定允许精确控制:哪个通道、哪个群组、哪个用户的消息,由哪个 Agent 处理。例如,可以将 Telegram 个人聊天路由到 "main" Agent,将 Discord 某个服务器路由到 "customer-support" Agent。

6.2 工具系统

6.2.1 工具注册与发现

src/agents/pi-tools.ts 负责为每个 Agent 运行时组装可用工具集。OpenClaw 内建了 30+ 工具,覆盖文件操作、代码执行、网络搜索、设备控制等领域:

图 6-2:Agent 工具体系全景

下图按功能域分类展示了 OpenClaw 内建的 30+ 工具。从文件操作(read/write/edit)到命令执行(exec/process)、从网络搜索(web_search/web_fetch)到浏览器控制(browser)、从 Agent 协作(sessions_spawn/sessions_send)到设备控制(canvas/tts),构成了 Agent "四肢"的完整能力图谱。

图 6-2:Agent 工具体系全景

工具组装需要大量上下文信息,CreateOpenClawCodingToolsOptions 携带了完整的运行时环境:

typescript
interface CreateOpenClawCodingToolsOptions {
  agentId?: string;
  sessionKey?: string;
  sessionId?: string;
  runId?: string;
  sandbox?: SandboxContext;
  workspaceDir?: string;
  modelProvider?: string;
  modelId?: string;
  modelContextWindowTokens?: number;
  abortSignal?: AbortSignal;
  // ... 20+ 更多参数
}

6.2.2 工具策略管线

并非所有工具对所有 Agent 都可用。src/agents/tool-policy.tssrc/agents/tool-policy-pipeline.ts 构建了多层策略过滤管线:

图 6-3:工具策略过滤管线

下图展示了工具从"全部可用"到"最终可用"的五级过滤管线。每一层过滤器独立生效:Agent 级策略控制工具白名单/黑名单,Owner-Only 策略限制敏感工具仅 owner 可用,通道级策略根据平台能力过滤(如 WhatsApp 不支持浏览器),Provider 策略排除模型不兼容的工具。

策略管线的执行顺序决定了安全边界的严格程度(安全模型的完整剖析详见第13章):

  1. Agent 级策略:通过 tools.allow 白名单或 tools.deny 黑名单控制
  2. Owner-Only 策略execprocessgateway 等敏感工具仅限 Owner 使用
  3. 通道级策略:语音通道禁用 TTS 回显,防止无限循环
  4. 模型 Provider 策略:某些 Provider 自带搜索功能,禁用 OpenClaw 的 web_search 以避免冲突
  5. 沙箱策略:沙箱模式下限制文件操作范围

6.2.3 exec 工具:权限审批流

exec 是最强大也最危险的工具——它赋予 Agent 执行任意 Shell 命令的能力。src/agents/bash-tools.ts 用精细的权限控制驯服这匹烈马:

typescript
// ACP 客户端的自动审批规则
const SAFE_TOOLS = new Set(["read", "search", "web_search", "memory_search"]);

export async function resolvePermissionRequest(
  params: RequestPermissionRequest,
  deps: PermissionResolverDeps = {}
): Promise<RequestPermissionResponse> {
  // 1. 安全工具自动通过
  // 2. read 限制在 CWD 范围内
  // 3. DANGEROUS_ACP_TOOLS 需要用户确认
  // 4. 30 秒超时(需要 TTY 终端)
}

这套机制落地了渐进式信任——安全操作自动放行,危险操作等待人工审批,超时即自动拒绝。简洁、果断、零妥协。

⚠️ 注意:当 Agent 以 Sub-agent(子 Agent)身份运行时,exec 工具的审批行为与主 Agent 不同。ACP 客户端会对 SAFE_TOOLS 集合内的工具自动放行,但对 DANGEROUS_ACP_TOOLS(如 exec)仍需要父 Agent 或用户确认。如果子 Agent 的任务需要频繁执行命令,建议在父 Agent 配置中明确设置 tools.exec.security: "full" 以减少审批延迟。

6.3 系统提示组装

6.3.1 Prompt 分段架构

src/agents/system-prompt.ts 中的 buildAgentSystemPrompt 是 Agent 行为的核心定义点。它将十多个独立模块组装成一份完整的系统提示:

图 6-4:系统提示组装流程

下图展示了 buildAgentSystemPrompt() 如何根据 promptMode 选择不同的组装路径。full 模式用于主 Agent,包含身份(SOUL.md)、工作规范(AGENTS.md)、用户画像(USER.md)等全部段落;minimal 模式用于 Sub-agent,仅注入工具定义和运行时信息,大幅节省上下文 token。

基于 VitePress 构建