Appearance
第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 的完整字段结构及其与 ToolConfig、SessionOverrides、ModelRef 等关联类型的关系。注意 AgentConfig 是纯声明式配置对象——Agent 的身份、模型选择、工具策略和安全约束全部通过配置字段表达,无需编写任何代码。

一个典型的 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 "四肢"的完整能力图谱。

工具组装需要大量上下文信息,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.ts 和 src/agents/tool-policy-pipeline.ts 构建了多层策略过滤管线:
图 6-3:工具策略过滤管线
下图展示了工具从"全部可用"到"最终可用"的五级过滤管线。每一层过滤器独立生效:Agent 级策略控制工具白名单/黑名单,Owner-Only 策略限制敏感工具仅 owner 可用,通道级策略根据平台能力过滤(如 WhatsApp 不支持浏览器),Provider 策略排除模型不兼容的工具。
策略管线的执行顺序决定了安全边界的严格程度(安全模型的完整剖析详见第13章):
- Agent 级策略:通过
tools.allow白名单或tools.deny黑名单控制 - Owner-Only 策略:
exec、process、gateway等敏感工具仅限 Owner 使用 - 通道级策略:语音通道禁用 TTS 回显,防止无限循环
- 模型 Provider 策略:某些 Provider 自带搜索功能,禁用 OpenClaw 的
web_search以避免冲突 - 沙箱策略:沙箱模式下限制文件操作范围
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。