会话散了场,记忆还在场:给 pi 装上项目记忆

先讲个事故

我的 CatsCo 体系里有三个项目:cats-company 是主产品,catslog 在后台收运行证据,xiaoba-cli 负责把 XiaoBa runtime 部署到一台台主机上。平时它们各干各的,相安无事。

前几天,它们仨在我面前吵起来了。

catslog 的上报一直 fetch failed;xiaoba-cli 在部署主机上拉取也超时。我先怀疑 CatsLog——服务挂了?翻了日志,进程活得好好的。再怀疑 XiaoBa——新版本有 bug?回滚了一个版本,症状依旧。最后连 cats-company 的接口都查了一遍,接口根本没变。

三个方向,全错。

最后的答案躲在网络层里。那台部署主机的出站被防火墙策略拦住了。到 CatsLog 端点的 TCP 连接,7 秒超时;到 github.com 的 TLS,也超时。一个原因,解释了所有症状。而服务进程、握手、设备注册,一切正常。这也是它最难抓的地方。坏的是“路”,不是“人”。

排查完,我把结论存进了当时的会话:这种症状组合,第一步先查主机的出站 443/8000 网络路径,别上来就翻服务代码。

部署主机出站受阻导致多个服务超时

后来类似的问题又冒出来。我打开一个新会话,问了差不多的问题。

它礼貌地把我当成了第一次见面的陌生人。我把当时踩坑的前因后果讲了一遍,它答得头头是道——全是通用知识。唯独那条用一晚上换来的结论,它无从知晓。

它随着九月的会话,一起散场了。

这也不只发生在 pi 身上。会话可以很聪明,但会话终究会结束。

这篇文章讲的就是我为 pi 写的 PowerContext 插件,它把项目级的上下文存在会话外面。散场的是会话,留在场上的,是记忆。

pi 接入项目记忆文章封面

欢迎大家关注 OceanBase 社区公众号 “老纪的技术唠嗑局”。在这里,我们会持续为大家更新与 #AI 和 #Data 相关的技术内容~

一、上下文在 pi 里死在哪些时刻

这篇文章的另一位主角是 pi,一个极简的终端 coding agent,TypeScript 写的,扩展生态是一等公民。装一个插件,只需要在配置里加一行 npm:xxx,重启即生效。

不过它的会话,就是本地的一份记录文件,进来什么记什么,结束什么都不带走。轻量是轻量,但记忆这块,天然留白。

留白到什么程度?pi 的会话生命周期里,上下文至少死四次——或者说,细节死四次:

  1. compaction。对话太长时,pi 会把旧消息压缩成一份摘要来腾窗口。压缩必然有损:那个“为什么改这三行”的完整推理,到摘要里可能只剩“调整了初始化逻辑”八个字。
  2. 新开会话。最常见的一种。pi 一敲,新会话干干净净。旧会话确实躺在本地文件里,但新会话不会主动去读它——文件躺在那,和你能接着用,是两回事。
  3. 会话切换。pi 支持恢复和切换历史会话,但切过去的那个会话,并不知道你中间在别处干了什么。
  4. 退出。终端一关,这一轮工作的“现场”就永远停在了那里。

四个时刻,同一个病根。pi 的会话是个孤立系统,边界一关,里面的上下文出不去,外面的经验也进不来。

好消息是,pi 把这些边界全都暴露成了扩展事件,等于把孤立系统改成了开放系统。在事件发生的地方,把该存的存出去,把该拿的拿回来。powercontext 插件的全部挂载点就这五个:

1
2
3
4
5
pi.on('before_agent_start', ...)     // 开工前:召回项目上下文
pi.on('agent_end', ...) // 每轮结束:把证据存出去
pi.on('session_before_compact', ...) // 压缩前:抢救完整证据
pi.on('session_before_switch', ...) // 切会话前:结账
pi.on('session_shutdown', ...) // 退出前:兜底

五个回调,就是集成的全部——没有一行代码碰 pi 的核心逻辑。说实话这让我有点意外:原以为要写不少 hack,实际上 pi 把边界划得足够清楚,插件想不克制都难。

压缩切会话退出时保存项目上下文

二、该 PowerContext 出场了

它是 OceanBase 开源的上下文运行时(PowerMem 2.0)。本地起一个 Server,数据落在 SQLite,把项目里值得留下的事存成 Memory,把“现在做到哪了”存成 Handoff,中间还有一层 Source 专门留原文证据。跨会话、跨 agent,谁来接手都能接着干。

原理在官方文档里写得很全,这篇不展开——主角是它在 pi 上的形态。

pi 通过本地服务保存证据记忆与进度

三、三个设计决定

插件在 pi 侧只做三类事:召回、采集、看门。记忆从哪来?你在 pi 里说过的、符合条件的话,会作为 Source 证据存进 Server,再由它提炼成 Memory。这条自动提炼的路,闸门在 Server 端的生成模型。决定三要讲的显式写入走另一条路,闸门是我本人的确认。两条路,各过各的闸。

有两类 prompt 永远不存——看着像带密钥的,和超过 20 万字节的长文本。插件也不同步完整对话记录,只留“说过什么”的原文证据。

而整个开发过程里,最让我纠结的,是三处“不做什么”。它们比“做什么”更能说明这个插件的性格。

决定一:Server 不在场,一切照旧。

