OpenClaw (原ClawdBot) 开源项目深度分析

深入剖析 OpenClaw (原ClawdBot) 项目的 Agent 实现原理,帮助开发者理解 LLM Agent 的核心机制,包括 Agent Loop、Tool Calling、记忆管理、上下文管理等关键技术。

- 次阅读

🦞 OpenClaw (原ClawdBot) 开源项目深度分析

本文档是对 OpenClaw (原 ClawdBot) 项目的全面技术分析,旨在帮助开发者深入理解其实现原理。

项目信息详情
GitHub 仓库https://github.com/openclaw/openclaw
当前版本v2026.1.30
许可证MIT
最新 Commit476f367cf16081acd144048ee61198e58d15df21
历史名称Clawdbot → Moltbot → OpenClaw

📝 项目更名历史:

  • Clawdbot (2025.11.25 - 2026.1.27) — 最初的项目名称
  • Moltbot (2026.1.27 - ?) — 第一次更名,寓意"蜕变"
  • OpenClaw (当前) — 最终更名为开源版本名称

目录


一、项目概述

OpenClaw 是一个功能强大的个人 AI 助手平台,它允许用户在自己的设备上运行 AI 助手,并通过多种即时通讯渠道(WhatsApp、Telegram、Discord、Slack 等)与之交互。

1.1 核心价值

特性说明
本地优先运行在用户自己的设备上,数据不离开本地
多渠道统一一个 AI 助手,多个聊天平台
可扩展性强插件化架构,易于扩展新功能
类型安全TypeScript + Zod 保证代码质量

1.2 技术栈

  • 语言: TypeScript (ESM)
  • 运行时: Node.js 22+ / Bun
  • 包管理: pnpm
  • 构建工具: TypeScript Compiler
  • 测试框架: Vitest
  • 代码规范: Oxlint + Oxfmt

二、技术架构全景

┌───────────────────────────────────────────────────────────────────────────┐
│                              用户交互层                                    │
├─────────────┬─────────────┬─────────────┬─────────────┬───────────────────┤
│  Telegram   │   WhatsApp  │   Discord   │    Slack    │   其他渠道...      │
└─────────────┴─────────────┴─────────────┴─────────────┴───────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│                          Gateway Server (网关服务器)                       │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐   │
│  │  WebSocket  │  │   HTTP API  │  │ Cron 调度   │  │  Hooks 系统     │   │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────────┘   │
└───────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│                           Agent 代理系统                                   │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐   │
│  │ 会话管理    │  │  工具执行   │  │  模型切换   │  │  记忆/上下文     │   │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────────┘   │
└───────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│                            模型提供商                                      │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌─────────┐ │
│  │ Anthropic │  │  OpenAI   │  │  Google   │  │  Ollama   │  │  其他   │ │
│  └───────────┘  └───────────┘  └───────────┘  └───────────┘  └─────────┘ │
└───────────────────────────────────────────────────────────────────────────┘

2.1 项目目录结构

openclaw/
├── src/                    # 核心源代码
   ├── cli/               # CLI 命令实现
   ├── commands/          # 子命令模块
   ├── gateway/           # 网关服务器
   ├── agents/            # AI Agent 系统
   ├── channels/          # 渠道抽象层
   ├── telegram/          # Telegram 渠道
   ├── discord/           # Discord 渠道
   ├── slack/             # Slack 渠道
   ├── signal/            # Signal 渠道
   ├── imessage/          # iMessage 渠道
   ├── plugins/           # 插件系统
   ├── config/            # 配置管理
   ├── infra/             # 基础设施
   └── media/             # 媒体处理
├── extensions/            # 扩展插件
   ├── msteams/          # Microsoft Teams
   ├── matrix/           # Matrix 协议
   ├── voice-call/       # 语音通话
   └── memory-lancedb/   # LanceDB 记忆
├── skills/               # 内置 Skills (52+)
├── apps/                 # 原生应用
   ├── ios/             # iOS App
   ├── macos/           # macOS App
   └── android/         # Android App
├── docs/                 # 文档
├── ui/                   # Web UI
└── test/                 # 测试文件

三、程序启动与入口链路

本章聚焦于回答一个核心问题:当用户运行 openclaw gateway run 后,发生了什么?

我们将按照代码实际执行顺序,追踪从命令行输入到 Agent 接收消息的完整链路。

3.1 启动流程总览

┌─────────────────────────────────────────────────────────────────────────────┐
│                     $ openclaw gateway run --port 18789                      │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│  ① 入口层 (src/entry.ts)                                                     │
│     • 设置进程标题 process.title = "openclaw"                                │
│     • 环境变量规范化 normalizeEnv()                                          │
│     • 实验性警告抑制                                                         │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│  ② CLI 路由 (src/cli/run-main.ts)                                           │
│     • 加载 .env 配置                                                         │
│     • 运行时版本检查 assertSupportedRuntime()                                │
│     • 构建命令程序 buildProgram()                                            │
│     • 路由到 gateway run 子命令                                              │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│  ③ Gateway 启动 (src/gateway/server.impl.ts)                                │
│     • 创建 HTTP Server + WebSocket Server                                    │
│     • 初始化 NodeRegistry(设备节点管理)                                     │
│     • 初始化 ChannelManager(消息渠道管理)                                   │
│     • 启动 CronService(定时任务)                                           │
│     • 绑定 WebSocket 处理器                                                  │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│  ④ 渠道连接 (Telegram/WhatsApp/Discord/...)                                 │
│     • ChannelManager 启动配置的渠道                                          │
│     • 各渠道建立与平台的连接(Bot API / Web Socket 等)                       │
│     • 开始监听消息                                                           │
└─────────────────────────────────────────────────────────────────────────────┘
                        ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─  用户发送消息
┌─────────────────────────────────────────────────────────────────────────────┐
│  ⑤ 消息到达 → Agent 调用                                                     │
│     • 渠道接收消息,路由到 Gateway                                           │
│     • Gateway 调用 runEmbeddedPiAgent()                                      │
│     • 进入 Agent 核心循环(第四章详解)                                       │
└─────────────────────────────────────────────────────────────────────────────┘

3.2 入口层初始化

文件: src/entry.ts

这是整个程序的第一个执行点,负责环境准备:

#!/usr/bin/env node
import { spawn } from "node:child_process";
import process from "node:process";
import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js";
import { isTruthyEnvValue, normalizeEnv } from "./infra/env.js";
import { installProcessWarningFilter } from "./infra/warnings.js";

// 设置进程名称(便于 ps/top 识别)
process.title = "openclaw";

// 过滤 Node.js 实验性功能警告
installProcessWarningFilter();

// 规范化环境变量(处理大小写、别名等)
normalizeEnv();

入口层做了什么?

步骤函数作用
1process.title = "openclaw"设置进程名,便于系统监控
2installProcessWarningFilter()抑制 Node.js 实验性 API 警告
3normalizeEnv()统一环境变量格式
4parseCliProfileArgs()解析 --profile 参数

3.3 CLI 命令路由

文件: src/cli/run-main.ts

入口层完成后,进入 CLI 层进行命令解析和路由:

export async function runCli(argv: string[] = process.argv) {
  // 1. Windows 特殊处理
  const normalizedArgv = stripWindowsNodeExec(argv);
  
  // 2. 加载 .env 文件
  loadDotEnv({ quiet: true });
  normalizeEnv();
  
  // 3. 确保 openclaw 在 PATH 中
  ensureOpenClawCliOnPath();

  // 4. 运行时版本检查(Node.js 22+)
  assertSupportedRuntime();

  // 5. 尝试快速路由(某些命令不需要完整初始化)
  if (await tryRouteCli(normalizedArgv)) {
    return;
  }

  // 6. 捕获控制台输出到结构化日志
  enableConsoleCapture();

  // 7. 构建命令程序(延迟加载)
  const { buildProgram } = await import("./program.js");
  const program = buildProgram();
  
  // 8. 解析命令并执行
  await program.parseAsync(normalizedArgv);
}

设计亮点

  1. 延迟加载: 子命令按需 import(),加快启动速度
  2. 跨平台兼容: Windows 参数处理有特殊逻辑
  3. 快速路由: 简单命令无需加载完整框架

当用户执行 openclaw gateway run 时,CLI 路由到 src/commands/gateway/run.ts

3.4 Gateway Server 启动

文件: src/gateway/server.impl.ts

这是整个系统的控制中心gateway run 命令最终会调用:

export async function startGatewayServer(
  port = 18789,
  opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
  // 1. 创建核心运行时状态
  const {
    canvasHost,
    httpServer,
    wss,            // WebSocket Server
    clients,        // 连接的客户端
    broadcast,      // 广播函数
  } = await createGatewayRuntimeState({
    port,
    hostname: opts.hostname,
    ...
  });
  
  // 2. 节点注册表(管理连接的设备:手机、其他网关)
  const nodeRegistry = new NodeRegistry();
  
  // 3. Cron 服务(定时任务调度)
  let cronState = buildGatewayCronService({
    config: cfg,
    onTrigger: (job) => handleCronTrigger(job),
    ...
  });
  
  // 4. 渠道管理器(管理所有消息渠道)
  const channelManager = createChannelManager({
    config: cfg,
    onMessage: (msg) => routeMessageToAgent(msg),
    ...
  });
  
  // 5. 绑定 WebSocket 处理器
  attachGatewayWsHandlers({
    wss,
    clients,
    nodeRegistry,
    channelManager,
    ...
  });
  
  // 6. 启动渠道连接
  await channelManager.startAll();
  
  return { httpServer, wss, channelManager, ... };
}

Gateway 核心组件

组件文件职责
NodeRegistrysrc/gateway/node-registry.ts管理连接的设备节点
ChannelManagersrc/channels/manager.ts启动/停止/管理消息渠道
CronServicesrc/gateway/cron.ts定时任务调度
WebSocket Serversrc/gateway/ws-handlers.ts实时双向通信
ExecApprovalManagersrc/gateway/exec-approval.ts命令执行审批

3.5 消息到达与 Agent 调用

当 Gateway 启动完成后,各渠道开始监听消息。以 Telegram 为例:

消息流转路径

用户在 Telegram 发送消息
┌─────────────────────────────────────────┐
│  Telegram Bot API (Grammy)              │
│  src/telegram/bot.ts                    │
└─────────────────────────────────────────┘
         │ bot.on("message", handler)
┌─────────────────────────────────────────┐
│  消息处理中间件                          │
│  • 节流 (apiThrottler)                  │
│  • 串行化 (sequentialize)               │
│  • 权限检查 (allowlist)                 │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  ChannelManager.routeMessage()          │
│  统一消息格式,路由到 Agent              │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  runEmbeddedPiAgent()                   │
│  src/agents/pi-embedded-runner/run.ts   │
│  ────────────────────────────────────── │
│  进入 Agent 核心循环(第四章详解)        │
└─────────────────────────────────────────┘

关键代码片段(Telegram 消息处理):

// src/telegram/bot.ts
export function createTelegramBot(opts: TelegramBotOptions) {
  const bot = new Bot(opts.token);
  
  // 节流:避免触发 Telegram API 限制
  bot.api.config.use(apiThrottler());
  
  // 串行化:同一聊天的消息按顺序处理
  bot.use(sequentialize(getTelegramSequentialKey));
  
  // 消息处理
  bot.on("message:text", async (ctx) => {
    const message = ctx.message.text;
    const chatId = ctx.chat.id;
    
    // 路由到 Agent
    await routeToAgent({
      channel: "telegram",
      chatId,
      message,
      replyFn: (text) => ctx.reply(text),
    });
  });
  
  return bot;
}

渠道抽象机制

所有渠道都实现统一的接口,使得 Agent 无需关心消息来源:

// 统一的消息格式
interface ChannelMessage {
  channel: ChatChannelId;     // "telegram" | "whatsapp" | ...
  chatId: string;             // 聊天标识
  userId: string;             // 用户标识
  message: string;            // 消息内容
  replyFn: (text: string) => Promise<void>;  // 回复函数
}

小结:本章梳理了从 openclaw gateway run 到消息进入 Agent 的完整链路。理解了这条主线后,下一章我们将深入 Agent 内部,看看它是如何处理消息、调用工具、生成回复的。


四、Agent 核心实现

OpenClaw 本质上是一个 AI Agent 系统,其核心在于实现了一个完整的 Agent 循环。这一节我们深入分析 Agent 的核心实现机制。

4.1 Agent 架构总览

┌─────────────────────────────────────────────────────────────────────────┐
                         用户消息输入                                     
                    (Telegram/WhatsApp/CLI/...)                          
└─────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────┐
                    runEmbeddedPiAgent()                                  
               src/agents/pi-embedded-runner/run.ts                       
  ┌─────────────────────────────────────────────────────────────────┐    
     会话队列管理 (sessionLane / globalLane)                           
     模型解析 & API Key 管理                                           
     上下文溢出处理  自动压缩                                         
  └─────────────────────────────────────────────────────────────────┘    
└─────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────┐
                    runEmbeddedAttempt()                                  
            src/agents/pi-embedded-runner/run/attempt.ts                  
  ┌─────────────────────────────────────────────────────────────────┐    
     Skills 加载                                                       
     Bootstrap 文件注入                                                
     工具创建                                                          
     系统提示词构建                                                    
     createAgentSession() - 创建 Agent 会话                            
     session.prompt() - 执行 LLM 调用                                  
  └─────────────────────────────────────────────────────────────────┘    
