Clawdbot(OpenClaw)架构深度拆解与实现全流程

2026-06-20阅读 0热度 0
ai 人工智能

最近这半年,但凡深入写过或用过Agent的朋友,大概率都踩过同一类坑:模型本身可能不是瓶颈,但系统一旦“动起手来”,工程侧的问题立刻暴露无遗。

并发乱成一锅粥、状态飘忽不定、日志像天书一样不可读、工具权限没有边界、失败了没法回放……说句扎心的话,提示词写得再漂亮,也兜不住这些底层问题。

今天这篇文章,我想把 OpenClaw(Clawdbot) 的架构拆开,好好聊聊。但我关注的不是它“有多牛”,而是它怎么通过工程化的设计,让整个系统变得更稳、更可控。

事情的起因,是前几天在X上刷到一篇关于Clawdbot架构的深度拆解帖。作者 @Hesamation 的出发点很实在:一开始只是想搞清楚它的记忆系统怎么工作、可靠性如何。结果一路挖下去,发现真正值得学习的东西,不是“它能做什么”,而是“它怎么把这些事做得更稳妥”。

太长不看版(6条核心判断)

  • • OpenClaw 本质上是一个 TypeScript CLI 进程,外加一个处理多渠道接入的 Gateway Server;它不是个Web App。
  • • 它把可靠性放在绝对优先级:默认串行,只有声明了才能并行(lane queue)。并发在这里不是“性能技巧”,首先是个“可靠性问题”。
  • • Agent Runner 更像一条装配线:模型选择与Key冷却、Prompt组装、历史加载、上下文窗口守护,一切就序后才开始驱动工具循环。
  • • 记忆系统不神秘:JSONL 转录(用来回放)+ Markdown 记忆文件(用来编辑);检索用的是 向量 + 关键词 的混合模式,底层存储是 SQLite(FTS5)。
  • • 工具调用的安全边界必须系统化:allowlist + 结构化拦截(重定向、命令替换、子Shell、链式执行等直接拒绝),别把“自觉”当机制。
  • • 浏览器交互不主要靠截图:人家用 语义快照(Accessibility Tree/ARIA),把“看网页”降维成“读结构”,成本更低,成功率也更稳。
深度拆解 Clawdbot(OpenClaw)架构与实现
图1:原始架构图
图2:OpenClaw 核心链路

先把它放回正确的分类里:它到底是什么

很多人一聊Agent,开口就是“自治”、“多智能体”、“进化”。但落地的时候,你得先问自己一个最根本的问题:你做的究竟是一个聊天机器人,还是一个“能在你机器上执行工具的系统”?

OpenClaw的定位非常清晰:它是一个运行在你机器上的进程,核心职责只有三件:

  • • 接收入口来自不同渠道的消息(Telegram、Discord、Slack等)。
  • • 调用LLM API(OpenAI、Anthropic、本地模型等)。
  • • 在本地或受控环境里执行工具(Shell、文件、浏览器、进程),再把结果回传给你。

这番话听起来很朴素,但它一下就把架构的重心给锁死了:执行的可控性状态的可追溯性失败的可解释性。所有设计都得为这三件事服务。

一条主链路讲清楚:消息进来之后发生了什么

把OpenClaw翻译成工程视角,就是一条清晰的流水线:

  1. 1. Channel Adapter:把不同渠道的输入统一成标准消息,并提取附件。
  2. 2. Gateway Server:会话协调器,决定这条消息该进入哪个会话、排到哪个队列。
  3. 3. Lane Queue:每个会话默认串行;只有你明确标注了低风险的任务,才允许并行。
  4. 4. Agent Runner:拼上下文、选模型、发起调用,驱动工具循环。
  5. 5. Agentic Loop:模型提出tool call → 执行 → 结果回填 → 下一轮,直到输出或触达轮次上限。
  6. 6. Response Path:把最终内容流式回写到渠道,同时把全过程写入可回放的转录文件(JSONL)。

这类架构的价值不在“酷”,而在于边界清楚。出了问题,你一眼就能看懂“卡在哪一步”,也更容易把问题隔离出来。

