Agent Loop 拆解:从 OpenClaw 到 nanobot 再到 PicoClaw

Posted on 四 19 3月 2026 in Journal

Abstract Agent Loop 拆解:从 OpenClaw 到 nanobot 再到 PicoClaw
Authors Walter Fan
Category learning note
Status v1.0
Updated 2026-03-19
License CC-BY-NC-ND 4.0

你有没有想过,一个 AI Agent 到底怎么"转"起来的?

大多数人用 ChatGPT、Claude 的体验是:输入一句话,等几秒,拿到答案。但如果你要让 AI 自己读文件、跑命令、搜网页、甚至调度子任务——光一个"问答"接口远远不够。

这背后需要一个循环引擎:不断地"想 → 做 → 看结果 → 再想 → 再做",直到任务完成。这个引擎,在 AI Agent 领域有个专门的名字:Agent Loop

最近我研究了三个开源 AI Agent 框架——OpenClaw(TypeScript)、nanobot(Python)、PicoClaw(Go)——它们的 Agent Loop 设计各有千秋,但核心范式惊人地一致。今天就来拆解一下这个"对话操作系统内核"。

Agent Loop 到底是什么?

先给一个直觉:Agent Loop 不是一个"函数调用",而更像是一个事件驱动的操作系统内核。它的职责可以概括为六层:

层次 职责 类比
接入层 从消息总线消费入站消息 操作系统的中断处理
路由层 根据 channel/peer/metadata 选择 agent 和 session 进程调度器
推理层 构造上下文、调用 LLM、多轮迭代 CPU 执行指令
工具层 并行执行 tool call、区分用户可见和模型可见的结果 系统调用
记忆层 维护 session 历史、summary、压缩与持久化 内存管理 + 磁盘 I/O
运维层 热重载、fallback、超时重试、资源清理 看门狗 + 故障恢复

如果用一句话总结:Agent Loop 是一个把路由、模型调用、工具执行、异步回流、记忆压缩和容错策略串成闭环的持续运行系统。

三个框架的 Agent Loop 概览

在横向对比之前,先看看三个项目的基本定位:

特性 OpenClaw nanobot PicoClaw
语言 TypeScript (Node.js) Python (asyncio) Go
定位 全功能自托管网关 超轻量个人助手框架 超低资源嵌入式 Agent
代码规模 大型,模块化 精简,<500 行核心 中等,单文件主循环
核心文件 pi-embedded-runner/run.ts + attempt.ts agent/loop.py pkg/agent/loop.go
最大迭代 可配置 40 (默认) 50 (默认, 0 时 fallback 到 20)
内存目标 无特殊限制 无特殊限制 <10 MB RSS
多 Agent 支持,隔离 workspace 单 Agent 支持,AgentRegistry
工具系统 6 层策略级联 ToolRegistry + MCP ToolRegistry + MCP
Context 压缩 自动 compaction (60% 阈值) memory_window 触发 consolidation summarization + forceCompression
Fallback 多模型 fallback chain 单 provider FallbackChain 带 cooldown

三者虽然语言和规模不同,但 Agent Loop 的骨架几乎是同构的。下面我们逐层拆解。

架构:Agent Loop 的组件关系

三个框架的 Agent Loop 都遵循相似的组件架构。以 PicoClaw 为参考(因为它的结构最清晰),核心组件关系如下:

@startuml
!theme plain
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
skinparam defaultFontSize 13

package "Agent Loop 通用架构" {

  [MessageBus] as MB #LightBlue
  note right of MB
    异步消息队列
    InboundMessage / OutboundMessage
  end note

  [AgentLoop] as AL #LightGreen
  note bottom of AL
    核心编排器
    Run() → processMessage()
    → runAgentLoop() → runLLMIteration()
  end note

  [ContextBuilder] as CB #LightYellow
  note right of CB
    上下文组装
    SystemPrompt + History
    + Skills + Memory
  end note

  [LLM Provider] as LLM #Orange
  note right of LLM
    模型调用
    支持 Fallback Chain
    多 Provider 路由
  end note

  [ToolRegistry] as TR #LightCoral
  note right of TR
    工具注册与执行
    内置 + MCP 扩展
    安全沙箱策略
  end note

  [SessionManager] as SM #Plum
  note right of SM
    会话持久化
    JSONL 格式
    Summary/Compaction
  end note

  [ChannelManager] as CM #LightSkyBlue
  note left of CM
    多通道管理
    Telegram/Discord
    Slack/CLI/...
  end note

  [AgentRegistry] as AR #Wheat
  note left of AR
    多 Agent 路由
    隔离 Workspace
    独立 Session
  end note
}

