
Pi Agent Loop 重构:单线程到 Lane-based Session
前言:架构演进的三重飞跃
2026-08-12 14:16 GMT+8
Pi 的核心——Agent Loop,彻底重构了。
这不是优化,不是补丁,而是根本性的架构革命。
老架构:内存里一个消息列表 + 进程里一个循环
- 本质单线程
- Slack 多 thread 塞不进去
- 崩了要重放日志
- 副作用重复执行
- 分支和压缩只能当补丁打
新架构:Lane-based Session
- Entry Tree + Lane 分离
- 天然并行
- 精确恢复
- 分支一等公民
换来三件事:
- 并行不打架——主对话和子任务同时推进
- 崩溃精确恢复——不重放日志、不重做副作用
- 分支成一等公民——历史像版本控制
一、老架构的问题
核心结构
老架构 = 内存消息列表 + 进程循环
形象比喻:
- 就像一个人只有一个笔记本
- 只能一件事一件事地记
- 记错了只能从头抄一遍
技术本质:
- 单线程:一个进程,一个循环
- 内存状态:消息存在内存里
- 顺序执行:一件事接一件事
三大痛点
痛点 1:并行塞不进去
场景:Slack 多 thread + 子 agent
问题:
- ❌ Slack 有多个 thread(多用户同时对话)
- ❌ 主对话要跑子任务(需要子 agent)
- ❌ 单线程处理不了
- ❌ 只能排队等
结果:
- Thread A 等 Thread B
- 主对话等子任务
- 用户等待
痛点 2:崩溃恢复麻烦
场景:进程崩溃
问题:
- ❌ 内存状态丢失
- ❌ 需要重放日志
- ❌ 副作用重复执行
- ❌ 可能重复发邮件、重复写文件
比喻:
- 就像停电后,你要从头把刚才做的事情再做一遍
- 已经发出的邮件,可能会再发一次
痛点 3:分支和压缩是补丁
场景:对话分支、摘要压缩
问题:
- ❌ 分支不是原生支持
- ❌ 压缩是事后补丁
- ❌ 历史管理混乱
比喻:
- 就像写文档时,分支只能另存为新文件
- 压缩只能手动删除前半段

二、新架构:Lane-based Session
核心思想
把树和游标拆开
以前:消息列表 = 树 + 游标混在一起 现在:树和游标分离
两部分:
- Entry Tree——树本身
- Lane——游标
Entry Tree:对话树
定义:
- Append-only(只增不改不删)
- 每条 entry 带有:
id:唯一标识parentId:父节点引用
- 全 Session 共享
包含什么:
- 消息(用户消息、AI 回复)
- Compaction 摘要
- Branch summary
- 任何对话相关的内容
特点:
- ✅ 只增加,不修改,不删除
- ✅ 天然形成树结构
- ✅ 所有内容都在树上
比喻:
- 就像 Git 提交历史
- 每个提交(entry)都有一个父提交(parentId)
- 历史一旦形成就不会改变
Lane:命名游标
定义:
- 一个命名位置
- 指向 Tree 的某个节点(leaf)
- 带有自己的配置
包含什么:
- 当前 leaf:指向 Tree 的某个 entry
- 模型配置:使用哪个 LLM
- thinking level:推理深度
- 工具集:可用的工具
- 队列:待处理的消息
- 进行中的 Operation:最多一个
比喻:
- 就像 Git 的分支(branch)
- 指向某个提交(leaf)
- 有自己的配置和状态
一个 Session = 多个 Lane:
- 一个 Slack channel = 一个 Session
- 每个 thread = 一个 Lane
- 主对话 = 一个 Lane
- 子任务 = 一个 Lane

Lane 如何工作
场景:Slack 多 thread
Session:#general channel Lane 1:主对话(main lane) Lane 2:thread A Lane 3:thread B
工作方式:
- Lane 1 处理主对话
- Lane 2 处理 thread A 的对话
- Lane 3 处理 thread B 的对话
- 三个 Lane 可以并行推进
场景:主对话 + 子任务
Lane 1:主对话 Lane 2:子任务(分析代码)
工作方式:
- Lane 1 继续处理用户消息
- Lane 2 并行执行子任务
- 子任务完成后,结果挂回 Lane 1
分叉如何工作
多个 Lane 可以锚在同一节点
场景:
- Lane 1 和 Lane 2 都指向 entry D
- Lane 1 append → entry E
- Lane 2 append → entry F
结果:
- Tree 自然分叉
- 不需要额外协调
- 历史清晰保留

