你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:
- 了解大厂经验
- 拥有和大厂相匹配的技术等
希望看什么,评论或者私信告诉我!
@TOC
作者按:当我第一次看到 NanoClaw 的源码时,我以为它只是又一个把 ChatGPT 接入 WhatsApp 的小玩意儿。但读完代码的那一刻,我意识到这是一个架构思路极为前卫的项目------它把"AI 可以重写自身代码"这件事当成第一公民来设计。本文将从底层原理出发,带你彻底搞懂 NanoClaw 的每一层设计决策。
一、它到底是什么?------ 三句话说清楚
NanoClaw 是一个运行在你本地机器上的个人 AI 助手框架。它做的事情可以用三句话概括:
- 监听:把来自 WhatsApp(以及通过技能扩展的 Telegram 等)的消息写入一个本地 SQLite 数据库。
- 判断:轮询数据库,判断哪些消息需要 AI 处理。
- 执行:把任务丢进一个**隔离容器(Docker/Apple Container)**里运行 AI 代理,再把结果回传。
听起来很简单?但魔鬼藏在细节里。
二、整体架构:一张图看懂数据流
scss
┌──────────────────────────────────────────────────────────┐
│ 宿主机 (Host) │
│ │
│ WhatsApp/Telegram │
│ │ │
│ ▼ │
│ ┌─────────────┐ 轮询 ┌──────────────────────┐ │
│ │ SQLite DB │ ◄────────► │ src/index.ts │ │
│ │ (消息队列) │ │ (指挥中心 / 调度员) │ │
│ └─────────────┘ └──────────┬───────────┘ │
│ │ │
│ │ 按群组分发 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ GroupQueue │ │
│ │ (并发控制) │ │
│ └────────┬─────────┘ │
│ │ │
└──────────────────────────────────────┼───────────────────┘
│ 启动容器
┌───────────────────▼──────────────────┐
│ 隔离容器 (Container) │
│ │
│ container/agent-runner/src/index.ts │
│ ├── Anthropic API (Claude 模型) │
│ ├── read_file / write_file 工具 │
│ └── bash 工具 │
│ │
│ 挂载: groups/<group_folder>/ │
└───────────────────────────────────────┘
这个架构的核心理念是职责分离:宿主机只管"派单",容器只管"干活",两者之间通过文件系统挂载和 stdin/stdout 管道通信,互不干扰。
三、数据层:SQLite 才是"真相的唯一来源"
3.1 为什么选 SQLite?
很多人第一反应会问:为什么不用 Redis?不用消息队列?答案很简单------零依赖,零运维。
NanoClaw 用 better-sqlite3 管理一个单文件数据库,涵盖以下表:
| 表名 | 作用 |
|---|---|
messages |
存储所有收到的消息,作为 AI 上下文 |
scheduled_tasks |
定时任务配置和下次运行时间 |
registered_groups |
已激活的群组信息(触发词、文件夹路径等) |
state |
系统级状态(消息处理游标等) |
3.2 游标机制:如何做到"断点续传"
这是 src/index.ts 里一个非常精妙的设计。系统维护两个关键时间戳:
lastTimestamp:消息循环扫描到哪里了("已读线")lastAgentTimestamp:AI 已经处理到哪条消息了("处理线")
scss
消息时间轴:
─────────────────────────────────────────────────────►
msg1 msg2 msg3 msg4 msg5 msg6 msg7
▲ ▲
lastAgentTimestamp lastTimestamp
(AI处理到这里) (已读到这里)
中间这段 = 已读但 AI 还没处理 = 待处理消息
当程序崩溃重启时,recoverPendingMessages() 会从这两个游标之间找到"漏网"的消息,重新入队处理。这就是所谓的故障恢复机制。
四、消息循环:一颗永不停跳的心脏
startMessageLoop() 是整个系统的主循环,每隔几百毫秒(POLL_INTERVAL)就询问一次数据库:有没有新消息?
markdown
每次 tick 的逻辑:
1. SELECT 出所有时间戳 > lastTimestamp 的新消息
2. 按 chatJid (群组ID) 分组
3. 对每个群组判断:
- 是否包含触发词(如 @Andy)?
- 如果是主群组,无需触发词
4. 更新 lastTimestamp(标记已读)
5. 派单:
┌── 该群组容器已在运行?
│ └── YES:直接 pipe 消息到容器 stdin
└── 容器不存在?
└── NO:enqueueMessageCheck(chatJid)
└── 等待 GroupQueue 分配资源后启动新容器
这个设计的精髓在于**"热容器"和"冷启动"的区分**。如果用户在持续对话,消息会直接喂给已有容器,响应极快;如果容器已因空闲超时(10分钟)被销毁,才会触发一次完整的冷启动流程。
五、容器化执行:安全隔离的艺术
5.1 为什么要用容器?
这是 NanoClaw 最关键的安全决策。AI 代理可以执行 bash 命令、读写文件------这些能力如果不加约束,一条错误的指令就可能删光你的硬盘。
容器解决了这个问题:
- 文件系统隔离 :容器只能看到挂载进来的
groups/<folder_name>/目录,宿主机的其他文件完全不可见。 - 网络隔离:可配置容器的出口网络权限,防止数据泄露。
- 进程隔离:容器崩溃不会波及宿主机进程。
5.2 容器内的 AI 代理是如何工作的?
在容器内部运行的是 container/agent-runner/src/index.ts。它的本质是一个工具调用循环(Tool Use Loop):
scss
接收 prompt(消息上下文)
│
▼
调用 Anthropic API(Claude 模型)
│
▼
模型返回 tool_use 或 text?
│ │
▼ ▼
执行工具 流式输出文字
(bash/read_file ↓
/write_file) 发回宿主机
│ (通过 stdout)
└────── 把 tool_result
返回给模型,继续对话
注意其中有一个特殊设计:AI 输出中的 <internal>...</internal> 标签会被过滤掉,不发送给用户。这让 AI 可以进行"内部思考"而不打扰用户体验。
六、Skills Engine:最令人拍案叫绝的设计
如果说容器化是 NanoClaw 的安全基石,那 Skills Engine 就是它的灵魂。
6.1 传统插件 vs. NanoClaw 技能
| 维度 | 传统插件系统 | NanoClaw Skills |
|---|---|---|
| 扩展方式 | 加载独立 DLL/JS 模块 | 直接重写源码 |
| 学习成本 | 需要学习宿主 API | 理解意图文件即可 |
| 死代码 | 不需要的功能依然存在 | 最终代码只含你用到的功能 |
| 底层访问 | 受宿主 API 限制 | 可触及任意文件(db.ts、index.ts...) |
| 回滚能力 | 通常没有 | 有完整事务 + 自动回滚 |
6.2 三路合并(Three-way Merge)的核心原理
当你运行 npx tsx scripts/apply-skill.ts .claude/skills/add-telegram 时,实际上是在执行一次自动化 Git 手术。
系统里永远维护着三份代码:
sql
Base(原始基线) ──┐
├─► git merge-file ──► 合并结果
Current(你的修改) ─┤
Skill(技能修改) ──┘
这和 git merge 的三路合并是完全相同的原理:
- Base → Current 的差异 = 你做的修改
- Base → Skill 的差异 = 技能要做的修改
- 如果两者修改的位置不重叠 → 自动合并成功
- 如果同一行都被修改了 → 产生冲突,暂停安装
底层直接调用 git merge-file 命令执行,这是一个极其务实的工程决策------不重复造轮子。
6.3 applySkill 的完整生命周期
skills-engine/apply.ts 的 applySkill() 函数是整个引擎的核心,它的执行流程是一个标准的数据库事务模型:
scss
1. [版本检查] 技能版本 vs 引擎版本是否兼容?
↓
2. [依赖/冲突检查] 依赖的技能是否已安装?有无互斥技能?
↓
3. [漂移检测] 你是否手动改过即将被修改的文件?
↓
4. [加锁] 创建 .lock 文件,防止并发安装
↓
5. [备份] 把所有受影响文件拷贝到 .nanoclaw/backups/
↓
6. [文件添加] 把 add/ 目录下的新文件复制到项目
↓
7. [三路合并] 对 modify/ 目录下每个文件执行 git merge-file
↓
8. [结构化操作] 修改 package.json 安装 npm 依赖
更新 .env.example 注入环境变量
(可能)修改 docker-compose.yml
↓
9. [运行 npm install]
↓
10. [执行后置命令] 运行 post_apply 脚本(如 tsc 编译)
↓
11. [运行测试] 如果测试失败 → 从 backups/ 完整回滚
↓
12. [提交状态] 写入 .nanoclaw/state.yaml 记录安装成功
↓
13. [清理] 删除备份,释放锁
失败时的回滚路径:任何一步出错,都会调用 restoreBackup() 把所有文件还原到安装前的状态,真正做到了幂等性。
6.4 Intent 文件:写给 AI 看的"手术说明书"
这是 NanoClaw 最有远见的设计之一。每个需要合并的文件旁边,都配有一个 *.intent.md:
bash
modify/
src/index.ts ← 要合并的目标文件
src/index.ts.intent.md ← 用自然语言描述"为什么要这么改"
当三路合并产生冲突时,意图文件会告诉 Claude Code:
"我要在消息路由逻辑里加一个 Telegram 分支,这个分支的不变量是:必须在 WhatsApp 分支之后注册,且必须复用
findChannel()工厂函数..."
这让 AI 能够像一名理解需求的开发者一样,而不是机械的代码替换器,来解决合并冲突。代码变更的意图被显式编码进了代码库------这本身就是一种先进的软件工程实践。
七、状态追踪:.nanoclaw/state.yaml
这个隐藏文件是整个技能生态系统的"注册表",记录了:
yaml
engine_version: "1.2.0"
installed_skills:
- name: add-telegram
version: "1.0.0"
installed_at: "2025-01-15T10:30:00Z"
file_hashes:
src/index.ts: "a3f8b2c1..."
src/config.ts: "d9e4f7a0..."
path_remaps:
old/path/file.ts: new/path/file.ts
其中 file_hashes 是漂移检测的关键:如果你手动修改了 src/index.ts,下次安装技能时哈希值不匹配,系统会给出警告------它知道你"动过刀"。
八、路径重映射(Path Remap):抵抗重构的能力
skills-engine/path-remap.ts 解决了一个实际工程问题:如果你把 src/channels/whatsapp.ts 重命名为 src/channels/whatsapp-bridge.ts,下次安装 add-telegram 技能时,它还能找到正确的注入位置吗?
答案是可以。重命名操作会被记录在 state.yaml 的 path_remaps 字段里,技能引擎在执行合并前会先做一次路径翻译。这让代码库在经过重构后,依然能正确应用技能。
九、定时任务与 IPC:被忽视的辅助系统
src/index.ts 的 main() 函数在启动消息循环之前,还会初始化两个子系统:
定时任务调度器(Scheduler) :读取数据库中的 scheduled_tasks 表,按时触发 AI 执行任务(如"每天早上 8 点发送天气播报")。
IPC 监视器(Inter-Process Communication):监听容器与宿主机之间的消息通道,处理诸如"容器请求注册新群组"这类跨边界的操作请求。
十、从源码里读出的设计哲学
读完整个代码库,我总结出 NanoClaw 的三个核心设计哲学:
1. 隔离是第一公民
所有 AI 执行都在容器里发生。这不是可选的优化,而是架构的基础假设。任何能力的扩展都不能绕过这道隔离墙。
2. AI 即操作者
整个系统的使用姿势不是"用户配置",而是"AI 编程"。想改触发词?让 AI 改代码。想加新功能?给 AI 提供意图文件,让它运行安装脚本。代码是配置,AI 是操作员。
3. 可组合胜过大而全
NanoClaw 的核心代码出奇地精简。它不预装任何你不需要的功能。Telegram 支持、定时任务高级功能、语音识别------所有这些都是可选的技能,你用到了再装,装了就织入代码,不装就不存在。这与"大而全框架"的思路截然相反。
结语:它预示了什么?
NanoClaw 目前还是一个相对小众的项目,但它展示了一种可能性:未来的软件可以不依赖传统的插件系统,而是直接通过 AI 来改造自身。
这种"AI 原生(AI-Native)"的设计模式------让 AI 参与软件自身的演化------或许才是下一代开发工具真正的形态。
Skills Engine 里那个调用 git merge-file 的小函数,承载的是比它看起来大得多的野心。
如果你想动手体验,可以在 GitHub 上找到 NanoClaw 的源码。建议从 src/index.ts 和 skills-engine/apply.ts 开始读,顺序正好和本文的结构一致。