CM --> MB : 发布入站消息
MB --> AL : 消费消息
AL --> AR : 路由到 Agent
AL --> CB : 组装上下文
AL --> LLM : 调用模型
LLM --> AL : 返回响应/ToolCalls
AL --> TR : 执行工具
TR --> AL : 返回工具结果
AL --> SM : 持久化会话
AL --> MB : 发布出站消息
MB --> CM : 路由到通道

@enduml

Agent Loop 通用架构

三个框架的关键差异体现在"层的厚度"上:

  • OpenClaw 的工具策略最复杂(6 层级联 allow/deny),Context Compaction 有独立的 token budget 管理,还支持 Block Reply Chunking(分段流式返回)。
  • nanobot 最精简,AgentLoop 核心不到 500 行 Python,但该有的都有:MessageBus、ToolRegistry、SessionManager、ContextBuilder、SubagentManager。
  • PicoClaw 在 Go 里用 atomic.Bool + sync.Map 实现并发控制,还内置了硬件工具(I2C/SPI),目标是跑在 $10 的 RISC-V 开发板上。

时序:一条消息的完整旅程

当用户在 Telegram 里发一句"帮我查一下今天的天气",这条消息经历了什么?用 PicoClaw 的实现来画一个完整的时序图:

@startuml
!theme plain
skinparam backgroundColor #FEFEFE
skinparam sequenceMessageAlign center
skinparam defaultFontSize 12

actor User as U
participant "Channel\n(Telegram)" as CH
participant "MessageBus" as MB
participant "AgentLoop\nRun()" as AL
participant "processMessage()" as PM
participant "runAgentLoop()" as RAL
participant "ContextBuilder" as CB
participant "runLLMIteration()" as RLI
participant "LLM Provider" as LLM
participant "ToolRegistry" as TR
participant "SessionManager" as SM

== 1. 消息接入 ==
U -> CH : 发送消息
CH -> MB : PublishInbound(\nInboundMessage)
MB -> AL : ConsumeInbound()

== 2. 路由与预处理 ==
AL -> PM : processMessage(ctx, msg)
PM -> PM : 语音转写 (如有)
PM -> PM : 检查是否命令 (/new, /help...)
PM -> PM : resolveMessageRoute()\n选择 Agent + SessionKey

== 3. 上下文组装 ==
PM -> RAL : runAgentLoop(agent, msg, opts)
RAL -> SM : 加载 session 历史
RAL -> CB : BuildMessages()\n组装 System Prompt\n+ History + Skills + Memory
RAL -> SM : 保存 user 消息

== 4. LLM 推理循环 (iteration 1) ==
RAL -> RLI : runLLMIteration(messages)
RLI -> LLM : Chat(messages, tools)
LLM --> RLI : Response(tool_calls:\n[web_search("今天天气")])

== 5. 工具执行 ==
RLI -> RLI : 追加 assistant 消息到 history
RLI -> TR : Execute("web_search",\n{"query": "今天天气"})
TR --> RLI : ToolResult{ForLLM: "...",\nForUser: "正在搜索..."}
RLI -> MB : PublishOutbound(ForUser)\n即时反馈
RLI -> RLI : 追加 tool result 到 history

== 6. LLM 推理循环 (iteration 2) ==
RLI -> LLM : Chat(messages + tool_result)
LLM --> RLI : Response(content:\n"今天天气晴, 25°C...")
RLI --> RAL : 返回 finalContent

== 7. 保存与响应 ==
RAL -> SM : 保存 assistant 消息
RAL -> SM : 检查是否需要 summarization
RAL --> PM : 返回响应文本
PM --> AL : 返回响应
AL -> MB : PublishOutbound(response)
MB -> CH : 路由到 Telegram
CH -> U : 显示回复

@enduml

消息处理时序图

这个流程在三个框架中几乎一致,区别在于具体实现细节:

阶段 OpenClaw nanobot PicoClaw
消息消费 WebSocket RPC 事件 bus.consume_inbound() (asyncio) bus.ConsumeInbound() (blocking)
并发控制 隐含在 Pi Agent 运行时中 asyncio.Lock 串行化 atomic.Bool + sync.Map
工具执行 支持并行 + async callback 顺序执行 顺序 + AsyncTool interface
中间反馈 Block Reply Chunking on_progress callback ForUser 即时发送
错误重试 Fallback chain + compaction retry 直接报错 maxRetries=2 + forceCompression

