用 1% 的代码实现 AI Agent 框架的全部灵魂——nanobot 深度解析

作者思考:当我们谈论 AI 能力的边界时,我们本质上是在谈论「上下文的边界」。而上下文,不应该被关在一个盒子里。


写在前面:为什么要看 nanobot?

2025 年,每周都有新的 AI Agent 框架冒出来。LangChain、AutoGen、CrewAI、OpenHands(原 OpenClaw)……每一个都有几万行代码、庞大的抽象层和学习曲线。

然后出现了 nanobot

它的 README 里有一行话让我停下来:

Inspired by OpenHands (formerly OpenClaw), but with ~99% less code.

99% 更少的代码。

这不是在炫技,这是一个架构问题。它在说:OpenClaw 的核心本质,只需要 1% 的代码就能表达。 剩下 99% 是什么?是企业级适配、各种边缘 case 的防御代码、UI、和工程化包袱。

如果你想真正理解 AI Agent 的工作原理,nanobot 是目前我见过最好的「解剖标本」。


一、先说结论:nanobot 是什么

一句话定位:一个极轻量的个人 AI 助手框架,把「带工具的有状态对话循环」这件事,用最精炼的方式实现出来。

它能做什么(不是 toy,是真实可用的)

能力维度 具体内容
多模型 OpenRouter、OpenAI、Claude、DeepSeek、Moonshot、阿里云、火山引擎、Ollama、本地 vLLM
多渠道 CLI、Telegram、Discord、Slack、飞书、钉钉、QQ、WhatsApp、邮件、企业微信、Matrix
工具系统 文件读写、Shell 执行、Web 搜索/抓取、MCP、定时任务 Cron、子代理 Spawn
记忆系统 持久化会话 + 自动记忆压缩归档(Memory Consolidation)
自动化 Heartbeat 心跳(30 分钟唤醒一次)+ Cron 定时任务
技能系统 用 Markdown + YAML 描述能力,自然语言注入 Prompt

二、项目结构:每一个目录都「恰好够用」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nanobot/
├── agent/ # 🧠 全部核心智能:循环、上下文、记忆、工具、子代理
│ ├── loop.py # AgentLoop ← 整个系统的「心脏」
│ ├── context.py # ContextBuilder:把历史变成 LLM 能吃的 messages
│ ├── memory.py # MemoryConsolidator:防止上下文无限膨胀
│ ├── skills.py # Skills 加载
│ ├── subagent.py # SubagentManager:并行子任务
│ └── tools/ # 内置工具实现(File / Shell / Web / MCP / Cron)
├── skills/ # 🎯 可复用能力(github、weather、tmux、summarize...)
├── channels/ # 📱 多渠道适配层(每个渠道只做协议转换)
├── bus/ # 🚌 MessageBus:解耦 Agent 和 Channel
├── cron/ # ⏰ 定时任务
├── heartbeat/ # 💓 周期性唤醒
├── providers/ # 🤖 LLM 提供方注册表
├── session/ # 💬 会话持久化
├── config/ # ⚙️ 配置 schema
└── cli/ # 🖥️ 命令行入口(typer)

惊人之处:这个目录结构和 OpenHands 几乎等价,但每个文件的规模小了一个数量级。

它没有「过度工程化」——没有 Event Sourcing、没有插件沙箱、没有复杂的状态机框架。它只是把最核心的几个对象用最直接的方式连起来。


三、架构核心:一张图说清楚一切

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
用户 / Telegram / Discord / 飞书


┌──────────────┐
│ MessageBus │ ← Inbound Queue / Outbound Queue
└──────┬───────┘


┌──────────────────────────────────────────┐
│ AgentLoop │
│ │
│ SessionManager → ContextBuilder │
│ ↓ ↓ │
│ [历史消息] [system prompt + 工具定义] │
│ ↓ │
│ LLM Provider.chat() │
│ ↓ │
│ 有 tool_calls? │
│ / \ │
│ 是 否 │
│ ↓ ↓ │
│ ToolRegistry.execute() final_answer │
│ ↓ ↓ │
│ 继续循环 写回 Session │
└──────────────────────────────────────────┘


