上下文与记忆类包:把大输出挡在外面
拆解六个上下文与记忆类 Pi 包——pi-context-mode、pi-lean-ctx、pi-hermes-memory、pi-total-recall、gentle-engram、@samfp/pi-memory——按技术路线、存储位置、是否需要额外模型调用三个维度切开,并指出「记忆」和「上下文压缩」是两件事。
上下文与记忆类包:把大输出挡在外面
模型跑一次 npm install,输出几千行;一次 tail -f 拉日志,几 MB;一个 schema.json dump 又是几千 token。这些东西进上下文之后,后面每一轮都在为它们付钱,而模型真正需要看的可能只是最后那 20 行报错。
于是你会想到两个方向:一是让上下文小一点,二是让模型记住之前的事。但这两个方向经常被混成一件事,而生态里六个包恰好走的是三条完全不同的路。把它们分不清,就会出现"我明明装了记忆包,上下文还是爆"——因为你装的是加法包,问题是减法问题。
Pi 官方不提供上下文压缩和记忆(内置能力的边界前面章节已经讲过)。这一层完全交给生态。这篇把六个包按三个维度切开:技术路线(减法 / 网关 / 加法)、存储位置(本地 SQLite 还是外部服务端)、是否需要额外模型调用。读完你能判断自己的痛点属于哪一类,并知道该装哪个、不该装哪个。
有个前提要先说清楚:「记忆」和「上下文压缩」是两件事。 压缩是在内容已经进入上下文之后想办法让它变小;记忆是把过去会话里的信息在下一轮重新送进上下文。前者省的是"这一轮有多长",后者花的是"每轮都要多带一点"。这六个包里,有的解决前者,有的只解决后者,还有一两个两头都沾。
六个包各管什么
先看全景。这六个包没有一个是"官方推荐",它们是六个不同作者对同一个问题的六种回答:
这张表里最值得注意的一行是 pi-total-recall。 它是六个里唯一做"包聚合"的——把 @samfp/pi-memory、pi-session-search、pi-knowledge-search 三个独立包打进自己的 tarball,在 manifest 里挂载三个 extension 入口。生态里其它包都是单点扩展,它是套装。
另一件值得先说的事:这六个包都不需要 API key。 存储全在本地,或者指向你自己的服务端。
三条技术路线
这是全文最需要记住的区分。减法、网关、加法是三种不同性质的操作,装错方向就是白花时间:
减法和加法方向是相反的。 减法想让上下文变小,加法想让上下文变大——只不过多出来的是有用信息。它们的收益完全取决于你的会话形态:如果你每天跑同一个命令十几次,减法收益巨大;如果你是每天开一个新会话干一件新事,加法才有意义。
网关路线是唯一"模型无感"的。 pi-lean-ctx 不改提示词、不加新工具,它只是把 Pi 内置的那几个文件操作工具换成了 ctx_ 前缀版本。这意味着模型学会的"怎么用 read"的知识继续有效,输出先过一道上下文选择层再进对话。代价是你得接受工具名变了。
减法:pi-context-mode
它是双重身份的——同时是一个 MCP server 和一个 Pi 扩展。这个设计不是巧合:MCP 那一半让别的宿主也能用,扩展那一半让它在 Pi 里注册工具。
11 个工具:
# 默认可见的 6 个
ctx_execute 执行命令,结果落库而不是进上下文
ctx_execute_file 执行文件内容
ctx_batch_execute 批量执行
ctx_fetch_and_index 抓取并索引
ctx_search 检索索引
ctx_index 建立索引
# 默认隐藏的 5 个
ctx_stats ctx_doctor ctx_upgrade ctx_purge ctx_insight后 5 个是默认隐藏的,需要时手动启用。这意味着 ctx_doctor(诊断)和 ctx_stats(统计)这类运维工具不会天天出现在模型的可选列表里占位置。
底层是一个 SQLite FTS5 知识库。全文检索靠 SQLite 自带的 FTS5 扩展实现,不依赖向量数据库,也不需要外部 embedding 服务——这是它和加法类包最大的实现差异。本地存储,所以也不需要 API key。
它没有斜杠命令,全部通过工具调用驱动。
网关:pi-lean-ctx
它是六个里路线最特别的一个。它不"减少"任何东西,它改变东西到达上下文的方式。
具体做法:把 Pi 内置的 read、bash、grep、find、ls 五个工具替换成 ctx_ 前缀版本。模型还是发起"读取文件""执行命令"这类动作,但输出先被路由到上下文选择层,再决定进对话的是哪一部分。
工具列表:
ctx_read ctx_shell ctx_grep ctx_find ctx_ls
lean_ctx ctx_compose ctx_compress ctx_edit ctx_expand
ctx_graph ctx_heatmap注意 ctx_compress 和 ctx_expand 这一对。 它是六个包里唯一把"压缩"做成显式工具的——压缩和还原都是可以调用的动作,不是设置项里的开关。加上 ctx_graph 和 ctx_heatmap 两个可视化工具,它能给你看一眼上下文选择的分布。
配置入口是 /lean-ctx 这一个斜杠命令。它对宿主的版本有要求:pi-coding-agent >=0.74.0、pi-tui、typebox ^1.0.0。
加法:三个记忆包的共同点
剩下四个包(pi-hermes-memory、pi-total-recall、gentle-engram、@samfp/pi-memory)做的是同一类事:把过去的信息重新送回当前上下文。差别在送什么、从哪取、怎么送。
pi-hermes-memory 是从 Hermes agent 移植过来的,所以它带的不只是记忆:
斜杠命令有 10 个,覆盖面很宽:
/memory-insights /memory-skills /memory-consolidate
/memory-interview /memory-switch-project /memory-index-sessions
/memory-sync-markdown /memory-preview-context /memory-policy
/learn-memory-tool其中最有价值的是 Secret Scanning——这是整个生态里唯一做「API key / token / SSH key 禁止落盘」的实现。 记忆系统会把对话里的东西存下来,这本身是个风险:某次对话里粘贴了一个密钥,它就进了你的长期存储。hermes-memory 明确拦这件事。
gentle-engram 的工具数量是六个包里最多的,17 个,分成几组:
# 存取
mem_save mem_search mem_context mem_get_observation
# 固定与评审
mem_pin mem_unpin mem_review mem_judge mem_compare
# 项目维度
mem_current_project mem_list_projects
# 会话生命周期
mem_session_start mem_session_end mem_session_summary
mem_capture_passive mem_save_prompt
# 运维
mem_doctormem_capture_passive 这个名字说明它的设计取向——被动捕获,不要求模型显式调用"记住这个"。配合 mem_pin(固定)和 mem_session_summary(会话摘要),它覆盖了"自动记 + 手动钉 + 定期总结"三种常见诉求。
它和前几个包最大的不同是存储不在本地:它需要一个外部 Engram 服务端 v2.1.0 或更高版本,可以是本机跑的,也可以是云端。这个设计的取舍很直接——多用户共享同一份大脑,或者多台机器同步。换来的是跨会话、跨压缩、跨 MCP 都能用,因为记忆根本不在这个进程里。
@samfp/pi-memory 的机制最需要展开讲,因为它是这条路里设计最讲究的一个。它的工作分两段:
第一段,会话结束时做 consolidation。 一个 LLM 调用读完整段对话,把里面的事实和教训抽出来,结构化写进库。
第二段,每轮开始前做检索注入。 按当前 prompt 去检索相关记忆,把结果包在 <memory> 块里注入上下文。
这里有个细节值得单独说:它不改 system prompt。 记忆内容是作为普通上下文块注入的,不是改写系统提示。这么做是为了保住前缀缓存——system prompt 一旦每轮变化,KV cache 就失效,重建成本极高。注入块放在提示的后段,不影响前面那一大段稳定前缀。
存储结构和配置:
{
"memory": {
"injectionMode": "context-hook",
"consolidationModel": "claude-sonnet-4-20250514",
"embedding": {
"enabled": false
}
}
}injectionMode 默认是 context-hook。数据库是 SQLite(WAL 模式),路径 ~/.pi/memory/memory.db,里面三张表:semantic、lessons、events。
consolidation 依赖模型,默认是 claude-sonnet-4-20250514。 关键的一句:在非 Anthropic provider 上这个调用是 no-op——不会报错,但也不会真的抽取。包也支持 ollama/qwen3:8b 这类本地模型。
embedding 默认关闭。 关掉的时候走 FTS5 全文检索;开了但失败会静默回退到 FTS5,不抛错。所以你不会从报错里知道降级发生了——这一点排障时会用到。
@samfp/pi-memory 的包名陷阱
这个包有一个必须先讲的安装坑,而且它的 README 顶部专门为此写了提醒。
真实仓库是 samfoy/pi-memory(仓库在 samfoy 名下,不是 samfp),但 npm 上的包名是 @samfp/pi-memory——带 @samfp/ scope。
为什么必须带 scope? 因为 npm 上存在一个无关的 pi-memory 包。不加 scope 直接 npm install pi-memory,装到的是另一个东西,然后你会花几个小时排查一个根本不相干的扩展为什么行为诡异。
正确的安装命令:
# 正确:必须带 @samfp/ scope
pi install npm:@samfp/pi-memory
# 错误:npm 上有一个无关的 pi-memory,会装错
pi install npm:pi-memory这条规则在整篇里都很重要,因为 pi-total-recall 打包的就是 @samfp/pi-memory——你通过 total-recall 装它时,拿到的是同一个 scope 下的正确包。
存储、依赖与模型调用
横向对比这三件事:
三条读表要点:
第一,"需要 API key"这一列全是"否",但这不等于零成本。 gentle-engram 需要一个自建服务端,@samfp/pi-memory 需要额外跑一次 consolidation 模型调用。前者是基础设施成本,后者是 token 成本——都比配 API key 更隐蔽。
第二,pi-total-recall 的 peerDependencies 是空的,这不代表它没有依赖。 它把三个包打进了自己的 tarball,依赖变成了内部依赖。这是第一章讲的"其他 Pi 包如果要作为依赖,必须包含在发布的 tarball 里"的实际应用。
第三,宿主包的版本约束不一样。 pi-context-mode 全部写 *,最宽松;pi-lean-ctx 和 gentle-engram 要求 pi-coding-agent >=0.74.0;pi-hermes-memory 要求 pi-ai 和 pi-coding-agent 都要 >=0.80.6。装之前对一下你的 Pi 版本。
工具调用长什么样
不同包的调用形态不一样,但调用通道是同一个。下面几个是最典型的用法,参数名以各包自身的工具 schema 为准:
{
"name": "ctx_search",
"arguments": {
"query": "auth middleware 配置",
"limit": 5
}
}{
"name": "memory_remember",
"arguments": {
"content": "这个项目的测试命令是 pnpm test,不是 npm test",
"type": "fact"
}
}{
"name": "session_search",
"arguments": {
"query": "上次改数据库迁移是在哪个分支"
}
}ctx_search 之后模型拿到的是索引里的片段,不是原始文件;memory_remember 之后还要等 consolidation 才会真正变成可检索的记忆。
谁负责什么
三种场景的可观察结果
场景一:单次工具输出把上下文撑爆,但任务本身不需要那些输出。 这是减法问题。装 pi-context-mode,让它把大输出落到本地索引,模型改用 ctx_search 取片段。可观察结果是工具调用仍然成功,但对话历史里不再出现几万行的原始输出,上下文长度曲线变得平缓。
场景二:想让工具行为不变,但输出自动变短。 这是网关问题,别用减法包。装 pi-lean-ctx,它替换掉内置 read/bash/grep/find/ls,模型侧几乎无感。可观察结果是工具名从 read 变成了 ctx_read,而同样的调用拿回来的内容短了很多——工具名变化本身就是它生效的信号。
场景三:模型第二天完全不记得昨天调过的接口签名。 这是加法问题,前面两个都解决不了。装 @samfp/pi-memory(注意 scope),跑一次 /memory-consolidate 把已有会话抽成记忆,下一个会话开新话题时 <memory> 块里会出现那条签名。可观察结果是检索注入的内容出现在对话历史里、而 system prompt 没有变化——后者说明前缀缓存保住了。
你现在能做什么
- 装
pi-context-mode,然后跑一次输出很大的命令,对比调用前后的上下文长度;顺便启用默认隐藏的ctx_stats和ctx_doctor看索引状态。 - 装
pi-lean-ctx,用/lean-ctx走一遍它的配置,确认内置的read/bash已被ctx_前缀版本接管,并试一次ctx_compress再ctx_expand。 - 装
@samfp/pi-memory(带 scope),在settings.json里加memory节,跑/memory-consolidate,然后用memory_search确认能检索到,再开新会话验证自动注入。 - 装
pi-total-recall,检查它的 tarball 里确实带了pi-memory、pi-session-search、pi-knowledge-search三个扩展入口,然后跑/knowledge-search-setup把本地知识接进来。 - 装
pi-hermes-memory,确认 Secret Scanning 生效:故意在对话里粘贴一个形似 API key 的字符串,看它是否被拒绝落盘。 - 想用
gentle-engram的话,先起一个 v2.1.0 以上的 Engram 服务端,确认mem_search能返回结果再谈别的。