找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4525

积分

0

好友

598

主题
发表于 1 小时前 | 查看: 1| 回复: 0

OpenClaw 的爆火激发了我的学习热情,周末花了几天时间阅读文档、调试源码、测试功能,手搓了一张图帮助我理解其核心运行流程。本文将围绕这张图,将我对 OpenClaw 的理解完整介绍给大家,让大家也感受到 Agent设计 的魅力。

OpenClaw核心运行流程手绘流程图

概述

OpenClaw 是一个通过 Gateway,连接即时通讯平台(Channel, 如 Telegram、Discord、Slack 等)与本地 AI Agent,能 24×7 运行的个人助手。它不仅仅是一个简单的消息转发器,而是一个具备完整会话管理并发控制记忆检索以及丰富工具支持的复杂 Agent 运行时环境。

个人评价:OpenClaw 的完成度非常高,尽管因为各种安全问题被质疑,但其产品思想和围绕这个核心思想设计的各种代码组件和交互,值得开发者反复学习。

OpenClaw 多平台AI代理网关架构图

本文将深入剖析 OpenClaw 的核心运行流程,结合实际代码和流程图讲解其底层设计原理,并重点介绍 OpenClaw的Agent核心设计

核心交互流程:从消息到 Agent

下图以 Telegram 为例,说明从收到消息,到运行 Agent,最后回调发送消息的一次核心运行流程。

Telegram机器人消息处理流程图

2.1 Gateway

如果 AI 助手需要 24×7 运行,那么必然需要一个持久运行的控制平面。这个控制平面需要:保持与所有消息渠道的长连接、管理会话状态、响应客户端请求、处理定时任务。Gateway 就是这个控制平面

Gateway 核心就是一个 HTTP 和 WebSocket 服务。其启动时与注册的 Channel(比如 Telegram 机器人)建立 WebSocket 连接,随时准备接收消息。

// src/gateway/server.impl.ts
// 简化版 Gateway 启动流程
export async function startGatewayServer(
  port = 18789,
  opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
  // 1. 设置端口环境变量
  process.env.OPENCLAW_GATEWAY_PORT = String(port);

  // 2. 加载并验证配置
  let configSnapshot = await readConfigFileSnapshot();

  // 3. 创建 WebSocket 服务器
  const wsServer = new WebSocket.Server({ port, host });

  // 4. 注册核心处理器
  const channelManager = createChannelManager(configSnapshot.config);
  const agentEventHandler = createAgentEventHandler(configSnapshot.config);
  const cronService = buildGatewayCronService(configSnapshot.config);

  // 5. 启动通道连接(WhatsApp、Telegram 等)
  await channelManager.startAll();

  // 6. 返回 close 方法用于优雅关闭
  return {
    close: (opts) => shutdownGateway(opts),
  };
}

Gateway 通过系统服务管理保持 24×7 运行:

# macOS 启动 Gateway
launchctl load ~/Library/LaunchAgents/ai.openclaw.gateway.plist

# Linux 启动 Gateway
systemctl --user enable --now openclaw-gateway.service

2.2 消息接入与分发

Telegram 使用的是 grammY 作为机器人框架,注册监听事件,回复普通消息。

import { Bot, webhookCallback } from "grammy";

const bot = new Bot(opts.token, client ? { client } : undefined);
const processMessage = createTelegramMessageProcessor({ bot, ... });
registerTelegramHandlers({ cfg, accountId, bot, processMessage, ... });

其中 createTelegramMessageProcessor 里使用核心分发函数 dispatchTelegramMessage 负责处理来自 Telegram 的消息,以及注册回复消息的回调函数 deliverReplies

