本文回答什么问题:AgentLoop 的 6 阶段流水线怎么实现?TurnContext 是什么?session_key / workspace / hooks / outbound 在源码里如何编排?
目标读者:LLM Agent 开发者 / 想修改核心循环的工程师
预计阅读时间:18 分钟
源码版本:GitHub HKUDS/nanobotmain 分支主线代码(仓库相对路径)
AgentLoop(nanobot/agent/loop.py,约 2058 行)是 nanobot 通道层回合编排者——把"用户消息"变成"Agent 回复"的全过程。本章聚焦主入口_dispatch和 6 阶段流水线源码。
1. 整体定位:AgentLoop vs AgentRunner
- AgentLoop(本章):回合——管 session_key / workspace / hooks / outbound 投递
- AgentRunner(第 11 章):多循环——管 LLM 调用 / 流式 / 工具执行 / 迭代上限
调试分流原则:通道问题看loop.py,模型问题看runner.py。
核心要点速查(建议收藏)
- 核心文件:
nanobot/agent/loop.py(约 2058 行) - 主入口:
AgentLoop._dispatch(msg)处理单回合;run_forever()主循环 - TurnContext:
runner+tools+hooks+session的载体,每个回合构造一次 - 6 阶段:Inbound → session 解析 → workspace 解析 → Context 构建 → Runner 多轮 → Outbound 投递
- 3 个关键子方法:
_resolve_session/_resolve_workspace/_handle_one_turn
2. 6 阶段流水线源码拆解
阶段 ① · Inbound 接入
asyncdef_dispatch(self,msg:InboundMessage):# ... 见阶段 ②AgentLoop.run_forever()主循环从bus.consume_inbound()拿消息,转交_dispatch。
阶段 ② · session 解析
asyncdef_dispatch(self,msg:InboundMessage):# 1. 解析 session_key(默认 f"{channel}:{chat_id}",可被 metadata["session_key"] 覆盖)session_key=msg.session_key# InboundMessage 已提供 property# 2. 解析 workspace(根据 chat_id 找 ~/.nanobot/workspaces/<key>/)workspace=self._resolve_workspace(msg)ifworkspaceisNone:awaitself._send_error(msg,"no workspace available")return_resolve_workspace决定 workspace scope——这是nanobot/security/workspace_access.py的核心逻辑(详见第 29 章)。
阶段 ③ · TurnContext 构造
# 3. 构造 TurnContext(把 runner / tools / hooks 打包)ctx=TurnContext(msg=msg,session_key=session_key,workspace=workspace,runner=self._runner,# AgentRunner 单例tools=self._tools,# ToolRegistry 单例hooks=self._hooks,# AgentTurnHook chainconfig=self._config,)TurnContext 是回合的"数据快照"——AgentRunner 通过ctx.runner.run(spec)拿到。
阶段 ④ · Hook 前置
# 4. pre_turn_hook(可取消整个回合)ifnotawaitself._hooks.before_turn(ctx):return# hook 决定取消典型用法:WebUI 的"批准"按钮 → 等用户批准后再放行 Runner。
阶段 ⑤ · Runner 多轮循环
# 5. 构造 AgentRunSpec(从 ctx 派生出 spec)spec=AgentRunSpec(messages=self._ctx_builder.build_messages(ctx),# 历史 + 当前tools=ctx.tools,max_iterations=self._config.max_iterations,)# 6. 跑 AgentRunner(可能 200 轮,详见第 11 章)result=awaitctx.runner.run(spec,hook=self._hooks)阶段 ⑥ · Outbound 投递 + Hook 后置
# 7. post_turn_hook(可改 result)awaitself._hooks.after_turn(ctx,result)# 8. 持久化 sessionawaitself._session_manager.append(ctx.session_key,result.messages)# 9. 投递 outbound(每个 OutboundMessage 进 MessageBus)forout_msginresult.outbound_messages:awaitself._bus.publish_outbound(out_msg)# 10. TurnEnd 事件(可选,带 latency)awaitself._bus.publish_outbound(OutboundMessage(channel=msg.channel,chat_id=msg.chat_id,content="",event=TurnEndEvent(latency_ms=elapsed_ms),))3. 4 个核心子方法
_resolve_session(msg)→ session_key
def_resolve_session(self,msg:InboundMessage)->str:returnmsg.session_key_overrideorf"{msg.channel}:{msg.chat_id}"_resolve_workspace(msg)→ Path
def_resolve_workspace(self,msg:InboundMessage)->Path|None:workspace_id=self._workspace_access.resolve(channel=msg.channel,chat_id=msg.chat_id,sender_id=msg.sender_id,)ifworkspace_idisNone:returnNonereturnPath.home()/".nanobot"/"workspaces"/workspace_id_handle_one_turn(ctx)→ OutboundMessage list
主流程,见 §2 阶段 ③-⑥。
_send_error(msg, reason)→ OutboundMessage
asyncdef_send_error(self,msg:InboundMessage,reason:str):awaitself._bus.publish_outbound(OutboundMessage(channel=msg.channel,chat_id=msg.chat_id,content=f"⚠{reason}",))4. 3 个核心决策
决策 1 · 为什么 AgentLoop 管 session_key / workspace,不管 LLM 调用?
单一职责:loop.py 改 session 路由不影响 LLM 行为;runner.py 改流式输出不影响 session 路由。
决策 2 · 为什么 TurnContext 是 dataclass 而非 dict?
类型安全 + IDE 自动补全;改字段会破坏编译。
决策 3 · 为什么 hooks 可以取消回合?
WebUI “abort” 按钮、审批流、配对确认都可以在 hook 里return False中止整个回合。
5. 常见问题 / 避坑
Q:AgentLoop 是单例吗?
A:。gateway启动时构造一次,多通道共享。
Q:_dispatch报错会怎样?
A:try/except包裹,捕获后_send_error(msg, str(exc))通知通道,继续run_forever()不退出。
Q:为什么用before_turn而不是pre_turn?
A:before_turn返回False取消回合;after_turn可改 result——3 种语义区分。
6. 小结
- 6 阶段流水线:Inbound → session → workspace → TurnContext → Hook → Runner → Hook → 持久化 → Outbound
- TurnContext是回合数据快照
- 调试分流:
- Hook 系统允许取消 / 改写 / 监控回合
本文要点速查
- 6 阶段源码定位:见 §2 各阶段代码片段
- TurnContext 字段:msg + session_key + workspace + runner + tools + hooks + config
- 3 个核心子方法:
_resolve_session/_resolve_workspace/_handle_one_turn - 下一步:第 11 章《AgentRunner LLM 循环》—— 阶段 ⑤ 详细展开
按角色推荐
- LLM Agent 开发者:必读(后续 11-16 章都基于本章 6 阶段)
- 系统架构师:必读(核心循环必读)
- LLM Provider 适配者:选读(知道 AgentRunner.run 怎么调即可)
- 聊天通道开发者:选读(知道通道消息怎么进 loop 即可)
- Tool / MCP 工具开发者:选读(知道 Tool 在 TurnContext 里)
下一步
- 第 11 章《AgentRunner LLM 循环》—— 阶段 ⑤ 的
_request_model+_execute_tools(主题群"Agent 核心",第 3 周) - 第 13 章《Hook 系统》——
before_turn/after_turn完整 hook 类型(主题群"Agent 核心",第 3 周) - 第 14 章《会话管理 SessionManager》—— 阶段 ⑧ 的 append + 原子写(主题群"Agent 核心",第 3 周)
tags:#nanobot#AI Agent#LLM#Python#源码解析#AgentLoop#编排