Pi Agent 的 Memory 机制:会话树、上下文与压缩如何协作
Pi Agent 的记忆机制更像一套上下文管理系统:完整历史保存在会话树里,当前模型只看到被挑选、压缩、转换后的上下文。
换句话说,Pi 的 memory 不是“模型自己记住了什么”,而是 Pi 在每次调用模型前,决定把哪些历史、规则、工具结果、摘要和动态上下文放到这一次请求里。
本文脉络: |
一、Pi Agent Memory 的五层结构
Pi 的 memory 不是单个模块,而是几层机制叠在一起。
可以这样理解:
| 层级 | 在 Pi 里的形态 | 解决的问题 |
|---|---|---|
| 工作记忆 | 当前 AgentMessage[] / LLM messages |
本轮模型能看到什么 |
| 情景记忆 | JSONL Session Tree | 保存对话、工具结果、模型切换、分支历史 |
| 摘要记忆 | Compaction entry | 长上下文下保留早期历史要点 |
| 程序性记忆 | AGENTS.md、Skills、Prompt Templates |
告诉 Agent 遇到某类任务该怎么做 |
| 可插拔记忆 | Extension 的 context / before_agent_start 事件 |
注入外部记忆、RAG、团队策略或动态状态 |
这五层里,只有第一层“工作记忆”会直接进入模型。其他层都要经过加载、选择、压缩、转换,最后变成本轮上下文的一部分。
这也是理解 Pi memory 的关键:记忆不等于存储,记忆等于“能在正确时机被带回上下文”。
二、工作记忆:当前 Context
Pi 每次调用模型时,都会构建一份上下文。它通常来自:
- 当前用户输入;
- 当前 session branch 上的历史消息;
- 工具返回的
ToolResultMessage; - system prompt;
- 被加载的项目规则、Skills 或模板;
- 压缩后的摘要;
- Extension 动态注入的内容。
这份上下文就是 Pi 的工作记忆。
它有两个特点。
第一,工作记忆是短暂的。模型调用结束后,模型不会“天然记住”这次内容。Pi 必须把消息和事件写回 session,后续调用才有机会重新取出来。
第二,工作记忆是有边界的。上下文窗口再大也会满,工具结果、长文件、连续对话都会把窗口撑爆。所以 Pi 不可能把完整历史永远塞给模型,而要在“完整历史”和“可执行上下文”之间做取舍。
三、情景记忆:JSONL Session Tree
Pi 的长期会话记忆主要落在 session 文件里。它不是一个简单的线性聊天记录,而是 append-only JSONL tree。
每个 entry 有自己的 id,并通过 parentId 指向父节点。这样一份 session 可以长成树,而不是只能一条线往下写。
它保存的不只是 user/assistant message,还包括:
| Entry 类型 | 记住了什么 |
|---|---|
| message | 用户输入、模型输出、工具结果 |
| model switch | 模型切换 |
| thinking level switch | 推理强度变化 |
| compaction | 压缩摘要 |
| branch summary | 会话树跳转时的分支摘要 |
| custom extension data | Extension 写入的自定义状态 |
| session info | session 名称等元信息 |
这套设计带来一个很实用的能力:失败路径不需要被覆盖。
编码任务经常要试错。模型改了 A 方案,发现不对,再 fork 到 B 方案,后来又回到某个中间节点。线性日志只能一路往下追加,很难表达“从这里分叉”;JSONL tree 可以保留探索过程。
所以 Pi 的情景记忆不是只为了“能继续聊天”,它还支撑 /tree、/fork、/clone、/export 这类会话操作。
四、摘要记忆:Compaction
当历史越来越长,Pi 不能把所有 entry 原样塞进上下文。这时就需要 Compaction。
Compaction 的直觉很简单:
完整历史太长 |
注意这里的细节:Compaction 不会删除原始历史。
原始 JSONL 还在,会话的审计历史还在。变化的是后续构建模型上下文时,Pi 可以从压缩后的摘要节点继续,而不是每次都带上早期全部消息。
Pi 的 Compaction 常见触发方式有两类:
| 触发方式 | 含义 |
|---|---|
| proactive compaction | 接近上下文上限时提前压缩 |
| overflow recovery | Provider 报上下文溢出后,压缩并重试 |
这个设计体现了 Pi 的一个边界意识:
会话文件:保存完整历史,方便追溯 |
这两个东西不要混为一谈。完整历史适合保存,完整历史不一定适合每次都喂给模型。
五、程序性记忆:AGENTS、Skills 与 Prompt Templates
Pi 的 memory 里还有一类很容易被忽略:程序性记忆。
它不是“上次聊过什么”,而是“遇到某类任务应该怎么做”。
在 Pi 里,这类记忆主要来自:
AGENTS.md:项目级或目录级的长期约定;- Skills:一类任务的操作手册、参考资料和脚本;
- Prompt Templates:固定格式的提示词模板;
- themes / resources:交互或输出层面的可复用资源。
这些内容通常由 ResourceLoader 在启动或 reload 时发现,再参与 system prompt 或上下文构建。
举个例子:你让 Pi 修改博客文章。如果项目里有写作 Skill,它就不需要每次从零猜 front matter、<!-- more -->、图片路径和构建命令。Skill 就像 Pi 的“操作手册记忆”。
所以,Pi 的 memory 不是只有“对话历史”。对编码 Agent 来说,项目规范和工作流记忆同样重要。
六、可插拔记忆:Extension context hook
Pi 没有把所有 memory 形态都硬编码进核心,这一点很重要。
如果你想接入外部长期记忆、RAG、团队知识库、用户偏好,比较自然的位置是 Extension。
比如 Extension 可以监听:
| 事件 | 可以做什么 |
|---|---|
before_agent_start |
修改 system prompt,加入本轮规则 |
context |
替换或扩展 messages,注入检索结果 |
tool_result |
对工具结果脱敏、压缩或补充解释 |
session_start |
从 session branch 恢复扩展状态 |
session_before_compact |
自定义压缩策略 |
一个最小的“外部记忆注入”思路可能是:
pi.on("context", async (event) => { |
这段代码的重点不是具体 API,而是插入位置:在模型调用前,Extension 可以把外部记忆转成 messages,让它真正进入本轮工作记忆。
这也是 Pi 设计比较克制的地方:核心提供稳定的 session、context、compaction 和事件插槽;具体要不要接向量库、数据库、知识图谱,交给扩展层。
七、一次调用前,Memory 如何变成 Context
把前面几层串起来,一次模型调用前大概是这样:
简化成文字:
当前 session leaf |
这里有两个边界很重要。
第一,transformContext() 面向 Agent 消息,适合做裁剪、动态上下文、压缩、RAG 或长期记忆注入。
第二,convertToLlm() 面向 Provider 协议,负责把 Pi 内部的 AgentMessage 转成模型真正能理解的 user、assistant、tool result 等消息。
也就是说,“记忆选择”和“协议转换”是两件事。前者决定模型该看什么,后者决定这些内容怎么发给模型。
八、Pi 这套设计的取舍
Pi 的 memory 机制有几个明显优点。
| 优点 | 说明 |
|---|---|
| 可追溯 | JSONL append-only 保存完整历史 |
| 支持分支 | tree 结构适合探索、fork、回退 |
| 不强塞历史 | Compaction 让上下文保持可用 |
| 可扩展 | Extension 可以接入外部 memory |
| 适合编码任务 | 工具结果、模型切换、项目规则都能进入同一套会话体系 |
但它也有取舍。
第一,Pi 默认不是一个“自动长期记住所有用户偏好”的系统。想做跨项目、跨会话、可检索的用户长期记忆,需要额外扩展。
第二,Compaction 本质是摘要,摘要就会损失细节。它适合保留任务目标、关键决策、文件变化和未完成事项,不适合替代完整审计历史。
第三,Extension 注入外部记忆时要克制。记忆越多不代表越好,塞太多反而会污染工作上下文,让模型抓不住当前任务。
我的建议是:先把 Pi 内建的 session tree、compaction、AGENTS、Skills 用好,再考虑接外部长期记忆。
九、常见问题
| 问题 | 回答要点 |
|---|---|
| Pi Agent 有“长期记忆”吗? | 有会话级长期历史,也就是 JSONL session tree;但默认不是向量库式的跨会话语义记忆。 |
| Pi 的 memory 和普通聊天记录有什么区别? | Pi 保存的是可分支的 session tree,不只是线性对话;还包含工具结果、模型切换、压缩、扩展状态等 entry。 |
| Compaction 会删除历史吗? | 不会。它追加摘要 entry,改变后续上下文构建方式;原始 JSONL 历史仍然保留。 |
| 模型每次都会看到完整历史吗? | 不会。模型只看到本轮 Provider 请求里的上下文,完整历史需要经过选择、压缩和转换。 |
| Skills 算 memory 吗? | 算程序性记忆。它不是记住某次对话,而是记住“这类任务应该怎么做”。 |
| 想接入向量记忆怎么办? | 更适合通过 Extension 的 context 事件做检索注入,而不是把向量记忆硬塞进核心流程。 |
最后用一句话收束:Pi Agent 的 memory 机制,本质是把“完整历史可追溯”和“当前上下文可执行”分开管理。