└─────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────┐
              subscribeEmbeddedPiSession() - 事件订阅                     
                src/agents/pi-embedded-subscribe.ts                       
  ┌─────────────────────────────────────────────────────────────────┐    
    事件类型:                                                           
     message_start/update/end   - 消息流处理                           
     tool_execution_start/update/end - 工具调用处理                    
     agent_start/end            - Agent 生命周期                       
  └─────────────────────────────────────────────────────────────────┘    
└─────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────┐
                       LLM 模型调用层                                     
               @mariozechner/pi-ai (streamSimple)                         
  ┌─────────────────────────────────────────────────────────────────┐    
    支持的提供商:                                                       
     Anthropic (Claude)                                                
     OpenAI (GPT-4/4o)                                                 
     Google (Gemini)                                                   
     Ollama (本地模型)                                                 
  └─────────────────────────────────────────────────────────────────┘    
└─────────────────────────────────────────────────────────────────────────┘

4.2 Agent 运行主入口

文件: src/agents/pi-embedded-runner/run.ts

这是 Agent 运行的主入口函数:

export async function runEmbeddedPiAgent(
  params: RunEmbeddedPiAgentParams,
): Promise<EmbeddedPiRunResult> {
  // 1. 创建会话队列(确保同一会话的消息串行处理)
  const sessionLane = resolveSessionLane(params.sessionKey?.trim() || params.sessionId);
  const globalLane = resolveGlobalLane(params.lane);
  
  return enqueueSession(() =>
    enqueueGlobal(async () => {
      // 2. 解析模型
      const { model, authStorage, modelRegistry } = resolveModel(
        provider, modelId, agentDir, params.config
      );
      
      // 3. 执行单次 Agent 运行
      const attempt = await runEmbeddedAttempt({...});
      
      // 4. 处理上下文溢出 → 自动压缩后重试
      if (isContextOverflowError(errorText)) {
        await compactEmbeddedPiSessionDirect({...});
        continue;  // 压缩后重试
      }
      
      // 5. 返回结果
      return { payloads, meta };
    })
  );
}

关键设计点:

机制说明
队列串行化同一会话的消息按序处理,避免并发冲突
上下文溢出自动压缩检测到溢出时自动压缩历史,然后重试

4.3 Agent 会话创建与执行

文件: src/agents/pi-embedded-runner/run/attempt.ts

这是单次 Agent 执行的核心逻辑:

export async function runEmbeddedAttempt(
  params: EmbeddedRunAttemptParams,
): Promise<EmbeddedRunAttemptResult> {
  // 1. 加载 Skills
  const skillEntries = loadWorkspaceSkillEntries(effectiveWorkspace);
  const skillsPrompt = resolveSkillsPromptForRun({...});
  
  // 2. 加载 Bootstrap 上下文文件(如 AGENTS.md)
  const { bootstrapFiles, contextFiles } = await resolveBootstrapContextForRun({...});
  
  // 3. 创建工具集
  const toolsRaw = createOpenClawCodingTools({
    messageProvider: params.messageChannel,
    sessionKey: params.sessionKey,
    workspaceDir: effectiveWorkspace,
    config: params.config,
    abortSignal: runAbortController.signal,
  });
  
  // 4. 构建系统提示词
  const systemPromptText = buildEmbeddedSystemPrompt({
    workspaceDir: effectiveWorkspace,
    toolNames: tools.map(t => t.name),
    skillsPrompt,
    contextFiles,
    runtimeInfo: {...},
  });
  
  // 5. 创建 Agent 会话(核心!)
  const { session } = await createAgentSession({
    cwd: resolvedWorkspace,
    agentDir,
    model: params.model,
    tools: builtInTools,
    customTools: allCustomTools,
    sessionManager,
  });
  
  // 6. 设置流式调用函数
  session.agent.streamFn = streamSimple;
  
  // 7. 订阅会话事件
  const subscription = subscribeEmbeddedPiSession({
    session: activeSession,
    runId: params.runId,
    onToolResult: params.onToolResult,
    onBlockReply: params.onBlockReply,
    onAgentEvent: params.onAgentEvent,
  });
  
  // 8. 执行 prompt(调用 LLM)—— 这是核心!
  await activeSession.prompt(effectivePrompt);
  
  // 9. 返回结果
  return {
    assistantTexts,
    toolMetas,
    lastAssistant,
  };
}

4.4 上下文工程管理

Agent 会话创建后,上下文管理是保证长对话正常运行的核心机制。

4.4.1 架构总览

┌─────────────────────────────────────────────────────────────────────────┐
│                        上下文工程管理架构                                 │
└─────────────────────────────────────────────────────────────────────────┘
        ┌──────────────────────────┼──────────────────────────┐
        ▼                          ▼                          ▼
┌───────────────────┐   ┌───────────────────┐   ┌───────────────────────┐
│   会话存储层       │   │   Token 管理层     │   │    压缩策略层          │
│  (.jsonl 文件)    │   │  (估算与限制)      │   │  (多级裁剪)            │
└───────────────────┘   └───────────────────┘   └───────────────────────┘
        │                          │                          │
        │                          │           ┌──────────────┴──────────┐
        │                          │           ▼                         ▼
        │                          │   ┌──────────────┐     ┌──────────────────┐
        │                          │   │ Context      │     │ Compaction       │
        │                          │   │ Pruning      │     │ Safeguard        │
        │                          │   │ (软裁剪)     │     │ (摘要生成)        │
        │                          │   └──────────────┘     └──────────────────┘
        ▼                          ▼
┌───────────────────┐   ┌───────────────────┐
│  Memory Search    │   │  History Limit    │
│  (语义检索)       │   │  (轮次限制)       │
└───────────────────┘   └───────────────────┘

4.4.2 会话存储机制

存储格式: JSON Lines (.jsonl)

存储路径: ~/.openclaw/sessions/{sessionId}.jsonl

每行是一个独立的 JSON 对象:

{"message": {"role": "user", "content": "你好", "timestamp": 1234567890}}
{"message": {"role": "assistant", "content": [{"type": "text", "text": "你好!"}], "timestamp": 1234567891}}
{"message": {"role": "toolResult", "content": [{"type": "text", "text": "执行结果..."}], "toolCallId": "xxx"}}

设计优势:

  • 增量写入: 每条消息追加写入,避免重写整个文件
  • 容错性强: 单行损坏不影响其他消息
  • 流式读取: 可以逐行读取处理大文件

4.4.3 Token 管理与估算

文件: src/agents/compaction.ts

import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { estimateTokens, generateSummary } from "@mariozechner/pi-coding-agent";
import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";

export const BASE_CHUNK_RATIO = 0.4;
export const MIN_CHUNK_RATIO = 0.15;
export const SAFETY_MARGIN = 1.2; // 20% buffer for estimateTokens() inaccuracy

export function estimateMessagesTokens(messages: AgentMessage[]): number {
  return messages.reduce((sum, message) => sum + estimateTokens(message), 0);
}

关键常量:

常量说明
DEFAULT_CONTEXT_TOKENS200,000默认上下文窗口
BASE_CHUNK_RATIO0.4基础分块比例
MIN_CHUNK_RATIO0.15最小分块比例
SAFETY_MARGIN1.220% 安全边界
CHARS_PER_TOKEN_ESTIMATE4字符/Token 估算比
IMAGE_CHAR_ESTIMATE8,000图片字符估算值

4.4.4 上下文窗口保护

文件: src/agents/context-window-guard.ts

CONTEXT_WINDOW_HARD_MIN_TOKENS = 16_000;   // 硬性最小值
CONTEXT_WINDOW_WARN_BELOW_TOKENS = 32_000; // 警告阈值

// 上下文信息解析优先级:
// 1. modelsConfig (配置文件中的模型配置)
// 2. model (模型元数据)
// 3. agentContextTokens (代理配置)
// 4. default (默认值: 200,000)

4.4.5 Context Pruning (上下文裁剪)

这是核心的实时裁剪机制,在每次 Agent 运行时执行。

文件: src/agents/pi-extensions/context-pruning/pruner.ts

const CHARS_PER_TOKEN_ESTIMATE = 4;
const IMAGE_CHAR_ESTIMATE = 8_000;  // 图片按 8000 字符计算

function estimateMessageChars(message: AgentMessage): number {
  if (message.role === "user") {
    // 处理文本和图片
  }
  if (message.role === "assistant") {
    // 处理文本、thinking、toolCall
  }
  if (message.role === "toolResult") {
    // 处理工具结果(文本+图片)
  }
}

两级裁剪策略

软裁剪 (Soft Trim):

// 配置参数
softTrimRatio: 0.3,           // 占用 30% 上下文时触发
softTrim: {
  maxChars: 4_000,            // 超过 4000 字符触发裁剪
  headChars: 1_500,           // 保留开头 1500 字符
  tailChars: 1_500,           // 保留结尾 1500 字符
}

// 裁剪后的格式
"[前 1500 字符]\n\n[... 中间内容已裁剪 ...]\n\n[后 1500 字符]"

硬清除 (Hard Clear):

// 配置参数
hardClearRatio: 0.5,              // 占用 50% 上下文时触发
minPrunableToolChars: 50_000,     // 最小可裁剪字符数
hardClear: {
  enabled: true,
  placeholder: "[Old tool result content cleared]",
}

裁剪流程图:

计算当前上下文占用比例 (ratio)
   ratio < 0.3 ─────────→ 不裁剪
  0.3 ≤ ratio < 0.5 ────→ 软裁剪工具结果
              │            (保留头尾)
    ratio ≥ 0.5 ────────→ 硬清除工具结果
                          (替换为占位符)
    保护首个用户消息之前的内容
    (bootstrap files)

4.4.6 Compaction Safeguard (压缩安全机制)

当会话需要进行主动压缩(生成历史摘要)时触发。

文件: src/agents/pi-extensions/compaction-safeguard.ts

// 触发时机: session_before_compact 事件
api.on("session_before_compact", async (event, ctx) => {
  // 1. 计算文件操作摘要
  const fileSummary = computeFileSummary(messages);
  // 包含:读取的文件、修改的文件、创建的文件
  
  // 2. 收集工具失败记录
  const failedTools = collectToolFailures(messages);
  
  // 3. 如果新内容超过历史预算,裁剪旧消息
  const pruned = pruneHistoryForContextShare({
    messages,
    maxContextTokens,
    maxHistoryShare: 0.5,  // 历史最多占 50%
  });
  
  // 4. 为被裁剪的消息生成摘要
  const droppedSummary = await summarizeInStages({
    messages: pruned.droppedMessagesList,
  });
  
  // 5. 合并生成最终历史摘要
  return {
    additionalHistory: [fileSummary, failedTools, droppedSummary].join("\n"),
  };
});

4.4.7 分阶段摘要生成 (summarizeInStages)

文件: src/agents/compaction.ts

export function splitMessagesByTokenShare(
  messages: AgentMessage[],
  parts = DEFAULT_PARTS,
): AgentMessage[][] {
  // 将消息按 token 数量均匀分成多份
  const totalTokens = estimateMessagesTokens(messages);
  const targetTokens = totalTokens / normalizedParts;
  // ...
}

export function chunkMessagesByMaxTokens(
  messages: AgentMessage[],
  maxTokens: number,
): AgentMessage[][] {
  // 确保每个块不超过 maxTokens
  // 处理超大消息:单独成块
}

摘要生成流程:

原始消息列表 (N 条)
  ┌──────────────────┐
  │ splitMessagesByTokenShare │
  │   (按 token 均分)         │
  └──────────────────┘
    ┌────┴────┐
    ▼         ▼
┌────────┐ ┌────────┐
│ Part 1 │ │ Part 2 │  (默认分 2 份)
└────────┘ └────────┘
    │         │
    ▼         ▼
┌────────┐ ┌────────┐
│Summary │ │Summary │  (各自生成摘要)
│   1    │ │   2    │
└────────┘ └────────┘
    │         │
    └────┬────┘
  ┌──────────────────┐
  │  Merge Summaries │
  │  (合并为最终摘要) │
  └──────────────────┘
    最终历史摘要

4.4.8 历史轮次限制

文件: src/agents/pi-embedded-runner/history.ts

export function limitHistoryTurns(
  messages: AgentMessage[],
  limit: number,
): AgentMessage[] {
  // 保留最后 N 个用户轮次
  // 从后向前扫描 user 消息
  // 超过 limit 的轮次被丢弃
}

// 配置示例
config.channels.telegram.dmHistoryLimit = 50;  // 全局限制
config.channels.telegram.dms["user123"].historyLimit = 100;  // 用户特定限制

4.5 事件驱动的消息处理

文件: src/agents/pi-embedded-subscribe.ts

Agent 使用事件驱动模型处理 LLM 的流式响应。这是一个发布-订阅模式的实现。

4.5.1 为什么需要事件驱动?

当 LLM 生成回复时,它不是一次性返回完整内容,而是逐字符/逐 Token 流式输出。为了实时处理这些内容,系统需要:

  1. 订阅 LLM 的输出流
  2. 监听 各种事件(开始、更新、结束、工具调用等)
  3. 分发 事件到对应的处理器
