
1. 整体架构为什么要拆成五个模块这两年 AI Agent 框架扎堆出现我见过不少团队直接把模型能调工具就当成一个 Agent 产品来做了。结果做大了之后聊天记录、工具调用、权限控制、多任务并行全部纠缠在一起改一处崩三处。OpenClaw 这个项目最值得借鉴的地方就是它在架构层面一开始就分开了五个边界清晰的部分Gateway、Memory、Skills、多 Agent 协作层、Runtime。这五个词看起来不新鲜但真正把它们各自的职责边界定死并在一个框架里顺畅协作的项目并不多。用一个生活化的类比来理解这五层把整个 Agent 系统想象成一家公司。Gateway 是前台所有外部联系——不管打电话、发邮件还是直接上门——都先经过这里由前台统一接待、登记、分配Memory 是档案室过去处理过的事情、客户偏好、历史决策都有记录需要的时候随时调取Skills 是工具间里面摆着各种专业设备员工不会自己造钻头但知道什么时候去工具间拿哪一把多 Agent 协作层是项目组编排一个复杂的活儿拆成几块分别交给擅长不同领域的小组去做最后汇总统筹Runtime 则是物业和保安给每个员工分配工位、限定活动范围、记录进出门禁确保整个公司的运作安全可控。这套分层设计解决的是 Agent 系统里最致命的三个问题第一是单一进程里状态混乱所有请求、会话、上下文堆在一起并发一高就出怪问题第二是工具调用不可控模型想调什么就调什么没有权限边界轻则浪费资源重则搞出安全事故第三是能力无法扩展早期 Demo 可能要加一个新功能就得改主程序而分层之后加一个新 Skill 或接一个新通道完全不用动核心代码。从实现角度看OpenClaw 的每个模块都遵循定义清晰的接口 可插拔的实现这一原则。Gateway 层定义统一的入站消息协议Memory 层定义统一的存取接口Skills 层定义统一的工具描述格式Runtime 层定义统一的执行环境约束。这种设计带来的直接好处是你可以把底层组件整个换成自己的实现而不影响其他模块。我觉得看这个框架的正确姿势不是把它当一个能跑的东西而是把它当一套关于 Agent 系统的架构参考答案。接下来我从五个核心模块逐个拆解重点讲清楚每个模块解决了什么问题、内部是怎么设计的、实际使用中容易在哪里踩坑。2. Gateway 层所有入口统一才能谈可控2.1 为什么不能直接暴露业务接口如果你只写过一个单机 Demo可能觉得 Gateway 有点多余——直接把 HTTP 接口对着 Agent 本体请求进来就处理返回就完事多简单。但只要你尝试把 Agent 接到真实业务场景里很快就会碰到一连串问题用户从命令行问问题、从客户端发消息、从网页表单提交请求每个渠道的消息格式完全不同同一个用户的会话可能被多个渠道同时发起上下文被切得七零八落某个渠道的请求超时了重试机制不知道该不该执行如果底层 Agent 正在运行一个耗时的多步骤任务新的请求进来是排队还是并行这些问题的答案都必须在 Gateway 层给出而不是让上层业务逻辑去随机应变。Gateway 的核心职责可以概括为三件事接入、路由、状态管理。接入指的是把不同类型的请求转换成一个统一的内部消息结构路由指的是根据消息内容决定交给哪个处理单元——可能是一个单个 Agent也可能是某个多 Agent 团队状态管理指的是维护用户会话的连续性保证同一个用户在不同渠道、不同时间点的请求能够串起来。2.2 统一消息协议的设计思路在 OpenClaw 里所有进入系统的请求最终都会归一成一种内部消息格式。我简化一下这个结构每条消息至少需要携带消息 ID、会话 ID、来源渠道、发送者标识、消息类型是文本、命令、还是事件、消息内容、时间戳这七个字段。message: id: msg_00187 session_id: sess_89032 channel: cli # 来源命令行 sender: user_42 # 发送者标识 type: text # 文本 / command / event content: 帮我总结一下这周的项目进度 timestamp: 2025-01-06T10:23:45Z为什么要把来源渠道单独拎出来一个字段因为你后续做策略判断时会需要它。实测中发现一个典型场景命令行工具的输入通常信任度较高可以直接触发高权限 Skill而来自外部消息应用的请求则需要更严格的校验。如果设计消息结构时没有渠道这个维度后面想加这种策略就要连协议一起改代价很大。同时统一消息结构必须是序列化友好的。这意味着它要能方便地转换成 JSON、能持久化存储、能在各模块之间通过网络或消息队列传递。实践上所有内部模块之间的通信都建议走同一个消息规范这样后面要接外部消息队列或做日志存档全部直接复用。2.3 会话关联与并发控制Gateway 层面最容易翻车的细节就是会话关联。别小看这个问题用户的对话一旦跨了渠道比如上午在命令行问了问题下午换客户端继续追问Gateway 需要根据用户标识和上下文内容决定是否继续原来的会话。如果没有仔细设计最常见的错误是把每次请求都当成新会话处理Agent 完全失忆用户体验直接崩掉。我建议在 Gateway 里面维护一个会话解析器它的逻辑大致是先看请求里有没有显式的 session_id没有的话再根据用户标识找到最近的会话如果近期没有活跃会话就创建一个全新的会话。这个逻辑看起来简单但实现时要特别注意并发场景——多个请求同时命中创建新会话这个判断可能一个用户生成多个会话记录。我的做法是对会话创建操作加锁保证同一用户标识在同一时刻只能创建一个会话。另外一个常见的问题是并发控制。一个用户连续发送两条消息Agent 正在执行第一个任务时第二条也进来了。有些场景让它们排队是合理的有些场景则应该让后到的消息打断当前任务。OpenClaw 中通过会话级别的执行令牌来控制每个会话同一时刻只允许一个任务在跑其余消息进入等待队列如果设置了打断策略高优先级消息可以撤销当前任务让 Agent 先处理新请求。这里的关键参数是队列长度上限和等待超时时间我通常把等待超时设为 30 秒超过就返回忙请稍后再试避免消息无限堆积。3. Memory 层让 Agent 不只是每次都重新开始3.1 上下文窗口的先天限制做大模型应用的人都会遇到一个硬伤模型的上下文窗口是有限的。哪怕是窗口很大的模型你也不可能把用户所有历史对话全部塞进去——既消耗 token又稀释注意力。更关键的在于一条有用的 Agent 记忆和一条噪声对话在语义价值上天差地别模型如果把它们一视同仁地嚼一遍表现反而会变差。所以 Memory 层的核心目标不是记住一切而是在需要的时候用最小的成本找到最相关的历史信息。这本质上是两个子问题怎么存以及怎么取。OpenClaw 的 Memory 层把记忆拆成了几种类型来分别处理工作记忆当前任务执行过程中的临时信息比如正在处理的文件路径、用户刚提出的需求的要点、中间计算结果。工作记忆的特点是生命周期短、跟当前任务强相关通常任务结束就可以清理。情节记忆过去某次完整交互的摘要。比如上周二用户要求把报告导出为 PDF 格式就是一条情节记忆。这类记忆帮助 Agent 理解用户的行为模式和偏好演进。语义记忆对事实性知识的提取与保存。比如用户所在团队的周报时间是每周五下午这类信息不依赖具体事件可以直接被复用到未来的任意对话中。程序记忆关于怎么做某事的经验。比如用户对月度报表习惯用表格形式呈现并且要求附带环比变化——这实际上是一个可复用的执行策略。3.2 存取机制向量检索与压缩策略在实现层面OpenClaw 采用了一种混合策略。核心思路是原始交互记录全量保存但不直接参与上下文构建系统定期对交互记录做摘要和关键信息提取生成结构化的记忆条目同时调用嵌入模型把记忆条目向量化存入向量索引中。当新消息进入需要回忆时系统会把当前消息向量化从记忆库里召回 top_k 条最相关的记忆再配合最近 N 条原始消息一起打包交给模型。这里有几个参数值得细说一下。top_k召回条数不能拍脑袋定。设太小关键信息可能漏设太大无关信息变成了噪声干扰模型判断。我的经验是先设 5然后根据实测效果逐步调整——如果你发现模型经常答非所问可能不是 top_k 太大而是召回内容本身不够精准。相似度阈值默认 0.7 左右比较常用。这个值决定了多相关才算相关。阈值设太高基本什么都召不回设太低什么五花八门的东西都往上下文里塞。实际调整时建议拿一批真实用户的典型问题做测试集算一下召回结果的准确率再反向调整阈值。记忆过期与更新不是所有记忆都永久有效。比如用户临时说这个季度我重点关注成本控制三个季度后这条记忆就没有意义了。OpenClaw 里可以给记忆条目打上创建时间最后访问时间定期做一次衰减权重计算长期未被访问的记忆在下一次召回里的权重会下降最终被归档或者淘汰。我强烈建议在生产环境给 Memory 层加上敏感信息过滤。记忆库里面存了大量用户原始输入如果原样向量化并长期保存一旦向量库被攻破或者导出就是数据安全事故。我的处理方式是在写入记忆之前先跑一道脱敏流程手机号、地址、账号 ID 这些字段用占位符替换之后再去做向量化和存储。4. Skills 层Agent 的手和眼睛4.1 为什么模型必须配合技能系统一个纯粹的大语言模型其实是个嘴强王者——它能回答很多事但没法真正执行任何事。它不知道现在几点不能调文件系统不能发邮件不能查询外部系统。Skills 层就是给模型装上手和眼睛。但 Skills 做的事情远不止给模型接几个函数这么简单。如果没有约束模型调用工具会非常随意参数填错、重复执行、调用不存在的技能名、在非必要的场景强行调用工具。所以 Skills 层必须提供一套完整的机制定义、发现、调用、校验、反馈。4.2 Skill 声明格式与解析OpenClaw 中的每个 Skill 都有一个 YAML 声明文件这个声明文件的核心价值是让模型看懂这个工具什么时候用、怎么用。我见过很多人写 Skill 声明时敷衍了事description 只写一句话参数说明模棱两可结果模型调用这个技能的正确率只有三成还怪模型太笨。实际上模型推理工具调用的准确率很大程度取决于描述写得好不好。一个合格的 Skill 声明应该包含这些元素skill: name: calc_discount description: 根据原始价格和折扣率计算最终成交价适用于商品定价、促销活动策划、报价单生成等场景。当用户询问优惠后的价格或需要计算折扣金额时使用。 input_fields: - name: original_price type: float required: true description: 商品或服务的原始价格单位是元必须是大于 0 的数字 - name: discount_rate type: float required: true description: 折扣率0.1 表示打一折0.85 表示打八五折取值区间为 0 到 1 run_command: python3 /skills/calc_discount.py timeout: 10 allowed_users: [user_42, team_sales] require_confirm: false看到没有description 不是给项目经理看的简介而是给模型看的调用说明书。写描述的时候要回答三个问题这个技能是做什么的、什么场景下应该调用它、什么场景下不应该调用它。什么场景不应该调用这一点尤其容易被忽略但它在减少误调用方面效果立竿见影。4.3 参数校验与执行闭环模型生成的调用参数往往不是百分之百规范的——可能缺字段、可能类型不对、可能枚举值超出范围。Skills 层拿到调用请求后第一步是 schema 校验不通过就直接返回结构化错误而不是把错误参数塞给底层脚本执行。这一步非常关键因为它避免了大量执行到一半才发现参数错了的尴尬局面。校验通过之后Skill 开始执行。执行结果要按统一格式返回给模型通常包括执行状态、输出内容、耗时和错误信息。为什么要统一格式因为模型需要靠这些反馈决定下一步动作——是继续尝试、还是换个方案、还是直接告诉用户失败原因。如果执行结果格式乱七八糟模型就像在黑暗里开车决策质量会急剧下降。4.4 权限模型与人工确认Skills 层另一个必须处理的问题是权限。不是所有技能都适合让模型自主调用。我通常把技能按危险程度分成三档只读型查天气、搜资料、操作型写文件、发消息、高风险型删除数据、转账付款、对外发布内容。只读型可以完全开放操作型建议在技能声明里做用户白名单控制高风险型无论什么情况都要加一道require_confirm: true模型会把请求和参数推给用户用户确认之后才真正执行。这个设计为我规避过好几次灾难性事故。有一次在测试环境里模型在对话中间自主调用了发送通知的技能给测试用户群发了一堆消息虽然没造成实际损失但足以让我意识到自主执行 无确认的可怕。从那之后我的所有项目里对外部系统有副作用产生的技能一律强制人工确认没有例外。5. 多 Agent 协作分工、路由与结果汇总5.1 单个 Agent 撑不住复杂任务当任务复杂到一定程度单个 Agent 再怎么说都会被几个问题卡死上下文塞满导致顾此失彼、多个专业领域混在一起导致一个模型兼顾不来、长链路任务中某一步失败需要整体重来。多 Agent 架构的出发点就很直接让专门的 Agent 处理专门的问题用一套协作机制把它们组织起来。但多 Agent 不是多开几个 Agent 实例这么简单。随便架一堆 Agent没有清晰的协作协议结果就是互相踩脚两个 Agent 同时操作同一份文件A 问 B 要结论但 B 还在等 A 的数据整个系统变成一个无人能看懂的聊天室。5.2 主从路由与专业 Agent 团队OpenClaw 采用的设计是经典的Supervisor Specialist模式我实际做项目也验证过这种模式最稳。一个总控 AgentSupervisor不直接干活它的职责单一而明确理解用户请求拆解子任务分发给合适的专业 AgentSpecialist然后汇总各专业 Agent 的结果生成最终回复。每个 Specialist 只负责自己擅长的一个领域配置一个独立且精简的系统提示词再加上自己领域内需要的 Skills。比如一个负责数据分析的 Agent它的提示词就是针对数据处理场景写的它只挂载数据查询和可视化相关的 Skill不需要关心邮件发送和日程管理。这种隔离有两个好处上下文干净模型不用在无关信息里筛关键点权限最小化每个 Agent 只拿到自己需要的工具攻击面明显变小。5.3 任务拆解和路由规则总控 Agent 怎么决定把任务分给谁这里有两条路线基于规则的意图路由或者基于模型的语义路由。规则路由的好处是可解释、稳定、零额外延迟。你可以定义关键词映射比如用户消息里包含天气气温降雨直接路由到天气 Agent包含排期日程会议路由到日历 Agent。缺点是遇到语义变化多样的请求就抓瞎。语义路由用嵌入模型计算用户消息和每个 Agent 职责描述之间的相似度选得分最高的 Agent 来接手。它的泛化能力更好但偶尔会出现路由错误——用户问帮我看看这个数据表格结果相似度最高的居然是邮件 Agent。所以实践中我建议把两种方式结合先跑规则路由规则没有命中再走语义路由两边都拿不准就回退给总控 Agent 自己处理。任务的拆解粒度也需要控制。拆得太粗每个 Agent 干的活仍然很杂拆得太细协作开销可能比干活本身还大。我常用的判断标准是如果一个子任务可以由单个 Agent 在最多两三步工具调用内完成那就不要继续往下拆了。5.4 结果汇总与冲突仲裁多 Agent 的最后一步是结果汇总。汇总不是简简单单把每个 Agent 的输出拼在一起而是要处理冲突和冗余。比如数据分析 Agent 给出了两组统计口径不一样的结果秘书 Agent 给的行程建议和预算 Agent 的计算结果有矛盾这时候总控 Agent 需要做出仲裁。OpenClaw 的做法是让总控 Agent 把每份子结果连同对应的理由一起放进上下文通过一次独立的推理步骤做整合。同时所有子 Agent 的返回值会带上置信度标记总控在整合时会对低置信度的结果进行标注并在最终回复里提示用户这部分结果置信度较低建议人工复核。我还遇到过一个很隐蔽的问题子 Agent 的返回结果里通常带着一小段我做了什么事的描述总控 Agent 汇总时容易把这些过程描述当成最终答案的一部分导致回复冗长且偏离重点。解决办法是明确定义子结果的结论摘要关键数据执行备注三个字段总控只优先读取结论摘要和关键数据执行备注默认折叠起来除非用户明确要求查看执行过程。6. Runtime 层隔离、调度与安全边界6.1 Runtime 到底管什么如果把 Gateway、Memory、Skills 和多 Agent 看作系统的大脑记忆和神经系统那 Runtime 就是承载这一切的身体——它决定了每个 Agent 和 Skill 在什么环境里跑、能碰什么资源、最多消耗多少资源、出问题了怎么处理。我见过不少团队把 Agent 框架做到 Skills 层就开始对接业务了完全不考虑 Runtime 层的问题。前期一切正常是因为只跑在开发机上单用户、低并发。等上了生产问题集中爆发某个 Skill 脚本内存泄漏把整个进程拖垮、某个 Agent 的递归调用把 CPU 占满、一次意外越权让文件系统被扫了一遍。这些问题的共同根源就是没给执行环节设定边界。Runtime 层在 OpenClaw 中的功能可以归纳为五点执行隔离、资源限制、生命周期管理、权限约束、可观测性。每一块都不能省。6.2 本地进程模式与失败恢复最轻量的运行模式是本地子进程模式每个 Skill 调用在独立的子进程里跑子进程崩溃不会影响主程序。这个模式胜在部署简单适合开发调试但隔离强度有限——子进程之间共享文件系统和网络一个恶意或有 Bug 的脚本仍然有可能读取不该碰的文件。生命周期管理的核心在于超时控制。一个 Skill 理论上应该在几秒内完成如果跑了 5 分钟还没结束多半是卡死或者陷入死循环。OpenClaw 里可以给每种 Skill 配置一个超时上限超时后 Runtime 主动杀掉子进程并给上层返回一个超时错误。这里有一个细节杀掉进程之后是否要回滚该进程产生的副作用比如它已经写了一部分文件。我的做法是在 Skill 内部尽量把写操作设计成先写临时文件、全部完成后原子重命名这样即使中途被杀也不会留下半成品。6.3 容器化执行模式的安全边界生产环境我强烈建议用容器方式执行 Skill 和独立 Agent 进程。每个执行实例有独立的文件系统、独立的资源配额、独立的网络策略主程序与执行实例之间只通过标准输入输出和网络接口通信。一个简化的容器执行设计包括这样几个限制项共享内存上限、CPU 配额、最大可写目录、禁止访问的路径列表、允许访问的外部服务白名单、只读的系统目录。这些限制会在容器启动时通过配置文件注入。如果 Skill 只是做数据处理那就根本不给它开放外部网络权限它想联网也联不了。容器方式的成本在于镜像构建和启动时间更长每个实例的冷启动延迟明显。解决思路是预启动一批常驻 worker 容器任务来了直接复用一旦某容器执行的任务异常直接把整个容器销毁重建比排查内部问题省事得多。6.4 日志与追踪设计Runtime 的可观测性是最后一道防线。我坚持的标准是每条请求都要能串成一条完整的链路用户消息进入 Gateway 时的消息 ID、路由决策内容、调用了哪个 Agent / Skill、每个环节的耗时、返回结果摘要、错误码。这条链路信息统一写入结构化日志字段里带上消息 ID这样排障时只要拿着用户反馈的消息 ID 一查就能还原当时发生了什么。指标层面我建议至少监控四个数字请求成功率、平均响应时间、Skill 执行超时次数、Runtime 容器重启次数。前两个反映服务质量后两个反映系统稳定性。哪怕是个人项目也应该定期看一眼这些数字——我踩过最深的坑就是一切看着正常实际上某一类请求已经连续失败一周了因为没有指标系统又静默重试掩盖了错误直到用户找上门才发现。7. 常见问题与排查实录这个部分我把项目实施过程中最常遇到的几个问题整理成一个速查表每个问题配合排查思路方便你直接对照使用。Gateway 收到消息但 Agent 没有响应先查消息是否成功通过路由规则。重点看两个位置会话锁有没有被别的请求占住导致排队卡死意图路由是否命中如果规则路由和语义路由都未命中消息会被丢给总控 Agent但总控 Agent 可能因为上下文过长在等待模型返回表现就是卡住。查证方式查看 Gateway 日志里的路由决策记录确认消息最终派发给了哪个组件。常见原因并发会话锁未释放、模型推理超时。解决办法给所有会话锁加超时自动释放给模型调用设兜底超时并返回友好提示。记忆召回结果为空Agent 完全不记得之前的对话这个问题我遇到时第一反应是向量库挂了排查后发现经常是更基础的原因。按顺序查嵌入模型服务是否正常记忆写入流程有没有跑很多情况是写入时脱敏流程抛异常导致写入被中断相似度阈值是不是设得太高正常相关的记忆被过滤掉了。查证方式手动拿一条历史对话跑一次嵌入和检索看返回结果。常见原因阈值过高、记忆写入失败、向量索引未刷新。解决办法阈值先从 0.7 往下调修复写入流程异常强制触发一次索引重建。Skill 执行老超时但单独跑脚本很快不是脚本本身慢而是 Runtime 给的执行环境资源受限或者外部依赖需要联网但网络策略没放行脚本卡在等待超时上。还有一种可能并发任务太多worker 队列排满了你的任务在排队而非真正执行。查证方式看 Runtime 的并发队列长度和实例资源使用率。常见原因网络策略受限、worker 数量不足、资源配额太小。解决办法按 Skill 的实际依赖放行对应的外部服务白名单加 worker 数量适度调大 CPU 和内存配额。多个 Agent 之间出现循环调用任务收不了尾这是多 Agent 系统最容易出现的死锁问题。A Agent 给 B Agent 派了个任务B 的处理结果被总控判为需要 A 再处理A 又派给 B形成无限循环。根治办法是给消息传递加最大跳数每条消息经过的 Agent 数量超过阈值就强制终止并返回错误。查证方式在消息结构里加一个 hop_count 字段排查时看日志里该字段是否在递增。常见原因任务拆解粒度不合理职责边界模糊。解决办法调整总控的拆解提示词明确不要将已经处理过的任务重新分发设置最大跳数超过即终止。Runtime 容器频繁重启任务成功率下降容器被 OOM 杀死是最常见的原因。Skill 脚本本身存在内存泄漏跑得越久占用越高最后超过配额被杀。另一个常见原因是容器里存在周期性的大任务被资源配额限住后反复触发重启。查证方式查看容器退出码和内存监控曲线。退出码 137 通常表示被系统强制杀死。常见原因内存泄漏、资源配额不足、有不受控的子进程在消耗资源。解决办法在 Skill 内加内存追踪定位泄漏位置对每个容器实例设更明确的资源上限宁可排队也不要让它们在临界点反复横跳。模型频繁调错 Skill正确率只有四成这是 Skills 层最常见的问题通常不是模型不行而是 Skill 描述写得不够清楚。描述太泛模型分不清什么场景该用这个技能参数说明不完整模型猜测参数含义就容易填错。我建议把所有 Skill 的 description 全部重写一遍用当用户想要……并且……时调用此技能这样带条件的句式同时把 input_fields 里的每一个字段都给出取值范围和示例值。改完过后实测调用正确率从四成直接拉到八成以上。最后再分享一个小经验排查这类问题不要凭感觉改配置。先把消息 ID、路由日志、模型调用的输入输出、Runtime 执行记录都打完整再一个环节一个环节地看。凡是能复现问题的 bug 都不可怕最耗时间的永远是那些日志缺失、无从下手的问题。日志打够很多看起来玄学的问题本质上只是某一层的参数设错了而已。我个人在实际使用中的体会是OpenClaw 这种分层架构的最终价值不在于某一层有多炫而在于它让整个系统每件事都有地方管。当你把消息交给 Gateway、把记忆交给 Memory、把能力交给 Skills、把分工交给多 Agent 协作、把风险交给 Runtime 之后剩下的就是不断往这个框架里加业务能力。这个套路适合你手头任何一个准备做深的 Agent 项目。