MessageBus → 发回给用户 / Telegram / Discord

核心对象只有 5 个,把它们理解透,你就理解了整个框架:

对象 职责
AgentLoop 整个流程的编排者
SessionManager 持久化的「对话记录本」
ContextBuilder 把 Session 变成 LLM 的 messages[]
ToolRegistry 工具的注册表和执行器
MemoryConsolidator 防止上下文溢出的「垃圾回收器」

四、为什么只用 1% 的代码?

这个问题的本质是:OpenHands 那 99% 的代码是在做什么?

答案是:处理边界情况、扩展性、可观测性、和复杂的多 Agent 调度。 这些对个人开发者来说,99% 的场景都用不到。

nanobot 做了一个清醒的取舍:

1. 用 litellm 代替自己写 Provider 适配层

1
2
3
4
5
6
7
# nanobot 里 LLM 调用的核心:用 litellm 屏蔽所有差异
response = litellm.completion(
model=model,
messages=messages,
tools=tool_defs,
...
)

OpenHands 为了支持更多模型和特殊情况,自己维护了大量的适配代码。nanobot 说:litellm 已经做得很好了,我直接用。

2. 用 Markdown 文件代替代码级 Skills 系统

OpenHands 的 Skills/Capabilities 是代码实现的,需要注册、接口、版本管理。

nanobot 的 Skill 是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
---
name: weather
description: Get weather information for any location
---

## Weather Skill

When the user asks about weather for a specific location:
1. Use the web_fetch tool to call wttr.in/{location}?format=j1
2. Parse the JSON response and extract current conditions
3. Format a human-readable weather report

Avoid making up weather data. Always fetch fresh data.

就这样。一个 .md 文件,注入进 system prompt,模型就知道怎么用了。自然语言就是接口。

3. 用 asyncio.Queue 代替复杂的事件系统

1
2
3
4
class MessageBus:
def __init__(self):
self._inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()
self._outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()

两个队列,搞定解耦。不需要 pub/sub 框架,不需要 EventBus 抽象,不需要序列化。

4. 工具系统:统一接口,零额外框架

1
2
3
4
5
6
7
class BaseTool:
name: str
description: str
parameters: dict # JSON Schema

async def __call__(self, **kwargs) -> str:
...

一个 dataclass + 一个 __call__,就是一个工具。ToolRegistry 做的事情也极简:

  • register(tool) → 存进 dict
  • get_definitions() → 返回 OpenAI 格式的 tool schema
  • execute(name, args) → 调用对应工具

五、核心循环:带工具的 Agent 状态机

这是整个框架最关键的部分,理解它就理解了现代 AI Agent 的 80%:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
messages = context.build_messages(session, user_input)

for i in range(max_iterations): # 默认 40 轮
response = provider.chat_with_retry(
messages=messages,
tools=tool_registry.get_definitions()
)

if response.has_tool_calls:
# 把这轮 assistant 消息加进去
messages = context.add_assistant_message(messages, tool_calls=response.tool_calls)

# 执行每个工具调用
for tc in response.tool_calls:
result = await tool_registry.execute(tc.name, tc.arguments)
messages = context.add_tool_result(messages, tc.id, result)
# 继续循环,把工具结果喂给 LLM
else:
# 剔除 <think>...</think> 推理标记
final = strip_think(response.content)
messages = context.add_assistant_message(messages, final)
break # ← 得到最终答案,退出

session.save(messages)

就是这么简单。不需要图状态机,不需要 Planner-Executor 分离,不需要 ReAct 框架。

LLM 本身就是最好的控制器——它决定什么时候调工具、调哪个、什么时候给最终答案。


六、Session / Memory / Context 三角关系

这是很多人搞不清楚的地方,nanobot 把它做得非常清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Session(硬盘)

│ 历史消息 + 时间戳

ContextBuilder(内存,临时)

│ system prompt
│ + skill 说明
│ + tool 定义
│ + 历史消息(截断后)
│ + 当前用户输入

messages[] → 送给 LLM