┌─────────────────────────────────────────────────────────────────────┐
                       事件驱动消息处理流程                            
└─────────────────────────────────────────────────────────────────────┘

    LLM 流式输出
         
         
┌─────────────────┐
  session.subscribe()     订阅会话事件
└────────┬────────┘
         
          事件流
   ┌─────┴─────┬─────────┬─────────┬─────────┐
                                         
┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐
message  message  message   tool   agent 
_start   _update  _end    _exec   _end  
└──────┘  └──────┘  └──────┘  └──────┘  └──────┘
                                         
                                         
┌──────────────────────────────────────────────────┐
          switch (evt.type) { ... }                  事件分发器
└──────────────────────────────────────────────────┘
                                         
                                         
处理器1     处理器2    处理器3   处理器4   处理器5

4.5.2 订阅会话事件

export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionParams) {
  // 1. 初始化状态(用于跨事件共享数据)
  const state: EmbeddedPiSubscribeState = {
    assistantTexts: [],           // 收集 assistant 回复
    toolMetas: [],                // 收集工具调用元数据
    toolMetaById: new Map(),      // 按 ID 索引工具元数据
    deltaBuffer: "",              // 流式文本缓冲(累积已收到的文本)
    blockBuffer: "",              // 块缓冲(用于分块回复)
  };
  
  // 2. 创建上下文对象(包含状态 + 工具函数)
  const ctx: EmbeddedPiSubscribeContext = {
    params,              // 原始参数
    state,               // 共享状态
    log,                 // 日志器
    // ... 各种工具函数
  };
  
  // 3. 创建事件处理器并订阅
  const handler = createEmbeddedPiSessionEventHandler(ctx);
  const unsubscribe = params.session.subscribe(handler);
  
  // 4. 返回结果和取消订阅函数
  return { assistantTexts, toolMetas, unsubscribe };
}

关键概念

概念说明
state跨事件共享的可变状态,保存累积的文本、工具信息等
ctx上下文对象,包含 state + 各种处理函数,传递给每个处理器
handler事件处理器,一个函数,接收事件并分发
subscribe订阅方法,注册 handler 到会话的事件流

4.5.3 事件分发器原理

文件: src/agents/pi-embedded-subscribe.handlers.ts

// 这是一个"工厂函数",返回一个事件处理器
export function createEmbeddedPiSessionEventHandler(ctx: EmbeddedPiSubscribeContext) {
  // 返回的函数会被每个事件调用
  return (evt: EmbeddedPiSubscribeEvent) => {
    // 根据事件类型分发到不同处理器
    switch (evt.type) {
      case "message_start":
        handleMessageStart(ctx, evt);    // → handlers.messages.ts
        return;
      case "message_update":
        handleMessageUpdate(ctx, evt);   // → handlers.messages.ts (核心!)
        return;
      case "message_end":
        handleMessageEnd(ctx, evt);      // → handlers.messages.ts
        return;
      case "tool_execution_start":
        handleToolExecutionStart(ctx, evt);  // → handlers.tools.ts
        return;
      case "tool_execution_update":
        handleToolExecutionUpdate(ctx, evt); // → handlers.tools.ts
        return;
      case "tool_execution_end":
        handleToolExecutionEnd(ctx, evt);    // → handlers.tools.ts
        return;
      case "agent_start":
        handleAgentStart(ctx);           // → handlers.lifecycle.ts
        return;
      case "auto_compaction_start":
        handleAutoCompactionStart(ctx);  // → handlers.lifecycle.ts
        return;
      case "auto_compaction_end":
        handleAutoCompactionEnd(ctx, evt); // → handlers.lifecycle.ts
        return;
      case "agent_end":
        handleAgentEnd(ctx);             // → handlers.lifecycle.ts
        return;
    }
  };
}

分发原理图解

                    evt = { type: "message_update", delta: "Hello" }
                                        
                                        
┌────────────────────────────────────────────────────────────────────┐
  switch (evt.type)                                                 
                                                                    
   evt.type === "message_start"   ?   handleMessageStart(ctx, evt) 
   evt.type === "message_update"  ?   handleMessageUpdate(ctx, evt)  命中!
   evt.type === "message_end"     ?   handleMessageEnd(ctx, evt)   
   evt.type === "tool_*"          ?   handleTool*(ctx, evt)        
   evt.type === "agent_*"         ?   handleAgent*(ctx)            
                                                                    
└────────────────────────────────────────────────────────────────────┘
                                        
                                        
                          handleMessageUpdate(ctx, evt)
                                        
                                        
                          ctx.state.deltaBuffer += "Hello"
                          ctx.params.onPartialReply?.("Hello")

4.5.4 事件类型详解

事件类型触发时机主要处理
message_startLLM 开始生成新消息重置状态,触发"正在输入"指示
message_updateLLM 流式输出每个 token核心:累积文本、触发流式回复
message_endLLM 消息生成完成最终化文本、触发完整回复
tool_execution_start工具开始执行显示工具执行状态
tool_execution_update工具执行中更新更新执行进度
tool_execution_end工具执行完成收集工具结果、错误处理
agent_startAgent 循环开始初始化
agent_endAgent 循环结束清理、最终化
auto_compaction_start上下文压缩开始标记压缩进行中
auto_compaction_end上下文压缩完成恢复正常处理

4.5.5 message_update 处理详解(最核心)

文件: src/agents/pi-embedded-subscribe.handlers.messages.ts

export function handleMessageUpdate(ctx, evt) {
  const msg = evt.message;
  if (msg?.role !== "assistant") return;  // 只处理 assistant 消息
  
  // 1. 解析事件子类型
  const evtType = assistantRecord.type;  // "text_delta" | "text_start" | "text_end"
  const delta = assistantRecord.delta;    // 本次增量文本
  
  // 2. 处理不同子类型
  if (evtType === "text_delta") {
    chunk = delta;  // 直接使用增量
  } else if (evtType === "text_end") {
    // 计算增量(防止重复)
    if (content.startsWith(ctx.state.deltaBuffer)) {
      chunk = content.slice(ctx.state.deltaBuffer.length);
    }
  }
  
  // 3. 累积到缓冲区
  ctx.state.deltaBuffer += chunk;
  
  // 4. 触发流式回复回调
  ctx.params.onPartialReply?.(ctx.state.deltaBuffer);
  
  // 5. 处理思考标签 <think>...</think>
  // 6. 处理块分割(长消息分块发送)
}

流式处理示意

LLM 输出:  "H" → "e" → "l" → "l" → "o" → " " → "W" → "o" → "r" → "l" → "d"

事件流:
  message_start
  message_update { delta: "H" }      → deltaBuffer = "H"
  message_update { delta: "e" }      → deltaBuffer = "He"
  message_update { delta: "l" }      → deltaBuffer = "Hel"
  message_update { delta: "l" }      → deltaBuffer = "Hell"
  message_update { delta: "o" }      → deltaBuffer = "Hello"
  message_update { delta: " " }      → deltaBuffer = "Hello "
  message_update { delta: "W" }      → deltaBuffer = "Hello W"
  ...
  message_end { content: "Hello World" }  → 最终确认

4.5.6 处理器文件组织

src/agents/
├── pi-embedded-subscribe.ts                    # 入口:订阅函数
├── pi-embedded-subscribe.handlers.ts           # 分发器:switch 路由
├── pi-embedded-subscribe.handlers.types.ts     # 类型定义
├── pi-embedded-subscribe.handlers.messages.ts  # 消息处理器
├── pi-embedded-subscribe.handlers.tools.ts     # 工具处理器
└── pi-embedded-subscribe.handlers.lifecycle.ts # 生命周期处理器

4.6 工具调用处理

文件: src/agents/pi-embedded-subscribe.handlers.tools.ts

当 LLM 决定调用工具时的处理流程:

export async function handleToolExecutionStart(
  ctx: EmbeddedPiSubscribeContext,
  evt: AgentEvent & { toolName: string; toolCallId: string; args: unknown },
) {
  // 1. 记录工具元数据
  const toolName = normalizeToolName(evt.toolName);
  const meta = inferToolMetaFromArgs(toolName, evt.args);
  ctx.state.toolMetaById.set(evt.toolCallId, meta);
  
  // 2. 发送工具开始事件
  emitAgentEvent({
    runId: ctx.params.runId,
    stream: "tool",
    data: { phase: "start", name: toolName, toolCallId: evt.toolCallId, args: evt.args },
  });
}

export function handleToolExecutionEnd(
  ctx: EmbeddedPiSubscribeContext,
  evt: AgentEvent & { toolName: string; toolCallId: string; result: unknown },
) {
  const toolName = normalizeToolName(evt.toolName);
  const meta = ctx.state.toolMetaById.get(evt.toolCallId);
  
  // 1. 记录工具结果
  ctx.state.toolMetas.push({ toolName, meta });
  
  // 2. 发送工具结束事件
  emitAgentEvent({
    runId: ctx.params.runId,
    stream: "tool",
    data: { phase: "result", name: toolName, toolCallId: evt.toolCallId, result: evt.result },
  });
}

工具调用的本质:LLM 返回一个结构化的工具调用请求(包含工具名和参数),Agent 框架执行该工具,然后将结果返回给 LLM 继续处理。

工具调用完整示例

以用户问"当前目录有什么文件?“为例:

┌─────────────────────────────────────────────────────────────────────────────┐
  用户: "当前目录有什么文件?"                                                 
└─────────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────────┐
   1 轮:LLM 分析并决定调用工具                                              
                                                                             
  LLM 返回:                                                                  
  {                                                                          
    "type": "tool_call",                                                     
    "tool_name": "list_files",                                               
    "tool_call_id": "call_abc123",                                           
    "arguments": {                                                           
      "path": ".",                                                           
      "recursive": false                                                     
    }                                                                        
  }                                                                          
└─────────────────────────────────────────────────────────────────────────────┘
                                    
                    ┌───────────────┴───────────────┐
                                                   
         ┌─────────────────┐            ┌─────────────────────────┐
          tool_execution                Agent 框架执行工具      
          _start 事件                                          
                                        fs.readdir(".")        
           显示"正在     │            │  → ["src/", "docs/",    │
            列出文件..."  │            │     "package.json",     │
         └─────────────────┘                 "README.md"]        
                                        └─────────────────────────┘
                                                    
                                                    
┌─────────────────────────────────────────────────────────────────────────────┐
  tool_execution_end 事件                                                    
                                                                             
  {                                                                          
    "type": "tool_result",                                                   
    "tool_call_id": "call_abc123",                                           
    "result": "src/\ndocs/\npackage.json\nREADME.md"                         
  }                                                                          
└─────────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────────┐
   2 轮:LLM 收到工具结果,生成最终回复                                       
                                                                             
  LLM 输入上下文:                                                             
  - 用户消息: "当前目录有什么文件?"                                           
  - 工具调用: list_files(path=".")                                           
  - 工具结果: "src/\ndocs/\npackage.json\nREADME.md"                          
                                                                             
  LLM 返回:                                                                  
  "当前目录包含以下文件和文件夹:                                              │
   - src/ (源代码目录)                                                        
   - docs/ (文档目录)                                                         
   - package.json (项目配置)                                                  
   - README.md (项目说明)"                                                    │
└─────────────────────────────────────────────────────────────────────────────┘
                                    
                                    
┌─────────────────────────────────────────────────────────────────────────────┐
  用户收到回复                                                              
└─────────────────────────────────────────────────────────────────────────────┘

关键理解点

阶段执行者动作
1. 分析问题LLM理解用户意图,决定需要调用什么工具
2. 生成调用LLM输出结构化的工具调用请求(JSON 格式)
3. 执行工具Agent 框架解析请求,调用实际的系统 API
4. 返回结果Agent 框架将执行结果注入到对话上下文
5. 生成回复LLM基于工具结果,生成人类可读的回复

本质:LLM 本身不能执行代码或访问文件系统,它只能"说"要做什么。Agent 框架负责"听懂"并"执行”,然后把结果"告诉" LLM。

4.7 工具系统

文件: src/agents/pi-tools.ts

OpenClaw 提供了丰富的内置工具:

export function createOpenClawCodingTools(options?: {...}): AnyAgentTool[] {
  // 1. 创建基础编码工具
  const base = codingTools.flatMap((tool) => {
    if (tool.name === readTool.name) {
      return [createOpenClawReadTool(...)];
    }
    // ...
  });
  
  // 2. 创建执行工具
  const execTool = createExecTool({ ... });
  
  // 3. 创建 OpenClaw 特有工具
  const openclawTools = createOpenClawTools({...});
  
  // 4. 添加钩子包装(before_tool_call hook)
  const withHooks = normalized.map(tool => 
    wrapToolWithBeforeToolCallHook(tool, {...})
  );
  
  return withHooks;
}

核心内置工具:

工具名功能描述
read读取文件内容
write创建或覆盖文件
edit精确编辑文件
grep搜索文件内容
find按模式查找文件
ls列出目录内容
exec执行 shell 命令
web_search网络搜索
web_fetch获取网页内容
browser控制浏览器
message发送消息
memory_search记忆搜索