三、换来三件事
① 并行不打架
以前:
- ❌ 单线程,只能一件事一件事做
- ❌ 子任务阻塞主对话
- ❌ 用户等待
现在:
- ✅ 多个 Lane 并行推进
- ✅ 主对话和子任务同时进行
- ✅ 谁也不堵谁的上下文
场景:Slack 多 thread
以前:
- Thread A 处理完 → Thread B 处理
- Thread B 处理完 → Thread C 处理
- 排队等待
现在:
- Thread A、B、C 同时处理
- 各自独立的 Lane
- 并行推进
场景:主对话 + 子任务
以前:
- 启动子任务 → 主对话等待 → 子任务完成 → 继续主对话
现在:
- 主对话继续
- 子任务并行执行
- 子任务完成,结果挂回
价值:
- 用户体验流畅
- 任务处理高效
- 资源利用率高
② 崩溃精确恢复
以前:
- ❌ 崩溃后内存状态丢失
- ❌ 需要重放日志
- ❌ 副作用重复执行
现在:
- ✅ Operation 状态写在 registers 里
- ✅ 原子落盘(要么全写,要么全不写)
- ✅ 新进程只读当前总状态
- ✅ 从安全边界续跑
- ✅ 不重放 journal
- ✅ 不重做已 settle 的副作用
什么是安全边界?
效果边界:
- 模型请求
- 工具执行
- Hook 调用
- 定时器
每个效果都是边界:
- 在执行效果之前暂停
- 记录当前状态
- 崩溃后从边界恢复
比喻:
- 就像游戏的存档点
- 每次执行重要操作前存档
- 崩溃后从存档点继续,不用从头开始
副作用如何不重复?
settled 副作用:
- 已经执行并确认的副作用
- 记录在 registers 中
- 不会重复执行
未 settled 副作用:
- 执行中未确认的副作用
- 崩溃后重新执行
- 因为没有确认完成
例子:
- 发送邮件:settled → 不重发
- 写文件:settled → 不重写
- 调用 API:settled → 不重调

③ 分支成一等公民
以前:
- ❌ 分支不是原生支持
- ❌ 压缩是事后补丁
- ❌ 历史管理混乱
现在:
- ✅ 分支是一等公民
- ✅
/tree、fork、navigate只是移动 lane 的 leaf - ✅ 废弃分支自动总结后挂回公共祖先
- ✅ 历史像版本控制
分支操作:
/tree:查看分支树
- 显示所有 Lane
- 显示 Tree 结构
- 显示当前 leaf
fork:创建新分支
- 从当前 leaf 创建新 Lane
- 新 Lane 可以独立 append
- 自然形成分叉
navigate:移动到另一个分支
- 移动 Lane 的 leaf
- 切换到另一个 entry
- 继续从那里 append
废弃分支自动总结:
场景:
- Lane 1 尝试某个方向
- 发现不对,废弃
- 自动总结后挂回公共祖先
价值:
- 历史保留完整"
- 不需要"删尾巴重来"
- 像版本控制一样自然

四、底层重构:Effect-driven 解释器
从单体循环到效果驱动
以前:
- 单体循环
- 紧耦合
- 难以测试
现在:
- Effect-driven 解释器
- 松耦合
- 易于测试
三个阶段
阶段 1:Plan(规划)
- 决定下一步做什么
- 选择要执行的效果
- 准备参数
阶段 2:Execute Effect(执行效果)
- 模型请求(调用 LLM)
- 工具执行(调用工具)
- Hook 调用(触发 Hook)
- 定时器(时间事件)
阶段 3:Settlement(结算)
- 处理效果结果
- 更新状态
- 写入 registers
为什么是 Effect-driven?
效果 = 边界
每个效果都是一个边界:
- 在执行效果之前暂停
- 记录当前状态
- 崩溃后从边界恢复
可测试性:
- 每个效果可以独立测试
- 不需要真实环境
- Mock 效果即可
可组合性:
- 效果可以组合
- 新效果可以添加
- 不影响现有效果

