知名开源项目 OpenClaw(Clawdbot) 刚火了没几天,简化版平替就来了。OpenClaw其代码量超过43万行,如果学习起来比较吃力,可以从 nanobot开始。nanobot 以约4000行核心代码实现了一个功能完备的个人AI助手框架,代码量仅为 OpenClaw 的1%,却保留了Agent应用所需的核心能力。这意味着开发者可以在一杯咖啡的时间里通读整个项目源码,理解每一个设计决策,并根据自己的需求进行定制。
本文将从架构设计的角度,带你深入理解 nanobot 的核心模块与设计哲学。
架构概览:六大核心模块
nanobot 的架构采用了解耦的模块化设计。核心分为六个主要模块:Agent核心(agent)、消息总线(bus)、通道管理(channels)、工具系统(tools)、技能系统(skills)、以及心跳服务(heartbeat)。每个模块职责清晰、边界分明,通过异步消息机制进行通信。
这种设计的好处是显而易见的:你可以独立地替换某个模块而不影响其他部分。比如想接入Discord而不是Telegram,只需实现一个新的Channel类;想增加新的工具能力,只需继承Tool基类即可。
接下来,我们逐一拆解每个核心模块的设计与实现。
Agent核心:智能交互的大脑
Agent模块是整个系统的核心,它的职责是接收用户消息、构建上下文、调用大语言模型、执行工具调用、并返回响应。这个过程被封装在 `AgentLoop` 类中。
Agent Loop的工作流程可以概括为一个循环:
当一条消息到达时,AgentLoop首先获取或创建对应的会话(Session),然后通过ContextBuilder构建包含系统提示词、历史对话、记忆上下文和技能信息的完整消息列表。接着,它将这些消息发送给LLM,等待返回。
如果LLM返回了工具调用请求,AgentLoop会执行相应的工具并将结果追加到消息列表中,然后再次调用LLM。这个过程会重复进行,直到LLM返回纯文本响应(无工具调用),或者达到迭代上限。
值得注意的是,nanobot设置了合理的迭代上限(默认20次),防止Agent陷入无限循环。这是一个工程化的考量,确保系统的健壮性。
ContextBuilder类负责构建Agent的"认知背景"。它会从工作空间加载多个引导文件(AGENTS.md、SOUL.md、USER.md等),读取记忆文件,并构建技能摘要。这些信息组合成一个完整的系统提示词,告诉LLM它是谁、能做什么、当前时间是什么、工作目录在哪里。
这种设计让Agent具备了"人格"和"背景知识",使其行为更加一致和可预测。
消息总线:解耦的通信枢纽
消息总线(MessageBus)是nanobot实现多渠道支持的关键。它采用了经典的发布-订阅模式,使用异步队列实现消息的传递。
MessageBus维护两个核心队列:inbound(入站)和outbound(出站)。入站队列接收来自各个聊天渠道的消息,出站队列存放Agent的响应。
这种设计的妙处在于:聊天渠道和Agent核心彼此不知道对方的存在。Telegram渠道只需要往入站队列推送消息,从出站队列拉取响应;Agent只需要从入站队列消费消息,往出站队列推送响应。它们通过消息总线这个"中间人"进行通信。
MessageBus还支持基于渠道名称的订阅机制。当Agent推送一条OutboundMessage时,消息总线会根据消息的channel字段,将其分发给对应的渠道处理器。这使得添加新渠道变得简单——只需注册一个新的订阅者即可。
消息本身通过两个数据类表示:InboundMessage代表入站消息,包含渠道类型、发送者ID、聊天ID、消息内容、时间戳和可选的媒体附件;OutboundMessage代表出站消息,结构类似但增加了reply_to字段用于回复特定消息。
通道系统:连接多元世界的桥梁
通道模块负责与具体的聊天平台对接。nanobot内置了Telegram和WhatsApp两个渠道实现,并通过抽象基类BaseChannel定义了统一的接口规范。
每个Channel需要实现三个核心方法:start(启动连接并监听消息)、stop(关闭连接)、send(发送消息)。此外,BaseChannel还提供了权限检查机制——通过allow_from配置项,可以限制只有特定用户才能与Bot交互,这对于个人助手场景很有必要。
当渠道接收到来自聊天平台的消息时,它会调用内部的_handle_message方法,该方法会先检查发送者权限,然后将消息包装成InboundMessage并发布到消息总线。
Telegram渠道使用python-telegram-bot库实现长轮询;WhatsApp渠道则更为复杂,它需要通过一个Node.js桥接服务(bridge目录)实现与WhatsApp Web API的对接,Python端通过WebSocket与该桥接服务通信。
这种分层设计使得nanobot可以灵活扩展到任何聊天平台——只要实现BaseChannel接口,就能让你的AI助手出现在新的渠道上。
工具系统:赋予Agent行动能力
如果说Agent的LLM调用是"思考",那么工具系统就是"行动"。nanobot通过Tools模块让Agent具备了与环境交互的能力。
每个工具都继承自Tool抽象基类,需要定义name(工具名称)、description(功能描述)、parameters(参数的JSON Schema)和execute(执行逻辑)。工具注册到ToolRegistry后,AgentLoop会在每次LLM调用时将所有工具的定义传递给模型,让模型知道有哪些能力可以使用。
nanobot内置了八种核心工具:ReadFileTool(读取文件)、WriteFileTool(写入文件)、EditFileTool(编辑文件)、ListDirTool(列出目录)、ExecTool(执行Shell命令)、WebSearchTool(网页搜索)、WebFetchTool(抓取网页内容)、MessageTool(发送消息到聊天渠道)。
其中,MessageTool是一个特别设计。它允许Agent主动向用户发送消息,而不仅仅是响应用户的请求。这在后台任务完成时通知用户等场景中非常有用。
SpawnTool则更为巧妙——它允许Agent"分身",创建一个子Agent在后台执行耗时任务。比如用户问"帮我查一下今天的天气和明天的日程",主Agent可以spawn两个子Agent分别处理,然后继续响应用户的其他请求,当子Agent完成后再汇报结果。
工具的执行结果会被包装成tool类型的消息,追加到对话历史中,供LLM在下一轮推理时参考。这种"思考-行动-观察"的循环,正是现代Agent框架的核心模式。
技能系统:可拓展的知识包
技能系统是nanobot的一个亮点设计。它允许开发者将特定领域的知识和使用方法封装成一个个"技能包",Agent可以按需加载和使用。
每个技能是一个包含SKILL.md文件的目录。SKILL.md以Markdown格式编写,告诉Agent如何使用某种工具或完成某类任务。技能可以声明依赖(比如需要某个命令行工具或环境变量),SkillsLoader会在启动时检查这些依赖是否满足。
nanobot内置了几个示例技能:github(与GitHub交互)、weather(查询天气)、tmux(管理终端会话)、summarize(内容摘要)等。用户也可以在工作空间的skills目录下创建自定义技能。
技能系统采用了"渐进式加载"策略:一些标记为always的核心技能会始终加载到Agent上下文中;其他技能只在摘要列表中出现,Agent可以通过read_file工具按需读取详细内容。这既保证了常用能力随时可用,又避免了上下文过于臃肿。
从设计理念来看,技能系统体现了一种优雅的抽象——Agent的能力不再硬编码在程序中,而是可以通过简单的文本文件来扩展。这降低了二次开发的门槛,即使不熟悉Python的用户也能创建新技能。
记忆系统:持久化的认知
Agent如果没有记忆,每次对话都从零开始,用户体验会大打折扣。nanobot通过Memory模块实现了简洁而实用的记忆机制。
记忆分为两种:长期记忆存放在MEMORY.md文件中,适合保存用户偏好、重要约定等持久信息;日常笔记按日期保存在memory/YYYY-MM-DD.md文件中,适合记录当天的事件和临时信息。
ContextBuilder在每次构建上下文时,会自动读取长期记忆和当天笔记,将它们作为系统提示词的一部分传递给LLM。这样,Agent就能"记住"之前的重要信息。
Agent可以通过WriteFileTool主动写入记忆。比如用户说"记住我喜欢Python胜过Java",Agent可以将这条信息追加到MEMORY.md中。下次对话时,这条信息就会出现在Agent的上下文里。
这套记忆系统虽然简单,却非常实用。它不依赖外部向量数据库,不需要复杂的检索算法,只是朴素的文件读写。对于个人助手场景来说,这种程度的记忆能力往往已经足够。
心跳服务:主动唤醒的守护者
传统的聊天机器人都是被动响应——用户说一句,Bot回一句。但一个真正智能的助手应该能主动行动,比如每天早上提醒日程、定期检查邮箱、定时执行任务等。
nanobot通过Heartbeat模块实现了这一能力。HeartbeatService是一个后台服务,每隔一段时间(默认30分钟)触发一次"心跳",唤醒Agent检查HEARTBEAT.md文件中是否有待执行的任务。
HEARTBEAT.md相当于一个任务清单。用户可以告诉Agent"每天早上9点提醒我喝水",Agent会将这条规则写入HEARTBEAT.md。当心跳触发时,Agent读取这个文件,判断是否需要执行某些操作,然后采取行动。
如果HEARTBEAT.md为空或没有需要执行的任务,Agent会返回一个HEARTBEAT_OK标记,表示"一切正常,无需操作",系统就会静默等待下一次心跳。
心跳机制让nanobot从"被动应答"进化为"主动服务",使其更像一个真正的个人助手,而不仅仅是一个问答机器人。
子Agent机制:异步任务的优雅处理
现实中的任务往往需要较长时间完成,如果让用户干等着,体验会很差。nanobot的SubagentManager解决了这个问题。
子Agent是一个轻量级的Agent实例,它与主Agent共享LLM服务,但拥有独立的上下文和工具集。主Agent通过SpawnTool创建子Agent后,子Agent在后台异步执行任务,主Agent可以继续响应用户的其他请求。
子Agent的工具集经过精心裁剪:它没有MessageTool(防止绕过主Agent直接骚扰用户),也没有SpawnTool(防止无限递归创建子Agent)。当子Agent完成任务后,它会将结果通过消息总线发回给主Agent,由主Agent决定如何向用户呈现。
这个设计体现了nanobot对"可控性"的重视——即使是Agent自己创建的子进程,也要受到适当的约束和监管。
总结
nanobot用约4000行代码实现了一个五脏俱全的AI助手框架,它的设计体现了几个值得学习的工程原则:
模块化与解耦。消息总线、渠道系统、工具系统各自独立,通过清晰的接口通信。这使得系统易于理解、易于测试、易于扩展。
约定优于配置。技能通过SKILL.md定义、记忆存放在固定位置、心跳任务写在HEARTBEAT.md。这些约定降低了认知负担,让用户能快速上手。
渐进式加载。技能摘要按需展示、工具定义动态注册。这些策略在保持功能丰富的同时,控制了上下文的膨胀。
安全边界。子Agent的能力限制、渠道的权限白名单、迭代次数上限。这些防护措施确保了系统在异常情况下不会失控。
对于想要学习Agent开发、理解LLM应用架构、或者构建个人AI助手的开发者来说,nanobot是一个非常适合的学习对象。它足够简单,让你能看清每一个细节;又足够完整,覆盖了实际应用所需的核心能力。
正如项目README所说:"The codebase is intentionally small and readable."(代码库刻意保持小巧和易读。)这或许就是nanobot给开发者社区带来的价值——证明了一个好的Agent框架不需要43万行代码,4000行就够了。
感兴趣的可以了解项目:https://github.com/HKUDS/nanobot