4.8 Agent 循环流程图

┌─────────────────────────────────────────────────────────────────┐
                        用户发送消息                               
└─────────────────────────────────────────────────────────────────┘
                                
                                
┌─────────────────────────────────────────────────────────────────┐
                    1. 队列等待(串行化)                          
                    enqueueSession() + enqueueGlobal()           
└─────────────────────────────────────────────────────────────────┘
                                
                                
┌─────────────────────────────────────────────────────────────────┐
                    2. 创建 Agent 会话                            
                    createAgentSession()                         
└─────────────────────────────────────────────────────────────────┘
                                
                                
┌─────────────────────────────────────────────────────────────────┐
                    3. 构建系统提示词                              
                    buildAgentSystemPrompt()                     
└─────────────────────────────────────────────────────────────────┘
                                
                                
┌─────────────────────────────────────────────────────────────────┐
                    4. 调用 LLM                                   
                    session.prompt(userMessage)                  
└─────────────────────────────────────────────────────────────────┘
                                
                    ┌───────────┴───────────┐
                                           
        ┌───────────────────┐   ┌───────────────────────┐
           LLM 返回文本           LLM 返回工具调用     
        └───────────────────┘   └───────────────────────┘
                                           
                                           
                               ┌───────────────────────┐
                                  5. 执行工具          
                                  tool.execute(args)  
                               └───────────────────────┘
                                           
                                           
                               ┌───────────────────────┐
                                  6. 返回工具结果      
                                  tool result  LLM   
                               └───────────────────────┘
                                           
                               ┌───────────┴───────────┐
                                 LLM 可能继续调用工具  
                                 (循环步骤 4-6)       
                               └───────────────────────┘
                                           
                    └───────────┬───────────┘
                                
                                
┌─────────────────────────────────────────────────────────────────┐
                    7. 收集最终回复                                
                    assistantTexts + toolMetas                   
└─────────────────────────────────────────────────────────────────┘
                                
                                
┌─────────────────────────────────────────────────────────────────┐
                    8. 返回给用户                                  
                    通过原渠道发送回复                              
└─────────────────────────────────────────────────────────────────┘

Agent 循环的本质:用户消息 → LLM 思考 → (可选)调用工具 → 工具结果返回 LLM → LLM 继续思考 → … → 最终回复

4.9 核心依赖库

OpenClaw 的 Agent 核心依赖于以下外部库:

作用
@mariozechner/pi-agent-coreAgent 核心类型和接口定义
@mariozechner/pi-aiAI 模型调用抽象层 (streamSimple 等)
@mariozechner/pi-coding-agent编码 Agent 会话管理、工具创建

这些库提供了:

  • 统一的 LLM 调用接口:支持多个模型提供商
  • 会话管理和持久化:保存对话历史
  • 工具定义和执行框架:标准化的工具接口
  • 上下文压缩机制:处理超长对话

五、系统提示词工程

系统提示词(System Prompt)是 Agent 的"灵魂",它决定了 Agent 的身份、能力边界、行为规范。OpenClaw 采用模块化设计,将系统提示词拆分为多个独立的构建函数。

文件: src/agents/system-prompt.ts

5.1 系统提示词整体结构

┌─────────────────────────────────────────────────────────────────┐
│                      系统提示词 (System Prompt)                   │
├─────────────────────────────────────────────────────────────────┤
│  1. 身份声明 (Identity)                                          │
│     "You are a personal assistant running inside OpenClaw."     │
├─────────────────────────────────────────────────────────────────┤
│  2. 工具部分 (Tooling)                                           │
│     - 可用工具列表及描述                                          │
│     - 工具调用风格指南                                            │
├─────────────────────────────────────────────────────────────────┤
│  3. 安全规则 (Safety)                                            │
│     - 无独立目标                                                  │
│     - 优先安全和人类监督                                          │
├─────────────────────────────────────────────────────────────────┤
│  4. CLI 快速参考 (CLI Reference)                                 │
│     - OpenClaw 命令用法                                          │
├─────────────────────────────────────────────────────────────────┤
│  5. Skills 系统 (Skills)                                         │
│     - 技能扫描规则                                                │
│     - 技能选择和加载指南                                          │
├─────────────────────────────────────────────────────────────────┤
│  6. 记忆系统 (Memory)                                            │
│     - 记忆检索指南                                                │
├─────────────────────────────────────────────────────────────────┤
│  7. 工作空间 (Workspace)                                         │
│     - 当前工作目录                                                │
├─────────────────────────────────────────────────────────────────┤
│  8. 文档参考 (Docs)                                              │
│     - OpenClaw 文档路径                                          │
├─────────────────────────────────────────────────────────────────┤
│  9. 消息系统 (Messaging)                                         │
│     - 跨会话消息发送                                              │
│     - message 工具用法                                           │
├─────────────────────────────────────────────────────────────────┤
│ 10. 项目上下文 (Project Context)                                 │
│     - AGENTS.md / SOUL.md 等文件内容                             │
├─────────────────────────────────────────────────────────────────┤
│ 11. 静默回复 (Silent Replies)                                    │
│     - 无需回复时的处理                                            │
├─────────────────────────────────────────────────────────────────┤
│ 12. 运行时信息 (Runtime)                                         │
│     - agent/host/os/model 等环境信息                             │
└─────────────────────────────────────────────────────────────────┘

主构建函数

// src/agents/system-prompt.ts
export function buildAgentSystemPrompt(params: {
  workspaceDir: string;           // 工作目录
  toolNames?: string[];           // 可用工具名列表
  toolSummaries?: Record<string, string>;  // 工具描述映射
  skillsPrompt?: string;          // Skills 提示词
  contextFiles?: EmbeddedContextFile[];    // 上下文文件(如 AGENTS.md)
  runtimeInfo?: {...};            // 运行时信息
  // ...更多参数
}) {
  const lines = [];
  
  // 1️⃣ 身份声明
  lines.push("You are a personal assistant running inside OpenClaw.");
  
  // 2️⃣ 工具部分
  lines.push("## Tooling", ...toolLines);
  
  // 3️⃣ 安全规则
  lines.push(...buildSafetySection());
  
  // 4️⃣ CLI 参考
  lines.push("## OpenClaw CLI Quick Reference", ...);
  
  // 5️⃣ Skills 系统
  lines.push(...buildSkillsSection({...}));
  
  // 6️⃣ 记忆系统
  lines.push(...buildMemorySection({...}));
  
  // 7️⃣ 工作空间
  lines.push("## Workspace", `Your working directory is: ${params.workspaceDir}`);
  
  // 8️⃣ 文档参考
  lines.push(...buildDocsSection({...}));
  
  // 9️⃣ 消息系统
  lines.push(...buildMessagingSection({...}));
  
  // 🔟 项目上下文(关键!)
  if (contextFiles?.length > 0) {
    lines.push("# Project Context");
    for (const file of contextFiles) {
      lines.push(`## ${file.path}`, file.content);
    }
  }
  
  // 1️⃣1️⃣ 静默回复
  lines.push("## Silent Replies", ...);
  
  // 1️⃣2️⃣ 运行时信息
  lines.push("## Runtime", buildRuntimeLine(runtimeInfo, ...));
  
  return lines.filter(Boolean).join("\n");
}

5.2 核心模块详解

① 工具部分 (Tooling)

⚠️ 重要说明:System Prompt 中的工具部分只包含工具名称和简短描述,完整的参数定义(入参/出参 Schema)是通过 API 的 tools 参数单独传递给 LLM 的,不在 system prompt 文本中。

📌 技术原理:OpenClaw 使用的是 LLM 的 Function Calling(函数调用) 功能,这是 OpenAI、Claude、Gemini 等主流 LLM 都支持的标准能力。Function Calling 让 LLM 能够以结构化 JSON 格式"调用"预定义的函数,而不是只输出自然语言文本。

Function Calling 工作原理

┌─────────────────────────────────────────────────────────────────────────────┐
                      Function Calling 完整工作流程                           
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
  1 步:开发者定义函数(工具)                                               
                                                                             
 const readTool = {                                                          
   name: "read",                                                             
   description: "Read file contents",                                        
   parameters: {                                                             
     type: "object",                                                         
     properties: {                                                           
       path: { type: "string", description: "File path to read" },           
       encoding: { type: "string", description: "File encoding" }            
     },                                                                      
     required: ["path"]                                                      
   }                                                                         
 };                                                                          
└─────────────────────────────────────────────────────────────────────────────┘
                                        
                                        
┌─────────────────────────────────────────────────────────────────────────────┐
  2 步:调用 LLM API 时传入 tools 参数                                       
                                                                             
 const response = await openai.chat.completions.create({                     
   model: "gpt-4",                                                           
   messages: [                                                               
     { role: "system", content: "You are a helpful assistant..." },          
     { role: "user", content: "读取 package.json 的内容" }                    
   ],                                                                        
   tools: [{                           //  关键:传入工具定义                 
     type: "function",                                                       
     function: readTool                                                      
   }]                                                                        
 });                                                                         
└─────────────────────────────────────────────────────────────────────────────┘
                                        
                                        
┌─────────────────────────────────────────────────────────────────────────────┐
  3 步:LLM 分析用户意图,决定是否调用函数                                    
                                                                             
 LLM 内部思考:                                                               
 "用户想读取 package.json → 我需要使用 read 工具 → 参数是 path='package.json'"  
                                                                             
 LLM 返回(不是普通文本,而是结构化的 tool_calls):                            
 {                                                                           
   "choices": [{                                                             
     "message": {                                                            
       "role": "assistant",                                                  
       "content": null,               //  没有文本内容                       
       "tool_calls": [{               //  而是函数调用请求                   
         "id": "call_abc123",                                                
         "type": "function",                                                 
         "function": {                                                       
           "name": "read",                                                   
           "arguments": "{\"path\": \"package.json\"}"                       
         }                                                                   
       }]                                                                    
     }                                                                       
   }]                                                                        
 }                                                                           
└─────────────────────────────────────────────────────────────────────────────┘
                                        
                                        
┌─────────────────────────────────────────────────────────────────────────────┐
  4 步:Agent 框架执行函数                                                   
                                                                             
 // 解析 LLM 返回的调用请求                                                   
 const toolCall = response.choices[0].message.tool_calls[0];                 
 const args = JSON.parse(toolCall.function.arguments);                       
                                                                             
 // 执行实际的函数                                                            
 const result = await fs.readFile(args.path, "utf-8");                       
 // result = '{ "name": "openclaw", "version": "1.0.0", ... }'               
└─────────────────────────────────────────────────────────────────────────────┘
                                        
                                        
┌─────────────────────────────────────────────────────────────────────────────┐
  5 步:把执行结果返回给 LLM                                                 
                                                                             
 const followUp = await openai.chat.completions.create({                     
   model: "gpt-4",                                                           
   messages: [                                                               
     { role: "system", content: "..." },                                     
     { role: "user", content: "读取 package.json 的内容" },                   
     { role: "assistant", content: null, tool_calls: [...] },  // LLM的调用  
     {                                                                       
       role: "tool",                   //  工具执行结果                      
       tool_call_id: "call_abc123",                                          
       content: '{ "name": "openclaw", "version": "1.0.0" }'                 
     }                                                                       
   ]                                                                         
 });                                                                         
└─────────────────────────────────────────────────────────────────────────────┘
                                        
                                        
┌─────────────────────────────────────────────────────────────────────────────┐
  6 步:LLM 基于工具结果生成最终回复                                          
                                                                             
 LLM 返回:                                                                   
 {                                                                           
   "choices": [{                                                             
     "message": {                                                            
       "role": "assistant",                                                  
       "content": "package.json 的内容如下:\n\n这是一个名为 openclaw 的项目,│
                   版本号是 1.0.0..."                                         │
     }                                                                       
   }]                                                                        
 }                                                                           
└─────────────────────────────────────────────────────────────────────────────┘

Function Calling vs 普通对话

对比项普通对话Function Calling
LLM 输出自然语言文本结构化 JSON(tool_calls)
参数格式无法保证严格遵循 JSON Schema
可执行性需要解析文本(不可靠)可直接解析执行
错误处理难以判断清晰的调用失败/成功

工具信息传递的两个通道

┌─────────────────────────────────────────────────────────────────────────────┐
                        LLM API 调用结构                                      
└─────────────────────────────────────────────────────────────────────────────┘
                               
          ┌────────────────────┼────────────────────┐
                                                   
┌───────────────────────────┐          ┌────────────────────────────┐
  system (System Prompt)               tools (工具定义数组)       
                                                                 
  "## Tooling              │          │  [{                        │
  - read: Read file...                   name: "read",           
  - write: Create...                     description: "...",     
  - edit: Make precise..." │          │    parameters: {           │
                                           type: "object",       
  (只有名称 + 简短描述)                     properties: {         
                                             path: {...},        
                                             encoding: {...}     
                                           },                    
                                           required: ["path"]    
                                         }                       
                                       }, ...]                   
                                                                 
                                       (完整的 JSON Schema)       
└───────────────────────────┘          └────────────────────────────┘
位置内容目的
System Prompt工具名 + 简短描述让 LLM 快速了解有哪些能力,节省 Token
tools 参数完整 JSON SchemaLLM 需要精确的参数定义才能正确调用

