AI Agnet的会话实现原理-Pi 的Session工作流程全解析(二) 📅 2026/8/18 10:12:24 上一篇我们了解了Pi的Session的整体架构设计、Session的树形数据结构、三种队列管理等等.本篇我们继续探索Pi的Session工作原理5.2 持久化时机Save PointSession 不是每改一个字符就 flush 一次磁盘而是按回合批量写message_end单条消息收尾时写一次user、assistant、toolResult 各写一次turn_end整个 turn 结束时 flush 所有待写配置变更model_change、thinking_level_change、active_tools_changeagent_end整个 Agent 运行结束时发settled事件这种设计的取舍批量写减少 IO 次数但若程序在中途崩溃最后一小段配置变更可能丢失——但用户消息和 AI 回复都已经写盘了不会丢对话内容。5.2.1 三个事件代表什么三层嵌套的生命周期message_end / turn_end / agent_end 不是三个并列的事件而是三层嵌套的生命周期边界。一次 Agent 运行可以包含多个 turn一个 turn 可以包含多条 message。从外到内一句话总结agent_end是外层天花板turn_end是中段里程碑message_end是内层原子。三者是包含关系不是并列出三次。三个事件分别代表什么事件生命周期含义每次 AgentLoop 触发次数携带数据典型用途agent_start / agent_end一次完整的runAgentLoop调用的开始和结束各1 次agent_end 会发settled事件让 TUI 知道可以解锁输入框agent_end 带messages: AgentMessage[]即这次 run 新产生的所有消息TUI 知道AI 这轮干完了可以给我看结果 / 让我继续打字了turn_start / turn_end一个 turn 的开始和结束。一个 turn 一次 assistant 回复 它所触发的所有工具调用 所有 toolResult可能多次。如果 assistant 调用了工具AgentLoop 会再开新 turn 把工具结果喂回去直到 assistant 给出stopturn_end 带message本轮的 assistant 消息toolResults本轮产生的 toolResult 列表在 turn 边界批量 flush 配置变更model_change 等也是prepareNextTurn钩子的触发点让 harness 决定下一轮要不要换模型/改 thinkingmessage_start / message_update* / message_end单条消息user / assistant / toolResult的开始、更新仅 assistant 流式期间、结束每个 turn 内至少 2 条user assistant如有工具则更多始终带message: AgentMessagemessage_update额外带assistantMessageEvent增量流式片段Session 落盘的最小单位。message_end 一触发那条消息就立刻appendEntry写进 JSONL实际触发的时序看 agent-loop.ts:109-198emit({ type: agent_start }) // 整个 loop 启动 1 次 emit({ type: turn_start }) // 第一个 turn 开始 emit({ type: message_start, prompt }) // user prompt emit({ type: message_end, prompt }) // user prompt 立刻结束无流式 emit({ type: message_start, assistantPartial }) // assistant 开始流式 emit({ type: message_update, ... }) // 流式过程中触发 N 次 emit({ type: message_end, assistantFinal }) // assistant 流完 // 如果有工具调用 emit({ type: tool_execution_start, ... }) emit({ type: tool_execution_end, ... }) emit({ type: message_start, toolResult }) emit({ type: message_end, toolResult }) emit({ type: turn_end, message, toolResults }) // turn 边界 // 如果需要继续assistant 还要看 toolResult 再回话 emit({ type: turn_start }) // 开新 turn // ... 再次流式 assistant ... emit({ type: turn_end, ... }) // 直到 assistant stopReason stop emit({ type: agent_end, messages }) // 整个 loop 收尾为什么是三层而不是两层message 层保证AI 一句话讲完就落盘——断电也不丢用户已看到的字。turn 层是真正的工作单元——一个 turn 结束后 harness 才会去检查要不要换模型/压缩/插入 steer 消息这些批量配置变更在 turn_end 时统一落盘避免每改一个就写一次。agent 层是是否还活着的信号——agent_end 一发TUI 立刻解锁输入框如果长时间没收到TUI 知道 Agent 还在忙可以显示AI 正在输入...。简单说message_end 关心内容不能丢turn_end 关心配置可以批量agent_end 关心什么时候让人继续打字。三者职责分明、各管一段。常见误解agent_start / agent_end ≠ 一次 Session关键区别Session 是整本对话笔记本可能跨多个工作日、几百条消息而 agent_start / agent_end 只是AI 响应一次用户输入的完整流程。一次 Session 里会有很多次agent_start / agent_end。概念范围触发时机持续多久Session整本对话笔记用户开/关应用、加载历史可能跨小时、天、甚至周agent_start / agent_end一次prompt()的完整执行用户敲一次回车 / 扩展主动prompt几秒到几分钟取决于工具调用turn_start / turn_end一次 assistant 回复 它的工具结果工具调用会开新 turn直到 assistantstop一次 LLM 调用 工具执行message_start / message_end单条消息每条 user/assistant/toolResult 都有毫秒到秒流式用代码佐证AgentHarness.prompt()是用户每发一条消息就会调一次的方法agent-harness.ts:608它内部executeTurn→runAgentLoop→ 触发一次完整的agent_start...agent_end。所以一次agent_start严格对应用户的一次输入 AI 完成这次输入处理的全过程而不是一次完整的会话。算账式理解一次 Session N 次agent_start / agent_end你每说一句话算一次一次 agent_start / agent_end 1~M 次 turn_start / turn_endAI 调一次工具就多一个 turn一次 turn_start / turn_end 2~K 条消息user assistant 0~N 个 toolResult所以三层事件和 Session 之间的关系是Session 是账本三层事件是这次记账里具体写哪几行、什么时候结算。把 agent_start / agent_end 误当成 Session 是常见误解——前者是这次响应后者是整本历史。5.3 上下文构建buildSessionContext从磁盘的整棵树到 LLM 看到的一维消息流中间有一个关键的翻译步骤这里的关键洞见树的形状由用户和工具决定分支、压缩但 LLM 看到的永远是一条线性消息流。实际干这件扁平化工作的是buildSessionContext这个纯函数session.ts:22——它接收从根到当前叶子的全部条目吐出SessionContext。Session 类本身只负责提供路径getBranch()Session.buildContext()是把这两步粘在一起的胶水方法。6. 架构设计分层与数据流6.1 分层架构分层的好处存储可替换默认是 JSONL 文件但通过 SessionStorage 接口可以换成 SQLite、内存、远程存储等。AgentHarness 不用改一行代码运行时和持久化解耦AgentHarness 只跟 Session 这个抽象打交道不关心 JSONL 怎么写易于测试用 MemorySessionStorage 就能跑 AgentHarness 单元测试不用碰磁盘6.2 关键数据流一次 prompt注意一个微妙之处同一回合内buildContext 被调用了两次——一次在 prepareNextTurnAgentHarness 准备上下文一次在 runAgentLoop 内部AgentLoop 真正调 LLM 前。这是因为 AgentHarness 的 prepareNextTurn 会先注入 steer 消息再让 AgentLoop 拿到最新上下文。7. 设计优点和缺点7.1 优点优点说明断电可恢复JSONL 追加写每条 message_end 都已落盘。程序崩溃后重开能完整恢复分支可追溯树形结构天然支持换个方向试试。旧分支不删除只是叶子指针移走压缩可回滚compaction 是插入式节点不删除老消息。需要时可以反压缩看到原文配置变更留痕模型切换、thinking level 变更、工具集变更都是 SessionTreeEntry。下次恢复时知道当时用的是什么可文本编辑器查看JSONL 一行一条 cat 就能读。开发者和用户都能用熟悉的工具排查问题存储后端可插拔SessionStorage 接口让 JSONL / 内存 / 远程存储都能用同一套代码扩展可注入消息CustomMessageEntry 让扩展往 LLM 上下文里塞东西不用改核心7.2 缺点 / 取舍缺点说明无并发写保护JSONL 追加写假设只有单一进程。多个 Agent 共享同一 session 会冲突文件会无限增长每条消息、每次配置变更都追加。没有老条目归档机制。Compaction 减少喂给 LLM 的内容但 JSONL 文件本身仍然在变长压缩会损失细粒度compaction 摘要由 LLM 生成可能丢失关键细节。一旦压缩老内容必须反摘要才能找回不支持流式读JSONL 是文本格式10MB 以上的 session 启动会比较慢。重启时要把整本读进来没有内置索引找包含某关键字的消息必须全量扫一遍压缩时机需要手动或启发式何时触发 compaction 由上层决定Session 本身不主动判断7.3 设计哲学总结Pi Session 的核心哲学可以归纳为一句话把对话和它发生的所有上下文都当成可追加的事件日志而不只是聊天内容。这与传统的聊天历史概念有本质区别传统的 IM 系统存的是消息内容Pi Session 存的是对话这台状态机的完整演化轨迹。这种设计让恢复、分支、压缩都变成了在树上操作而不是在聊天记录上操作从而获得了前面列出的所有优点。8. 与著名 Agent 实现的对比这一节挑几个有代表性的 Agent 框架看它们的会话/状态设计对比 Pi 的方案。8.1 对比表实现状态模型持久化分支压缩断电恢复Pi SessionJSONL 条目树本地文件原生支持原生支持原生支持LangGraphStateGraph, 显式节点和边Checkpointer 抽象, 默认内存通过多图分支不内置需要 Postgres 等后端OpenAI Assistants APIThread Message Run服务端托管不支持服务端自动服务端自动Anthropic Claude SDKmessages 数组, 客户端管理无, 客户端自行实现客户端实现客户端实现客户端实现AutoGPT / BabyAGI任务列表 记忆本地文件 / 向量库不支持不内置部分Mastra类似 LangGraph 的工作流 Memory 抽象可插拔存储后端通过多工作流通过 working memory通过 checkpointer8.2 几个有代表性的设计差异(1) Pi vs OpenAI Assistants客户端 vs 服务端OpenAI Assistants 把 Thread 状态完全托管在服务端你只需要调 API。好处是简单坏处是你看不见状态、不能改格式、不能跑本地模型。Pi 反过来状态全在客户端 JSONL 里你拥有全部数据可以换存储、换模型、换 UI。(2) Pi vs LangGraph事件日志 vs 状态机LangGraph 的核心是图——开发者显式定义节点函数和边条件状态在节点之间流动。Pi 的核心是事件日志——没有显式定义流程所有流程都从事件序列中浮现。LangGraph 适合流程固定、状态复杂的场景Pi 适合流程自由、事件丰富的场景尤其是 Coding Agent 这种用户问什么就做什么的场景。(3) Pi vs Anthropic SDK自己实现 vs 自己实现Anthropic 官方的 Python/TS SDK 把 messages 数组完全交给开发者自己管理连持久化都没有。Pi 实际上是把 Anthropic SDK 应该做但没做的事做了——给你一个完整的、持久化的、可分支的会话层。两者是互补关系Pi 在 SDK 之上又建了一层。(4) Pi 的独特之处Pi Session 在几个维度上有自己的特色JSONL 条目树 parentId 链表同时支持线性追加最近路径和树形分支历史路径配置变更也是一类条目模型切换、thinking level 变更都被记到 Session 里这是少有的设计compaction 不删除原文只插入摘要节点原文物理保留三种队列steer / followUp / nextTurn 覆盖了打断当前轮、等本轮结束后开新轮、idle 时立即开新轮三种典型场景9. 一图总结