活动:runLLMIteration 的决策流程

Agent Loop 最核心的逻辑在 runLLMIteration——这个方法决定了"模型说要做什么"和"实际去做什么"之间的桥梁。用活动图来展示:

@startuml
!theme plain
skinparam backgroundColor #FEFEFE
skinparam activityFontSize 13
skinparam defaultFontSize 12

title Agent Loop - runLLMIteration 活动图

start

:初始化 iteration = 0;
:选择模型候选 (主模型/轻量模型);
:固定本轮 tier (防止中途换模型);

repeat

  :iteration++;

  if (iteration > MaxIterations?) then (yes)
    :返回 "达到最大迭代次数";
    stop
  endif

  :调用 LLM Provider.Chat(messages, tools);

  if (调用出错?) then (yes)
    if (是 Context Window 溢出?) then (yes)
      :forceCompression()\n丢弃最旧一半历史\n插入压缩说明;
      if (重试次数 < maxRetries?) then (yes)
        :重试 LLM 调用;
      else (no)
        :返回错误;
        stop
      endif
    else (no)
      if (有 Fallback 模型?) then (yes)
        :切换到下一个 Fallback 模型;
      else (no)
        :返回错误;
        stop
      endif
    endif
  endif

  :解析 LLM 响应;

  if (response 包含 tool_calls?) then (no)
    :finalContent = response.content;
    :跳出循环;
  else (yes)
    :将 assistant + tool_calls\n追加到消息历史;

    partition "工具执行 (可并行)" {
      :遍历每个 tool_call;
      :调用 ToolRegistry.Execute(name, args);

      if (工具返回 ForUser?) then (yes)
        :即时发送给用户\n(中间反馈);
      endif

      :将 ForLLM 结果\n作为 tool message 追加;
    }
  endif

repeat while (还有 tool_calls 且未超过 MaxIterations)

:保存 assistant 最终回复到 session;

if (session 消息数 > 阈值?) then (yes)
  :异步触发 summarization\n(不阻塞响应);
endif

:返回 finalContent;

stop

@enduml

runLLMIteration 活动图

这个流程里有几个值得深挖的设计决策:

1. 为什么要固定"模型 tier"?

PicoClaw 在进入迭代前会选定模型候选,一旦选定就不再切换。原因很实际:防止工具链中途换模型导致行为漂移。比如你用 Claude 调了一个工具,中间换成了 GPT,GPT 可能不认前面的 tool result 格式,整个对话就崩了。

2. Context Window 溢出的紧急压缩

三个框架都有类似的逻辑:当 LLM 返回"上下文太长"错误时,不是直接报错,而是砍掉最旧一半历史,插入一条系统消息说明"部分历史已被压缩",然后重试。这就像操作系统的 OOM killer——粗暴但有效。

OpenClaw 做得更精细,它有一个 token budget 概念:目标是压缩到 context window 的 70%,而且会把压缩摘要写入 MEMORY.md 做持久化。

3. ForUser vs ForLLM 的分离

工具执行的结果分两路: - ForUser:即时展示给用户的中间信息,比如"正在搜索..." - ForLLM:喂回模型的结构化结果,作为下一轮推理的输入

这个设计让用户不用干等模型跑完所有工具,可以实时看到进度——体验上差别很大。

三者对比:设计哲学

从 Agent Loop 的实现可以窥见三个项目截然不同的设计哲学:

OpenClaw:企业级的"瑞士军刀"

OpenClaw 的 Agent Loop 是三者中最复杂的。它的亮点:

  • 6 层工具策略级联:Profile → Provider → Global → Agent → Group → Sandbox,颗粒度细到可以按 agent + provider + 群组组合来控制工具权限
  • Block Reply Chunking:长回复按段落/句子/换行切分,逐块流式推送
  • Prompt Modesfull / minimal / none 三档,subagent 用 minimal 避免 token 浪费
  • Compaction + Memory Flush:压缩摘要可以写入 MEMORY.md 做跨 session 记忆

代价是——代码量大,配置项多。光 system prompt 就有 16 个分段,从 Tooling 到 Voice TTS 到 Reply Tags。

nanobot:极简主义的 Python 力量

nanobot 的核心 AgentLoop 不到 500 行。它的策略是:

  • asyncio.Lock 串行化:所有消息处理都走同一把锁,简单暴力地避免竞态
  • memory_window 阈值:超过 100 条未归档消息就触发 consolidation
  • process_direct():CLI 和 Cron 的快捷入口,绕过 MessageBus 直接调用
  • WeakValueDictionary 管理 consolidation lock:自动 GC,不泄漏