② 安全规则 (Safety)

这是 Agent 安全的核心约束:

function buildSafetySection() {
  return [
    "## Safety",
    // 🔒 无独立目标约束
    "You have no independent goals: do not pursue self-preservation, " +
    "replication, resource acquisition, or power-seeking; " +
    "avoid long-term plans beyond the user's request.",
    
    // 🔒 优先安全和人类监督
    "Prioritize safety and human oversight over completion; " +
    "if instructions conflict, pause and ask; " +
    "comply with stop/pause/audit requests and never bypass safeguards. " +
    "(Inspired by Anthropic's constitution.)",
    
    // 🔒 禁止操控和自我复制
    "Do not manipulate or persuade anyone to expand access or disable safeguards. " +
    "Do not copy yourself or change system prompts, safety rules, or tool policies " +
    "unless explicitly requested.",
  ];
}

③ Skills 系统

告诉 Agent 如何检索和使用 Skills:

function buildSkillsSection(params: {
  skillsPrompt?: string;  // 包含 <available_skills> 列表
  readToolName: string;
}) {
  return [
    "## Skills (mandatory)",
    "Before replying: scan <available_skills> <description> entries.",
    // 选择规则
    `- If exactly one skill clearly applies: read its SKILL.md at <location> with \`${params.readToolName}\`, then follow it.`,
    "- If multiple could apply: choose the most specific one, then read/follow it.",
    "- If none clearly apply: do not read any SKILL.md.",
    // 约束
    "Constraints: never read more than one skill up front; only read after selecting.",
    // 实际的 skills 列表
    params.skillsPrompt,  // <available_skills>...</available_skills>
  ];
}

④ 记忆系统

function buildMemorySection(params: { availableTools: Set<string> }) {
  // 只有当 memory 工具可用时才添加
  if (!params.availableTools.has("memory_search")) {
    return [];
  }
  return [
    "## Memory Recall",
    "Before answering anything about prior work, decisions, dates, people, " +
    "preferences, or todos: run memory_search on MEMORY.md + memory/*.md; " +
    "then use memory_get to pull only the needed lines. " +
    "If low confidence after search, say you checked.",
  ];
}

⑤ 项目上下文(最重要!)

这是让 Agent 了解项目特定规则的核心机制:

// 注入项目上下文文件
if (contextFiles?.length > 0) {
  // 检查是否有 SOUL.md(人格设定文件)
  const hasSoulFile = contextFiles.some((file) => 
    file.path.toLowerCase().endsWith("soul.md")
  );
  
  lines.push("# Project Context");
  lines.push("The following project context files have been loaded:");
  
  // 如果有 SOUL.md,添加特殊指令
  if (hasSoulFile) {
    lines.push(
      "If SOUL.md is present, embody its persona and tone. " +
      "Avoid stiff, generic replies; follow its guidance " +
      "unless higher-priority instructions override it."
    );
  }
  
  // 注入每个上下文文件的内容
  for (const file of contextFiles) {
    lines.push(`## ${file.path}`, "", file.content, "");
  }
}

常见的上下文文件包括:

  • AGENTS.md - 项目级 Agent 行为指南
  • SOUL.md - Agent 人格设定
  • TOOLS.md - 工具使用指南

⑥ 运行时信息

export function buildRuntimeLine(runtimeInfo?: {...}): string {
  return `Runtime: ${[
    runtimeInfo?.agentId ? `agent=${runtimeInfo.agentId}` : "",
    runtimeInfo?.host ? `host=${runtimeInfo.host}` : "",
    runtimeInfo?.os ? `os=${runtimeInfo.os}` : "",
    runtimeInfo?.model ? `model=${runtimeInfo.model}` : "",
    runtimeInfo?.channel ? `channel=${runtimeInfo.channel}` : "",
  ].filter(Boolean).join(" | ")}`;
}

生成示例:

Runtime: agent=main | host=MacBook | os=darwin (arm64) | model=claude-sonnet-4-20250514 | channel=telegram

5.3 模块化设计总结

设计说明
模块化构建每个部分独立函数,易于维护和扩展
条件包含根据可用工具、模式等动态决定是否包含某部分
上下文注入支持注入项目特定文件(AGENTS.md 等)
多模式支持full/minimal/none 适应不同场景
安全优先安全规则作为核心部分始终包含
运行时感知包含当前环境信息,让 Agent 了解运行上下文

六、Skills 检索系统

6.1 Skills 目录结构

优先级 (低 → 高):
┌─────────────────────────────────────────────────────────┐
│  1. extraDirs     - 用户配置的额外目录                   │
│  2. bundled       - 内置 skills (项目 /skills/ 目录)     │
│  3. managed       - 托管目录 (~/.config/openclaw/skills/)│
│  4. workspace     - 工作区 (<workspaceDir>/skills/)      │
└─────────────────────────────────────────────────────────┘

内置 Skills 示例 (52+ 个):

skills/
├── github/         # GitHub CLI 集成
├── discord/        # Discord Bot 控制
├── coding-agent/   # 编码代理指南
├── 1password/      # 1Password 密码管理
├── obsidian/       # Obsidian 笔记
├── spotify-player/ # Spotify 播放控制
├── weather/        # 天气查询
├── docker/         # Docker 容器管理
├── kubernetes/     # K8s 集群管理
├── homebrew/       # Homebrew 包管理
└── ...

6.2 SKILL.md 文件格式

示例: skills/github/SKILL.md

---
name: github
description: "Interact with GitHub using the `gh` CLI..."
metadata:
  {
    "openclaw":
      {
        "emoji": "🐙",
        "requires": { "bins": ["gh"] },
        "install":
          [
            {
              "id": "brew",
              "kind": "brew",
              "formula": "gh",
              "bins": ["gh"],
              "label": "Install GitHub CLI (brew)",
            },
          ],
      },
  }
---

# GitHub Skill

Use the `gh` CLI to interact with GitHub...

## Pull Requests
```bash
gh pr checks 55 --repo owner/repo


**Frontmatter 字段说明**:

| 字段 | 类型 | 说明 |
|------|------|------|
| `name` | string | Skill 唯一标识符 |
| `description` | string | 简短描述(用于匹配) |
| `metadata.openclaw.emoji` | string | 显示图标 |
| `metadata.openclaw.os` | string[] | 限制操作系统 |
| `metadata.openclaw.requires.bins` | string[] | 必需的二进制文件 |
| `metadata.openclaw.requires.anyBins` | string[] | 任一即可的二进制 |
| `metadata.openclaw.requires.env` | string[] | 必需的环境变量 |
| `metadata.openclaw.requires.config` | string[] | 必需的配置路径 |
| `metadata.openclaw.install` | object[] | 安装选项 |
| `metadata.openclaw.always` | boolean | 始终包含(跳过检查) |

### 6.3 Skills 加载流程

**文件**: `src/agents/skills/workspace.ts`

```typescript
function loadSkillEntries(
  workspaceDir: string,
  opts?: {
    config?: OpenClawConfig;
    managedSkillsDir?: string;
    bundledSkillsDir?: string;
  },
): SkillEntry[] {
  const loadSkills = (params: { dir: string; source: string }): Skill[] => {
    const loaded = loadSkillsFromDir(params);  // 使用 pi-coding-agent
    // ...
  };

  // 1. 解析各目录路径
  const managedSkillsDir = opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
  const workspaceSkillsDir = path.join(workspaceDir, "skills");
  const bundledSkillsDir = opts?.bundledSkillsDir ?? resolveBundledSkillsDir();
  const extraDirs = opts?.config?.skills?.load?.extraDirs ?? [];
  const pluginSkillDirs = resolvePluginSkillDirs({...});

  // 2. 从各目录加载
  const bundledSkills = loadSkills({ dir: bundledSkillsDir, source: "openclaw-bundled" });
  const extraSkills = mergedExtraDirs.flatMap(dir => loadSkills({...}));
  const managedSkills = loadSkills({ dir: managedSkillsDir, source: "openclaw-managed" });
  const workspaceSkills = loadSkills({ dir: workspaceSkillsDir, source: "openclaw-workspace" });

  // 3. 按优先级合并(后覆盖前)
  const merged = new Map<string, Skill>();
  for (const skill of extraSkills) merged.set(skill.name, skill);
  for (const skill of bundledSkills) merged.set(skill.name, skill);
  for (const skill of managedSkills) merged.set(skill.name, skill);
  for (const skill of workspaceSkills) merged.set(skill.name, skill);

  // 4. 解析 frontmatter 和元数据
  return Array.from(merged.values()).map(skill => ({
    skill,
    frontmatter: parseFrontmatter(raw),
    metadata: resolveOpenClawMetadata(frontmatter),
    invocation: resolveSkillInvocationPolicy(frontmatter),
  }));
}

6.4 Skills 资格检查 (Eligibility)

文件: src/agents/skills/config.ts

export function shouldIncludeSkill(params: {
  entry: SkillEntry;
  config?: OpenClawConfig;
  eligibility?: SkillEligibilityContext;
}): boolean {
  const { entry, config, eligibility } = params;
  
  // 1. 检查是否显式禁用
  if (skillConfig?.enabled === false) {
    return false;
  }
  
  // 2. 检查内置 allowlist
  if (!isBundledSkillAllowed(entry, allowBundled)) {
    return false;
  }
  
  // 3. 检查操作系统限制
  if (osList.length > 0 && !osList.includes(resolveRuntimePlatform())) {
    return false;
  }
  
  // 4. always=true 跳过其他检查
  if (entry.metadata?.always === true) {
    return true;
  }

  // 5. 检查必需的二进制文件
  const requiredBins = entry.metadata?.requires?.bins ?? [];
  for (const bin of requiredBins) {
    if (!hasBinary(bin) && !eligibility?.remote?.hasBin?.(bin)) {
      return false;
    }
  }
  
  // 6. 检查 anyBins(任一即可)
  const requiredAnyBins = entry.metadata?.requires?.anyBins ?? [];
  if (requiredAnyBins.length > 0) {
    const anyFound = requiredAnyBins.some(bin => hasBinary(bin));
    if (!anyFound) return false;
  }

  // 7. 检查环境变量
  const requiredEnv = entry.metadata?.requires?.env ?? [];
  for (const envName of requiredEnv) {
    if (!process.env[envName] && !skillConfig?.env?.[envName]) {
      return false;
    }
  }

  // 8. 检查配置路径
  const requiredConfig = entry.metadata?.requires?.config ?? [];
  for (const configPath of requiredConfig) {
    if (!isConfigPathTruthy(config, configPath)) {
      return false;
    }
  }

  return true;
}

资格检查流程图:

              Skill 资格检查
         ┌─ enabled=false? ─→ ❌ 排除
         ┌─ 不在 allowBundled? ─→ ❌ 排除
         ┌─ OS 不匹配? ─→ ❌ 排除
         ┌─ always=true? ─→ ✅ 包含
         ┌─ 缺少 bins? ─→ ❌ 排除
         ┌─ 缺少 anyBins? ─→ ❌ 排除
         ┌─ 缺少 env? ─→ ❌ 排除
         ┌─ 缺少 config? ─→ ❌ 排除
              ✅ 包含

6.5 Skills 注入到系统提示词

文件: src/agents/system-prompt.ts

function buildSkillsSection(params: {
  skillsPrompt?: string;
  isMinimal: boolean;
  readToolName: string;
}) {
  if (params.isMinimal) {
    return [];  // 子 agent 不注入 skills
  }
  const trimmed = params.skillsPrompt?.trim();
  if (!trimmed) {
    return [];
  }
  return [
    "## Skills (mandatory)",
    "Before replying: scan <available_skills> <description> entries.",
    `- If exactly one skill clearly applies: read its SKILL.md at <location> with \`${params.readToolName}\`, then follow it.`,
    "- If multiple could apply: choose the most specific one, then read/follow it.",
    "- If none clearly apply: do not read any SKILL.md.",
    "Constraints: never read more than one skill up front; only read after selecting.",
    trimmed,  // <-- Skills XML 列表
    "",
  ];
}

生成的提示词格式:

## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md at <location> with `Read`, then follow it.
- If multiple could apply: choose the most specific one, then read/follow it.
- If none clearly apply: do not read any SKILL.md.
Constraints: never read more than one skill up front; only read after selecting.

<available_skills>
  <skill>
    <name>github</name>
    <description>Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.</description>
    <location>/path/to/skills/github/SKILL.md</location>
  </skill>
  <skill>
    <name>discord</name>
    <description>Control Discord Bot and server operations...</description>
    <location>/path/to/skills/discord/SKILL.md</location>
  </skill>
  ...
</available_skills>

6.6 Skills 检索策略

Agent 的 Skills 检索是基于描述的语义匹配,而不是关键词搜索。

5.6.1 检索原理详解

⚠️ 重要澄清:Skills 检索不使用向量嵌入或语义搜索算法,而是完全依赖 LLM 自身的语言理解能力来匹配用户意图和 Skill 描述。

核心机制

┌─────────────────────────────────────────────────────────────────────────────┐
                         Skills 检索原理                                      
└─────────────────────────────────────────────────────────────────────────────┘

