主题
OpenClaw 源码解读(五)Agent 智能体系统
本文基于 OpenClaw 2026.3.2 源码,深入解读 Agent 智能体系统的架构设计与实现细节。
一、模块概览
Agent 智能体系统是 OpenClaw 的"大脑",负责接收用户消息、调用 AI 模型、执行工具、管理对话上下文,并将回复发送回用户。它是整个系统中最复杂、文件最多的模块,也是连接渠道层和 AI 模型层的核心枢纽。
1.1 核心源码分布
| 目录/文件 | 行数(约) | 职责 |
|---|---|---|
src/agents/pi-embedded-runner/run.ts | ~900+ | 核心运行引擎(最重要的文件) |
src/agents/pi-embedded-runner/run/attempt.ts | ~360+ | 单次 AI 调用尝试(工具循环) |
src/agents/pi-embedded-runner/runs.ts | ~400+ | 运行队列管理与并发控制 |
src/agents/pi-embedded-subscribe.ts | ~1000+ | 流式输出订阅与事件分发 |
src/agents/pi-embedded-runner/system-prompt.ts | ~200+ | 系统提示词组装 |
src/agents/openclaw-tools.ts | ~216 | 内置工具集合注册 |
src/agents/tool-catalog.ts | ~326 | 工具目录定义与 Profile 管理 |
src/agents/tools/common.ts | ~100+ | 工具公共抽象 |
src/agents/context.ts | ~120 | Agent 运行上下文 |
src/agents/compaction.ts | ~400+ | 上下文压缩(长对话摘要化) |
src/agents/model-auth.ts | ~350+ | 模型认证(API Key / OAuth / AWS) |
src/agents/model-fallback.ts | ~350+ | 模型故障转移链 |
src/agents/agent-scope.ts | ~280+ | Agent 配置解析与工作区路径 |
src/agents/defaults.ts | ~280+ | Agent 默认配置与模型解析 |
src/agents/session-write-lock.ts | ~300+ | 会话文件分布式锁 |
src/agents/context-window-guard.ts | ~60+ | 上下文窗口大小保护 |
src/agents/lanes.ts | ~4 | 运行车道定义 |
src/agents/system-prompt.ts | ~100+ | 系统提示词核心逻辑 |
1.2 系统全景
┌───────────────────────────────────────────────────────────────────────┐
│ Agent 智能体系统全景 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 入站消息(来自 Channel 系统) │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Run Queue(运行队列) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Session 1│ │ Session 2│ │ Session 3│ ... │ │
│ │ │ (排队中) │ │ (运行中) │ │ (排队中) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Run Engine(运行引擎) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Model Auth │ │ System │ │ Context │ │ │
│ │ │ 模型认证 │ │ Prompt │ │ Window │ │ │
│ │ │ Profile轮询 │ │ 系统提示词 │ │ 窗口保护 │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Attempt Loop(尝试循环) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────┐ ┌─────────┐ ┌──────┐ ┌───────┐ │ │ │
│ │ │ │Model │→│ Stream │→│ Tool │→│ Model │ │ │ │
│ │ │ │ Call │ │ Result │ │ Exec │ │ Call │ │ │ │
│ │ │ └──────┘ └─────────┘ └──────┘ └───────┘ │ │ │
│ │ │ ↑ │ │ │ │ │ │
│ │ │ └───────────┴───────────┴──────────┘ │ │ │
│ │ │ (工具循环直到无工具调用) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────┼──────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Compaction │ │ Fallback │ │ Auth Profile│ │ │
│ │ │ 上下文压缩 │ │ 模型切换 │ │ Key 轮询 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Subscribe(流订阅系统) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │text_delta│ │tool_call │ │reasoning │ │usage │ │ │
│ │ │ 文本流 │ │ 工具调用 │ │ 推理过程 │ │ 用量统计 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 出站回复(返回 Channel 系统) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘Agent 智能体系统/01-infographic-agent-system-overview-1775150695696.png)
二、运行引擎——最核心的 run.ts
2.1 函数签名
typescript
// src/agents/pi-embedded-runner/run.ts
export async function runEmbeddedPiAgent(
params: RunEmbeddedPiAgentParams
): Promise<RunEmbeddedPiAgentResult>这个函数是整个 Agent 系统的入口。它有 60+ 个参数,是整个项目中参数最多的函数之一。
关键参数分组:
| 分组 | 参数 | 说明 |
|---|---|---|
| 会话标识 | sessionId, sessionKey, sessionFile | 会话文件和键 |
| 消息输入 | prompt, images, trigger | 用户消息和触发方式 |
| 渠道上下文 | messageChannel, agentAccountId, messageTo, groupId | 消息来源渠道 |
| 模型配置 | config, thinkLevel, reasoningLevel, verboseLevel | 模型和推理配置 |
| 流回调 | onPartialReply, onBlockReply, onReasoningStream, onToolResult | 流式输出回调 |
| 控制 | abortSignal, timeoutMs, disableTools | 中断和超时控制 |
2.2 九阶段启动序列
runEmbeddedPiAgent 的执行分为九个阶段:
阶段 1: Workspace 解析
├── resolveAgentWorkspaceDir() → 确定工作区路径
├── resolveAgentDir() → 确定 Agent 数据目录
└── ensureAuthProfileStore() → 加载认证 Profile 存储
阶段 2: 模型解析
├── resolveRunModel() → 解析模型名称(provider/model)
├── resolveContextWindowInfo() → 确定上下文窗口大小
└── evaluateContextWindowGuard() → 检查窗口是否过小
阶段 3: Auth Profile 候选列表
├── resolveAuthProfileCandidates() → 按优先级排列 API Key
└── isProfileInCooldown() → 跳过冷却中的 Profile
阶段 4: API Key 激活
├── resolveApiKeyForProfile() → 获取 Key
├── refreshCopilotToken() → GitHub Copilot 特殊处理
└── applyApiKeyInfo() → 应用到当前运行
阶段 5: 系统提示词构建
├── buildSystemPrompt() → 组装系统提示词
├── runBeforePromptBuild hook → 插件前置钩子
└── 注入渠道特化提示 + 工具说明
阶段 6: 主运行循环
├── runEmbeddedAttempt() → 执行一次 AI 调用
├── 处理工具调用循环
└── 处理流式输出事件
阶段 7: 错误恢复
├── 上下文溢出 → 自动压缩(compaction)
├── 认证失败 → 切换 Auth Profile
├── thinking 不支持 → 降级 think level
└── 模型不可用 → 故障转移到 fallback 模型
阶段 8: 用量统计
├── 累加 token 使用量
└── 记录 prompt/completion 分布
阶段 9: 返回结果
├── 构建 payloads(回复内容列表)
├── 构建 meta(运行元数据)
└── 记录 session 元信息Agent 智能体系统/02-infographic-nine-stage-startup-1775150696538.png)
2.3 主运行循环——核心中的核心
typescript
const MAX_RUN_LOOP_ITERATIONS = resolveMaxRunRetryIterations(profileCandidates.length);
while (true) {
if (runLoopIterations >= MAX_RUN_LOOP_ITERATIONS) {
// 超过最大重试次数 → 返回错误
return { payloads: [{ text: "Request failed...", isError: true }], meta: { ... } };
}
runLoopIterations += 1;
// 1. 执行一次 AI 调用尝试
const attempt = await runEmbeddedAttempt({ ...params });
// 2. 分析尝试结果
if (attempt.aborted) break;
if (attempt.timedOut) break;
// 3. 检测上下文溢出
if (contextOverflowError) {
if (overflowCompactionAttempts < MAX_OVERFLOW_COMPACTION_ATTEMPTS) {
// 自动压缩上下文 → 重试
overflowCompactionAttempts++;
continue;
}
// 压缩次数耗尽 → 工具结果截断 → 重试
if (!toolResultTruncationAttempted) {
toolResultTruncationAttempted = true;
continue;
}
}
// 4. 检测认证错误
if (assistantErrorText && isFailoverErrorMessage(assistantErrorText)) {
const reason = classifyFailoverReason(assistantErrorText);
if (reason === "auth") {
// Copilot Token 刷新 → 重试
if (await maybeRefreshCopilotForAuthError(assistantErrorText, copilotAuthRetry)) {
authRetryPending = true;
continue;
}
// 切换 Auth Profile → 重试
await markAuthProfileFailure({ store, profileId, reason });
const advanced = await advanceAuthProfile();
if (advanced) continue;
}
if (reason === "thinking") {
// thinking 不支持 → 降级
if (downgradeThinking()) continue;
}
}
// 5. 成功 → 返回结果
return { payloads: [...], meta: { ... } };
}2.4 自动恢复机制总结
| 错误类型 | 恢复策略 | 最大尝试次数 |
|---|---|---|
| 上下文溢出 | 自动压缩(Compaction) | 3 次 |
| 工具结果过长 | 截断工具结果 | 1 次 |
| Auth 认证失败 | 切换 Auth Profile | Profile 数量 |
| Copilot Token 过期 | 刷新 Token | 1 次 |
| Thinking 不支持 | 降级 think level | 3 级降级 |
| 模型不可用 | 故障转移到 fallback | fallback 数量 |
Agent 智能体系统/03-infographic-run-loop-recovery-1775150697270.png)
三、Attempt(单次 AI 调用尝试)
3.1 尝试流程
typescript
// src/agents/pi-embedded-runner/run/attempt.ts
export async function runEmbeddedAttempt(params: RunEmbeddedAttemptParams)每次 attempt 代表一次完整的 AI 模型调用,包含工具循环:
调用模型 API(streaming)
│
▼
接收流式响应
├── text_delta → 推送给 subscribe 系统
├── tool_call → 执行工具
│ ├── 工具成功 → 记录结果 → 再次调用模型
│ └── 工具失败 → 记录错误 → 再次调用模型
├── reasoning → 推理过程(thinking)
└── message_end → 本轮结束3.2 工具名称标准化
AI 模型返回的工具调用名称可能有空格、大小写不一致等问题。系统在分发前做了三层标准化:
typescript
function normalizeToolCallNameForDispatch(rawName: string, allowedToolNames?: Set<string>): string {
const trimmed = rawName.trim();
// 1. 精确匹配
if (allowedToolNames.has(trimmed)) return trimmed;
// 2. 标准化后匹配(下划线/连字符统一等)
const normalized = normalizeToolName(trimmed);
if (allowedToolNames.has(normalized)) return normalized;
// 3. 大小写不敏感匹配
const folded = trimmed.toLowerCase();
for (const name of allowedToolNames) {
if (name.toLowerCase() === folded) return name;
}
return trimmed;
}3.3 Tool Call ID 自动修复
某些模型返回的 tool call 可能缺少 ID 或 ID 有空格。系统自动修复:
typescript
function normalizeToolCallIdsInMessage(message: unknown): void {
const usedIds = new Set<string>();
// 收集已有 ID
for (const block of content) {
if (isToolCallBlockType(block.type) && typeof block.id === "string") {
usedIds.add(block.id.trim());
}
}
// 为缺失 ID 的 tool call 生成唯一 ID
let fallbackIndex = 1;
for (const block of content) {
if (!block.id?.trim()) {
let fallbackId = "";
while (!fallbackId || usedIds.has(fallbackId)) {
fallbackId = `call_auto_${fallbackIndex++}`;
}
block.id = fallbackId;
usedIds.add(fallbackId);
}
}
}3.4 Ollama 兼容层
本地 Ollama 模型通过 OpenAI 兼容 API 调用时,需要注入 num_ctx 参数:
typescript
function shouldInjectOllamaCompatNumCtx(params): boolean {
if (params.model.api !== "openai-completions") return false;
if (!isOllamaCompatProvider(params.model)) return false;
return resolveOllamaCompatNumCtxEnabled({ ... });
}
// 自动检测 Ollama 端点
function isOllamaCompatProvider(model): boolean {
if (providerId === "ollama") return true;
// localhost:11434 → 大概率是 Ollama
if (isLocalhost && parsed.port === "11434") return true;
return false;
}Agent 智能体系统/04-infographic-attempt-tool-loop-1775150698128.png)
四、流订阅系统——实时事件分发
4.1 架构
pi-embedded-subscribe.ts 是一个巨大的事件处理器工厂(~1000+ 行),它将 AI 模型的流式输出转化为结构化事件:
AI 模型流式输出
│
├── text_delta (文本增量)
│ ├── stripBlockTags() → 去除 <think>/<final> 标签
│ ├── shouldSkipAssistantText() → 去重检测
│ ├── blockChunker.add() → 分块缓冲
│ └── onPartialReply(text) → 推送到渠道
│
├── text_end (文本结束)
│ ├── finalizeAssistantTexts() → 确认最终文本
│ └── blockChunker.drain() → 刷新分块缓冲
│
├── reasoning_delta (推理增量)
│ └── onReasoningStream(text) → 推送推理过程
│
├── tool_call (工具调用)
│ ├── emitToolSummary() → 推送工具摘要
│ └── onAgentEvent("tool_call", ...) → 推送事件
│
├── tool_result (工具结果)
│ ├── emitToolResultMessage() → 推送结果
│ └── recordAssistantUsage() → 记录用量
│
└── message_end (消息结束)
├── recordAssistantUsage() → 最终用量
└── 状态重置4.2 去重机制
流式输出中存在已知的重复问题(某些 Provider 的 text_end 会重复完整内容):
typescript
const shouldSkipAssistantText = (text: string) => {
if (state.lastAssistantTextMessageIndex !== state.assistantMessageIndex) {
return false;
}
const trimmed = text.trimEnd();
if (trimmed && trimmed === state.lastAssistantTextTrimmed) {
return true; // 完全相同 → 跳过
}
const normalized = normalizeTextForComparison(text);
if (normalized.length > 0 && normalized === state.lastAssistantTextNormalized) {
return true; // 标准化后相同 → 跳过
}
return false;
};4.3 消息工具去重
当 Agent 通过 messaging tool 主动发送了消息后,最终的 block reply 不应重复发送相同内容:
typescript
const MAX_MESSAGING_SENT_TEXTS = 200;
const MAX_MESSAGING_SENT_TARGETS = 200;
const MAX_MESSAGING_SENT_MEDIA_URLS = 200;系统维护三个缓冲区跟踪已发送的文本/目标/媒体 URL,并在 block reply 阶段进行对比抑制。
4.4 分块输出(Block Chunking)
对于不支持流式编辑的渠道(如 IRC、Signal),文本按块发送:
typescript
const blockChunker = blockChunking ? new EmbeddedBlockChunker(blockChunking) : null;Chunker 在收到足够文本后,按照 onBlockReply 回调分块推送,避免一次性发送过长消息。
4.5 Thinking 标签处理
AI 模型的"思考过程"被 <think> 标签包裹,订阅系统负责剥离:
typescript
const stripBlockTags = (text: string, state: { thinking: boolean; final: boolean }) => {
// 1. 处理 <think> 块(状态化,剥离内容)
// 2. 处理 <final> 块(提取最终回复)
// 3. 处理内联代码(避免误剥离代码中的标签)
};4.6 压缩重试协调
当运行引擎触发上下文压缩时,订阅系统需要等待压缩完成:
typescript
const noteCompactionRetry = () => {
state.pendingCompactionRetry += 1;
ensureCompactionPromise();
};
const resolveCompactionRetry = () => {
state.pendingCompactionRetry -= 1;
if (state.pendingCompactionRetry === 0 && !state.compactionInFlight) {
state.compactionRetryResolve?.(); // 释放等待者
}
};Agent 智能体系统/05-infographic-subscribe-events-1775150698879.png)
五、工具系统——Agent 的"手脚"
5.1 工具目录
typescript
// src/agents/tool-catalog.ts
const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [
// ── 搜索与文件 ──
{ id: "web_search", section: "search", profiles: ["coding", "messaging"] },
{ id: "web_fetch", section: "search", profiles: ["coding", "messaging"] },
{ id: "pdf", section: "search", profiles: ["coding"] },
// ── 记忆 ──
{ id: "memory_search", section: "memory", profiles: ["coding"] },
{ id: "memory_get", section: "memory", profiles: ["coding"] },
// ── 会话 ──
{ id: "sessions_list", section: "sessions", profiles: ["coding", "messaging"] },
{ id: "sessions_history", section: "sessions", profiles: ["coding", "messaging"] },
{ id: "sessions_send", section: "sessions", profiles: ["coding", "messaging"] },
{ id: "sessions_spawn", section: "sessions", profiles: ["coding"] },
{ id: "subagents", section: "sessions", profiles: ["coding"] },
{ id: "session_status", section: "sessions", profiles: ["minimal", "coding", "messaging"] },
// ── UI ──
{ id: "browser", section: "ui", profiles: [] },
{ id: "canvas", section: "ui", profiles: [] },
// ── 消息 ──
{ id: "message", section: "messaging", profiles: ["messaging"] },
// ── 自动化 ──
{ id: "cron", section: "automation", profiles: ["coding"] },
{ id: "gateway", section: "automation", profiles: [] },
// ── 节点 ──
{ id: "nodes", section: "nodes", profiles: [] },
// ── Agent ──
{ id: "agents_list", section: "agents", profiles: [] },
// ── 媒体 ──
{ id: "image", section: "media", profiles: ["coding"] },
{ id: "tts", section: "media", profiles: [] },
];5.2 四种 Tool Profile
Profile 控制不同场景下可用的工具集:
| Profile | 工具范围 | 典型场景 |
|---|---|---|
minimal | session_status 仅 | 极简模式,纯对话 |
coding | 搜索+记忆+会话+定时+图像+PDF | 编程助手 |
messaging | 搜索+会话+消息 | 消息转发助手 |
full | 全部工具(无限制) | 完全自主 Agent |
5.3 工具组(Tool Groups)
工具被自动分组,支持配置级别的批量控制:
typescript
const CORE_TOOL_GROUPS = {
"group:openclaw": [...所有核心工具...],
"group:search": ["web_search", "web_fetch", "pdf"],
"group:memory": ["memory_search", "memory_get"],
"group:sessions": ["sessions_list", "sessions_history", "sessions_send", ...],
"group:ui": ["browser", "canvas"],
"group:messaging": ["message"],
"group:automation": ["cron", "gateway"],
"group:nodes": ["nodes"],
"group:agents": ["agents_list"],
"group:media": ["image", "tts"],
};5.4 工具注册流程
typescript
// src/agents/openclaw-tools.ts
export function createOpenClawTools(options?: OpenClawToolsOptions): AnyAgentTool[] {
const tools: AnyAgentTool[] = [
createBrowserTool({ ... }),
createCanvasTool({ ... }),
createNodesTool({ ... }),
createCronTool({ ... }),
createTtsTool({ ... }),
createGatewayTool({ ... }),
createAgentsListTool({ ... }),
createSessionsListTool({ ... }),
createSessionsHistoryTool({ ... }),
createSessionsSendTool({ ... }),
createSessionsSpawnTool({ ... }),
createSubagentsTool({ ... }),
createSessionStatusTool({ ... }),
...(webSearchTool ? [webSearchTool] : []),
...(webFetchTool ? [webFetchTool] : []),
...(imageTool ? [imageTool] : []),
...(pdfTool ? [pdfTool] : []),
];
// 插件工具(来自扩展)
const pluginTools = resolvePluginTools({
context: { ... },
existingToolNames: new Set(tools.map(t => t.name)), // 去重
toolAllowlist: options?.pluginToolAllowlist,
});
return [...tools, ...pluginTools];
}关键设计:
- 条件注册:
webSearchTool、imageTool等依赖配置是否启用 - 去重保护:
existingToolNames确保同名工具不会重复注册 - Allowlist 控制:
pluginToolAllowlist限制插件可注册的工具
Agent 智能体系统/06-infographic-tool-system-1775150699605.png)
六、模型认证——多来源 API Key 解析
6.1 五级认证优先级
typescript
// src/agents/model-auth.ts
export async function resolveApiKeyForProvider(params): Promise<ResolvedProviderAuth> {
// Level 1: 指定 Profile ID → 直接使用该 Profile
if (profileId) {
return await resolveApiKeyForProfile({ store, profileId });
}
// Level 2: Provider Auth Override → 配置级覆盖(如 aws-sdk)
const authOverride = resolveProviderAuthOverride(cfg, provider);
if (authOverride === "aws-sdk") return resolveAwsSdkAuthInfo();
// Level 3: Auth Profile Store → 按优先级遍历 Profile
const order = resolveAuthProfileOrder({ cfg, store, provider, preferredProfile });
for (const candidate of order) {
const resolved = await resolveApiKeyForProfile({ store, profileId: candidate });
if (resolved) return { apiKey: resolved.apiKey, profileId: candidate, ... };
}
// Level 4: 环境变量 → ANTHROPIC_API_KEY, OPENAI_API_KEY 等
const envResolved = resolveEnvApiKey(provider);
if (envResolved) return { apiKey: envResolved.apiKey, ... };
// Level 5: 自定义 Provider 配置 → models.json 中的 apiKey
const customKey = getCustomProviderApiKey(cfg, provider);
if (customKey) return { apiKey: customKey, ... };
throw new Error(`No API key found for provider "${provider}".`);
}6.2 环境变量映射
系统为 30+ 个 Provider 预定义了环境变量映射:
typescript
const envMap: Record<string, string> = {
openai: "OPENAI_API_KEY",
anthropic: "ANTHROPIC_API_KEY", // 也支持 ANTHROPIC_OAUTH_TOKEN
google: "GEMINI_API_KEY",
groq: "GROQ_API_KEY",
xai: "XAI_API_KEY",
openrouter: "OPENROUTER_API_KEY",
minimax: "MINIMAX_API_KEY",
// ... 30+ 个 Provider
};6.3 认证模式
| 模式 | 说明 | 典型 Provider |
|---|---|---|
api-key | 静态 API Key | OpenAI, Anthropic, Google |
oauth | OAuth Token(可刷新) | GitHub Copilot, Anthropic OAuth |
token | Bearer Token | AWS, 自定义 |
aws-sdk | AWS SDK 默认链 | Amazon Bedrock |
6.4 Auth Profile 冷却机制
当某个 Profile 认证失败时,系统将其标记为"冷却"状态,避免短时间内重复尝试失败的 Key:
typescript
// 在运行循环中
if (isProfileInCooldown(authStore, candidate)) {
profileIndex += 1; // 跳过冷却中的 Profile
continue;
}
// 失败时标记
await markAuthProfileFailure({
store: authStore,
profileId,
reason: "auth", // "auth" | "rate_limit" | "timeout"
});冷却期过后,系统会探测性地重试主 Profile(30s 间隔,每次最多等冷却期结束前 2 分钟才探测)。
Agent 智能体系统/07-infographic-model-auth-1775150700479.png)
七、模型故障转移——多模型 Fallback 链
7.1 候选模型解析
typescript
// src/agents/model-fallback.ts
function resolveFallbackCandidates(params): ModelCandidate[] {
const candidates = createModelCandidateCollector(allowlist);
// 1. 主模型(当前使用的)
candidates.addExplicitCandidate(normalizedPrimary);
// 2. 配置的 fallback 列表
for (const raw of modelFallbacks) {
candidates.addExplicitCandidate(resolved.ref);
}
// 3. 配置的默认模型(如果不同于主模型)
if (primary?.provider && primary.model) {
candidates.addExplicitCandidate({ provider: primary.provider, model: primary.model });
}
return candidates;
}7.2 Fallback 执行
typescript
async function runWithModelFallback<T>(params): Promise<ModelFallbackRunResult<T>> {
const candidates = resolveFallbackCandidates({ ... });
const attempts: FallbackAttempt[] = [];
for (const candidate of candidates) {
const result = await runFallbackAttempt({
run: params.run,
provider: candidate.provider,
model: candidate.model,
attempts,
});
if ("success" in result) return result.success; // 成功 → 返回
// 失败 → 记录 → 尝试下一个候选
attempts.push({ provider: candidate.provider, model: candidate.model, error: result.error });
params.onFallbackAttempt?.({ ... });
}
// 全部失败 → 抛出聚合错误
throwFallbackFailureSummary({ attempts, candidates, ... });
}7.3 跨 Provider 智能 Fallback
特殊逻辑:当用户运行的 Provider 与配置的 Provider 不同时,不会盲目使用配置的 fallback:
typescript
if (normalizedPrimary.provider !== configuredPrimary.provider) {
// 只有当前模型已在 fallback 链中时才使用配置的 fallback
const isConfiguredFallback = configuredFallbacks.some(fb =>
sameModelCandidate(resolved.ref, normalizedPrimary)
);
return isConfiguredFallback ? configuredFallbacks : [];
}Agent 智能体系统/08-infographic-model-fallback-1775150701218.png)
八、上下文压缩(Compaction)
8.1 为什么需要压缩
AI 模型有上下文窗口限制(如 128K tokens)。长对话会超出限制,此时需要将旧消息摘要化。
8.2 压缩流程
检测上下文溢出(context overflow error)
│
▼
计算自适应分块比例 computeAdaptiveChunkRatio()
│ → 消息越大,分块越小
▼
安全预处理 stripToolResultDetails()
│ → 不把工具结果详情送进摘要模型
▼
分块 chunkMessagesByMaxTokens()
│ → 按 token 上限切割消息序列
▼
逐块摘要 summarizeChunks()
│ → 每块调用 AI 生成摘要
│ → 后续块附带前序摘要作为上下文
▼
渐进式降级 summarizeWithFallback()
│ → 完整摘要失败 → 排除超大消息 → 部分摘要
│ → 部分摘要也失败 → 纯文字记录
▼
替换旧消息为摘要 → 重试 AI 调用8.3 关键常量
typescript
const SAFETY_MARGIN = 1.3; // Token 估算安全倍率(chars/4 不精确)
const BASE_CHUNK_RATIO = 0.4; // 基础分块比例(窗口的 40%)
const MIN_CHUNK_RATIO = 0.15; // 最小分块比例
const SUMMARIZATION_OVERHEAD_TOKENS = 4096; // 摘要化的额外开销8.4 分阶段摘要
超长对话用 summarizeInStages() 分段处理:
typescript
export async function summarizeInStages(params): Promise<string> {
const parts = normalizeParts(params.parts ?? DEFAULT_PARTS, messages.length);
const splits = splitMessagesByTokenShare(messages, parts);
let summary = params.previousSummary;
for (const split of splits) {
summary = await summarizeWithFallback({
...params,
messages: split,
previousSummary: summary,
});
}
return summary;
}Agent 智能体系统/09-infographic-context-compaction-1775150702017.png)
九、Agent 配置解析
9.1 多 Agent 架构
OpenClaw 支持配置多个 Agent,每个有独立的模型、工作区和工具集:
json5
{
agents: {
default: "main",
entries: [
{
id: "main",
model: "anthropic/claude-sonnet-4-20250514",
workspace: "~/projects",
tools: { profile: "coding" },
},
{
id: "helper",
model: "openai/gpt-4o",
workspace: "~/temp",
tools: { profile: "messaging" },
}
]
}
}9.2 Agent 配置解析链
typescript
// src/agents/agent-scope.ts
export function resolveAgentConfig(cfg, agentId): ResolvedAgentConfig | undefined {
const entry = resolveAgentEntry(cfg, normalizeAgentId(agentId));
return {
name: entry.name,
workspace: entry.workspace,
agentDir: entry.agentDir,
model: entry.model,
skills: entry.skills,
memorySearch: entry.memorySearch,
humanDelay: entry.humanDelay,
heartbeat: entry.heartbeat,
identity: entry.identity,
groupChat: entry.groupChat,
subagents: entry.subagents,
sandbox: entry.sandbox,
tools: entry.tools,
};
}9.3 模型解析——三级 Fallback
Agent 级别模型 → agents.entries[].model
│ (未配置)
▼
默认 Agent 模型 → agents.defaults.model
│ (未配置)
▼
全局默认 → "anthropic/claude-sonnet-4-20250514"fallback 列表的解析也类似:
typescript
export function resolveEffectiveModelFallbacks(params): string[] | undefined {
// 1. Agent 级 fallback
const agentFallbacks = resolveAgentModelFallbacksOverride(cfg, agentId);
// 2. 如果有 session 模型覆盖,还看默认 fallback
if (params.hasSessionModelOverride) {
return agentFallbacks ?? resolveAgentModelFallbackValues(cfg.agents?.defaults?.model);
}
return agentFallbacks;
}9.4 工作区路径解析
typescript
export function resolveAgentWorkspaceDir(cfg, agentId) {
// 1. Agent 显式配置 → agents.entries[].workspace
const configured = resolveAgentConfig(cfg, id)?.workspace;
if (configured) return resolveUserPath(configured);
// 2. 默认 Agent → agents.defaults.workspace
if (id === defaultAgentId) {
const fallback = cfg.agents?.defaults?.workspace;
if (fallback) return resolveUserPath(fallback);
return resolveDefaultAgentWorkspaceDir(process.env); // ~/openclaw-workspace
}
// 3. 非默认 Agent → ~/.openclaw/workspace-<agentId>
return path.join(resolveStateDir(process.env), `workspace-${id}`);
}Agent 智能体系统/10-infographic-agent-config-1775150702974.png)
十、会话写锁——分布式文件锁
10.1 为什么需要锁
同一个 session 可能被多个来源同时访问(Gateway 的多个 Node、CLI 直接调用等)。会话文件(JSONL)需要独占写入保证一致性。
10.2 锁实现
typescript
// src/agents/session-write-lock.ts
type LockFilePayload = {
pid?: number;
createdAt?: string;
starttime?: number; // /proc/pid/stat field 22(进程启动时间)
};锁文件包含 PID 和进程启动时间,用于检测"僵尸锁"(进程已死但锁文件残留)。
10.3 三层清理保障
typescript
const CLEANUP_SIGNALS = ["SIGINT", "SIGTERM", "SIGQUIT", "SIGABRT"] as const;- 信号处理器:捕获退出信号 → 释放所有持有的锁
- 看门狗:定期检查锁持有时间是否超过最大限制(默认 5 分钟)
- 陈旧检测:
inspectSessionLock()检查 PID 是否存活 + 进程启动时间是否匹配
typescript
const DEFAULT_STALE_MS = 30 * 60 * 1000; // 30 分钟无活动 → 视为陈旧
const DEFAULT_MAX_HOLD_MS = 5 * 60 * 1000; // 单次最长持有 5 分钟
const DEFAULT_WATCHDOG_INTERVAL_MS = 60_000; // 看门狗每分钟检查一次
const DEFAULT_TIMEOUT_GRACE_MS = 2 * 60 * 1000; // 超时后 2 分钟宽限期Agent 智能体系统/11-infographic-session-lock-1775150703901.png)
十一、上下文窗口保护
typescript
// src/agents/context-window-guard.ts
export const CONTEXT_WINDOW_HARD_MIN_TOKENS = 16_000;
export const CONTEXT_WINDOW_WARN_BELOW_TOKENS = 32_000;11.1 窗口大小解析——四级优先级
typescript
export function resolveContextWindowInfo(params): ContextWindowInfo {
// 1. 配置中指定的模型上下文窗口 → models.providers[].models[].contextWindow
const fromModelsConfig = ...;
// 2. 模型自报的上下文窗口 → model.contextWindow
const fromModel = ...;
// 3. 默认值
const baseInfo = fromModelsConfig ?? fromModel ?? { tokens: defaultTokens };
// 4. Agent 级上限帽 → agents.defaults.contextTokens
const capTokens = cfg?.agents?.defaults?.contextTokens;
if (capTokens && capTokens < baseInfo.tokens) {
return { tokens: capTokens, source: "agentContextTokens" };
}
return baseInfo;
}11.2 保护级别
| 条件 | 行为 |
|---|---|
| tokens < 16K | shouldBlock: true — 拒绝运行 |
| tokens < 32K | shouldWarn: true — 警告但继续 |
| tokens >= 32K | 正常运行 |
十二、插件钩子系统
Agent 支持两个插件钩子,允许插件在 AI 调用前修改提示词和消息:
typescript
type PromptBuildHookRunner = {
hasHooks: (hookName: "before_prompt_build" | "before_agent_start") => boolean;
// 钩子 1: 构建提示词之前
runBeforePromptBuild: (
event: { prompt: string; messages: unknown[] },
ctx: PluginHookAgentContext,
) => Promise<PluginHookBeforePromptBuildResult | undefined>;
// 钩子 2: Agent 开始运行之前
runBeforeAgentStart: (
event: { prompt: string; messages: unknown[] },
ctx: PluginHookAgentContext,
) => Promise<PluginHookBeforeAgentStartResult | undefined>;
};钩子可以返回修改后的 prompt 和 messages,但不能改变其他参数。
十三、完整数据流
13.1 消息处理全流程
用户消息到达
│
▼
Channel 系统构建 MsgContext
│ → agentId, sessionKey, prompt, images
▼
runEmbeddedPiAgent() 入口
│
├── 阶段 1: 解析工作区和 Agent 目录
├── 阶段 2: 解析模型(provider + model)
├── 阶段 3: 构建 Auth Profile 候选列表
├── 阶段 4: 激活 API Key
├── 阶段 5: 构建系统提示词
│
▼
主运行循环 while(true)
│
├── runEmbeddedAttempt()
│ │
│ ├── 构建消息数组(系统提示 + 历史消息 + 用户消息)
│ ├── 调用 AI 模型 API(streaming)
│ ├── 订阅流事件(pi-embedded-subscribe)
│ │ ├── text_delta → onPartialReply() → 渠道流式输出
│ │ ├── tool_call → 执行工具 → 将结果追加到消息
│ │ ├── reasoning → onReasoningStream()
│ │ └── message_end → 本轮结束
│ └── 返回 attempt 结果
│
├── 成功?
│ ├── 是 → 跳出循环
│ └── 否 → 错误恢复
│ ├── 上下文溢出 → compaction → continue
│ ├── Auth 失败 → advanceAuthProfile → continue
│ ├── Thinking 不支持 → 降级 → continue
│ └── 不可恢复 → 返回错误
│
▼
构建最终结果
├── payloads: [{ text, mediaUrls }] → 回复内容
├── meta: { durationMs, usage, ... } → 运行元数据
└── 返回给 Channel 系统13.2 工具调用循环
AI 模型返回 tool_call
│
▼
normalizeToolCallNameForDispatch()
│ → 修正工具名称(空格/大小写)
▼
normalizeToolCallIdsInMessage()
│ → 修复缺失的 tool call ID
▼
分发到对应工具实现
│ → createBrowserTool / createWebSearchTool / ...
▼
工具执行
├── 成功 → 返回结果文本
└── 失败 → 返回错误文本
│
▼
工具结果追加到消息数组
│
▼
再次调用 AI 模型
│ → 模型决定是否继续调用工具
│
├── 有工具调用 → 重复循环
└── 无工具调用 → 返回最终文本回复十四、设计模式总结
14.1 重试+恢复模式(Retry with Recovery)
主运行循环是一个带有多种恢复策略的重试循环。每种错误类型有专属的恢复路径,全部恢复失败后才返回错误。
14.2 策略模式(Strategy Pattern)
thinkLevel: "low" | "medium" | "high"→ 推理强度策略toolProfile: "minimal" | "coding" | "messaging" | "full"→ 工具集策略deliveryMode→ 出站发送策略
14.3 工厂方法模式(Factory Method)
每个工具通过 createXxxTool() 工厂函数创建,接受上下文参数生成具体实现。
14.4 观察者模式(Observer Pattern)
流订阅系统中 onPartialReply、onToolResult、onReasoningStream 等回调构成观察者网络。
14.5 责任链模式(Chain of Responsibility)
五级认证优先级链、三级模型解析链、七级路由优先级链。
14.6 状态机模式(State Machine)
订阅系统内部维护复杂状态:
IDLE → STREAMING → TOOL_CALLING → STREAMING → DONE
↓
COMPACTING → STREAMING (重试)14.7 渐进式降级模式
- 摘要化:完整摘要 → 部分摘要 → 纯文字记录
- Thinking:high → medium → low → disabled
- Auth Profile:primary → secondary → ... → env fallback
Agent 智能体系统/12-infographic-design-patterns-1775150704880.png)
十五、调试建议
15.1 关键断点位置
| 文件 | 位置 | 断点目的 |
|---|---|---|
pi-embedded-runner/run.ts | while (true) 循环入口 | 观察主运行循环 |
pi-embedded-runner/run.ts | await runEmbeddedAttempt() | 观察 AI 调用参数 |
pi-embedded-runner/run.ts | contextOverflowError 判断 | 压缩触发时机 |
pi-embedded-runner/run.ts | advanceAuthProfile() | Auth Profile 切换 |
pi-embedded-runner/run/attempt.ts | 函数入口 | 单次尝试参数 |
pi-embedded-subscribe.ts | pushAssistantText() | 文本流推送 |
pi-embedded-subscribe.ts | emitToolResultMessage() | 工具结果推送 |
compaction.ts | summarizeChunks() | 压缩过程 |
model-auth.ts | resolveApiKeyForProvider() | 认证解析 |
model-fallback.ts | runFallbackAttempt() | 模型切换 |
openclaw-tools.ts | createOpenClawTools() | 工具注册 |
tool-catalog.ts | resolveCoreToolProfilePolicy() | Profile 应用 |
15.2 测试命令
bash
# Agent 核心测试
pnpm test -- --reporter verbose src/agents/pi-embedded-runner/
# 订阅系统测试
pnpm test -- --reporter verbose src/agents/pi-embedded-subscribe.test.ts
# 压缩测试
pnpm test -- --reporter verbose src/agents/compaction.test.ts
# 模型认证测试
pnpm test -- --reporter verbose src/agents/model-auth.test.ts
# 模型故障转移测试
pnpm test -- --reporter verbose src/agents/model-fallback.test.ts
# 工具目录测试
pnpm test -- --reporter verbose src/agents/tool-catalog.test.ts
# Agent 配置解析测试
pnpm test -- --reporter verbose src/agents/agent-scope.test.ts
# 上下文窗口保护测试
pnpm test -- --reporter verbose src/agents/context-window-guard.test.ts十六、文件依赖图
src/agents/context-window-guard.ts ← 零外部依赖
src/agents/lanes.ts ← 零外部依赖
src/agents/agent-scope.ts ← 依赖 config 模块
↑
src/agents/defaults.ts ← 依赖 agent-scope
↑
src/agents/model-auth.ts ← 依赖 agent-scope + config
↑
src/agents/model-fallback.ts ← 依赖 model-auth + defaults
↑
src/agents/tool-catalog.ts ← 零外部依赖(纯定义)
↑
src/agents/openclaw-tools.ts ← 依赖 tool-catalog + 各工具实现
↑
src/agents/compaction.ts ← 依赖 model 调用
↑
src/agents/pi-embedded-subscribe.ts ← 依赖 compaction + 工具分发
↑
src/agents/pi-embedded-runner/run/attempt.ts ← 依赖 subscribe + tools
↑
src/agents/pi-embedded-runner/run.ts ← 依赖一切(主入口)
↑
src/agents/pi-embedded-runner/runs.ts ← 依赖 run.ts(队列管理)
src/agents/session-write-lock.ts ← 独立模块(文件锁)
src/agents/system-prompt.ts ← 依赖 config + agent-scope十七、设计洞察
17.1 为什么运行引擎如此复杂
run.ts 有 900+ 行,因为它需要处理真实世界中 AI API 的所有"不靠谱"情况:
- API Key 过期:需要自动切换到备用 Key
- 模型不可用:需要自动切换到备用模型
- 上下文爆炸:需要自动压缩历史消息
- Thinking 不支持:需要自动降级推理级别
- Copilot Token 刷新:OAuth Token 有有效期
- 工具调用异常:工具名错误、ID 缺失、结果过长
这些都不是"边界情况"——它们在生产环境中频繁发生。运行引擎的核心价值就是自动处理这些问题,让用户无感知。
17.2 为什么工具名需要三层标准化
不同 AI 模型对工具名的处理差异很大:
- GPT 系列可能返回
webSearch而注册名是web_search - Claude 可能在工具名前后加空格
- 某些模型可能全大写
WEB_SEARCH
三层标准化(精确 → 规范化 → 大小写不敏感)以最小侵入方式解决这些兼容性问题。
17.3 订阅系统为什么需要去重
AI 流式 API 有已知问题:
text_end可能重复发送完整内容- 迟到的
text_end可能在message_end之后到达 - 某些 Provider 在重连后重放部分流
订阅系统通过 shouldSkipAssistantText() 的双重去重(原始文本 + 标准化文本)确保用户不会收到重复消息。
17.4 Auth Profile 冷却 vs 探测的平衡
当主 Profile 认证失败后,系统会切换到 fallback Profile。但不能永远不尝试主 Profile——它可能只是临时限流。解决方案:
主 Profile 失败 → 标记冷却(cooldown)
│
▼ 使用 fallback Profile
│
每 30s 探测主 Profile(throttled probe)
│
├── 探测成功 → 切回主 Profile
└── 探测失败 → 继续使用 fallback
│
冷却期结束(距离到期 < 2 分钟时开始探测)
│
▼ 自动切回主 Profile17.5 上下文压缩的"安全优先"设计
压缩过程有多处安全考虑:
stripToolResultDetails()→ 工具结果详情不进入摘要模型(防信息泄露)SAFETY_MARGIN = 1.3→ Token 估算加 30% 余量(chars/4 低估多字节字符)isOversizedForSummary()→ 超大消息直接标注跳过(防摘要模型溢出)- 三级降级 → 完整摘要失败不会直接报错,而是尝试部分摘要