// src/telegram/bot-message-dispatch.ts
export const dispatchTelegramMessage = async ({
  context,
  bot,
  cfg,
  runtime,
  // ... 更多参数
}) => {
  const { msg, chatId, isGroup, historyKey, route } = context;

  // 1. 分发消息
  const { queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({
    ctx: ctxPayload,
    cfg,
    dispatcherOptions: {
      // 2. 回复消息回调
      deliver: async (payload, info) => {
        const result = await deliverReplies({
          replies: [payload],
          chatId: String(chatId),
          token: opts.token,
          runtime,
          bot,
          // ... 更多选项
        });
      },
      onError: (err, info) => {
        runtime.error?.(`telegram reply failed: ${String(err)}`);
      },
    },
  });
};

一些特殊消息能力,需要通过 message 工具来发送,而非直接通过 deliverReplies 来发送消息。后续工具技能会具体介绍。

2.3 Session Key 机制

SessionKey 是 OpenClaw 中用于标识和路由会话的核心概念。

由于 OpenClaw 支持非常多的 Channel 账号、以及私聊、群组、Thread 等各种会话形式,需要一种命名方式来唯一识别不同的会话。

// src/routing/session-key.ts
export function buildAgentMainSessionKey(params: {
  agentId: string;
  mainKey?: string | undefined;
}): string {
  const agentId = normalizeAgentId(params.agentId);
  const mainKey = normalizeMainKey(params.mainKey);
  return `agent:${agentId}:${mainKey}`;
}

export function buildAgentPeerSessionKey(params: {
  agentId: string;
  mainKey?: string | undefined;
  channel: string;
  accountId?: string | null;
  peerKind?: "dm" | "group" | "channel" | null;
  peerId?: string | null;
  // ...
}): string {
  // 对于 DM: agent:main:channel:account:dm:peerId
  // 对于群组: agent:main:channel:group:groupId
  // ...
}

SessionKey 的格式示例:

  • 主会话: agent:main:main
  • Telegram DM: agent:main:telegram:default:dm:123456789
  • Telegram 群组: agent:main:telegram:group:1001234567890

2.4 Agent Loop

OpenClaw 支持调用已有的 CLI Agent,比如 Claude Code 等;但默认情况下也嵌入了基于 Pi-Agent框架 执行 Agent 运行时。该框架具备极高的扩展性,满足 OpenClaw 定制化需求(比如大模型供应商支持、Session 管理、工具定制化、流式输出、消息订阅)。

如下图所示,消息进入 AgentSession 后,通过 ReAct 范式执行 Agent Loop(包括工具调用),通过注册 reply 回调(监听 message_end 事件)将 AI 回复的消息通过机器人发送给 Telegram。

Pi Coding Agent 架构手绘流程图

故障转移机制

为了能 24×7 持续运行,不能因为一些异常就停止。因此 OpenClaw 实现了完整的故障转移机制:

  1. Auth Profile 轮换: 当一个 API Key 遇到速率限制或认证失败时,自动切换到下一个可用的 Profile
  2. 上下文溢出自动压缩: 当会话过长时,自动压缩历史消息
  3. 思考级别降级: 当模型不支持扩展思考模式时,自动降级到基本模式
// src/agents/pi-embedded-runner/run.ts
export async function runEmbeddedPiAgent(
  params: RunEmbeddedPiAgentParams,
): Promise<EmbeddedPiRunResult> {
  // 会话级别并发控制(串行处理)
  const sessionLane = resolveSessionLane(params.sessionKey?.trim() || params.sessionId);
  // 全局并发控制(默认并发度 4)
  const globalLane = resolveGlobalLane(params.lane);

  return enqueueSession(() =>
    enqueueGlobal(async () => {
      const started = Date.now();
      // 模型解析和上下文窗口验证
      const { model, error, authStorage, modelRegistry } = resolveModel(
        provider,
        modelId,
        agentDir,
        params.config,
      );

      const ctxInfo = resolveContextWindowInfo({
        cfg: params.config,
        provider,
        modelId,
        modelContextWindow: model.contextWindow,
        defaultTokens: DEFAULT_CONTEXT_TOKENS,
      });

      // 认证配置管理和故障转移
      const profileOrder = resolveAuthProfileOrder({
        cfg: params.config,
        store: authStore,
        provider,
        preferredProfile: preferredProfileId,
      });

      // 主执行循环,支持故障转移
      while (true) {
        const attempt = await runEmbeddedAttempt({
          sessionId: params.sessionId,
          sessionKey: params.sessionKey,
          // ... 大量参数
        });

        // 处理上下文溢出,自动压缩
        if (isContextOverflowError(errorText)) {
          if (!overflowCompactionAttempted) {
            const compactResult = await compactEmbeddedPiSessionDirect({
              sessionId: params.sessionId,
              sessionKey: params.sessionKey,
              // ...
            });
            if (compactResult.compacted) {
              continue; // 使用压缩后的会话重试
            }
          }
        }

        // 处理认证/速率限制故障转移
        if (shouldRotate) {
          const rotated = await advanceAuthProfile();
          if (rotated) continue;
        }

        return {
          payloads: payloads.length ? payloads : undefined,
          meta: {
            durationMs: Date.now() - started,
            agentMeta,
            aborted,
            systemPromptReport: attempt.systemPromptReport,
          },
        };
      }
    }),
  );
}

2.5 Agent 核心设计

OpenClaw 并没有从 0 构造 Agent 核心,而是使用开源的 Pi-Agent 框架。但 OpenClaw 也定制了其中核心组件,将其打造成开箱即用的、能力丰富的本地个人助手。下面着重从并发控制(Queue)、会话管理(Session)、记忆系统(Memory)和工具技能(Tools & Skills) 几个角度介绍。

队列与并发控制

为了解决群组聊天或高频交互中的“消息竞争”问题,OpenClaw 参考 Pi Agent 设计了一套精密的 Queue 系统。

3.1 队列模式(Queue mode)

队列模式对比图:collect/followup vs steer

  • collect - 收集模式(默认):将所有排队的消息合并成单个后续回复
  • steer - 转向模式:立即注入到当前 agent 回合中
  • followup - 跟进模式:当前运行结束后,为下一个 agent 回合排队
  • steer-backlog - 转向+积压模式:现在转向当前回合,然后保留消息用于后续回合
// src/auto-reply/reply/queue/types.ts
export type QueueMode = "steer" | "followup" | "collect" | "steer-backlog" | "interrupt" | "queue";

export type QueueSettings = {
  mode: QueueMode;
  debounceMs?: number; // 防抖延迟(毫秒),默认1000ms
  cap?: number; // 队列容量上限,默认20
  dropPolicy?: "old" | "new" | "summarize"; // 默认summarize
};

Collect 的消息示例:

{
  "id": "e1c9d464",
  "message": {
    "content": [
      {
        "text": "[Queued messages while agent was busy]\n\n---\nQueued #1\n[Slack x +1s 2026-02-09 16:58 GMT+8] 算了 [slack message id: x channel: x]\n[message_id: x]\n\n---\nQueued #2\n[Slack x +4s 2026-02-09 16:58 GMT+8] 查一下天津的 [slack message id: x channel: x]\n[message_id: x]",
        "type": "text"
      }
    ],
    "role": "user",
    "timestamp": 1770627650797
  },
  "parentId": "7588527b",
  "timestamp": "2026-02-09T09:00:50.802Z",
  "type": "message"
}

3.2 队列处理逻辑

Steer:利用 pi-agent 的 steer 能力,在 Agent loop 中插入消息。

// 如果存在运行中的session,则插入消息
if (shouldSteer && isStreaming) {
  const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
  ...
  void handle.queueMessage(text);
}
// 实际:使用pi-agent框架的steer插入消息
const queueHandle: EmbeddedPiQueueHandle = {
  queueMessage: async (text: string) => {
    await activeSession.steer(text);
  }
  ...
};
// pi-agent的agent-loop
let pendingMessages: AgentMessage[] = (await config.getSteeringMessages?.()) || [];
while (true) {
  ...
  // 如果存在待插入的消息,则直接加入到状态中
  if (pendingMessages.length > 0) {
    for (const message of pendingMessages) {
      currentContext.messages.push(message);
      newMessages.push(message);
    }
    pendingMessages = [];
  }
  ...
}

Follow Up 和 Collect 模式:

// src/auto-reply/reply/queue/drain.ts
export function scheduleFollowupDrain(
  key: string,
  runFollowup: (run: FollowupRun) => Promise<void>,
): void {
  const queue = FOLLOWUP_QUEUES.get(key);
  if (!queue || queue.draining) return;

  queue.draining = true;
  void (async () => {
    try {
      while (queue.items.length > 0 || queue.droppedCount > 0) {
        await waitForQueueDebounce(queue);

        if (queue.mode === "collect") {
          // 批量收集模式:合并所有等待消息为一个 Prompt
          const items = queue.items.splice(0, queue.items.length);
          const summary = buildQueueSummaryPrompt({ state: queue, noun: "message" });

          const prompt = buildCollectPrompt({
            title: "[Queued messages while agent was busy]",
            items,
            summary,
            renderItem: (item, idx) => `---\nQueued #${idx + 1}\n${item.prompt}`.trim(),
          });

          await runFollowup({ prompt, run, enqueuedAt: Date.now() });
          continue;
        }

        // Followup 模式:处理下一个消息
        const next = queue.items.shift();
        await runFollowup(next);
      }
    } finally {
      queue.draining = false;
    }
  })();
}

3.3 并发控制

OpenClaw 使用两层并发控制:

  1. 会话级别: 同一会话内的消息串行处理,避免状态混乱
  2. 全局级别: 默认并发度为 4,允许最多 4 个会话同时处理

Telegram 还专门使用 bot.use(sequentialize(getTelegramSequentialKey())) 序列化所有消息。

// src/agents/pi-embedded-runner/run/lanes.ts
export function resolveSessionLane(sessionId: string): string {
  return `session:${sessionId}`;
}

export function resolveGlobalLane(lane?: string): string {
  return lane || `global:default`;
}

// enqueueCommandInLane
export async function runEmbeddedPiAgent(
  params: RunEmbeddedPiAgentParams,
): Promise<EmbeddedPiRunResult> {
  // 全局并发度4:即保障active的会话,不超过4个
  const enqueueGlobal = params.enqueue ?? ((task, opts) => enqueueCommandInLane(globalLane, task, opts));
  const enqueueSession = params.enqueue ?? ((task, opts) => enqueueCommandInLane(sessionLane, task, opts));

  return enqueueSession(() =>
    enqueueGlobal(async () => { ... })
  )
}

会话管理:Session

一般 session 的对话数据也被称为短期记忆。

与 Agent 的多轮交互需要维持一次会话中历史所有消息(UserMessage, AI Message,Tool Result Message 等),一般 Agent 会存储在内存中(简单场景)或数据库(云端场景)中。

OpenClaw 作为本地 Agent,使用 Pi Agent 自带的 Session 管理工具,采用本地文件系统作为 Session存储工具,解决会话数据的持久化问题

Session Key生成与存储机制说明图

4.1 存储结构

物理存储路径: ~/.openclaw/agents/<agentId>/sessions/

  • session.json: 记录所有 Session 的元数据映射
  • <sessionId>.jsonl: 存储具体的对话日志(JSON Lines 格式,便于追加写入)

4.2 生命周期管理

Agent Session 并不会一直共享同一个上下文,否则上下文窗口很容易超长(虽然可以执行 Session 超长压缩,但总归不是一个有效率的做法)。因此 OpenClaw 实现了自动化的会话生命周期管理:

  • 每日重置: 每天自动生成新的 SessionId(通过检测日期变化)
  • 空闲归档: 默认 60 分钟无交互后归档当前 Session
  • 子 Agent 管理: 子 Agent 的 Session 同样遵循 60 分钟自动归档策略

因此一个 SessionKey,可能存在多个 SessionId。比如同样在 Telegram 私聊机器人,今天对话使用的上下文和昨天是完全不同的。

代码编辑器界面展示Session JSON数据

4.3 Session 加载和管理

会话管理流程图

获取 Sesssion 的所有配置:session.json

// src/config/sessions/store.ts
export function loadSessionStore(
  storePath: string,
  opts: LoadSessionStoreOptions = {},
): Record<string, SessionEntry> {
  // 从磁盘加载
  let store: Record<string, SessionEntry> = {};
  try {
    const raw = fs.readFileSync(storePath, "utf-8");
    const parsed = JSON5.parse(raw);
    if (isSessionStoreRecord(parsed)) {
      store = parsed;
    }
  } catch {
    // 忽略缺失/无效的 store;我们会重新创建
  }
  return structuredClone(store);
}

根据 SessionKey 和对应的 SessionId,获取当前会话的历史文件 sessionFile: sessions/<sessionId>.jsonl。作为 Pi Agent 的 SessionManager。

// 构造Session Manager,创建AgentSession
sessionManager = guardSessionManager(
  // 获取当前会话的历史文件sessionFile: <sessionId>.jsonl
  SessionManager.open(params.sessionFile),
  {
    agentId: sessionAgentId,
    sessionKey: params.sessionKey,
    ...
  }
);

({ session } = await createAgentSession({
  cwd: resolvedWorkspace,
  agentDir,
  authStorage: params.authStorage,
  modelRegistry: params.modelRegistry,
  model: params.model,
  thinkingLevel: mapThinkingLevel(params.thinkLevel),
  tools: builtInTools,
  customTools: allCustomTools,
  sessionManager,
  ...
}));

会话新增消息,加锁后存入文件。

export async function updateSessionStore<T>(
  storePath: string,
  mutator: (store: Record<string, SessionEntry>) => Promise<T> | T,
): Promise<T> {
  return await withSessionStoreLock(storePath, async () => {
    // 在锁内重新读取以避免覆盖并发写入
    const store = loadSessionStore(storePath, { skipCache: true });
    const result = await mutator(store);
    await saveSessionStoreUnlocked(storePath, store);
    return result;
  });
}

记忆系统:Memory

OpenClaw 拥有一个完善的记忆系统,通过对记忆相关的 Markdown 文件的实时索引和混合检索来实现长期记忆。下图将介绍记忆存储、记忆索引、记忆检索的流程:

Memory系统综合架构图

5.1 记忆存储

除了 Agent 固定加载 的一些相关文件(比如 AGENTS.md,USER.md, IDENTITY.md 等)之外,我们还需要 agent 记住其他类型的事情或特定日期发生的事情,这些记忆存储在 ~/.openclaw/workspace 下:

  • MEMORY.mdmemory.md: 全局长期记忆
  • memory/*.md: 目录中的所有 Markdown 文件
  • 额外路径: 通过 memorySearch.extraPaths 配置
// src/memory/internal.ts
export async function listMemoryFiles(
  workspaceDir: string,
  extraPaths?: string[],
): Promise<string[]> {
  const result: string[] = [];
  const memoryFile = path.join(workspaceDir, "MEMORY.md");
  const altMemoryFile = path.join(workspaceDir, "memory.md");
  const memoryDir = path.join(workspaceDir, "memory");

  // 添加主记忆文件
  await addMarkdownFile(memoryFile);
  await addMarkdownFile(altMemoryFile);

  // 递归遍历 memory 目录
  try {
    const dirStat = await fs.lstat(memoryDir);
    if (!dirStat.isSymbolicLink() && dirStat.isDirectory()) {
      await walkDir(memoryDir, result);
    }
  } catch {}

  // 处理额外路径
  const normalizedExtraPaths = normalizeExtraMemoryPaths(workspaceDir, extraPaths);
  for (const inputPath of normalizedExtraPaths) {
    const stat = await fs.lstat(inputPath);
    if (stat.isDirectory()) {
      await walkDir(inputPath, result);
    } else if (stat.isFile() && inputPath.endsWith(".md")) {
      result.push(inputPath);
    }
  }

  // 去重
  return deduped;
}

Memory 写入机制:

  • memoryFlush(session 自动压缩) 只在 context tokens 快满的情况下执行
  • prompt: 使用类似“记住我”、“记住这个”、“今天”等,agent 会保存记忆到本地文件。
  • sessionFlush: /new 新 session 保存旧 session 到 memory/<YYYY-MM-DD>-slug.md

记忆写入示例聊天截图

配置使用 sources: ["memory", "sessions"] 后也会将 session 文件纳入到记忆检索范围中。

5.2 混合检索

通过给 Agent 提供 memory_searchmemory_get 工具,允许其在合适的时候通过 query 检索历史记忆中相关的文本片段(RAG 范式)。

在 OpenClaw 中,使用了典型的混合检索方案,即同时通过关键词精确搜索 + 向量语义检索,对候选结果计算加权得分,给 agent 展示最相关的几条。

基于本地个人 Agent 的定位,OpenClaw 的精确检索和向量检索都默认使用 Sqlite 作为数据库存储(结果是一个 agents.sqlite 文件)。

Memory混合检索架构图

Memory 检索工具:

// src/agents/tools/memory-tool.ts
export function createMemorySearchTool(options: {
  config?: OpenClawConfig;
  agentSessionKey?: string;
}): AnyAgentTool | null {
  return {
    label: "Memory Search",
    name: "memory_search",
    description:
      "Mandatory recall step: semantically search MEMORY.md + memory/*.md " +
      "(and optional session transcripts) before answering questions about " +
      "prior work, decisions, dates, people, preferences, or todos; " +
      "returns top snippets with path + lines.",
    parameters: MemorySearchSchema,
    execute: async (_toolCallId, params) => {
      const query = readStringParam(params, "query", { required: true });
      const maxResults = readNumberParam(params, "maxResults");
      const minScore = readNumberParam(params, "minScore");

      const { manager, error } = await getMemorySearchManager({ cfg, agentId });
      if (!manager) {
        return jsonResult({ results: [], disabled: true, error });
      }

      const results = await manager.search(query, {
        maxResults,
        minScore,
        sessionKey: options.agentSessionKey,
      });

      return jsonResult({ results, provider: status.provider, model: status.model });
    },
  };
}

MemoryManager 执行混合检索:

// src/memory/manager.ts
export class MemoryIndexManager {
  async search(
    query: string,
    opts?: { maxResults?: number; minScore?: number; sessionKey?: string },
  ): Promise<MemorySearchResult[]> {
    // 关键词搜索
    const keywordResults = hybrid.enabled
      ? await this.searchKeyword(cleaned, candidates).catch(() => [])
      : [];

    // 向量搜索
    const queryVec = await this.embedQueryWithTimeout(cleaned);
    const vectorResults = hasVector
      ? await this.searchVector(queryVec, candidates).catch(() => [])
      : [];

    // 合并结果
    if (!hybrid.enabled) {
      return vectorResults.filter((entry) => entry.score >= minScore).slice(0, maxResults);
    }
    const merged = this.mergeHybridResults({
      vector: vectorResults,
      keyword: keywordResults,
      vectorWeight: hybrid.vectorWeight,
      textWeight: hybrid.textWeight,
    });

    return merged.filter((entry) => entry.score >= minScore).slice(0, maxResults);
  }
}

下面简单示例如何使用 Sqlite-vec 执行 KNN 向量检索:

db.exec(`CREATE VIRTUAL TABLE vec_items USING vec0(embedding float[4])`);
// 插入
const insert = db.prepare(`INSERT INTO vec_items(rowid, embedding) VALUES (?, ?)`);
const data = [[1, [0.1, 0.1, 0.1, 0.1]], [2, [0.2, 0.2, 0.2, 0.2]]];
for (const [id, vec] of data) {
  insert.run(BigInt(id), new Float32Array(vec));
}
// KNN 搜索
const query = new Float32Array([0.15, 0.15, 0.15, 0.15]);
const rows = db
  .prepare(`SELECT rowid, distance FROM vec_items WHERE embedding MATCH ? ORDER BY distance LIMIT 3`)
  .all(query);

5.3 索引写入

记忆检索依赖的本地 sqlite 数据库,是在特定时机触发索引写入的。比如,在监听到 Memory 文件变更后,会将其同步索引到本地数据库中,方便后续进行记忆检索。如果开启了 session 来源,也会在 session 更新文件后,执行索引写入。

文件变更触发同步与索引写入流程图

单个文件的索引写入可以分为三个部分:

  1. 文件分块:先将文件根据 chunkTokens 配置分为固定大小的块,为了增强前后文的感知,允许一定 token 的分块重叠。
  2. Embedding 计算和缓存:利用配置的远程或本地 embedding 模型计算 chunk 的语义向量。同时由于经常多次索引,因此可以利用 embedding 缓存来避免重复计算。
  3. 写入本地数据库:写入索引检索依赖 chunks_vecchunks_fts 两种表。

工具技能 (Tools & Skills)

OpenClaw 提供了极其丰富且开箱即用的工具技能,这也是其能大火的原因之一。除了通用 CLI Agent 都有的文件系统访问、Shell 命令执行、webSearch 工具外,还有获取 Gateway、Session 等运行状态的自感知能力,以及内置的多个实用的插件工具(比如 bird,message,browser,天气等)。

限于篇幅和时间,这部分更详细的内容放在后续的文章中介绍。下面介绍几个典型的工具,来说明其运作原理。

6.1 工具示例:message

工具集成本身是 agent 的通用能力,因此下面介绍 OpenClaw 比较独特的 message 工具。与特定 Channel 集成,能够达到丝滑的交互效果:

  • 发送多条消息:AI 可以在最终回复用户之前或之后,先发送一张图片、一个文件或者另一条补充消息。(主动式 Agent 的基础)
  • 富文本与复杂交互:显式设置 buttons(内联按钮)、card(卡片)、poll(投票)等高级功能。
  • 引用与回复:精准控制 replyTo(回复特定消息 ID),而不仅仅是回复当前最后一条。

日语N5练习题聊天界面截图

{
  "action": "send",
  "buttons": "[[{\"text\":\"A. 下午好\", \"callback_data\":\"n5_quiz_wrong\"}, {\"text\":\"B. 再见\", \"callback_data\":\"n5_quiz_correct\"}], [{\"text\":\"C. 谢谢\", \"callback_data\":\"n5_quiz_wrong\"}, {\"text\":\"D. 早上好\", \"callback_data\":\"n5_quiz_wrong\"}]]",
  "channel": "telegram",
  "message": "📚 **日语N5练习题**\n\n**さようなら** 的中文意思是什么?",
  "target": "123456"
}

6.2 Skills 示例:bird

复习一下 Skills 的工作原理,通过文件系统+渐进式披露,标准化和模块化技能提示词。

Skills演化流程图

Skills 将从三个位置加载:

  1. 内置 Skills:随安装包一起发布(npm 包或 OpenClaw.app),比如 bird,github 等
    bird SKILL.md文件截图
  2. 托管/本地 Skills~/.openclaw/skills
  3. 工作区 Skills<workspace>/skills

下面以 bird skills 介绍,如何搜索和总结 Twitter(X)上的内容。

使用bird技能搜索Twitter的聊天截图

6.3 自定义工具和技能

OpenClaw 还允许用户自定义加入工具和技能。当然官方推荐使用 clawhub 命令安装 https://clawhub.ai/ 里的技能或通过 plugin 命令安装插件。

# 技能安装,以artifacts-builder为例
npm i -g clawhub
clawhub install artifacts-builder

# 插件安装
openclaw plugins list
openclaw plugins install @openclaw/voice-call

你也可以直接让 openclaw 帮你安装;或者直接将 skills 添加到目录 .openclaw/workspace/skills/

安装GitHub技能聊天截图

6.4 工具策略

翻看 OpenClaw 的代码,其实最让我震惊的是其灵活的配置能力。以工具策略为例,OpenClaw 支持多个层级的工具配置策略,即特定的场景可以配置不同的工具组合可见性。

  1. 全局策略: config.tools
  2. 全局按提供商策略: config.tools.byProvider[providerOrModelId]
  3. Agent 策略: config.agents.[agentId].tools
  4. Agent 按提供商策略: config.agents.[agentId].tools.byProvider[providerOrModelId]
  5. 群组策略: config.groups.[groupId].tools (针对特定群组)

总结

其实整体来看 OpenClaw 在 Agent 核心层面没有特别多新颖的地方,但作为一个完成度特别高的产品:24×7运行的本地个人助手,其架构设计思想和扩展集成值得我们反复学习

本文从核心交互流程出发,了解消息接入和分发链路,以及 SessionKey 的机制,一步一步深入 Agent 核心设计。了解到 Queue 如何解决复杂的并发消息问题,以及 OpenClaw 如何管理 session 文件解决短期记忆的持久化问题,如何通过记忆文件保存和检索长期记忆。

如果你对这类 开源项目 的深入解析感兴趣,欢迎在 云栈社区 进行更深入的交流探讨。




上一篇:前端缓存为什么没人提?解锁10倍性能提升的实战策略
下一篇:FastAPI依赖注入实战:3步实现代码复用与优雅资源管理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-23 06:35 , Processed in 0.683056 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表