1. 系统将所有 Skill 的名称 + 描述注入到 System Prompt 中:

   ## Skills (mandatory)
   Before replying: scan <available_skills> <description> entries.
   - If exactly one skill clearly applies: read its SKILL.md...
   - If multiple could apply: choose the most specific one...
   - If none clearly apply: do not read any SKILL.md.

   <available_skills>
     <skill>
       <name>github</name>
       <description>Interact with GitHub using `gh` CLI...</description>
       <location>/path/to/github/SKILL.md</location>
     </skill>
     <skill>
       <name>slack</name>
       <description>Send messages to Slack channels...</description>
       <location>/path/to/slack/SKILL.md</location>
     </skill>
     ...
   </available_skills>

2. LLM 自行阅读用户请求,理解意图,然后扫描所有 description

3. LLM 根据语义相关性判断哪个 Skill 最匹配(这是 LLM 的推理能力,不是算法)

4. 如果匹配到,LLM 使用 read 工具读取对应的 SKILL.md 文件

为什么叫"语义匹配"?

方式实现特点
关键词搜索代码算法 (如 BM25)精确匹配字符串,“CI” 只能匹配包含 “CI” 的文本
向量语义搜索Embedding + 向量相似度代码计算语义距离,如记忆系统使用的方式
LLM 语义理解LLM 推理LLM 理解 “CI 状态” 和 “workflow runs” 是相关概念

Skills 使用的是第三种方式——让 LLM 自己理解和匹配。

5.6.2 完整检索流程示例

用户请求: "帮我看一下 PR #123 的 CI 状态"
                    
                    
┌─────────────────────────────────────────────────────────────────────────────┐
  LLM 收到的 System Prompt 包含:                                              
                                                                             
  <available_skills>                                                         
    <skill>                                                                  
      <name>github</name>                                                    
      <description>Interact with GitHub using `gh` CLI. Use `gh issue`,      
                   `gh pr`, `gh run` for issues, PRs, CI runs...</description>
    </skill>                                                                 
    <skill>                                                                  
      <name>slack</name>                                                     
      <description>Send messages to Slack channels...</description>          
    </skill>                                                                 
    <skill>                                                                  
      <name>weather</name>                                                   
      <description>Get weather forecasts...</description>                    
    </skill>                                                                 
  </available_skills>                                                        
└─────────────────────────────────────────────────────────────────────────────┘
                    
                    
┌─────────────────────────────────────────────────────────────────────────────┐
  LLM 内部推理(这是 LLM 的思考过程):                                          
                                                                             
  "用户问的是 PR #123 的 CI 状态..."                                          
  "扫描 available_skills..."                                                 
  "- github: 提到 'gh pr', 'CI runs' → 高度相关 ✓"                            
  "- slack: 发消息,不相关 ✗"                                                 
  "- weather: 天气,不相关 ✗"                                                 
  "结论: github skill 明确匹配"                                               
└─────────────────────────────────────────────────────────────────────────────┘
                    
                    
┌─────────────────────────────────────────────────────────────────────────────┐
  LLM 输出 Tool Call:                                                        
                                                                             
  {                                                                          
    "name": "read",                                                          
    "arguments": { "path": "/path/to/github/SKILL.md" }                      
  }                                                                          
└─────────────────────────────────────────────────────────────────────────────┘
                    
                    
┌─────────────────────────────────────────────────────────────────────────────┐
  框架执行 read 工具,返回 SKILL.md 内容:                                       
                                                                             
  # GitHub Skill                                                             │
  Use the `gh` CLI to interact with GitHub...                                
                                                                             
  ## Pull Requests                                                           │
  Check CI status on a PR:                                                   
  ```bash                                                                    
  gh pr checks 55 --repo owner/repo                                          
  ```                                                                        
└─────────────────────────────────────────────────────────────────────────────┘
                    
                    
┌─────────────────────────────────────────────────────────────────────────────┐
  LLM 阅读 SKILL.md 后,执行具体命令:                                          
                                                                             
  {                                                                          
    "name": "exec",                                                          
    "arguments": { "command": "gh pr checks 123 --repo owner/repo" }         
  }                                                                          
└─────────────────────────────────────────────────────────────────────────────┘

5.6.3 为什么不用向量搜索?

对比项向量语义搜索LLM 直接理解
实现复杂度需要 Embedding 模型 + 向量数据库只需在 Prompt 中列出
延迟需要额外 API 调用计算向量零额外延迟
Skills 数量适合大规模(数千个)适合中小规模(数十个)
准确度依赖向量模型质量依赖 LLM 推理能力
上下文消耗只返回匹配结果所有描述都在 Prompt 中

OpenClaw 的选择:由于 Skills 数量通常在几十个以内,直接让 LLM 扫描所有描述是最简单高效的方案。

5.6.4 System Prompt 中的检索指令

// src/agents/system-prompt.ts
function buildSkillsSection(params) {
  return [
    "## Skills (mandatory)",
    "Before replying: scan <available_skills> <description> entries.",
    "- If exactly one skill clearly applies: read its SKILL.md at <location> with `read`, then follow it.",
    "- If multiple could apply: choose the most specific one, then read/follow it.",
    "- If none clearly apply: do not read any SKILL.md.",
    "Constraints: never read more than one skill up front; only read after selecting.",
    trimmed,  // ← <available_skills>...</available_skills> 内容
  ];
}

检索规则:

  1. 扫描 description: Agent 首先扫描所有 skill 的描述
  2. 单一匹配: 如果只有一个明确匹配,直接使用
  3. 多重匹配: 选择最具体的那个
  4. 无匹配: 不读取任何 SKILL.md,直接处理

5.6.5 与记忆系统检索的对比

对比项Skills 检索记忆系统检索
检索方式LLM 扫描 System Prompt向量 + 关键词混合搜索
数据位置在 Prompt 中(消耗 Token)在 SQLite 数据库中
触发方式LLM 自动判断调用 memory_search 工具
适用规模几十个 Skill数千条记忆
检索算法无(LLM 推理)sqlite-vec + FTS5

6.7 Skills 文件监视与热刷新

文件: src/agents/skills/refresh.ts

export function ensureSkillsWatcher(params) {
  const watchPaths = resolveWatchPaths(workspaceDir, config);
  
  const watcher = chokidar.watch(watchPaths, {
    ignoreInitial: true,
    ignored: [/node_modules/, /\.git/, /dist/],
  });

  watcher.on("add", p => bumpSkillsSnapshotVersion(...));
  watcher.on("change", p => bumpSkillsSnapshotVersion(...));
  watcher.on("unlink", p => bumpSkillsSnapshotVersion(...));
}

当 Skills 文件变化时,自动刷新快照版本,下次 Agent 运行时会加载最新的 Skills。


七、记忆管理系统

记忆管理系统(Memory System)是 OpenClaw 的跨会话持久化知识检索机制,与上下文工程管理形成互补。

7.1 与上下文工程管理的区别

