小龙虾到底怎么设计的?技术人来看看这个深度解析:一张图拆解OpenClaw的Agent核心设计。
作者小厶地址https://zhuanlan.zhihu.com/p/2004505448938767308经授权发布如需转载请联系原作者OpenClaw的爆火激发了我的学习热情周末花了几天时间阅读文档、调试源码、测试功能手搓了一张图帮助我理解核心运行流程。本文将围绕这张图将我对OpenClaw的理解完整介绍给大家让大家也感受到Agent设计的魅力。OpenClaw核心运行流程01概述OpenClaw 是一个通过Gateway连接即时通讯平台Channel, 如 Telegram、Discord、Slack 等与本地AI Agent 能24×7 运行的个人助手。它不仅仅是一个简单的消息转发器而是一个具备完整会话管理、并发控制、记忆检索以及丰富工具支持的复杂 Agent 运行时环境。个人评价OpenClaw的完成度非常高尽管因为各种安全问题被质疑但其产品思想和围绕这个核心思想设计的各种代码组件和交互值得开发者反复学习。OpenClaw架构图本文档将深入剖析 OpenClaw 的核心运行流程结合实际代码和流程图讲解其底层设计原理。并重点介绍OpenClaw的Agent设计。02核心交互流程从消息到 Agent下图以Telegram为例说明从收到消息到运行Agent最后回调发送消息的一次核心运行流程。2.1 Gateway如果 AI 助手需要 24×7 运行那么必然需要一个持久运行的控制平面。这个控制平面需要保持与所有消息渠道的长连接、管理会话状态、响应客户端请求、处理定时任务。Gateway 就是这个控制平面。Gateway核心就是一个HTTP和WebSocket服务。其启动时与注册的Channel比如Telegram机器人建立WebSocket连接随时准备接收消息。// src/gateway/server.impl.ts // 简化版 Gateway 启动流程 export async function startGatewayServer( port18789, opts:GatewayServerOptions{}, ):PromiseGatewayServer{ // 1. 设置端口环境变量 process.env.OPENCLAW_GATEWAY_PORTString(port); // 2. 加载并验证配置 let configSnapshotawaitreadConfigFileSnapshot(); // 3. 创建 WebSocket 服务器 const wsServernewWebSocket.Server({port,host}); // 4. 注册核心处理器 const channelManager createChannelManager (configSnapshot.config); const agentEventHandler createAgentEventHandler (configSnapshot.config); const cronService buildGatewayCronService (configSnapshot.config); // 5. 启动通道连接WhatsApp、Telegram 等 awaitchannelManager.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.service2.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}awaitdispatchReplyWithBufferedBlockDispatcher({ ctx:ctxPayload, cfg, dispatcherOptions:{ // 2. 回复消息回调 deliver:async(payload,info){ const resultawaitdeliverReplies({ 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 agentIdnormalizeAgentId(params.agentId); const mainKeynormalizeMainKey(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:mainTelegram DM: agent :main :telegram :default :dm :123456789Telegram 群组: agent :main :telegram :group :10012345678902.4 Agent LoopOpenClaw支持调用已有的CLI Agent比如Claude Code等但默认情况下也嵌入了基于Pi-Agent框架 执行Agent运行时。该框架具备极高的扩展性满足OpenClaw定制化需求比如大模型供应商支持、Session管理、工具定制化、流式输出、消息订阅。如下图所示消息进入AgentSession后通过ReAct范式执行Agent Loop包括工具调用通过注册reply回调监听message_end事件将AI回复的消息通过机器人发送给Telegram。故障转移机制为了能24 × 7 持续运行不能因为一些异常就停止。因此OpenClaw 实现了完整的故障转移机制1. Auth Profile 轮换: 当一个 API Key 遇到速率限制或认证失败时自动切换到下一个可用的 Profile2. 上下文溢出自动压缩: 当会话过长时自动压缩历史消息3. 思考级别降级: 当模型不支持扩展思考模式时自动降级到基本模式// src/agents/pi-embedded-runner/run.ts export async function runEmbeddedPiAgent( params:RunEmbeddedPiAgentParams, ):PromiseEmbeddedPiRunResult{ // 会话级别并发控制串行处理 const sessionLane resolveSessionLane(params.sessionKey ?. trim() || params.sessionId); // 全局并发控制默认并发度 4 const globalLaneresolveGlobalLane(params.lane); return enqueueSession(() enqueueGlobal(async(){ const startedDate.now(); // 模型解析和上下文窗口验证 const {model,error,authStorage,modelRegistry} resolveModel( provider, modelId, agentDir, params.config, ); const ctxInforesolveContextWindowInfo({ cfg:params.config, provider, modelId, modelContextWindow:model.contextWindow, defaultTokens:DEFAULT_CONTEXT_TOKENS, }); // 认证配置管理和故障转移 const profileOrderresolveAuthProfileOrder({ cfg:params.config, store:authStore, provider, preferredProfile:preferredProfileId, }); // 主执行循环支持故障转移 while (true){ const attemptawaitrunEmbeddedAttempt({ sessionId:params.sessionId, sessionKey:params.sessionKey, // ... 大量参数 }); // 处理上下文溢出自动压缩 if (isContextOverflowError(errorText)){ if (!overflowCompactionAttempted){ const compactResultawaitcompactEmbeddedPiSessionDirect({ sessionId:params.sessionId, sessionKey:params.sessionKey, // ... }); if (compactResult.compacted){ continue;// 使用压缩后的会话重试 } } } // 处理认证/速率限制故障转移 if (shouldRotate){ const rotatedawaitadvanceAuthProfile(); 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、会话管理Sesssion、记忆系统Memory和工具技能Tools Skills几个角度介绍。03队列与并发控制为了解决群组聊天或高频交互中的消息竞争问题OpenClaw 参考Pi Agent设计了一套精密的 Queue 系统。3.1 队列模式Queue modecollect - 收集模式默认将所有排队的消息合并成单个后续回复steer - 转向模式立即注入到当前agent回合中followup - 跟进模式当前运行结束后为下一个 agent 回合排队steer-backlog - 转向积压模式现在转向当前回合然后保留消息用于后续回合// src/auto-reply/reply/queue/types.ts export typeQueueModesteer|followup|collect|steer-backlog|interrupt|queue; export typeQueueSettings{ 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 GMT8] 算了 [slack message id: x channel: x]\n[message_id: x]\n\n---\nQueued #2\n[Slack x 4s 2026-02-09 16:58 GMT8] 查一下天津的 [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){ awaitactiveSession.steer(text); } ... }; // pi-agent的agent-loop let pendingMessages:AgentMessage[](awaitconfig.getSteeringMessages?.())||[]; while (true){ ... // 如果存在待插入的消息则直接加入到状态中 if (pendingMessages.length0){ 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)Promisevoid, ) :void { const queueFOLLOWUP_QUEUES.get(key); if (! queue || queue.draining) return; queue.draining true; void (async(){ try{ while (queue.items.length0 || queue.droppedCount0){ awaitwaitForQueueDebounce(queue); if (queue.modecollect){ // 批量收集模式合并所有等待消息为一个 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(), }); awaitrunFollowup({prompt,run,enqueuedAt:Date.now()}); continue; } // Followup 模式处理下一个消息 const next queue.items.shift(); awaitrunFollowup(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, ):PromiseEmbeddedPiRunResult{ // 全局并发度4即保障active的会话不超过4个 const enqueueGlobalparams.enqueue ?? ((task,opts)enqueueCommandInLane(globalLane,task,opts)); const enqueueSessionparams.enqueue ?? ((task,opts)enqueueCommandInLane(sessionLane,task,opts)); return enqueueSession(() enqueueGlobal(async(){...}) ) }04会话管理Session一般session的对话数据也被称为短期记忆与Agent的多轮交互需要维持一次会话中历史所有消息UserMessage, AI MessageTool Result Message等一般Agent会存储在内存中简单场景或数据库云端场景中。OpenClaw 作为本地Agent使用Pi Agent自带的Session管理工具采用本地文件系统作为 Session存储工具解决会话数据的持久化问题。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私聊机器人今天对话使用的上下文和昨天是完全不同的。4.3 Session 加载和管理获取Sesssion的所有配置session.json// src/config/sessions/store.ts export function loadSessionStore( storePath:string, opts:LoadSessionStoreOptions{}, ):Recordstring,SessionEntry{ // 从磁盘加载 let store:Recordstring,SessionEntry{}; try{ const raw fs.readFileSync(storePath,utf-8); const parsed JSON5.parse(raw); if (isSessionStoreRecord(parsed)){ storeparsed; } } catch { // 忽略缺失/无效的 store我们会重新创建 } returnstructuredClone(store); }根据SessionKey和对应的SessionId获取当前会话的历史文件sessionFile: sessions/sessionId.jsonl。作为Pi Agent的SessionManager。// 构造Session Manager创建AgentSession sessionManagerguardSessionManager( // 获取当前会话的历史文件sessionFile: sessionId.jsonl SessionManager.open(params.sessionFile),{ agentId:sessionAgentId, sessionKey:params.sessionKey, ... }); ({session}awaitcreateAgentSession({ cwd:resolvedWorkspace, agentDir, authStorage:params.authStorage, modelRegistry:params.modelRegistry, model:params.model, thinkingLevel:mapThinkingLevel(params.thinkLevel), tools:builtInTools, customTools:allCustomTools, sessionManager, ... }));会话新增消息加锁后存入文件。export async function updateSessionStoreT( storePath:string, mutator:(store:Recordstring,SessionEntry)PromiseT|T, ):PromiseT{ return awaitwithSessionStoreLock(storePath,async(){ // 在锁内重新读取以避免覆盖并发写入 const store loadSessionStore(storePath,{skipCache : true}); const result awaitmutator(store); awaitsaveSessionStoreUnlocked(storePath,store); return result; }); }05记忆系统MemoryOpenClaw 拥有一个完善的记忆系统通过对记忆相关的Markdown文件的实时索引和混合检索来实现长期记忆。下图将介绍记忆存储、记忆索引、记忆检索的流程5.1 记忆存储除了Agent固定加载的一些相关文件比如AGENTS.mdUSER.md, IDENTITY.md等之外我们还需要agent记住其他类型的事情 或 特定日期发生的事情这些记忆存储在 ~/.openclaw/workspace 下MEMORY.md 或 memory.md: 全局长期记忆memory/*.md: 目录中的所有 Markdown 文件额外路径: 通过 memorySearch.extraPaths 配置// src/memory/internal.ts export async function listMemoryFiles( workspaceDir:string, extraPaths ?: string[], ):Promisestring[]{ const result : string[][]; const memoryFilepath.join(workspaceDir,MEMORY.md); const altMemoryFilepath.join(workspaceDir,memory.md); const memoryDirpath.join(workspaceDir,memory); // 添加主记忆文件 awaitaddMarkdownFile(memoryFile); awaitaddMarkdownFile(altMemoryFile); // 递归遍历 memory 目录 try{ const dirStatawaitfs.lstat(memoryDir); if (! dirStat.isSymbolicLink() dirStat.isDirectory()){ awaitwalkDir(memoryDir,result); } }catch{} // 处理额外路径 const normalizedExtraPaths normalizeExtraMemoryPaths(workspaceDir,extraPaths); for (const inputPath of normalizedExtraPaths){ const stat awaitfs.lstat(inputPath); if (stat.isDirectory()){ awaitwalkDir(inputPath,result); }else if(stat.isFile() inputPath.endsWith(.md)){ result.push(inputPath); } } // 去重 return deduped; }Memory写入机制memoryFlushsession自动压缩 只在context tokens快满的情况下执行prompt: 使用类似“记住我”、“记住这个”、“今天”等agent会保存记忆到本地文件。sessionFlush: /new 新session保存旧 session到memory/YYYY-MM-DD-slug.md配置使用sources: [memory, sessions] 后也会将session文件纳入到记忆检索范围中。5.2 混合检索通过给Agent提供memory_search和memory_get工具允许其在合适的时候通过query检索历史记忆中相关的文本片段RAG范式。在OpenClaw中使用了典型的混合检索方案即同时通过关键词精确搜索 向量语义检索对候选结果计算加权得分给agent展示最相关的几条。基于本地个人Agent的定位OpenClaw的精确检索和向量检索都默认使用Sqlite作为数据库存储结果是一个agents.sqlite文件。Memory检索工具// src/agents/tools/memory-tool.ts export functioncreateMemorySearchTool(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 queryreadStringParam(params,query,{required:true}); const maxResultsreadNumberParam(params,maxResults); const minScorereadNumberParam(params,minScore); const {manager,error} awaitgetMemorySearchManager({cfg,agentId}); if (!manager){ return jsonResult({results:[],disabled : true,error}); } const resultsawaitmanager.search(query,{ maxResults, minScore, sessionKey:options.agentSessionKey, }); return jsonResult ({results,provider:status.provider, model:status.model}); }, }; }MemoryManager执行混合检索// src/memory/manager.ts export class MemoryIndexManager{ asyncsearch( query : string, opts ?: {maxResults ?: number;minScore ?: number;sessionKey ?: string}, ):PromiseMemorySearchResult[]{ // 关键词搜索 const keywordResultshybrid.enabled ? await this.searchKeyword(cleaned,candidates) .catch(()[]) :[]; // 向量搜索 const queryVec await this.embedQueryWithTimeout(cleaned); const vectorResultshasVector ? await this.searchVector(queryVec,candidates).catch(()[]) :[]; // 合并结果 if (!hybrid.enabled){ return vectorResults.filter((entry) entry.scoreminScore).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 insertdb.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) , newFloat32Array(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更新文件后执行索引写入。单个文件的索引写入可以分为三个部分文件分块先将文件根据chunkTokens配置分为固定大小的块为了增强前后文的感知允许一定token的分块重叠。Embedding计算和缓存利用配置的远程或本地embedding模型计算chunk的语义向量。同时由于经常多次索引因此可以利用embedding缓存来避免重复计算。写入本地数据库写入索引检索依赖chunks_vec和chunks_fts两种表。06工具技能 (Tools Skills)OpenClaw提供了极其丰富且开箱即用的工具技能这也是其能大火的原因之一。除了通用CLI Agent都有的文件系统访问、Shell命令执行、webSearch工具外还有获取Gateway、Session等运行状态的自感知能力以及内置的多个实用的插件工具比如birdmessagebrowser天气等。限于篇幅和时间这部分更详细的内容放在后续的文章中介绍。下面介绍几个典型的工具来说明其运作原理。6.1 工具示例message工具集成本身是agent的通用能力因此下面介绍OpenClaw比较独特的message工具。与特定Channel集成能够达到丝滑的交互效果发送多条消息AI 可以在最终回复用户之前或之后先发送一张图片、一个文件或者另一条补充消息。主动式Agent的基础富文本与复杂交互显式设置 buttons内联按钮、card卡片、poll投票等高级功能。引用与回复精准控制 replyTo回复特定消息 ID而不仅仅是回复当前最后一条。// message工具调用 { 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 将从三个位置加载1. 内置 Skills随安装包一起发布npm 包或 OpenClaw.app比如birdgithub等2. 托管/本地 Skills~/.openclaw/skills3. 工作区 Skillsworkspace/skills下面以bird skills介绍如何搜索和总结TwitterX上的内容。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/6.4 工具策略翻看OpenClaw 的代码其实最让我震惊的是其灵活的配置能力。以工具策略为例OpenClaw支持多个层级的工具配置策略即特定的场景可以配置不同的工具组合可见性。1. 全局策略: config.tools2. 全局按提供商策略: config .tools. byProvider [providerOrModelId]3. Agent 策略: config .agents .[agentId] .tools4. Agent 按提供商策略: config .agents .[agentId] .tools .byProvider [providerOrModelId]5. 群组策略: config .groups .[groupId] .tools (针对特定群组)07总结其实整体来看OpenClaw在Agent核心层面没有特别多新颖的地方但作为一个完成度特别高的产品24×7运行的本地个人助手其架构设计思想和扩展集成值得我们反复学习。本文从核心交互流程出发了解消息接入和分发链路以及SessionKey的机制一步一步深入Agent核心设计。了解到Queue如何解决复杂的并发消息问题以及OpenClaw如何管理session文件解决短期记忆的持久化问题如何通过记忆文件保存和检索长期记忆。