当 Session 太长:
Session → MemoryConsolidator → 调 LLM 做摘要 → 归档老消息 → 插入「摘要消息」
  • Session 是持久状态(存文件)
  • Context 是临时视图(只为下一次 LLM 调用服务)
  • MemoryConsolidator 是「垃圾回收」(防止 token 爆炸)

七、实际 Demo:5 分钟跑起来

安装

1
2
3
pip install nanobot-ai
# 或从源码
git clone https://github.com/HKUDS/nanobot.git && cd nanobot && pip install -e .

初始化

1
2
nanobot onboard
# 生成 ~/.nanobot/config.json 和 workspace 模板

配置模型(以 DeepSeek 为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"providers": {
"openrouter": {
"apiKey": "sk-or-v1-xxx"
}
},
"agents": {
"defaults": {
"model": "deepseek/deepseek-chat",
"provider": "openrouter"
}
}
}

跑起来

1
2
3
4
5
6
7
8
# 单次问答
nanobot agent -m "帮我写一个 Python 快速排序,并跑一下验证"

# 交互模式
nanobot agent

# 接 Telegram(配好 token 后)
nanobot gateway

看看它实际干了什么

1
2
3
4
5
6
7
8
9
10
> nanobot agent -m "帮我分析一下当前目录下代码的复杂度"

🔧 list_dir(path=".")
📄 read_file(path="agent/loop.py")
📄 read_file(path="agent/context.py")
💭 thinking...

当前项目结构精炼,agent/loop.py 是核心,约 300 行,
实现了完整的 LLM + 工具调用循环。主要复杂度集中在
_run_agent_loop() 和 _process_message() 两个方法...

它会自己决定要读哪些文件、读多少、怎么分析——你不需要告诉它。


八、渠道系统的设计哲学:协议适配,仅此而已

nanobot 有 10+ 个渠道,但每个渠道的实现都极其简单,因为它们只做一件事:

1
2
平台原生消息格式 → InboundMessage → MessageBus
MessageBus → OutboundMessage → 平台原生 API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 一个最简化的渠道实现示意
class MyChannel:
async def start(self):
async for raw_msg in self.platform.listen():
inbound = InboundMessage(
channel="my_channel",
chat_id=raw_msg.chat_id,
content=raw_msg.text,
sender=raw_msg.user_id
)
await self.bus.publish_inbound(inbound)

async for outbound in self.bus.consume_outbound("my_channel"):
await self.platform.send(outbound.chat_id, outbound.content)

AgentLoop 完全不知道渠道的存在。它只关心 InboundMessageOutboundMessage

这个设计意味着:理论上,你可以用 10 行代码接入任何新渠道。


九、亮点汇总

亮点 说明
Markdown-as-Skills 用自然语言定义 Agent 能力,无需编写代码接口
心跳机制 每 30 分钟自动唤醒,执行 HEARTBEAT.md 中定义的周期任务
子代理 SpawnTool 让 Agent 可以派生子 Agent 处理并行子任务
MCP 原生支持 直接接 MCP 服务器,工具生态立刻扩展到整个 MCP 市场
记忆压缩 对话变长时自动 LLM 摘要,永远不会 token 爆炸
多实例隔离 不同 config + 不同 workspace,同时跑多个机器人互不干扰
安全边界 restrictToWorkspace + allowFrom 白名单,可安全部署
零框架依赖 核心逻辑不依赖任何 Agent 框架,只用 litellm + asyncio

十、我的思考:跨平台上下文,才是 AI 能力的真正边界

以下是我个人的延伸思考,不局限于 nanobot,但 nanobot 是触发这个思考的起点。

上下文决定智能的上限

nanobot 让我清晰地意识到一件事:AI Agent 的能力,本质上是它的上下文的函数。

你给它更多相关信息,它就能做出更好的决策。给它文件系统访问,它就能读写代码。给它 Shell,它就能执行命令。给它 Web 搜索,它就能获取最新信息。

但我们现在的上下文,几乎都被困在单一平台的边界里

  • Cursor 里的 Agent 只能看到你的代码文件
  • ChatGPT 的 Agent 只能看到你上传的文件
  • Telegram 上的 nanobot 只能看到你发送的消息

这不应该是 AI 的极限。

跨端上下文:一个被低估的架构方向

想象这样一个场景:

你在 Cursor 里写代码,IDE Agent 的上下文里不仅有你的代码文件,还有:

  • 你在浏览器里刚看的那篇 Stack Overflow 答案(来自浏览器插件)
  • 你在 Slack 里和同事讨论这个 bug 的消息记录(来自 Slack 渠道)
  • 你昨天在手机上记的那条设计思路(来自移动端笔记同步)
  • 你公司内网文档里的相关 API 文档(来自企业知识库连接器)

这些信息分散在不同的「端」上,但它们都和你当前的任务高度相关。

如果 Agent 能同时看到这些,它的决策质量将指数级提升。

技术上怎么实现?

nanobot 的架构给了我们一个线索。它的 MessageBus + Channel 设计天然就是一个「多源上下文聚合器」的骨架:

1
2
3
4
5
6
7
浏览器插件 Channel


MessageBus ←── Slack Channel
│ ←── 手机端 Channel
▼ ←── 知识库 Channel
AgentLoop(上下文现在来自四面八方)

每个「数据源」都可以实现为一个 Channel,向 MessageBus 推送「上下文增强消息」。Agent 在构建 Context 的时候,聚合所有来源的相关信息。

更进一步,可以设计一个上下文路由层

1
2
3
4
5
6
7
用户的每个 Action(切换文件、搜索关键词、查看文档)


Context Router(根据当前任务语义,从各端拉取相关上下文)


ContextBuilder(注入给 AgentLoop)

这不是幻想。浏览器插件可以监听页面访问事件,IDE 插件可以监听文件切换事件,手机 App 可以通过 webhook 推送,企业知识库可以通过 MCP 接入。

协议是现成的(MCP、OpenAI messages 格式),缺的只是「连接器」和「语义路由」。

为什么这件事很重要?

现在的 AI Coding 工具的天花板,不是模型能力,是上下文质量

当你问 Claude「为什么这个函数会 crash」,它能看到的只是你圈选的那几行代码。但实际上,crash 的原因可能在:

  • 三天前你修改的另一个文件(本地 git 历史)
  • 你昨天看的那个 issue(浏览器历史)
  • 你和后端同事对齐 API 格式的那条 Slack 消息(另一个工具)

模型本身没有问题,是上下文残缺了。

跨平台、跨端的上下文聚合,本质上是在解决这个问题:给 AI 提供和人类工作时同等丰富的信息环境

人类程序员在解决 bug 时,会同时开着 IDE、浏览器、Slack、设计稿——他的「工作上下文」天然是多端的。AI Agent 现在还做不到这一点,但架构上已经没有障碍了。

nanobot 在这条路上的位置

nanobot 今天已经做到了「多渠道」——你可以从 Telegram 给它发消息,同时它在服务你的 CLI。这是跨端上下文的一个初步形态。

但更有趣的方向是:不只是多渠道输入,而是多源上下文感知

Agent 不应该只等着用户发消息,它应该主动感知用户在不同端的行为,理解当前的工作语境,在用户还没开口之前,就已经准备好了相关的上下文。

这就是 Heartbeat 机制的价值所在——nanobot 已经在尝试让 Agent 有「主动性」。下一步是让这种主动性建立在「跨端感知」的基础上。


十一、尾声:为什么要读这种代码

很多人学 AI 框架,都是先看文档,然后调 API,然后写 demo,然后”用起来了”。

但真正理解发生了什么,需要找到一份代码量恰好够你一个人读透的实现。

nanobot 就是这样一份代码。它不是玩具,它有真实的生产能力;它也不是黑盒,每一行代码都有清晰的意图。

读完 nanobot,你会理解:

  • 一条消息在 Agent 系统里是怎么流动的
  • LLM 的 tool calling 协议是怎么被工程化的
  • 记忆系统为什么需要压缩,以及怎么压缩
  • 一个「多渠道 AI 助手」的最小可行架构是什么样的

然后,你会开始想:如果上下文不只来自这个工具,而是来自我工作时的整个数字世界,会发生什么?

这个问题,值得我们认真去做。


如果你也在思考 AI Agent 的上下文边界问题,欢迎交流。

参考项目:nanobot-ai