再说一个很实用但经常被忽略的点:响应链路本身也应该被当成系统的一部分。帖子中提到,LLM调用是流式输出,最终响应通过渠道回到用户;同时会话会被持久化成JSONL(每行一个JSON,对应着用户消息、工具调用、执行结果、模型响应等)。这意味着你“看到的输出”只是冰山一角,下面还有一条可回放的证据链。

默认串行,显式并行:lane queue 为什么这么关键

我见过太多Agent项目,一开始只是想“多开几个async/await”,结果半年后,全都变成了同一个鬼样子:

  • • 日志像一团毛线,交织在一起完全看不懂。
  • • 共享状态一多,竞态条件(race condition)开始像幽灵一样挥之不去。
  • • “偶现”问题占据了你绝大多数的调试时间。

OpenClaw的克制之处在于:它把并发的选择权,从“开发者随手写的异步”收回到一个显式的系统约束里。这个思路,和Cognition那篇“Don't Build Multi-Agents”的博文不谋而合:一个简单的async设置,就足够让你陷入日志交织、状态竞争的泥潭。

lane queue 的直觉

  • • 每个会话都有自己的“泳道”(lane)。
  • • 泳道里默认 串行执行
  • • 只有你明确标注“低风险、可并行”的任务,才会进入并行泳道(比如某些定时任务)。

这会强迫你换一个心理模型:从“我该锁哪里”转向“哪些任务真的能安全并行”。Lane是对队列的一层抽象,它把“串行”作为默认架构,而不是事后补丁。作为开发者,你正常写代码就行了,队列帮你处理竞态条件。

让并发“站到台前”

从工程实践的角度,我建议你把并发决策做成三个明确的层级:

  • 默认串行:先保证任何一条链路都能稳定复现。
  • 显式并行:只开放少数“无共享状态、幂等、可重试”的任务。
  • 隔离失败域:并行任务失败了,不能影响主会话;失败原因在日志里能单独定位。

你会发现,这套约束比任何“并发技巧”都值钱,因为它直接减少了你未来的debug成本。

图3:lane queue的并发策略

Agent Runner:把“提示词工程”变成“上下文装配线”

真正能跑起来的系统,往往都把提示词当成装配线上的一环,而不是拿来搞宗教崇拜的。

在OpenClaw的描述里,Runner做的事非常具体:

  • Model Resolver:决定用哪个模型;Key失效就标记冷却并切换;主模型失败自动换备用。
  • System Prompt Builder:动态拼装系统提示词,把工具、技能、记忆都整合进去。
  • Session History Loader:加载会话历史(来自 .jsonl 转录文件)。
  • Context Window Guard:上下文快满时进行压缩(总结)或降级/停止,避免“撑爆了才知道”。

这套拆分带来两个立竿见影的好处:

  • • 你能把“模型质量”和“系统质量”拆开看:模型偶尔波动时,系统依然可控。
  • • 你能做复盘:因为历史、工具调用、工具结果都是结构化记录,能回放、能对账。

顺着这条线,原帖还强调了LLM调用层的两个关键点:

  • 流式输出:让“生成过程”可观察,也更利于把中间态回传给上层。
  • 多厂商抽象:同一层对接不同的模型提供方;如果模型支持,还可以请求“扩展思考”能力。

工程上的启示是:你不该把“换模型、换Key、失败兜底”这些逻辑写散在业务代码里,而应该收敛在Runner的职责边界内。

Agentic Loop:奇迹发生的地方,也是事故高发区

工具循环大家都懂:tool call → 执行 → 回填 → 下一轮,直到输出或触达轮次上限(比如20轮)。

但工程上真正需要盯住的是三件事:

  • 终止条件:什么时候该停?停得是否可解释?
  • 工具输出的格式:工具返回的是“日志”,还是“可被模型消费的证据”?
  • 回填策略:回填太多会撑爆上下文,回填太少模型又会“失明”。

Context Window Guard的价值也在这里:把“上下文爆炸”做成一个显式组件,而不是靠经验去拍脑袋。你可以不追求每次都成功,但你不能接受失败时完全无从定位。

记忆:别神化,文件也能打