五、实际场景
场景 1:Slack 多 thread 并行
场景:
- Slack channel
#general - 3 个 thread 同时活跃
以前:
- Thread A 处理完 → Thread B → Thread C
- 排队等待
- 用户等待
现在:
- Session =
#general - Lane 1 = main
- Lane 2 = thread A
- Lane 3 = thread B
- 三个 Lane 并行推进
体验:
- ✅ 用户感觉实时响应
- ✅ 不需要等待
- ✅ 体验流畅
场景 2:主对话 + 子任务
场景:
- 用户要求分析代码库
- 同时继续提问
以前:
- 启动子任务 → 主对话等待 → 子任务完成 → 继续主对话
- 用户等待
现在:
- Lane 1:主对话,继续回答用户
- Lane 2:子任务,并行分析代码库
- 子任务完成,结果挂回 Lane 1
- Lane 1 继续用结果回答
体验:
- ✅ 主对话不阻塞
- ✅ 用户可以继续提问
- ✅ 子任务并行完成
场景 3:崩溃恢复
场景:
- 进程崩溃
以前:
- 重启进程
- 重放日志
- 副作用重复执行
- 可能重复发邮件
现在:
- 重启进程
- 读取 registers
- 获取当前总状态
- 定位安全边界
- 从边界续跑
- 不重放 journal
- 不重做已 settle 的副作用
体验:
- ✅ 快速恢复
- ✅ 精确恢复
- ✅ 副作用不重复
场景 4:分支探索
场景:
- 尝试三个不同的解决方案
以前:
- 方案 1 不对 → 删除重来
- 方案 2 不对 → 删除重来
- 方案 3 对了 → 继续
现在:
- Fork 出 Lane 1(方案 1)
- Fork 出 Lane 2(方案 2)
- Fork 出 Lane 3(方案 3)
- 三个 Lane 并行探索
- Lane 1、2 废弃 → 自动总结挂回
- Lane 3 成功 → 继续发展
体验:
- ✅ 历史保留完整
- ✅ 可以回顾所有尝试
- ✅ 像版本控制一样自然
六、总结
核心变化
从:
- 内存消息列表 + 进程循环
- 单线程
- 崩溃重放
- 分支补丁
到:
- Entry Tree + Lane
- 多 Lane 并行
- 精确恢复
- 分支一等公民
三大价值
1. 并行不打架
- 主对话和子任务同时推进
- 谁也不堵谁的上下文
- 用户体验流畅
2. 崩溃精确恢复
- 不重放 journal
- 不重做已 settle 的副作用
- 快速、精确恢复
3. 分支成一等公民
/tree、fork、navigate只是移动 lane 的 leaf- 废弃分支自动总结
- 历史像版本控制
底层改进
Effect-driven 解释器:
- Plan → Execute Effect → Settlement
- 每个效果都是边界
- 可测试、可组合
为什么这次重构是革命性的?
1. 不是优化,是重新设计
- 不是修补老架构
- 是从零开始重新思考
2. 解决根本问题
- 不是绕过问题
- 是从根本上解决问题
3. 带来质的飞跃
- 不是 10% 的提升
- 是 10 倍的提升
记住
老架构像单线程笔记本,新架构像多线程版本控制系统。
从"只能一件事一件事做"到"多件事并行做"。
从"崩溃后从头来过"到"崩溃后精确恢复"。
从"分支是补丁"到"分支是一等公民"。
这不是优化,这是革命。
附:Lane vs Git 分支对比
| 维度 | Lane | Git 分支 |
|---|---|---|
| 指向 | Tree 的 entry | 提交(commit) |
| 包含 | leaf + 配置 + 队列 + Operation | 提交指针 |
| 并行 | 多 Lane 并行推进 | 多分支并行开发 |
| 分叉 | 自然分叉 | 自然分叉 |
| 合并 | 自动总结挂回 | 手动合并 |
| 恢复 | 从安全边界续跑 | 从任意点继续 |
| 历史 | 完整保留 | 完整保留 |
核心区别:
- Lane 不仅仅是历史指针,还包含运行时状态
- Lane 支持并行执行
- Lane 有自动总结机制