nanobot 证明了一个观点:500 行 Python 够了。但它的代价是功能天花板较低——没有多模型 fallback,没有细粒度权限控制。

PicoClaw:面向 $10 硬件的极限工程

PicoClaw 的设计目标是跑在 <10 MB RAM 的 RISC-V 板子上。它的独特之处:

  • Go 的 goroutine 天然并发:summarization 用 go func() 异步启动,不阻塞响应
  • 内置硬件工具:I2C/SPI 工具在 Linux 上可直接操控传感器
  • FallbackChain 带 cooldown:失败的 provider 会被冷却一段时间再重试
  • 原子操作atomic.Bool 控制运行状态,比锁更轻量

PicoClaw 的 95% 代码都是 AI 自动生成的——这本身就是对 Agent Loop 能力的最佳证明。

共性模式:Agent Loop 的设计公式

抛开实现差异,三个框架的 Agent Loop 有一套共通的设计公式:

while (running) {
    msg = bus.consume()                    // 1. 接入
    agent, session = route(msg)            // 2. 路由
    context = build(history + prompt + skills) // 3. 上下文

    while (iteration < max) {              // 4. 推理循环
        response = llm.chat(context, tools)
        if (no tool_calls) break

        for (tc in tool_calls) {           // 5. 工具执行
            result = tools.execute(tc)
            if (result.forUser) send(result)
            context.append(result.forLLM)
        }
    }

    session.save(context)                  // 6. 持久化
    if (shouldSummarize) async { summarize() }  // 7. 记忆压缩
    bus.publish(response)                  // 8. 响应
}

这个模式可以提炼出几条通用原则:

  1. 消息驱动,不是轮询:用 MessageBus 解耦通道和处理逻辑
  2. 有界迭代:MaxIterations 防止无限循环(模型幻觉可能无止境地调工具)
  3. 先写后读:工具结果先写入 history,下一轮 LLM 才能看到
  4. 异步压缩:记忆管理不应阻塞用户响应
  5. 优雅降级:Context 溢出 → 压缩重试 → Fallback 模型 → 报错(最后一道防线)

总结

@startmindmap
!theme plain
skinparam backgroundColor #FEFEFE

* Agent Loop 拆解
** 是什么
*** 对话操作系统内核
*** 六层架构: 接入/路由/推理/工具/记忆/运维
*** "想 → 做 → 看 → 再想" 循环
** 三个框架
*** OpenClaw (TS)
**** 企业级,6 层工具策略
**** Block Reply Chunking
**** Compaction + Memory Flush
*** nanobot (Python)
**** 极简 <500 行核心
**** asyncio.Lock 串行化
**** memory_window 阈值归档
*** PicoClaw (Go)
**** <10MB 嵌入式目标
**** goroutine 异步
**** 硬件工具 I2C/SPI
** 核心流程
*** Run() 主循环
*** processMessage() 路由
*** runAgentLoop() 上下文
*** runLLMIteration() 推理+工具
** 关键设计
*** 有界迭代 (MaxIterations)
*** ForUser vs ForLLM 分离
*** Context 溢出紧急压缩
*** 模型 tier 固定不漂移
*** 异步 Summarization
** 通用公式
*** 消息驱动 + 有界循环
*** 先写后读 + 异步压缩
*** 优雅降级链

@endmindmap

Agent Loop 拆解思维导图

可执行清单

如果你也想设计自己的 Agent Loop,这几条可以直接照着做:

  • [ ] 定义迭代上限:建议 20-50,看你的 token 预算
  • [ ] 实现 ForUser/ForLLM 分离:别让用户干等,工具执行中就给反馈
  • [ ] 准备 Context 溢出方案:至少做"砍一半重试",高级一点做 summarization
  • [ ] MessageBus 解耦:通道和处理逻辑不要直接耦合
  • [ ] Session 持久化用 JSONL:追加写入,性能好,出错也只丢最后一条
  • [ ] Fallback 模型链:主模型挂了有 plan B,别让用户看到 500 错误
  • [ ] 异步记忆压缩:summarization 放后台跑,不阻塞当前响应

你在设计自己的 Agent Loop 时,最头疼的问题是什么?是工具编排、记忆管理,还是多模型路由?欢迎留言讨论。

扩展阅读


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。