我很喜欢它对待“记忆”的态度:简单、可解释、可迁移。去神秘化,做得非常到位。

它主要靠两条线:

  1. 1. JSONL 会话转录:每一行一个JSON对象,包含用户消息、工具调用、执行结果、模型响应。
  2. 2. Markdown 记忆文件:放在 MEMORY.mdmemory/ 目录下。

这相当于把记忆拆成了两类:

  • • “发生过什么”(转录,偏事实、偏审计)
  • • “应该记住什么”(摘要与沉淀,偏经验、偏复用)

检索:向量 + 关键词的混合

很多系统只做向量检索,最后都会踩一个坑:你以为找的是“精确概念”,结果召回的都是一堆“语义相近但不对”的东西。

OpenClaw的思路更稳当一些:向量检索负责语义召回,关键词匹配负责精确命中。原帖给了一个很直观的例子:你搜 “authentication bug”,系统既能命中写了 “auth issues” 这种同义表达(语义),也能命中精确短语(关键词)。这能明显降低“看起来相关但其实不对”的噪声。

在实现上,它用了SQLite:

  • • 向量搜索:SQLite(向量索引的实现细节这里就不展开了,重点在于最终都落地到了同一个存储里)。
  • • 关键词搜索:SQLite FTS5。

嵌入模型(embedding provider)也是可配置的。

同步:Smart Syncing + 文件监控