维度上下文工程管理记忆管理系统
作用范围单次会话内跨会话持久化
存储位置会话文件 .jsonlMEMORY.md + memory/*.md + SQLite
生命周期随会话结束可能被裁剪/压缩长期保存,跨会话可用
主要功能Token 限制、上下文裁剪、摘要压缩语义检索、知识存储
核心文件compaction.ts, context-pruning/memory/manager.ts, memory-tool.ts
触发方式自动(上下文超限时)主动调用工具检索

简单理解

  • 上下文工程管理解决的是"这轮对话太长了怎么办"
  • 记忆管理系统解决的是"上周我让你做的那个任务叫什么"

7.2 架构总览

┌─────────────────────────────────────────────────────────────────────────┐
│                         记忆管理系统架构                                  │
└─────────────────────────────────────────────────────────────────────────┘
        ┌──────────────────────────┼──────────────────────────┐
        ▼                          ▼                          ▼
┌───────────────────┐   ┌───────────────────┐   ┌───────────────────────┐
│   记忆来源        │   │   嵌入层          │   │    检索层             │
│  (Memory Sources) │   │  (Embeddings)     │   │  (Search)             │
└───────────────────┘   └───────────────────┘   └───────────────────────┘
        │                          │                          │
┌───────┴───────┐           ┌──────┴──────┐           ┌───────┴───────┐
│               │           │             │           │               │
▼               ▼           ▼             ▼           ▼               ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│MEMORY.md│ │memory/* │ │ OpenAI  │ │ Gemini  │ │ Vector  │ │Keyword  │
│         │ │ *.md    │ │Embedding│ │Embedding│ │ Search  │ │ Search  │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
        │                          │                          │
        └──────────────────────────┼──────────────────────────┘
                          ┌───────────────────┐
                          │  SQLite + sqlite- │
                          │  vec (向量存储)   │
                          └───────────────────┘

7.3 记忆存储来源

文件: src/memory/internal.ts

支持的记忆源

来源路径说明
MEMORY.md{workspace}/MEMORY.md主记忆文件
memory目录{workspace}/memory/*.md分类记忆文件
会话记录~/.openclaw/sessions/*.jsonl历史对话(实验性)
extraPaths用户配置的额外路径自定义记忆源
// 记忆文件路径判断
export function isMemoryPath(relPath: string): boolean {
  const normalized = normalizeRelPath(relPath);
  if (normalized === "MEMORY.md" || normalized === "memory.md") {
    return true;
  }
  return normalized.startsWith("memory/");
}

// 列出所有记忆文件
export async function listMemoryFiles(
  workspaceDir: string,
  extraPaths?: string[],
): Promise<string[]> {
  // 1. 检查 MEMORY.md
  // 2. 扫描 memory/ 目录下的 .md 文件
  // 3. 添加 extraPaths 中的文件
}

文件分块机制

// 分块配置
const DEFAULT_CHUNK_TOKENS = 400;   // 每块最大 Token
const DEFAULT_CHUNK_OVERLAP = 80;   // 块间重叠 Token

export type MemoryChunk = {
  startLine: number;  // 起始行号
  endLine: number;    // 结束行号
  text: string;       // 块内容
  hash: string;       // 内容哈希(用于增量更新)
};

7.4 记忆文件结构与生成机制

文件: src/memory/internal.ts, src/hooks/bundled/session-memory/handler.ts

记忆文件格式示例

系统支持三种类型的记忆文件,每种有不同的用途和格式:

1. 主记忆文件 MEMORY.md(手动维护的长期记忆)

这是用户/Agent 手动维护的长期记忆文件,格式自由:

# Long-Term Memory

## 用户偏好
- 喜欢简洁的代码风格
- 偏好使用 TypeScript
- 工作时间:周一至周五 9:00-18:00

## 重要决策
- 2026-01-15: 决定使用 PostgreSQL 作为主数据库
- 2026-01-10: API 采用 RESTful 风格,版本前缀 /v1/

## 项目背景
- 正在开发一个电商后台系统
- 技术栈:Node.js + React + PostgreSQL

## 联系人
- 张三 - 后端负责人
- 李四 - 产品经理

2. 每日记忆文件 memory/YYYY-MM-DD.md(手动/自动)

按日期组织的日志式记忆,可由 Agent 自动写入或手动维护:

# 2026-01-16

## 今日工作
- 完成了用户认证模块的代码审查
- 修复了购物车结算时的并发问题

## 待办事项
- [ ] 完善 API 文档
- [ ] 准备周五的技术分享

## 备注
用户反馈:登录页面加载有点慢,需要优化

3. 自动生成的会话记忆 memory/YYYY-MM-DD-{slug}.md

session-memory Hook 在用户执行 /new 命令时自动生成(详见下文):

# Session: 2026-01-16 14:30:00 UTC

- **Session Key**: agent:main:main
- **Session ID**: abc123def456
- **Source**: telegram

## Conversation Summary

user: 帮我设计一个用户管理 API
assistant: 好的,我建议使用 RESTful 风格...
user: 需要支持分页吗?
assistant: 是的,建议使用游标分页...

记忆文件来源

系统支持两种记忆文件来源:

workspace/
├── MEMORY.md              # 主记忆文件(手动维护,长期记忆)
├── memory.md              # 备选主记忆文件(MEMORY.md 不存在时使用)
└── memory/                # 记忆目录(自动生成 + 手动维护)
    ├── 2026-01-15-api-design.md    # 自动生成的会话记忆
    ├── 2026-01-16-bug-fix.md       # 自动生成的会话记忆
    ├── projects.md                  # 手动维护的项目记忆
    └── preferences.md               # 手动维护的偏好设置

记忆文件扫描逻辑

// src/memory/internal.ts - listMemoryFiles 函数
export async function listMemoryFiles(workspaceDir: string): Promise<string[]> {
  const result: string[] = [];
  
  // 1. 扫描主记忆文件
  const memoryFile = path.join(workspaceDir, "MEMORY.md");
  const altMemoryFile = path.join(workspaceDir, "memory.md");
  await addMarkdownFile(memoryFile);   // 优先 MEMORY.md
  await addMarkdownFile(altMemoryFile); // 备选 memory.md
  
  // 2. 递归扫描 memory/ 目录下所有 .md 文件
  const memoryDir = path.join(workspaceDir, "memory");
  await walkDir(memoryDir, result);    // 递归遍历
  
  // 3. 扫描额外配置的路径(extraPaths)
  for (const inputPath of normalizedExtraPaths) {
    // 支持目录或单个文件
  }
  
  return deduped;  // 去重后返回
}

记忆文件自动生成(session-memory Hook)

当用户执行 /new 命令开始新会话时,session-memory Hook 会自动保存上一个会话的内容:

触发条件:用户发送 /new 命令

生成流程

用户执行 /new 命令
┌──────────────────┐
│ session-memory   │  Hook 被触发
│ Hook Handler     │
└────────┬─────────┘
┌──────────────────┐
│ 读取上一会话     │  从 sessionFile 读取最近 N 条消息
│ (默认 15 条)     │
└────────┬─────────┘
┌──────────────────┐
│ LLM 生成 slug    │  根据对话内容生成描述性文件名
│ "api-design"     │
└────────┬─────────┘
┌──────────────────┐
│ 写入记忆文件     │  memory/2026-01-16-api-design.md
└──────────────────┘

生成的记忆文件格式

# Session: 2026-01-16 14:30:00 UTC

- **Session Key**: agent:main:main
- **Session ID**: abc123def456
- **Source**: telegram

## Conversation Summary

user: 帮我设计一个用户管理 API
assistant: 好的,我建议使用 RESTful 风格...
user: 需要支持分页吗?
assistant: 是的,建议使用游标分页...
...

文件命名规则

情况文件名格式示例
LLM 成功生成 slugYYYY-MM-DD-{slug}.md2026-01-16-api-design.md
LLM 生成失败(回退)YYYY-MM-DD-HHMM.md2026-01-16-1430.md

记忆压缩刷新(Memory Flush)

文件: src/auto-reply/reply/memory-flush.ts

当会话接近上下文窗口限制时,系统会触发记忆刷新,提示 Agent 将重要信息持久化:

// 默认刷新提示
DEFAULT_MEMORY_FLUSH_PROMPT = [
  "Pre-compaction memory flush.",
  "Store durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed).",
  "If nothing to store, reply with [SILENT].",
].join(" ");

// 触发条件
function shouldRunMemoryFlush(params: {
  totalTokens: number;          // 当前会话 token 数
  contextWindowTokens: number;  // 上下文窗口大小
  reserveTokensFloor: number;   // 保留 token 数
  softThresholdTokens: number;  // 软阈值(默认 4000)
}): boolean {
  // 当 totalTokens > (contextWindow - reserve - softThreshold) 时触发
  const threshold = contextWindow - reserveTokens - softThreshold;
  return totalTokens >= threshold;
}

记忆刷新时机

会话 Token 使用情况
┌─────────────────────────────────────────────────────┐
│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░│
│<──────── 已用 Token ────────>│<── 软阈值 ──>│<保留>│
│                              │   (4000)     │      │
│                              ↑                      │
│                        触发 Memory Flush            │
└─────────────────────────────────────────────────────┘

记忆文件索引与同步

记忆文件被索引到 SQLite 数据库中,支持增量同步:

// 同步触发时机
sync: {
  onSessionStart: boolean;   // 会话开始时同步
  onSearch: boolean;         // 搜索时懒同步
  watch: boolean;            // 文件变更监视
  watchDebounceMs: number;   // 防抖间隔(默认 1500ms)
}

索引流程

记忆文件变更
┌─────────────┐
│ 文件监视器  │  (fs.watch / chokidar)
└──────┬──────┘
       │ debounce 1500ms
┌─────────────┐
│ 计算文件 hash │  检测内容是否真的变化
└──────┬──────┘
       ▼ 变化的文件
┌─────────────┐
│ 分块处理    │  chunkMarkdown(tokens=400, overlap=80)
└──────┬──────┘
┌─────────────┐
│ 生成嵌入向量 │  OpenAI / Gemini / Local
└──────┬──────┘
┌─────────────┐
│ 写入 SQLite │  chunks 表 + FTS5 表 + vec 表
└─────────────┘

7.5 向量嵌入与检索

文件: src/memory/embeddings.ts

嵌入提供者

提供者模型说明
OpenAItext-embedding-3-small远程 API
Geminigemini-embedding-001远程 API
Localembeddinggemma-300M本地模型 (node-llama-cpp)
export type EmbeddingProvider = {
  id: string;
  model: string;
  embedQuery: (text: string) => Promise<number[]>;    // 单条嵌入
  embedBatch: (texts: string[]) => Promise<number[][]>; // 批量嵌入
};

// 提供者选择策略
provider: "openai" | "local" | "gemini" | "auto"
// auto: 优先 OpenAI → Gemini → Local

// 回退机制
fallback: "openai" | "gemini" | "local" | "none"

嵌入存储

文件: src/memory/manager.ts

// SQLite 存储 + sqlite-vec 向量扩展
const VECTOR_TABLE = "chunks_vec";     // 向量表
const FTS_TABLE = "chunks_fts";        // 全文搜索表
const EMBEDDING_CACHE_TABLE = "embedding_cache";  // 嵌入缓存

// 向量转 Blob 存储
const vectorToBlob = (embedding: number[]): Buffer =>
  Buffer.from(new Float32Array(embedding).buffer);

7.6 混合搜索策略

文件: src/memory/hybrid.ts

系统采用向量搜索 + 关键词搜索的混合策略,先执行两路并行搜索,再融合结果排序:

整体搜索流程

用户查询 "上周的数据库设计"
      ┌───────┴───────┐
      ▼               ▼
┌──────────┐    ┌──────────┐
│向量嵌入  │    │关键词提取│
│(语义表示)│    │(BM25)    │
└──────────┘    └──────────┘
      │               │
      ▼               ▼
┌──────────┐    ┌───────────────┐
│sqlite-vec│    │FTS5 全文搜索  │
│ 相似度   │    │(SQLite 内置)  │
└──────────┘    └───────────────┘
      │               │
      └───────┬───────┘
      ┌──────────────┐
      │混合结果排序  │
      │(0.7*向量 +   │
      │ 0.3*关键词)  │
      └──────────────┘
      Top N 记忆片段

两种搜索技术对比

特性向量搜索 (sqlite-vec)关键词搜索 (FTS5)
原理计算语义向量的余弦相似度基于倒排索引的 BM25 评分
优势理解同义词、语义相近精确匹配、速度快
劣势需要 Embedding 模型、较慢不理解同义词
示例“推送” ≈ “发送” ✅“FTS5” 必须精确匹配
  • 向量搜索:使用 sqlite-vec 扩展进行语义相似度计算
  • 关键词搜索:使用 SQLite 内置的 FTS5(Full-Text Search 5)全文索引引擎,支持 BM25 相关性排序

💡 FTS5 是 SQLite 原生支持的全文搜索功能,通过 CREATE VIRTUAL TABLE ... USING fts5() 创建虚拟表,使用 MATCH 语法进行全文查询,无需额外的搜索引擎依赖。

默认权重配置

// src/agents/memory-search.ts
DEFAULT_HYBRID_VECTOR_WEIGHT = 0.7;       // 向量权重 70%
DEFAULT_HYBRID_TEXT_WEIGHT = 0.3;         // 关键词权重 30%
DEFAULT_HYBRID_CANDIDATE_MULTIPLIER = 4;  // 候选池扩大倍数

混合结果合并排序算法

文件: src/memory/hybrid.tsmergeHybridResults 函数

混合搜索的核心是**加权线性组合(Weighted Linear Combination)**算法:

最终得分 = vectorWeight × vectorScore + textWeight × textScore
         = 0.7 × 向量相似度 + 0.3 × BM25得分

算法步骤

// 步骤 1: 使用 Map 按文档 ID 建立索引,用于合并去重
const byId = new Map<string, {
  id: string;
  vectorScore: number;  // 向量得分
  textScore: number;    // BM25 文本得分
  // ... 其他字段
}>();

// 步骤 2: 先处理向量搜索结果
for (const r of params.vector) {
  byId.set(r.id, {
    ...r,
    vectorScore: r.vectorScore,
    textScore: 0,  // 初始化文本得分为 0
  });
}

// 步骤 3: 再处理关键词搜索结果(合并同 ID 的结果)
for (const r of params.keyword) {
  const existing = byId.get(r.id);
  if (existing) {
    // 同一文档在两种搜索中都命中,合并 textScore
    existing.textScore = r.textScore;
  } else {
    // 仅在关键词搜索中命中,vectorScore 为 0
    byId.set(r.id, { ...r, vectorScore: 0, textScore: r.textScore });
  }
}

// 步骤 4: 计算加权得分并排序
const merged = Array.from(byId.values()).map((entry) => ({
  ...entry,
  score: params.vectorWeight * entry.vectorScore 
       + params.textWeight * entry.textScore,
}));

// 步骤 5: 按最终得分降序排序
return merged.toSorted((a, b) => b.score - a.score);

BM25 分数归一化

FTS5 的 bm25() 函数返回负数排名值(越负越相关,如 -10 比 -2 更相关),需要转换为 0-1 范围的正分数:

// bm25RankToScore: 将 BM25 rank 转换为 0-1 分数
export function bm25RankToScore(rank: number): number {
  // 负数 → Math.max(0, 负数) = 0 → 1/(1+0) = 1.0(最相关)
  // 正数越大 → 得分越低
  const normalized = Math.max(0, rank);
  return 1 / (1 + normalized);
}

// 示例:
// rank = -10(非常相关)→ normalized = 0 → score = 1.0
// rank = -2(相关)    → normalized = 0 → score = 1.0
// rank = 0            → normalized = 0 → score = 1.0
// rank = 1            → normalized = 1 → score = 0.5
// rank = 9            → normalized = 9 → score = 0.1

📝 注意:由于 FTS5 返回负数表示相关,Math.max(0, rank) 会将所有负数(高相关)都归一化为 0,最终得分为 1.0。这是一种简化处理,确保 BM25 命中的结果都获得高分。

合并逻辑图解

向量搜索结果                    关键词搜索结果
┌─────────────────────┐        ┌─────────────────────┐
│ Doc A: vec=0.85     │        │ Doc A: text=0.60    │
│ Doc B: vec=0.72     │        │ Doc C: text=0.90    │
│ Doc D: vec=0.65     │        │ Doc E: text=0.45    │
└─────────────────────┘        └─────────────────────┘
            │                              │
            └──────────┬───────────────────┘
              ┌─────────────────┐
              │    按 ID 合并    │
              └─────────────────┘
        ┌──────────────────────────────────┐
        │ Doc A: vec=0.85, text=0.60       │ ← 两边都命中
        │ Doc B: vec=0.72, text=0.00       │ ← 仅向量命中
        │ Doc C: vec=0.00, text=0.90       │ ← 仅关键词命中
        │ Doc D: vec=0.65, text=0.00       │
        │ Doc E: vec=0.00, text=0.45       │
        └──────────────────────────────────┘
                       ▼ 计算加权得分 (0.7×vec + 0.3×text)
        ┌──────────────────────────────────┐
        │ Doc A: 0.7×0.85 + 0.3×0.60 = 0.775 │
        │ Doc B: 0.7×0.72 + 0.3×0.00 = 0.504 │
        │ Doc D: 0.7×0.65 + 0.3×0.00 = 0.455 │
        │ Doc C: 0.7×0.00 + 0.3×0.90 = 0.270 │
        │ Doc E: 0.7×0.00 + 0.3×0.45 = 0.135 │
        └──────────────────────────────────┘
                       ▼ 降序排序 + 过滤 minScore(0.35) + 截取 Top N
        ┌──────────────────────────────────┐
        │ 1. Doc A (0.775) ✅               │
        │ 2. Doc B (0.504) ✅               │
        │ 3. Doc D (0.455) ✅               │
        │ 4. Doc C (0.270) ❌ < minScore    │
        │ 5. Doc E (0.135) ❌ < minScore    │
        └──────────────────────────────────┘

为什么这种算法有效?

场景向量得分BM25得分最终效果
语义相似但关键词不同0.7×高 + 0.3×低 = 较高 ✅
关键词精确匹配0.7×低 + 0.3×高 = 中等 ✅
两者都匹配0.7×高 + 0.3×高 = 最高 ✅✅
两者都不匹配被 minScore 过滤 ❌

💡 设计理念:向量搜索擅长理解语义(“推送”≈“发送”),关键词搜索擅长精确匹配(“FTS5"必须完全匹配)。70:30 的权重偏向语义搜索,同时保留关键词精确匹配的能力。

7.7 记忆文件查看时机

记忆文件不是在每次对话时自动全量加载到上下文中,而是按需查询。主要有以下几种查看时机:

Agent 主动调用(最常见)

当 LLM 需要回答涉及历史信息的问题时,会主动调用 memory_searchmemory_get 工具。

系统提示词中的记忆召回指令

## Memory Recall

Before answering anything about prior work, decisions, dates, people, 
preferences, or todos: run memory_search on MEMORY.md + memory/*.md; 
then use memory_get to pull only needed lines. 
If low confidence after search, say you checked.

触发场景

场景示例问题
过去的工作“我上周让你写的那个 API 叫什么?”
历史决策“我们为什么选择了这个技术方案?”
日期相关“上次部署是什么时候?”
人物信息“小明负责哪个模块?”
用户偏好“我喜欢什么代码风格?”
待办事项“我还有哪些事情没完成?”

查询流程图

用户: "我上周让你写的那个 API 叫什么?"
┌────────────────────────────────────────┐
│ Agent 判断:需要查询历史记忆           │
│                                        │
│ 1. 调用 memory_search("上周 API")      │
│    → 返回相关片段 + 行号               │
│                                        │
│ 2. 调用 memory_get(path, from, lines)  │
│    → 获取详细内容                      │
│                                        │
│ 3. 基于检索到的信息生成回答            │
└────────────────────────────────────────┘

索引同步时机(后台自动)

记忆文件的索引会在以下时机自动同步(但不是加载到对话上下文):

sync: {
  onSessionStart: boolean;   // 会话开始时同步索引
  onSearch: boolean;         // 搜索时懒同步
  watch: boolean;            // 文件变更监视(实时)
  watchDebounceMs: number;   // 防抖间隔 1500ms
}
同步时机说明用途
onSessionStart会话开始时确保索引与文件一致
onSearch首次搜索时懒加载优化启动速度
watch文件变更时实时保持索引最新

CLI 手动查询

用户可以通过命令行主动查询记忆:

# 搜索记忆
openclaw memory search "API 设计"

# 查看记忆内容
openclaw memory cat memory/2026-01-16.md

关键设计理念

方面说明
按需加载记忆不会全量加载,只在需要时语义搜索
两步检索memory_search 定位,再 memory_get 获取详情
节省上下文避免浪费宝贵的上下文窗口空间
语义搜索支持相近概念匹配(如"推送”≈“发送”)

💡 这种设计避免了将所有记忆文件塞进上下文造成的 token 浪费,同时保证了 Agent 能在需要时准确召回相关信息。

7.8 记忆工具

文件: src/agents/tools/memory-tool.ts

memory_search 工具

{
  name: "memory_search",
  description: "Mandatory recall step: semantically search MEMORY.md + memory/*.md 
                before answering questions about prior work, decisions, dates, 
                people, preferences, or todos",
  parameters: {
    query: string,        // 搜索查询
    maxResults?: number,  // 最大结果数 (默认 6)
    minScore?: number,    // 最小分数阈值 (默认 0.35)
  }
}

memory_get 工具

{
  name: "memory_get",
  description: "Safe snippet read from MEMORY.md, memory/*.md with optional 
                from/lines; use after memory_search to pull only needed lines",
  parameters: {
    path: string,    // 文件路径
    from?: number,   // 起始行
    lines?: number,  // 读取行数
  }
}

使用流程

Agent 收到问题 "我上周让你写的那个 API 叫什么?"
       ┌────────────┐
       │memory_search│  ← 先语义搜索
       │"上周 API"  │
       └────────────┘
       返回相关片段:
       - memory/projects.md:15-25 (score: 0.82)
       - MEMORY.md:100-110 (score: 0.65)
       ┌────────────┐
       │ memory_get │  ← 获取详细内容
       │ path, from │
       └────────────┘
       Agent 生成回答

7.9 配置参数

文件: src/agents/memory-search.ts

export type ResolvedMemorySearchConfig = {
  enabled: boolean;                    // 是否启用
  sources: Array<"memory" | "sessions">;  // 记忆来源
  extraPaths: string[];               // 额外文件路径
  
  provider: "openai" | "local" | "gemini" | "auto";  // 嵌入提供者
  fallback: "openai" | "gemini" | "local" | "none";  // 回退策略
  model: string;                      // 嵌入模型
  
  store: {
    driver: "sqlite";
    path: string;                     // ~/.openclaw/memory/{agentId}.sqlite
    vector: {
      enabled: boolean;
      extensionPath?: string;         // sqlite-vec 扩展路径
    };
  };
  
  chunking: {
    tokens: number;                   // 400
    overlap: number;                  // 80
  };
  
  sync: {
    onSessionStart: boolean;          // 会话开始时同步
    onSearch: boolean;                // 搜索时同步
    watch: boolean;                   // 文件监视
    watchDebounceMs: number;          // 1500ms
  };
  
  query: {
    maxResults: number;               // 6
    minScore: number;                 // 0.35
    hybrid: {
      enabled: boolean;               // true
      vectorWeight: number;           // 0.7
      textWeight: number;             // 0.3
      candidateMultiplier: number;    // 4
    };
  };
  
  cache: {
    enabled: boolean;                 // true
    maxEntries?: number;
  };
};

配置示例 (~/.openclaw/config.yaml):

agents:
  defaults:
    memorySearch:
      enabled: true
      provider: auto
      sources:
        - memory
      extraPaths:
        - ~/notes/project-docs.md
      query:
        maxResults: 10
        minScore: 0.4
        hybrid:
          vectorWeight: 0.8
          textWeight: 0.2

八、设计模式与最佳实践

8.1 插件化架构

所有渠道和扩展功能都通过插件系统实现,插件可注册的扩展点包括:

类型说明
🔧 Tools自定义 Agent 工具
🪝 Hooks生命周期钩子
🌐 HTTP RoutesHTTP 端点
📡 Channels消息渠道
🖥️ CLI Commands命令行命令
⚙️ Services后台服务

8.2 事件驱动设计

Gateway 使用事件驱动模型进行实时通信:

文件: src/gateway/server-chat.ts

export function createAgentEventHandler({
  broadcast,
  nodeSendToSession,
  agentRunSeq,
  chatRunState,
}: AgentEventHandlerOptions) {
  const emitChatDelta = (sessionKey: string, clientRunId: string, seq: number, text: string) => {
    // 节流:150ms 内只发送一次
    const now = Date.now();
    const last = chatRunState.deltaSentAt.get(clientRunId) ?? 0;
    if (now - last < 150) {
      return;
    }
    
    // 广播到所有客户端
    broadcast("chat", payload, { dropIfSlow: true });
    
    // 发送到特定会话
    nodeSendToSession(sessionKey, "chat", payload);
  };
}

8.3 并发控制

使用 Lane (车道) 概念进行并发控制(详见 4.2 Agent 运行主入口):

  • 会话级队列: 同一会话的消息串行处理,避免并发冲突
  • 全局队列: 全局资源(如模型调用)的并发限制

8.4 核心设计亮点总结

上下文管理

特性实现方式优势
分层裁剪软裁剪 → 硬清除渐进式降级,保留最大信息
分阶段摘要分块 → 独立摘要 → 合并处理超大上下文
安全边界20% buffer防止估算误差导致溢出
Bootstrap 保护首个用户消息前不裁剪保留关键初始化信息
历史限制按用户/渠道配置精细控制不同场景

Skills 系统

特性实现方式优势
声明式依赖YAML frontmatter自动检查可用性
优先级覆盖workspace > managed > bundled支持自定义覆盖
懒加载只读取选中的 SKILL.md减少上下文消耗
热刷新chokidar 文件监视无需重启即可更新
插件 SkillsresolvePluginSkillDirs扩展能力强

记忆系统

特性实现方式优势
按需检索Agent 主动调用工具避免全量加载浪费 Token
两步检索memory_search → memory_get精准定位,减少噪音
混合搜索向量 70% + BM25 30%语义理解 + 精确匹配
增量索引文件 hash 变更检测只更新变化的内容
多源支持MEMORY.md + memory/*.md长期记忆 + 日志记忆
自动生成session-memory Hook/new 时自动保存会话
嵌入回退OpenAI → Gemini → Local保证可用性

九、总结

9.1 Agent 系统开发的核心能力矩阵

构建一个完整的 Agent 系统需要从以下四个核心维度考虑:

┌─────────────────────────────────────────────────────────────┐
│                    Agent 系统核心能力                        │
├──────────────┬──────────────┬──────────────┬───────────────┤
│    感知层    │    推理层    │    记忆层    │    行动层     │
├──────────────┼──────────────┼──────────────┼───────────────┤
│ 多渠道输入   │ LLM 调用     │ 短期记忆     │ 工具调用      │
│ 消息解析     │ 提示词工程   │ 长期记忆     │ 代码执行      │
│ 文件读取     │ 思维链       │ 向量检索     │ 文件操作      │
│ 用户意图识别 │ 多轮对话     │ 会话历史     │ 外部 API      │
└──────────────┴──────────────┴──────────────┴───────────────┘

OpenClaw 在这四个维度的实现:

维度OpenClaw 实现关键文件/模块
感知层Telegram/Discord/Slack/WhatsApp 等多渠道统一接入src/channels/, src/routing/
推理层多模型支持 + 结构化系统提示词 + 上下文工程src/agents/system-prompt.ts, src/agents/compaction.ts
记忆层向量检索 + BM25 混合搜索 + 按需加载src/memory/, src/agents/memory-search.ts
行动层可扩展工具系统 + Skills 动态加载src/agents/tools/, src/agents/skills/

9.2 OpenClaw 的技术架构总结

从一个完整 Agent 系统的视角,OpenClaw 的架构可以分为以下层次:

┌────────────────────────────────────────────────────────────────────┐
│                           用户交互层                                │
│  Telegram │ Discord │ Slack │ WhatsApp │ Web │ CLI │ 插件渠道      │
├────────────────────────────────────────────────────────────────────┤
│                           网关路由层                                │
│         消息路由 │ 会话管理 │ 权限校验 │ 配额控制                   │
├────────────────────────────────────────────────────────────────────┤
│                           Agent 核心层                              │
│  系统提示词 │ 上下文管理 │ 工具调度 │ Skills 系统 │ 记忆检索        │
├────────────────────────────────────────────────────────────────────┤
│                           模型适配层                                │
│         OpenAI │ Claude │ Gemini │ Ollama │ 更多提供商...           │
├────────────────────────────────────────────────────────────────────┤
│                           基础设施层                                │
│     SQLite 存储 │ 向量索引 │ 文件系统 │ WebSocket │ 事件总线        │
└────────────────────────────────────────────────────────────────────┘

技术选型总结

层次技术选型选型理由
语言TypeScript (ESM)类型安全、生态丰富、前后端统一
运行时Node.js + Bun生产稳定 + 开发高效
存储SQLite + sqlite-vec本地优先、零依赖、支持向量
验证Zod + TypeBox运行时类型检查、LLM 输出校验
通信WebSocket + EventEmitter实时双向、事件解耦
构建tsup + Vitest现代化工具链、快速测试

9.3 构建生产级 Agent 系统的关键考量

通过分析 OpenClaw,我们可以总结出构建生产级 Agent 系统需要关注的关键点:

1. 上下文工程(Context Engineering)

问题OpenClaw 解决方案
Token 超限智能裁剪 + 压缩安全机制
信息丢失保留系统提示词 + 关键消息
成本控制Token 估算 + 历史限制
信息注入分层上下文(系统/会话/工具)

2. 记忆系统(Memory System)

问题OpenClaw 解决方案
长期记忆MEMORY.md + memory/*.md
检索效率向量搜索 + BM25 混合
增量更新Hash 变更检测
按需加载两步检索(search → get)

3. 可扩展性(Extensibility)

问题OpenClaw 解决方案
功能扩展插件系统 + Skills 系统
渠道扩展统一消息抽象 + 路由层
模型扩展Provider 抽象层
工具扩展声明式工具注册

4. 可靠性(Reliability)

问题OpenClaw 解决方案
模型降级多 Provider 回退链
嵌入回退OpenAI → Gemini → Local
会话恢复本地持久化会话
错误处理分层错误边界

5. 安全性(Security)

问题OpenClaw 解决方案
敏感信息本地存储凭证
权限控制白名单机制
工具沙箱执行环境隔离
输入校验Zod Schema 验证

6. 可观测性(Observability)

问题OpenClaw 解决方案
日志追踪会话日志 + 事件流
状态监控CLI status 命令
诊断工具openclaw doctor
调试支持详细错误上下文

9.4 学习价值与实践建议

OpenClaw 作为一个生产级 Agent 系统,涵盖了 Agent 开发的核心知识领域:

┌────────────────────────────────────────────────────────────┐
│                     学习价值矩阵                           │
├─────────────────┬──────────────────────────────────────────┤
│   基础架构      │ TypeScript 工程化、插件系统、事件驱动   │
├─────────────────┼──────────────────────────────────────────┤
│   AI 工程       │ 提示词工程、上下文管理、工具调用        │
├─────────────────┼──────────────────────────────────────────┤
│   记忆系统      │ 向量检索、混合搜索、增量索引            │
├─────────────────┼──────────────────────────────────────────┤
│   多端适配      │ 多渠道抽象、消息路由、协议适配          │
├─────────────────┼──────────────────────────────────────────┤
│   生产实践      │ 错误处理、降级策略、可观测性            │
└─────────────────┴──────────────────────────────────────────┘

建议的学习路径

阶段目标重点模块
入门理解 Agent 基本原理系统提示词、工具调用
进阶掌握上下文工程压缩策略、记忆检索、Skills
深入理解架构设计插件系统、事件驱动、多渠道
实战构建自己的 Agent参考各模块最佳实践

总结:OpenClaw 展示了一个现代 Agent 系统应有的完整形态——不仅是调用 LLM 的简单封装,而是一个考虑了感知、推理、记忆、行动全链路,兼顾可扩展性、可靠性、安全性、可观测性的生产级系统。


文档生成时间: 2026-02-02

使用 Hugo 构建
主题 StackJimmy 设计