OpenClaw、pi-agent 与 nanobot:一个好用的 Agent 是怎样炼成的
这篇文章先讲清楚 Agent 的基础调用链,再对比三者实现的差异,包括:模型兼容层、工具调用回填、skills、memory、heartbeat 等。
一、先理解 Agent 在做什么
我们先统一一下语言。当你和一个 AI Agent 对话时,背后发生了这些事:
| 术语 | 含义 |
|---|---|
| messages | 模型可见的上下文,按时间顺序排列 |
| tools | 模型可调用的函数声明(名称、参数 JSON Schema、描述) |
| tool_call | 模型请求调用工具的指令 |
| tool_result | 工具执行后的结果,作为下一轮模型输入的一部分 |
| session | 同一对话线程的长期状态 |
| loop | 模型生成 → 工具执行 → 结果回填 → 再次生成,直到结束 |
理解这些概念很重要,因为生产环境里的问题往往就出在这些细节上:
- provider 参数不兼容,请求被 400 拒绝
- memory 和 skills 注入太多,上下文爆掉
- 多会话并发时,消息串线
- 心跳和主对话抢资源,延迟失控
除了功能丰富度以外,这些也是 OpenClaw/pi-agent 与其他轻量 Agent 框架如 nanobot 等的体验拉开差距的地方。
二、一次完整的 Agent 调用
所有 Agent 系统都遵循同一个主干流程。理解了它,再看不同框架的实现差异就清晰多了。
2.1 通用调用流程
用户输入
→ 选择会话 / 组装上下文(system + history + memory + skills)
→ 调用 LLM(携带 tools 声明)
→ 解析流式输出
→ 若有 tool_calls:执行工具 → 追加结果 → 回到 LLM
→ 若无 tool_calls:产出最终回答
→ 持久化会话 / 记忆 / 遥测数据
这个流程看似简单,但真实工程的难点集中在四个地方:
- provider 兼容层怎么做
- tool_call/tool_result 如何保证跨模型兼容
- context 怎么控制长度和噪声
- loop 在中断、重试、并发下如何保持一致性
2.2 OpenClaw 的完整链路
OpenClaw 是一个平台级的实现,它的调用链路最完整。让我们看看一条消息从用户发出到返回,经历了哪些环节:
User
|
| 1) 发送消息
v
Channel Adapter(解析来源账号/会话键)
|
| 2) 路由解析
v
Route Resolver(命中 agent + session)
|
| 3) 进入队列(session lane / global lane)
v
Embedded Runner(openclaw 核心)
|
| 4) 组装上下文(system + history + skills + memory)
| 5) 创建/恢复 pi session
v
pi-agent / pi-ai
|
| 6) streamSimple(model, context, tools)
v
LLM Provider
|
| 7) 流式返回 text/thinking/toolcall
v
pi-agent Loop
|
| 8) 执行工具(memory_search、exec、web 等)
| 9) 回填 tool_result,再次请求 LLM
v
Reply Dispatcher
|
| 10) 分块/打字效果/路由回渠道
v
User
2.3 nanobot 的简化链路
nanobot 走的是轻量路线,链路更直接:
User → Channel → MessageBus(inbound)
→ AgentLoop
→ ContextBuilder
→ LiteLLMProvider.chat(...)
→ [有 tool_calls?]
→ ToolRegistry.execute(...)
→ 追加 tool result
→ 再次调用 LLM
[无 tool_calls]
→ 返回最终回答
→ MessageBus(outbound) → User
三、三者分别在哪一层
这三个项目不在同一个抽象层,理解它们的定位很重要:
| 项目 | 层级 | 职责 |
|---|---|---|
| pi-agent + pi-ai | 内核层 | 负责 loop 语义和模型兼容 |
| OpenClaw | 平台层 | 直接嵌入 pi 运行时,补齐路由、调度、会话治理、插件和多渠道 |
| nanobot | 平台+内核(轻量复刻) | 借鉴 openclaw 的主干思路,用更小代码体量实现完整闭环 |
读这三个项目,可以按「内核 → 平台 → 轻量复刻」的顺序。这样你能先理解核心机制,再看平台如何包装,最后看如何用最小代码量实现类似能力。
四、内核层:pi-ai + pi-agent
4.1 pi-ai:provider 兼容是核心资产
pi-ai 对外暴露统一的流式接口,内部通过 API 注册表分发到不同的 provider 适配器。这层做了大量细节兼容工作:
max_tokens/max_completion_tokens字段差异developer/systemrole 的差异thinking/reasoning参数的不同格式- tool result 是否需要
name字段 - 不同 provider 对
tool_call_id长度和字符集的限制 - OpenAI-compatible 网关的非标准行为
这些兼容逻辑集中在适配层,loop 层因此可以保持稳定。
4.2 结构化的事件流
pi-ai 的事件模型包含三类增量事件,最后统一收敛到 done/error:
start
→ text_* 事件(文本输出)
→ thinking_* 事件(思考过程)
→ toolcall_* 事件(工具调用)
→ done / error
这种设计带来几个好处:
- 可以把「思考」和「回答」分开展示
- 工具参数可以边流边拼,减少大 JSON 一次性解析失败
- usage/cost 可在流中累计,便于预算和监控
4.3 pi-agent 的 loop 语义
pi-agent 的 loop 采用双层语义:
- 内层:处理 tool call 链
- 外层:处理 follow-up 消息
它还支持 steering/interruption——工具执行过程中如果用户插入新消息,系统可以跳过剩余工具并写入「skipped」结果,防止旧任务继续污染上下文。这对长任务和多轮工具链很重要。
五、平台层:OpenClaw
OpenClaw 直接复用 pi 的内核,重点解决平台治理问题。
5.1 OpenClaw 新增了哪些能力
OpenClaw 的主要价值在 loop 外围,体现在四个方面:
1. 会话与并发治理
- 会话级 lane 串行化
- 全局 lane 限流
- 同会话避免并发写入冲突
2. 运行生命周期治理
- timeout、abort、retry
- compaction 与重试协同
- 多种 hook 节点(输入前、输出后、工具前后)
3. 上下文治理
- system prompt 动态拼装
- history 清洗、截断、校验
- 图片输入检测和注入
4. 渠道与路由
- channel 插件化接入
- account/peer/group/role 到 agent/session 的映射
看完这些能力,就能理解 OpenClaw 为什么会有更大的代码规模。
5.2 工具调用在 OpenClaw 里怎么走
OpenClaw 的工具链采用「组装管线」模式,大致步骤如下:
- 组装基础工具(读写编辑、exec/process、web 等)
- 追加平台工具(message、sessions、subagents、cron、browser、canvas 等)
- 合并插件工具和渠道工具
- 按 profile/agent/group/sandbox/subagent 规则做 allow/deny 过滤
- 对工具参数 schema 做 provider 兼容清洗(如 Gemini、Claude 兼容)
- 包装 before_tool_call / after_tool_call hook
- 包装 abort 信号,确保超时中止可传播到工具执行
最后才把这些工具交给 pi 会话。
5.3 工具回填的常见坑
工具回填是 Agent 系统最容易出错的地方。以下是几个常见坑和规避方法:
| 问题 | 解决方案 |
|---|---|
| 顺序错误 | assistant(tool_calls) 必须先入历史,再追加每个 tool_result |
| ID 不兼容 | 不同 provider 对 ID 长度和字符集限制不同,需要规范化 |
| 空 assistant | 部分模型不接受「无内容且无 tool_calls」的消息,构建时要过滤 |
| 截断导致孤立 | 历史压缩后可能出现 orphaned tool_result,调用前要做配对修复 |
| 用户中断 | 执行中收到新消息时,剩余工具应该跳过,避免旧任务继续写上下文 |
这些细节通常决定系统稳不稳定。
工具回填时序图
User Message
→ LLM
→ assistant(tool_calls)
→ append assistant(tool_calls) to messages
→ for each tool_call:
→ execute tool
→ if success: tool_result(isError=false)
→ if exception: tool_result(isError=true, error text)
→ append tool_result
→ LLM (next round with updated messages)
→ final assistant reply
回填阶段有两个硬规则:
assistant(tool_calls)必须先入历史,再追加每个tool_result- 工具失败也要回填
tool_result,否则下一轮模型缺上下文,容易重复调用或直接报错
5.4 skills 的注册和加载
OpenClaw 的 skills 有明确的来源优先级和筛选机制:
- 来源:workspace、project/personal agents、managed、bundled、extra/plugin 目录
- 过滤:按 OS、二进制依赖、环境变量、配置项判断是否可用
- prompt 控长:按数量和字符上限截断,超限会给出截断提示
- 调用策略:支持前置自动纳入,也支持通过 read 工具按需读取
- 用户命令:可从 skill 元数据生成命令映射,冲突会自动去重
这套机制的核心目标是:让模型能发现可用技能,同时把 token 成本压在可控范围。
5.5 memory 的加载机制
OpenClaw 的 memory 是两条并行机制:
在线检索链
- 提供
memory_search/memory_get工具 - 检索后只注入需要片段,支持引用与注入预算控制
- 后端支持向量检索 + FTS 混合模式
长期沉淀链
- 在 compaction 前触发 memory flush
- 把 durable 信息写回 memory 文件,降低长期遗忘
这比「只把 MEMORY.md 整文件塞进 prompt」稳定得多。
5.6 heartbeat 的实现
OpenClaw 的 heartbeat 有调度语义:
- 主队列忙时跳过,避免和实时对话抢资源
- wake 支持合并和退避重试
- heartbeat-only 回合可做降噪,减少对主对话上下文污染
这种方式更适合长期运行,心跳噪声更低。
六、轻量层:nanobot
nanobot 的定位很清楚:完整能力 + 小体量代码。
6.1 provider 层
nanobot 默认走 LiteLLM,本地保留一层 registry:
- 模型前缀与网关识别
- 环境变量与参数覆盖
- 个别 provider(如 Codex)走专门实现
这种设计的好处是落地快、维护成本也可控。
6.2 loop 层
nanobot 的 loop 是标准闭环:
- 有 tool_calls 就执行工具并回填
- 没有 tool_calls 就输出并结束
max_iterations控制循环上限
它没有实现 pi-agent 那套完整的 steering/follow-up 语义,复杂场景控制能力因此更轻。
6.3 skills 加载
nanobot 的 skills 机制非常直观:
- 扫描 workspace skills + builtin skills
- 解析前置元数据(描述、依赖、always 标记)
- always 技能直接注入系统上下文
- 其余技能只注入摘要和路径,模型需要时通过
read_file再读取完整 SKILL.md
「摘要 + 按需读取」的策略在工程上很有效,尤其适合控制 prompt 膨胀。
6.4 memory 与 heartbeat
- memory:采用
MEMORY.md+HISTORY.md双文件,会话过长后触发 consolidation - heartbeat:定时读取
HEARTBEAT.md,执行一轮并检查HEARTBEAT_OK
实现简单,排查成本低,适合小团队快速迭代。
七、内置工具与自带 Skills
7.1 OpenClaw
内置工具
| 类别 | 工具 | 用途 |
|---|---|---|
| 编码与文件 | read、write、edit、exec、process、apply_patch | 代码改写与本地自动化 |
| 知识与网络 | web_search、web_fetch、image、browser、canvas | 检索、抓取、页面交互 |
| 会话与编排 | sessions_*、subagents、cron、message、tts | 多会话协作和任务调度 |
| 记忆能力 | memory_search、memory_get | 长期上下文检索(由 memory slot 提供) |
| 平台扩展 | nodes、gateway 以及 channel/plugin 注入工具 | 连接外部系统 |
自带 Skills
| 类别 | Skills |
|---|---|
| 开发与效率 | coding-agent、github、gh-issues、tmux、session-logs、summarize |
| 文档与知识 | notion、obsidian、apple-notes、bear-notes、blogwatcher、nano-pdf |
| 通信与协作 | discord、slack、imsg、voice-call、bluebubbles |
| 多媒体与创作 | openai-image-gen、openai-whisper、video-frames、camsnap、songsee |
| 生活与设备自动化 | apple-reminders、things-mac、openhue、spotify-player、sonoscli、weather、food-order |
| 平台与生态 | clawhub、skill-creator、wacli、oracle(用于技能管理和系统接入) |
主仓库内置技能约 50+,扩展仓库另有 feishu-doc、feishu-drive、feishu-perm、feishu-wiki 等垂直技能。
7.2 nanobot
内置工具
| 类别 | 工具 | 用途 |
|---|---|---|
| 文件与执行 | read_file、write_file、edit_file、list_dir、exec | 文件操作与命令执行 |
| 网络访问 | web_search、web_fetch | 信息检索与网页抓取 |
| 交互与调度 | message、spawn、cron | 消息发送、子进程、定时任务 |
可选 MCP 工具可动态接入
自带 Skills
| 类别 | Skills |
|---|---|
| 开发协作 | github、tmux |
| 知识处理 | summarize、memory |
| 自动化与生态 | cron、weather、clawhub、skill-creator |
7.3 pi-agent / pi-mono
内置工具
| 场景 | 工具 |
|---|---|
| pi-agent-core | 不预置具体工具,强调由宿主按场景注入 |
| pi-coding-agent 默认 | read、bash、edit、write(覆盖核心编码闭环) |
| pi-coding-agent 只读 | read、grep、find、ls(适合审阅与诊断) |
自带 Skills
pi-agent-core没有独立的产品级内置 skills 包pi-mono主要提供示例与运行时能力,生产中的技能体系通常由宿主应用(如 OpenClaw)提供
八、nanobot 的改进方向
作者在体验 nanobot 时,发现 nanobot 目前在个人场景里仍会遇到几类典型失效:
第一类:调用链失效
一次工具调用报错后没有良好的恢复路径,模型容易重复发起相同调用,最后耗尽迭代次数。
第二类:模型链路失效
provider 的边界差异、限流或短时故障出现时,回退和重试策略不够稳,用户看到的就是「同一个问题有时能做、有时直接失败」。
第三类:状态失效
长会话里 memory 与历史上下文逐渐污染,heartbeat 又可能引入噪声,最终表现为「越聊越偏,越跑越不可靠」。
这些问题对个人体验影响很大,一旦 Agent 出现不稳定,很难自行恢复。因为在使用这类 Agent 的场景下,通常并不具备随时运维的条件,比如春节假期用手机在海边写下这篇文章的笔者。
改进路径
内核层应优先借鉴 pi-agent/pi-ai:
- 把 loop 的中断恢复
- 工具回填一致性
- provider 兼容与重试/fallback
直接做成默认能力,减少后期补丁式修复。
运行时层可以借鉴 OpenClaw:
- 把 heartbeat 调度
- skills 加载治理
- session 状态管理
做成统一机制,减少「功能各自可用、组合后失稳」的情况。
这样 nanobot 才能保持轻量,同时真正跨过「个人可生产使用」的门槛。