记忆文件的更新也不需要一套“专用记忆API”。更朴素的方式就够了:Agent用“写文件工具”写入 memory/*.md,文件监控器检测到变化后触发同步与索引更新。

它还提到一个很贴心的细节:新对话开始时,会有一个钩子把上一轮对话抓出来,写一份Markdown总结。你可以把它理解成“把经验沉淀变成默认动作”,而不是全靠用户手动整理。这点我非常认可:工具越底层,越不容易被你自己未来的“架构升级”搞死。

原帖作者也提到,这套记忆系统和 @CamelAIOrg 实现的工作流记忆非常相似:没有复杂的记忆合并,没有按周/月的记忆压缩。简单可解释,胜过复杂的意大利面。

代价:没有遗忘曲线

它的记忆会长期保存,旧记忆与新记忆权重接近,这也就意味着“它不太会自然遗忘”。这既是优势也是风险:

  • • 优势:可追溯、可解释,复盘很方便。
  • • 风险:过期的经验可能持续被召回;你需要显式的版本化、有效期或冲突解决策略。

如果你打算照搬这一套,我建议至少补两个机制:

  • • 给记忆加上 updated_atconfidence 的元信息(哪怕只是写在Markdown里)。
  • • 定期把“已过期/已被推翻”的结论写成一条新记忆,去覆盖旧结论,而不是悄悄删掉。

工具与“电脑操作”:强大,但别自欺欺人

OpenClaw给Agent的能力很直白:你给它一台电脑,它就能用。

它的 exec 工具支持三种执行环境:

  • 沙箱(Sandbox):默认在容器里跑命令。
  • 宿主机:直接在本机执行。
  • 远程设备:在远程机器执行。

配套的还有三类常见能力:

  • • 文件系统工具:读、写、编辑。
  • • 浏览器工具:基于Playwright,并使用语义快照。
  • • 进程管理:启动后台任务、查看状态、终止进程。

这里我要强调一句非常现实的话:只要你允许“宿主机执行”,你就等于把最终的责任从系统转交给了用户本人。所以它后面那套安全护栏,才显得没那么“锦上添花”,而更像是一件必需品。

安全:护栏不靠祈祷,靠 allowlist

很多人做工具调用安全,最后都会变成一句话:“请模型不要做危险操作。”这在工程上等同于没有。

OpenClaw的做法更像 Claude Code:维护一个 允许列表(allowlist),把“能跑什么命令”当成一个可审计的配置文件。原帖里给出了一个示例:配置会记录每个agent允许的命令模式,以及上次使用时间。大概长这样:

{
  "agents": {
    "main": {
      "allowlist": [
        { "pattern": "/usr/bin/npm", "lastUsedAt": 1706644800 },
        { "pattern": "/opt/homebrew/bin/git", "lastUsedAt": 1706644900 }
      ]
    }
  }
}

另外,一些“明显安全”的命令会被默认放行(比如 jqgrepcutsortuniqheadtailtrwc)。

同时,它还做了一个我很欣赏的判断:不仅拦住命令本身,还拦住 危险的Shell结构,比如:

  • • 重定向(>
  • • 命令替换($(...)
  • • 子Shell((...)
  • • 链式执行(|| / &&

原帖给了几个会被直接拒绝的例子:

# 这些在执行前就会被拒绝:
npm install $(cat /etc/passwd)     # 命令替换
cat file > /etc/hosts              # 重定向
rm -rf / || echo "failed"          # 链式执行
(sudo rm -rf /)                    # 子 Shell

为什么这很重要?因为真正的攻击,往往会把危险行为藏在一段看起来无害的组合里,而不是明着写“直接跑rm”。

你可以把这套机制理解成一句话:在用户允许的范围内,尽可能给Agent自主权。你不需要在提示词里反复喊“别乱来”。系统层面直接定义“你最多只能走到哪”,效果会更稳定。

浏览器:语义快照,比截图更像“工程选择”

OpenClaw的浏览器工具不以截图为主,而是使用 语义快照(Semantic Snapshots)。它的本质是把网页转成可访问性树(Accessibility Tree / ARIA)的结构化文本,让Agent去“读结构”,而不是“看像素”。

Agent看到的页面大概是这样:

- button "Sign In" [ref=1]
- textbox "Email" [ref=2]
- textbox "Password" [ref=3]
- link "Forgot password?" [ref=4]
- heading "Welcome back"
- list
  - listitem "Dashboard"
  - listitem "Settings"

它带来的优势非常现实:

  • 体积:截图可能到5MB级别;语义快照往往不到50KB。
  • 成本:Token开销通常只是图像方案的一小部分。
  • 精度:从“点坐标”变成“选节点引用”,成功率更稳定。
  • 速度:纯文本解析比CV处理快得多。

你可以把它当成一种工程化的取舍:只要任务不是强视觉依赖(比如识别图表细节、读验证码、找颜色差异),语义快照往往更可靠。浏览网页本质上不一定是个视觉任务——这正是语义快照能成立的前提。

我会怎么抄:10个可以直接落地的改造点

如果你正在做Agent工程化,这里是我觉得“抄了就能立刻变稳”的10件事:

  1. 1. 默认串行先跑稳:先让一条链路能稳定复现,再谈并行与性能。
  2. 2. 把并发变成系统决策:用lane queue + 会话隔离;并行必须显式声明。
  3. 3. Runner组件化:把Model Resolver / Prompt Builder / History Loader / Context Guard拆开,方便替换与观测。
  4. 4. 把工具调用当事件:每次tool call都写JSONL(请求、参数、结果、耗时、错误码),保证能回放。
  5. 5. 工具输出要做“证据化”:工具返回尽量结构化(表格/JSON摘要),避免把噪声日志塞满上下文。
  6. 6. 记忆先文件化:用 MEMORY.md / memory/*.md,配上 updated_at、来源、置信度,比黑盒DB更可控。
  7. 7. 检索别只押宝在向量上:向量召回 + 关键词精确命中;必要时再加一个“必须命中词”的hard filter。
  8. 8. 安全从allowlist开始:明确允许什么、拒绝什么,写成配置;危险的shell结构直接拒绝。
  9. 9. 浏览器优先用语义快照:能读结构就别看像素;把强视觉任务(验证码、图表、颜色等)单独隔离出来。
  10. 10. 失败要可解释:把“环境不满足”、“偶发失败”、“策略拦截”分开报错,别让用户只看到一句冷冰冰的“失败了”。

收个尾:工程化Agent的“现实感”

我越来越觉得,Agent真正的分水岭是:失败时你能不能解释清楚。至于“能不能做”,反而没那么稀缺了。

提示词当然重要,模型当然重要。但真正让系统可用的,是这些看似朴素的工程约束:队列、日志、文件、allowlist、可访问性树。它们不性感,却很可靠。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策