旧对话为什么没有恢复代码:Pi Session Tree 与上下文投影
发布时间:2026/8/15 15:35:35 作者:尧图编辑部 阅读量:1,286

回到旧对话为什么没有恢复代码Pi Session Tree 与上下文投影你让 Coding Agent 修复登录问题。它先改了 Token 校验测试失败你退回到“读取认证模块”那条消息换成检查数据库时区。对话看起来回到了过去但工作区里第一次尝试留下的文件改动、已安装依赖甚至数据库写入可能仍然存在。这不是一个界面小问题而是 Agent 状态模型的核心边界回到旧对话节点只能改变模型接下来读取哪条历史不能自动回滚已经发生的环境副作用。Pi 的 Session Tree 很适合解释这个边界。它没有把会话当作不断追加的消息数组而是把完整事件保存在树中再选择一条活动路径投影成模型上下文。本文基于 Piv0.82.1的官方文档与源码回答三个问题Session 为什么是一棵树Branch、Fork、Clone 到底改了什么自研 Harness 还必须补上哪一层才能让“会话回退”不再冒充“系统回滚”。一、先看真正会出错的地方假设一次任务产生了下面的轨迹A 用户修复登录问题 └─ B Agent读取 auth.ts ├─ C 路线一修改 Token 校验 │ └─ D 测试失败但已经改过文件 └─ E 路线二检查数据库时区 └─ F 测试通过用户从 D 回到 B再选择路线二。对于对话系统这意味着当前路径从A→B→C→D切换为A→B→E→F。但环境状态并不会因此自动回到 BC 写过的文件、启动的进程、发出的请求仍可能存在。如果系统把“当前对话路径”与“当前工作区状态”混成一个概念至少会出现三类事故模型以为某段代码还没改实际文件已经被旧分支修改新分支重复执行旧分支已经产生的外部副作用复盘时只看到成功路径无法解释污染从哪条失败路线进入。因此本文只有一条主线Session Tree 管理执行记录的分支可靠回滚必须由独立的环境版本机制完成。二、四种“历史”必须拆开Pi 的模型可以拆成四层JSONL Session File ↓ 通过 id / parentId 重建 Session Tree ↓ 选择当前 Leaf Active Path ↓ 过滤、转换、处理压缩边界 LLM Context第一层是磁盘上的 JSONL 文件。它保存 Header 和全部 Entry包括已经放弃的路线。第二层是 Session Tree。除 Header 外Entry 通过自己的id和父节点parentId建立关系。物理文件按行追加逻辑历史仍然可以分叉。第三层是 Active Path也就是从根节点到当前 Leaf 的唯一路径。上例当前 Leaf 为 F 时活动路径是A→B→E→FC 和 D 仍在文件里但不属于当前路线。第四层才是 LLM Context。Pi 还会从活动路径中选择能进入模型的 Entry并把 Compaction Summary、Branch Summary 或扩展消息转换成模型可读消息。所以三个常见等号都不成立完整 Session ≠ 当前活动路径 当前活动路径 ≠ 模型最终上下文 模型上下文 ≠ 当前工作区状态这比“Pi 用 JSONL 保存聊天记录”更接近真实设计。三、树是怎样写进线性 JSONL 的Pi Session 第一行是 Header后续每行是一个 Entry。Header 保存格式版本、Session ID、创建时间、工作目录以及可选的parentSession它没有 Entry 的id和parentId因此不属于树。Entry 的最小关系可以表示为interfaceSessionEntryBase{type:string;id:string;parentId:string|null;timestamp:string;}正常追加时新 Entry 的父节点是当前 Leaf。回到旧节点继续时新 Entry 会成为那个旧节点的另一个 Child。A.parentId null B.parentId A C.parentId B D.parentId C E.parentId B F.parentId EJSONL 的优势是追加简单、局部损坏容易定位、可以逐行审计也容易扩展 Entry 类型。Pi 的 Session 不只保存聊天消息还可记录模型切换、Thinking Level、Compaction、Branch Summary、扩展状态、扩展消息、Label 和 Session 信息。其中一个容易忽略的边界是custom用于持久化扩展状态但不会进入 LLM Contextcustom_message才会被转换成模型能看到的消息。持久化在 Session 里并不等于模型自动可见。JSONL 也不是万能数据库。它不天然提供多进程事务、复杂查询、分布式一致性或多用户并发写。对于本地默认工作流它很实用对于多人共享和高并发服务仍需要独立存储层。四、Branch、Fork、Clone 分别改变什么这三个操作经常被统称为“开分支”但语义不同。操作是否创建新 Session 文件历史边界接下来怎样继续/tree中 Branch否仍保留同一 JSONL 中的完整树把当前 Leaf 移到旧 Entry 后继续追加/fork是复制活动路径到所选旧 User Message 之前把旧 Prompt 恢复到编辑器可修改后重提/clone是复制活动路径到目标 Entry并包含它以空编辑器开始新的后续任务Branch 适合在同一份实验记录里比较两条路线。失败路线与成功路线都留在一棵树中便于复盘。Fork 适合“回到提出问题之前再换一种问法”。官方 README 描述的交互流程是选择活动路径中的旧 User Message复制到该消息之前并把原 Prompt 恢复到编辑器。Clone 适合“保留当前有效状态另开一个后续任务”。SDK 用position: at表示包含目标 Entry。CLI 的--fork path|id又是另一个入口固定版本文档描述为从指定 Session 复制。工程上不能只看到同一个单词就假设所有入口的复制范围和编辑器行为完全相同。Fork 或 Clone 还会替换活动 Session Runtime。SDK 文档明确提醒订阅者需要重新连接新 Runtime这说明 Session 切换不仅是复制文件名也会影响扩展与监听生命周期。五、模型为什么不会看到所有分支保存整棵树是为了审计和导航不是为了每轮把全部历史塞进 Context Window。固定版本源码中的buildContextEntries()从指定 Leaf 沿parentId回溯到根再反转为 Active Path并处理最近的 Compaction 边界。buildSessionContext()再把路径中的 Entry 转成 Agent Message普通message进入上下文compaction和branch_summary以摘要消息进入custom_message可以进入纯custom状态则不会进入。这个过程最好叫“上下文投影”而不是“加载聊天记录”functionprojectContext(events:Event[],leafId:string,policy:ContextPolicy):AgentMessage[]投影有两个价值。第一旧失败路线可以保留而不会永久占用当前模型上下文。第二模型看到的内容变成可解释的结果哪条 Leaf、哪条路径、哪些 Entry 类型、哪次 Compaction共同决定了本轮输入。Pi 还支持 Branch Summary把离开路线中仍有价值的信息带到新路径。但摘要是有损表示不能替代原始分支而且“摘要如何生成”与“摘要 Entry 如何进入上下文”是两层问题。本文只确认固定版本的 Entry 与投影边界不把未运行的摘要质量写成 Runtime 结论。六、最危险的误解Session Tree 不是 GitEntry 的父引用、从旧节点分叉和 Label 的确容易让人联想到 Git。这个类比只能帮助理解树不能推导出 Git 的工作区语义。Pi Session Tree 没有自动提供文件树快照Merge Commit基于内容寻址的对象数据库分支间独立工作目录数据库与外部服务的事务回滚。回到旧 Entry 后文件系统不会自动恢复。此前已经安装的依赖、修改的数据库、启动的子进程、发送的网络请求或创建的 Git Commit也不会被撤销。因此需要把两件事写成不同合同Conversation State Branching ≠ Environment State Branching这是本文基于官方 Session 语义得出的工程判断不是声称 Pi 官方已经实现工作区回滚。如果任务有真实副作用Branch 前后至少要记录session_leaf_id:Bworkspace_version:git:4b7c2e1database_checkpoint:sandbox-db-17process_scope:container:task-204external_effects:-ticket:none-email:none只有 Session Leaf 与环境版本一起变化系统才能回答“我回到了哪里”以及“环境是否真的与那一刻一致”。七、给自研 Harness 的四层接口借鉴 Pi 时不必照搬全部 TUI 和 JSONL 细节但应保留四个角色。Event Store保存完整轨迹interfaceEvent{id:string;parentId:string|null;type:string;payload:unknown;timestamp:string;}它负责可追溯不负责决定模型看什么。Active Cursor明确当前节点不要默认“文件最后一行就是当前状态”。树一旦分叉最后写入和当前选择是两个概念。Context Projection生成模型输入投影函数应记录 Leaf、策略版本、压缩边界和被排除的 Entry 类型。这样模型输入才可复现、可审计。Workspace Version约束真实副作用工作区版本可以是 Git SHA、Worktree、容器快照、临时数据库版本或业务 Checkpoint。它不一定与每条消息一一对应但必须在发生工具副作用前后可核对。四层合起来才是一份可执行状态完整事件图 当前活动节点 上下文投影策略 工作区与外部状态版本对于低风险、短会话、没有工具副作用的助手线性消息数组可能更简单。树形 Session 不是所有产品的默认答案只有当系统需要回退、比较路线、保留失败轨迹或长期审计时它的复杂度才有回报。八、如何验收这套状态模型不要只测试“树能不能画出来”。最小验收应围绕开头的登录案例在 B 后执行路线一产生文件修改并记录工作区版本Branch 回 B确认活动路径不再包含 C、D确认 C、D 仍能从完整 Session Tree 查询构建新 Context确认模型只收到A→B与明确允许携带的摘要检查文件系统是否仍含路线一改动若仍存在系统必须提示环境漂移而不是宣称已回滚Fork 与 Clone 分别验证复制边界、编辑器状态和新 Session ID切换 Runtime 后确认订阅和扩展状态重新绑定导出 Session 前检查源码、路径、命令输出和 Tool Result 的隐私风险。Batch 08 的 Fixture 可以帮助检查树投影算法但它只是PASS-SPEC-NOT-RUNTIME。只有真实 Pi CLI 生成 Session、完成导航并核对工作区结果才能升级为 Runtime 证据。本稿没有完成这一步。九、结论Pi Session Tree 的价值不是让聊天界面看起来更高级而是把“完整发生过什么”和“模型这一次应该看到什么”拆开。它通过id、parentId和当前 Leaf 保留所有路线再把一条 Active Path 投影为 LLM Context。Branch 在同一文件内移动活动节点Fork 从旧问题之前创建新 SessionClone 复制到目标 Entry 后继续。它们管理的是会话轨迹不是文件、数据库和外部副作用。所以自研 Harness 最重要的补充不是再做一个树形 UI而是把 Session Leaf 与 Workspace Version 绑定对话回退时系统必须明确环境已恢复、环境未恢复或恢复状态未知。只要这条边界没有建立“回到旧对话”就可能给用户一种虚假的安全感。建立之后Session Tree 才真正从聊天记录功能变成可审计的 Agent 状态导航层。参考资料Pi Session Formathttps://github.com/earendil-works/pi/blob/v0.82.1/packages/coding-agent/docs/session-format.mdPi Coding Agent READMESession Branchinghttps://github.com/earendil-works/pi/blob/v0.82.1/packages/coding-agent/README.mdPi SDKSession 与 Runtime Fork APIhttps://github.com/earendil-works/pi/blob/v0.82.1/packages/coding-agent/docs/sdk.mdPi SessionManagerhttps://github.com/earendil-works/pi/blob/v0.82.1/packages/coding-agent/src/core/session-manager.ts