举个具体的场景。你正改到一半,手滑把 Server 进程关了。下一轮对话,插件照常尝试召回,超时,放弃——pi 拿到的 prompt 和没装插件时一模一样,你甚至不会有任何感知。等 Server 重新起来,下下轮自动恢复。唯一的痕迹,是终端角落里一行诊断输出,提示刚才有一次冲刷没成功、可以用 powercontext doctor 排查——录 demo 的当天我就撞上过一回,工作流零中断。

召回、采集、冲刷,全部如此:fail-open。代价当然也要认。Server 长时间不在场的那段日子,采集会错过,记忆会缺一角。我接受这个交换,宁可缺一段记忆,不让工作流断一次。记忆系统是给工作加 buff 的,不是在工作路径上加锁的。没有比“为了保存记忆而丢掉一次工作流”更本末倒置的事。

决定二:召回的内容,是“不可信历史证据”。

每个 turn 开始前,插件向 Server 请求一份带预算的上下文——默认 8000 字节。这个数字是刻意的小:装不下一个项目的历史,只装得下“现在需要知道的事”,预算逼着系统做选择,而不是把仓库历史一股脑塞进窗口。

注入时这份内容还被明确标注:这是历史证据,不是指令。当前的用户请求、system prompt、仓库规范,永远压过召回内容。这是在防一个真实的风险——一周前存下的“先别升级依赖”,不该变成今天一条必须执行的命令。顺带一个自然会冒出来的问题:它和 compaction 的摘要有什么区别?究其根本,摘要是压缩后的转述,证据是可溯源的原文——记忆里的任何一句话,都能追到出自哪次对话。

决定三:写进记忆的事,你得点头。

读取可以随时发生(pc_search、pc_memory_list),但凡是持久化写入——pc_remember、修订、退休——在交互会话里都要过一道确认;没有交互 UI 时,宁可拒绝,绝不静默落盘。无人值守的 CI agent 就是典型:它提议的记忆,一律不入库。

记忆的价值在于“真的发生过”,所以写入必须是真的。agent 可以提议,人类才能拍板。

这三条归到底是同一种性格:**插件对 pi 的工作流保持谦逊。**只在边界上工作,不占领主干道。

故障继续工作历史核验与写入确认

四、上手

三条命令:

1
2
3
4
5
6
7
8
# 1. 安装 PowerContext CLI 与本地 Server(uv 管理,不污染系统 Python)
uv tool install "powercontext[cli,server]==0.2.0"

# 2. 起 Server:默认 127.0.0.1:8000,数据落本地 SQLite
powercontext server run

# 3. 给 pi 装上插件(注意和 CLI 保持同一个 ref)
powercontext setup pi --ref powercontext-v0.2.0

插件按项目目录自动绑定 Scope,同一个项目的会话共享一份记忆,不同项目互不串台。采集来的证据由 Server 端提炼成 Memory,这步需要一个配置好的生成模型;没配也不用慌,手动记录和召回照常工作,只是自动提炼这一环先空着。

之后在项目里开新的 pi 会话,正常提需求、正常改代码——采集在幕后发生,你什么都看不到。直到哪天隔了一周回来,新会话里随口问一句“上次这个为什么这么改”,它答得上来。

想自己动手时,/pc 命令族是入口:

1
2
3
/pc search "三方连调"   # 搜项目记忆
/pc stats # 看存了多少
/pc doctor # 体检:连接、Scope、配置

要换机器、换 agent 接手,pc_handoff_* 会把“现在做到哪了”打包成一份交接单。上下文跟着项目走,不跟着终端走。

效果是最好的说明书。把那次排查的结论存进 PowerContext 之后,我在一个全新的 pi 会话里,把开头的问题原样又问了一遍:“catslog 上报一直 fetch failed,xiaoba-cli 那边拉取也超时,这种三方连调问题怎么排查?”

存这一笔,受益的不只是下一个自己。接手的同事换台机器、开个新会话,问的仍然可以是同一个问题。

这一次,PowerContext 把两段真实排查史放进了上下文:九月那次 XiaoBa 主机出站被拦的结论(一个原因同时解释两个症状,且别误判为服务故障),和三个项目的联调拓扑。于是它没有上来就看代码,先翻出旧结论。旧结论在它手里是要重新检验的证据,不是免检的定理。然后它设计了一套“三点定位法”,同一条 curl 从 CatsLog 服务器、XiaoBa 主机、任意第三台国内机器各跑一遍,结果组合直接定位故障点。判读规则里甚至直接引用旧结论:“①②通③不通,就是 9/6 那个结论,查 XiaoBa 主机出站。”

末尾还有一句让我会心一笑的叮嘱:“根因确认后告诉我,我把这两轮诊断的对比存进 memory,避免下次再被旧结论带偏。”——你看,它把记忆当证据用,而不是当答案抄。

召回证据后重新核验并记录结论

散场之后

回到开头那个事故。出站拦截后来解除了,但那条排查结论,曾经只活在九月的那个会话里。

现在不会了。曲终人不见,江上数峰青——散场难免,散场之后留下什么,是可以设计的。

上下文死在哪里,就在哪里留下坐标。

参考链接

pi 接入 PowerContext 横版文章海报

OceanBase 社区内容分隔装饰

往期内容推荐

PowerContext 人与 Agent 协作文章

Tibo 谈 Codex 与 Harness 文章

MiniMax Code 多模态项目上下文文章

GPT-6 Astra 与 Codex 上下文文章

了解更多

OceanBase 社区小助手交流二维码

添加社区小助手,加入微信交流群~