1. 项目概述:为什么要用 Nanobot 来“拆”框架
先说结论:OpenClaw 是目前个人 AI 助理类开源项目里,把“agent 能力”和“消息平台接入”这两件事拆得最干净的项目之一。而 Nanobot 则是它早期核心模块的前身或同源实验项目(具体关系在下文展开),体积小、依赖少、逻辑直白,非常适合拿来做架构学习的切入点。
我第一次看到 OpenClaw 的时候,第一反应是“又一个聊天机器人框架”。但真正把它跑起来以后,发现它的定位和市面上的 chatbot 框架完全不一样。它的核心不是“对话”,而是“执行”。OpenClaw 内置了一套任务规划、工具调用、权限审批、记忆存储的完整链路,交互入口反而是其次——你可以接 Discord、接 Telegram、接网页,也可以直接用命令行敲。这种“以 agent 能力为中心,消息平台只是壳”的设计思路,恰恰是很多自研 agent 项目的演进方向。
而 Nanobot 这个项目,你可以把它理解成一个“极简版的 agent 引擎原型”。它没有复杂的插件体系,没有花哨的 UI,甚至没有一个像样的 web 界面,但它把 agent 最核心的几条链路以最朴素的方式写了出来:模型调用、工具注册、消息循环、上下文管理。读它的源码,相当于看一张没有修饰过的架构草图,比直接啃 OpenClaw 那种几百个文件的工程要友好得多。
所以这篇文章的目标读者非常明确:
- 想自己设计一个 AI agent 框架,但不知道从哪下手的人;
- 已经在用 OpenClaw,但只停留在“配置好、能跑”层面,想深入理解内部机制的人;
- 被各种“分布式 agent 架构”、“微服务编排”概念轰炸,想找个最小可用范例回归本质的人。
我会从整体架构的视角来拆,不逐行讲代码(那会写成一本书),而是把 Nanobot 的模块划分、数据流、扩展点讲清楚,然后对照 OpenClaw 看它怎么把这份设计放大成完整的工程实现。这样你读完,既对 Nanobot 了然于胸,也对 OpenClaw 的骨架有了直观认知。
2. 整体设计思路与架构拆解
2.1 从“一个 agent 最少需要什么”出发
在动手读源码之前,先问自己一个问题:如果让你从零写一个 agent,最少需要哪几个部分?
我自己的答案是四个:入口、大脑、手脚、记忆。入口负责接收外部指令或消息;大脑负责调用模型做推理和规划;手脚负责执行具体的动作(比如查天气、写文件、调 API);记忆负责在多次对话之间保存状态和上下文。
Nanobot 的源码结构,几乎就是按这个思路组织的。它的核心目录非常精简,大致可以分为:
- 入口层:负责启动、加载配置、建立消息通道;
- 引擎层:包含 agent 的主循环——接收输入、拼装 prompt、调用模型、解析输出、决定是否调用工具;
- 工具层:以注册表的形式管理外部能力,每个工具就是一个带描述的函数;
- 存储层:负责会话历史、上下文摘要、用户偏好的持久化。
这个划分本身没什么稀奇,真正的亮点在于层与层之间的依赖方向。引擎层不直接依赖任何具体的模型厂商 SDK,也不直接 import 某个工具的实现。它依赖的是接口——模型调用是一个统一的 adapter,工具调用是一个统一的 protocol。这意味着你替换模型、新增工具,都不需要改动引擎核心逻辑。
这一点在架构设计里叫“依赖倒置”,听起来高大上,但 Nanobot 用很朴素的方式实现了。比如模型调用部分,源码里有一个LLMBackend的抽象基类,所有具体模型(OpenAI、Anthropic、本地 Ollama)都只是它的子类。引擎在运行时拿到的是基类的引用,具体调用哪个模型,完全由配置文件决定。
2.2 OpenClaw 和 Nanobot 的关系:放大与重构
那 OpenClaw 是 Nanobot 的什么?根据我扒源码和 commit 历史的观察,OpenClaw 可以理解为 Nanobot 的“工业化版本”。两者共享同一套灵魂——agent 引擎与消息平台解耦、工具即插即用、状态可持久化——但 OpenClaw 在工程层面做了大量增强:
- 多平台接入:Nanobot 可能只接了命令行或单个平台,OpenClaw 内置了 Discord、Telegram、Web 等多套 adapter,而且 adapter 和 agent 引擎完全隔离,加新平台不需要改引擎;
- 权限审批系统:OpenClaw 有一个
exec-approvals.json的机制,某些高危险操作(比如执行 shell 命令、删除文件)必须经过审批才会放行,这是生产环境落地必须的功能,Nanobot 没有; - 技能包机制:OpenClaw 把“工具”升级成了“技能包”(Skill),不仅包含函数实现,还包含触发条件、参数说明、依赖环境,更像一个完整的插件体系;
- 工作区隔离:OpenClaw 默认在
~/.openclaw/workspace下给 agent 一个独立工作目录,所有文件操作被限制在这个目录内,避免 agent 乱跑。
换句话说,读 Nanobot 是看“地基”,读 OpenClaw 是看“整栋楼怎么在地基上盖起来”。把两个项目的源码对照着看,你对架构的理解会是立体的:知道最小的闭环长什么样,也知道规模化之后哪些地方必须加固。
2.3 这个设计解决了什么问题
我见过很多人做 agent 项目,最大的痛点不是模型能力不够,而是代码耦合太深。对话逻辑里直接写死了 OpenAI 的调用代码,工具函数散落在各个回调里,想加一个新平台要改一堆文件。到最后,项目能跑,但没人敢动。
Nanobot 这种“入口-引擎-工具-存储”的分层架构,解决的核心问题就两个:
替换成本:换模型、换平台、加工具,都是局部改动,不需要动引擎主干。这一点在模型快速迭代的当下极为重要——今天用 GPT-4o,明天换 Claude,后天换本地 Qwen,如果架构耦合,每次切换都是一次手术。
可测试性:因为引擎不依赖具体实现,你可以用 mock 工具跑通整条链路,不用真的调用外部服务。我在本地调试的时候,经常把 LLM 换成“返回固定字符串”的假模型,把工具换成“打印参数”的假工具,把所有网络依赖都切断,核心循环的 bug 很快就能暴露。
3. 核心链路与实现细节剖析
3.1 主循环:agent 的“思考-行动-观察”回路
读完 Nanobot 源码,我觉得最值得学习的不是某个类的设计,而是主循环的写法。它朴素得像伪代码,但每一步都踩在点子上。我用伪代码还原一下这个循环的骨架:
while True: message = receive_message() # 从平台/命令行拿到新消息 history = load_history(user_id) # 加载该用户的会话历史 prompt = build_prompt(history, message) # 拼装完整 prompt response = llm.chat(prompt) # 调用模型 action = parse_action(response) # 解析模型输出,看是否要调工具 if action is not None: result = execute_tool(action) # 执行工具 memory.save(action, result) # 记录到上下文 response = llm.chat(prompt + result) # 把工具结果喂回给模型 else: memory.save(message, response) # 纯对话,直接存历史 send_message(response)这个循环看起来简单,但有几个细节值得琢磨:
- 历史加载是分用户维度的:每个用户有独立的上下文,不会串味。这是多用户 agent 的基本要求,但很多项目初始版本会忽略,等上线才发现问题;
- 工具调用是循环式的:模型第一次输出可能只包含“调用工具 A”的意图,拿到结果后需要再喂回给模型一次,让模型根据结果决定下一步。这是 ReAct 模式的最简实现,无限循环的风险靠“最大轮数”来兜底;
- Prompt 构建是一个独立函数:这一点极其重要。把 prompt 的拼接逻辑单独抽出来,意味着你可以调整上下文策略(窗口截断、摘要压缩)而不碰主循环。
3.2 工具注册机制:让 agent 有能力“动手”
Nanobot 源码里第二个值得学习的设计是工具注册表。具体的实现方式并不花哨,核心就是一个字典:工具名映射到处理函数。但它的巧妙在于每个工具除了函数本身,还带了一份“给模型看的说明”。
比如一个天气查询工具,注册时会像这样描述自己:
工具名: weather_query 描述: 查询指定城市的当前天气,输入参数为城市名称(中文)。 参数: city (string, 必填)这段描述会出现在每次请求的 system prompt 里。模型“看到”这个描述后,才知道在什么场景下应该发起这个工具的调用。你可以把它理解为给模型的一份“岗位说明书”——模型本身不知道你的系统里有什么能力,全靠这份说明来“发现”。
我在自己的项目里也复刻了这套机制,踩过一个坑:工具描述写得太简略,模型经常不调用工具,或者用错参数。后来学乖了,描述里加上了使用场景、参数约束、返回格式示例,调用准确率明显提升。这属于“工具描述也是一种提示工程”的范畴,Nanobot 源码里虽然没有长篇大论讲这个,但设计上已经把这个点暴露得很清楚。
3.3 状态存储与持久化:agent 的记忆是怎么保存的
没有记忆的 agent 是“金鱼”——你说完上一句,它下一句就忘了。Nanobot 的解决方案很务实:把每次对话的工具调用和模型回复都追加到一个会话记录文件里,下次开启对话时重新加载。
这种方案看起来笨,但好处是:
- 实现简单,不需要引入数据库;
- 调试友好,直接打开文件就能看到完整的对话历史;
- 可移植性强,换机器直接把目录拷走,记忆跟着走。
OpenClaw 在这一点上做了升级,引入了结构化的存储层,支持 SQLite 和文件两种模式。但核心思路没变:把记忆当作可持久化的状态数据,而不是塞在内存里的临时变量。这个思想在 agent 架构设计里很重要——一旦你开始把记忆当作一等公民来设计,后续做多轮对话、跨会话记忆、用户偏好学习都会顺畅很多。
4. 实操:本地部署并动态观测架构运转
4.1 环境准备与最小部署
这里我用 OpenClaw 的部署流程来演示,因为它的安装体验相对友好,也最能体现架构设计对运维的影响。我自己在 Windows 11 和 Linux 上都跑过,过程差异不大。
官方推荐的方式是脚本安装,一条命令搞定。以 Linux/macOS 为例(Windows 用 PowerShell 或 WSL 同理):
curl -fsSL https://openclaw.org/install.sh | bash装完之后,二进制会被放到~/.openclaw/bin目录下。首次运行时会自动创建配置目录:
~/.openclaw/ ├── config.json # 主配置文件 ├── exec-approvals.json # 危险操作审批规则 ├── skills/ # 技能包目录 ├── workspace/ # agent 工作目录 └── logs/ # 运行日志注意自动生成的exec-approvals.json,这个是 OpenClaw 权限体系的核心——里面记录了哪些命令允许执行、哪些需要审批。默认情况下,agent 是不能直接执行 shell 命令的,这是在保护你的机器。
接下来配置模型。打开config.json,填上你的模型接入信息:
{ "model": { "provider": "openai", "model": "gpt-4o-mini", "api_key_env": "OPENAI_API_KEY" }, "agent": { "workspace_dir": "~/.openclaw/workspace", "approval_file": "~/.openclaw/exec-approvals.json" } }如果你不想用 OpenAI,而是用本地模型(比如 Ollama),只需要换 provider 字段:
{ "model": { "provider": "ollama", "model": "qwen2.5:7b", "base_url": "http://localhost:11434" } }这就是架构解耦带来的好处——上层逻辑完全不变,只动配置。我在切换模型时,经常连 agent 进程都不用重启(部分版本支持热加载),这在模型选择焦虑的当下非常实用。
4.2 用“加一个技能包”验证架构可扩展性
部署跑通之后,我建议你亲手加一个自定义技能包,这是验证你对架构理解是否到位的最佳方式。OpenClaw 的技能包本质是一个目录,包含一个描述文件和一个实现脚本。
以一个hello技能为例,在~/.openclaw/skills/hello/下创建两个文件。
第一个是SKILL.md,描述技能功能:
--- name: hello description: 当用户打招呼或询问你能做什么时,使用此技能,返回一句友好的自我介绍。 ---第二个是run.py,实现具体逻辑:
import json def run(params): return json.dumps({ "reply": "你好,我是你的 AI 助理。我可以帮你查资料、写文件、执行简单任务,你直接告诉我就行。" }, ensure_ascii=False)保存后,在对话里说“你好”,agent 就会自动匹配到这个技能并调用run.py,把返回内容当作回复。整个过程不需要重启服务,不需要改主配置。
你可能会问:这个技能的调用路径是怎么跑通的?答案就在架构的分层里。技能包只负责“做什么”,匹配逻辑(模型根据 skill 描述决定是否调用)在引擎层,执行环境(python 解释器)在运行时层。各层各司其职,这就是为什么加新能力像插 U 盘一样简单。
4.3 源码调试:设断点观察每一层的职责
如果你想更直观地理解架构,我强烈推荐在源码上加断点。克隆 Nanobot 或 OpenClaw 的源码仓库后,用 VS Code 的调试器跑起来,按下面的顺序打断点:
- 入口层(main 函数):观察启动流程做了什么——加载配置、初始化存储、注册默认技能;
- 引擎层(agent loop):观察一次用户消息进来后,prompt 是怎么被构建的,模型返回后又是怎么被解析的;
- 工具层(skill run):观察工具调用前后的上下文变化,确认工具结果是否被正确地写回了记忆。
这个“三步断点法”能帮你把每个模块的输入输出看得清清楚楚。我每次学习一个新框架都会用类似的方法——不是从头到尾读代码,而是追踪一条完整的请求链路,把沿途每个函数的参数和返回值打出来,很快就能建立对架构的直觉。
5. 常见问题与排查技巧实录
5.1 大坑预警:Exec Approvals 导致的工具调用失败
无论你是第一次部署 OpenClaw,还是已经跑了一阵子,大概率会遇到一个奇特的报错,日志里长这样:
legacy exec approvals exist at /root/.openclaw/exec-approvals.json这个提示的意思是:系统检测到老的审批规则文件还在。OpenClaw 升级后,审批文件格式可能有变化,但为了兼容,旧文件会被保留并用在新版本里。问题在于,如果你在旧文件里配置过某些命令的自动放行,新版本可能不认这些老格式规则,导致 agent 调用工具时反复被拦截。
排查思路就两步:
- 打开
~/.openclaw/exec-approvals.json,看看文件里的规则格式是否和新版本文档一致; - 如果不一致,备份后删除该文件,重启 OpenClaw 让它重新生成一份默认的。
我个人的经验是:在本地开发环境,直接把自动审批范围放宽,不然每次工具调用都弹审批,调试效率极低。等上线正式环境,再收紧规则。这在架构层面其实是优点——权限和引擎完全解耦,你可以在不同环境用不同的审批策略。
5.2 模型上下文溢出:长会话下 agent“失忆”
用一段时间后你会发现,随着会话历史越堆越长,模型开始“忘记”前面的内容,或者回复质量明显下降。这不是 bug,而是上下文窗口达到了上限。
Nanobot 的解决方案是简单粗暴的“只取最近 N 条消息”,这在长会话下不太够。OpenClaw 则引入了摘要机制——当历史超过阈值时,把早期对话总结成一段摘要塞进上下文,用摘要替代原始内容。
如果你的项目也遇到类似问题,我的建议是采用“摘要+最近窗口”的双层策略:
- 早期对话 → 压缩成摘要,保留关键信息;
- 最近对话 → 完整保留,保证即时上下文;
- 工具执行结果 → 只保留最后几次,更早的只留结论。
这套策略的落地其实不难,核心是在每次对话后增量更新摘要,而不是等上下文满了才临时压缩。我在自己维护的项目里就是这么做的,效果好很多,而且对模型成本的节省也立竿见影。
5.3 问题速查表:最常踩的 5 个坑
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动后 agent 无响应,日志无输出 | 模型 API Key 未配置或无效 | 检查环境变量OPENAI_API_KEY或配置文件中的 key 字段 |
| 消息平台接入后收不到消息 | Webhook 地址或 Token 配置错误 | 重新检查平台开发者后台的回调地址,与本地配置逐字符比对 |
| agent 一直说“我没有这个工具” | 工具描述过于模糊,模型无法匹配 | 重写 SKILL.md 中的 description,加入触发场景和示例 |
| 工具执行报权限错误 | exec-approvals.json 中未配置放行 | 在审批文件中加入该命令的允许规则,或手动批准一次 |
| 对话长了之后回复开始跑题 | 上下文窗口溢出,早期信息被截断 | 开启摘要功能,或增加上下文窗口大小上限 |
这些坑我基本都在实际运行中踩过,尤其是权限和上下文这两块,几乎是每个 agent 项目从 demo 走向稳定运行必须跨过的门槛。
6. 从源码到自研:架构设计的三个可迁移经验
读 Nanobot 和 OpenClaw 源码,最大的收获不是能照搬某个类或者某段代码,而是提炼出三个可以迁移到任何自研项目的设计原则。
第一个原则:让核心引擎保持“愚蠢”。引擎只负责调度和编排,不关心具体模型是哪家的、工具做了什么、平台消息格式长什么样。越愚蠢的核心越稳定,也越容易扩展。我见过很多人把业务逻辑写在 agent 主循环里,导致每加一个新需求就要动核心代码,这是灾难的开始。
第二个原则:用“描述”代替“硬编码”。Nanobot 让工具通过自然语言描述向模型展示自己,这个思想可以推而广之——不只是工具,任何 agent 能力都可以用“一段结构化的描述”来暴露给模型。配置化、声明式的设计看似比硬编码多写几步,但换来的是运行时动态发现能力的巨大灵活性。
第三个原则:一切状态要可持久化、可重建。无论记忆、审批规则、还是技能配置,都应该落盘。这不仅是容灾的需要,更重要的是它让 agent 变得可调试——你随时可以打开文件看状态,而不是对着一个黑盒猜。只要状态可以导出和导入,换环境、做备份、团队协作就不再是噩梦。
我第一次看完这两个项目的源码后,着手重构了自己维护的一个内部工具项目,把原本耦合在业务代码里的“模型调用”和“工具逻辑”拆开,引入了简单的注册表和配置文件。改造之后,团队的同事自己就能加新工具,不再需要我改代码然后重新部署。这就是好的架构带来的实际价值——它不只服务当前功能,更是服务未来的维护效率。
如果你也正处在“想搞懂 agent 框架内部构造”的阶段,我建议你别停留在用别人的框架搭 demo,找一个像 Nanobot 这样小而美的项目,把源码下载下来,断点跟一遍主循环,亲手加一个工具或者改一条记忆逻辑。这个过程会比读十篇架构分析文章都有用。等你对最小闭环有了手感,再回头看 OpenClaw,你会发现那些看起来复杂的模块,其实都逃不开我们今天聊的这几个基础问题:入口在哪、大脑怎么想、手脚怎么动、记忆怎么存。把这四个问题想清楚,任何 agent 架构在你眼里都会变得通透。