@yeyudeh/pi-compaction-files0.3.0
Caps Pi's compaction file lists (<read-files>/<modified-files>): drops dead, duplicate and temp paths, hides sensitive ones, strips blocks the summarizer re-seeds into the narrative, and keeps the cost per turn constant.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-compaction-files
Pi 每次压缩都会在摘要末尾追加两个没有上限的文件清单,并且永久继承。这个扩展把它们封顶, 于是每轮的固定成本不再随压缩次数增长。
一个长期真实会话实测(3 万+ 条目、112 次压缩):
| 上下文里的文件清单 | 每次模型调用的成本 | |
|---|---|---|
| Pi 默认 | 110,924 B / 1,930 条(占摘要 57%) | ≈27,700 token |
| 装本扩展 | 1,594 B / 38 条(4.8%) | ≈399 token,恒定 |
Pi 官方文档把这种增长写成了预期行为(docs/compaction.md → “Cumulative File Tracking”):
Default compaction and branch summarization track files cumulatively. […] Compaction also carries file lists from the previous Pi-generated compaction. […] File tracking therefore accumulates across default compactions and nested default branch summaries. Pi does not automatically carry file lists from extension-generated summaries whose
fromHookfield istrue; extensions manage their owndetailsformat.
最后一句就是本扩展所依据的公开契约,不是 monkey patch。
安装
pi install npm:@yeyudeh/pi-compaction-files
# 或直接从 git —— 这种跟的是 main 分支
pi install git:github.com/yeyudeH/pi-compaction-files
先试一次不安装:
pi -e npm:@yeyudeh/pi-compaction-files
两种都在 Pi 0.37 上实测过:扩展加载,stderr 打 self-check ok。
git 形式跟 main;要固定在某个版本用 pi install git:github.com/yeyudeH/pi-compaction-files#v0.2.0。
确认已加载:
/filelist
看到类似输出即可,重点是最后一行的自检结果:
enabled=true style=native keepModified=60 keepRead=40 dropMissing=true
archiveSensitive=false tool=false indexRecent=12 failStreak=0/2
pi-self-check=ok dist=…/@earendil-works/pi-coding-agent/dist
ok: compaction.js: fromHook inheritance guard present
…
扩展在下次启动 Pi 时才生效(扩展是进程启动时加载的)。但已经存在会话里的旧摘要会在 每一轮被就地改写(不写会话文件),所以已经很臃肿的会话立刻变小。
它做了什么
1. 从源头缩短清单
session_before_compact 调用 Pi 自己的 compact()(摘要文本与 Pi 原生完全一致),
然后把文件清单块换短。因为结果来自 hook,Pi 会记 fromHook: true 并停止继承上一次的清单
—— 雪球在源头断掉,而不是每轮重新修剪。
2. 剥掉所有完整块,不只是末尾那个
Pi 把真块追加在末尾,但摘要模型会把上一次的块抄进正文。真实会话里实测到:
offset 29181 1594 B 38 条 ← 上一次压缩的块,被抄进正文
offset 33349 75 B 1 条 ← Pi 追加的真块
只取最后一处的实现会让前一份原样留在上下文里,每轮都付,并且继续滚大。本扩展剥掉所有完整块、 把路径并进一个有上限的清单。正文里只是提到标签的文字不会被误伤(正则要求标签独占一行且 有配对闭合标签)。112 次真实压缩里共 141 处提及,误吞 0 处。
3. 过滤路径
- 丢掉已不存在的路径(
dropMissing) - 丢掉临时噪音:
/tmp/、/dev/shm/、/node_modules/、/.cache/、/proc/、/sys/ - 同一路径的相对/绝对两种写法去重(Pi 两种都存)
- 命中
sensitivePatterns的路径只计数、不列出 - 按最近修改时间保留
keepModified/keepRead条
4. 连会话文件里的存档也脱敏
Pi 会把完整清单写进 compaction 条目的 details(不渲染进上下文)。只从 prompt 里隐藏
路径,并不会把它从磁盘上的会话文件里去掉。 一个真实会话里,140,048 条存档路径中有
1,643 条(1.17%,21 种)命中敏感模式 —— 凭据形态的文件名、agent 配置、私密目录里的截图。
0.2.0 起存档也过滤(archiveSensitive: false,默认)。若你希望保留完整清单好让
session_files 工具能检索,设成 true。
已存在的条目不会被追溯改写,过滤只作用于新的压缩。
配置
可选文件:~/.pi/pi-compaction-files.json
{
"enabled": true,
"style": "native",
"tool": false,
"keepModified": 60,
"keepRead": 40,
"dropMissing": true,
"indexRecent": 12,
"archiveSensitive": false,
"compactThinking": "medium",
"outputBudgetRatio": 0.8,
"outputHardCapRatio": 0.9,
"retryOnOverflow": true,
"summaryMaxTokens": 0,
"summaryHardTrim": false,
"excludePatterns": ["/tmp/", "/dev/shm/", "/node_modules/", "/.cache/", "/proc/", "/sys/"],
"sensitivePatterns": ["/credential", "/secret", "/token", ".key", "/password", "apikey", "/.pi/agent/settings.json", "/.pi/agent/models.json"],
"piDistDir": null
}
| 字段 | 默认 | 作用 |
|---|---|---|
enabled |
true |
总开关。PI_FILELIST_DISABLE=1 可在运行时整体关闭。 |
style |
native |
native 保留 Pi 原生块只缩短;index 换成 <session_files> 索引 |
tool |
false |
是否注册 session_files 检索工具(只有 index 样式有意义) |
keepModified |
60 |
上下文里保留多少条修改路径(新的优先) |
keepRead |
40 |
上下文里保留多少条读取路径 |
indexRecent |
12 |
仅 index 样式:索引里列几条。不设这个上限时 index 比 native 更长(kept=38 时实测 +506 字);设了则短 854 字。若加了头尾说明后索引仍不更短,这一次自动退回 native |
dropMissing |
true |
丢掉已不存在的路径 |
archiveSensitive |
false |
false = 敏感形态路径连磁盘存档也剔掉 |
excludePatterns |
见上 | 命中即整条丢弃 |
compactThinking |
medium |
压缩请求自己用的思考强度,不再沿用会话级别 |
outputBudgetRatio |
0.8 |
压缩请求的总输出(思考 + 摘要)应落在 Pi 自身上限(0.8 × reserveTokens)的这个比例内。0 表示不限制 |
outputHardCapRatio |
0.9 |
把推理服务对这次请求强制执行的输出上限压到 Pi 上限的这个比例,思考+摘要再也撞不到天花板。0 表示不动 Pi 的原上限 |
retryOnOverflow |
true |
实际用量超过预算时,用更严的指令 + 降一档思考重做一次,取更省的那次 |
summaryMaxTokens |
0 |
可选的第二道限制,只管摘要正文(字符数/4)。0 表示关闭;主控是 outputBudgetRatio |
summaryHardTrim |
false |
清单收到最紧之后摘要还是超上限时,是否连正文一起裁掉(默认只收紧清单并告警) |
sensitivePatterns |
见上 | 命中即计数但不列出。请把你自己的私密目录加进来 |
piDistDir |
自动 | 自检找不到 Pi dist/ 时才需要;也支持环境变量 PI_CODING_AGENT_DIST |
效果
对比 Pi 默认(101 条真实原生压缩实测)
实测拟合增长斜率:每次压缩 +1,108 B,无上限。
| 清单字节数 | 每次调用 token | |
|---|---|---|
| Pi 默认(装上本扩展那个时点) | 110,924 B | 27,731 |
| Pi 默认(再推 40 次压缩) | 155,234 B | 38,809 |
| 本扩展 | 1,594 B | 399 |
清单上限是 keepModified + keepRead,与压缩次数无关。
0.1.0 vs 0.2.0(同一份真实数据并排跑)
| 压缩 | 输入 | 0.1.0 输出 | 0.2.0 输出 | 残留块 0.1.0 → 0.2.0 |
|---|---|---|---|---|
| 最近 6 次里的 5 次 | — | 完全相同 | 完全相同 | 1→1, 2→2, 1→1, 1→1, 1→1 |
| 有被抄块的那一次 | 33,424 | 33,424 | 33,281 | 3 → 1 |
5/6 输入本来已经干净,所以没有回归;第 6 次正是 0.2.0 存在的理由。
按观测参数推演(原生 +1,108 B/次;被抄块 1,594 B,11 次接管里出现 1 次):
| +N 次压缩 | Pi 默认 | 0.1.0 按观测率 | 0.1.0 最坏(每次都抄) | 0.2.0 |
|---|---|---|---|---|
| 5 | 116 KB | 2,319 B | 9,564 B | 1,594 B |
| 20 | 133 KB | 4,492 B | 33,474 B | 1,594 B |
| 40 | 155 KB | 7,390 B | 65,354 B | 1,594 B |
扩展自身的开销
实测:冷路径(无缓存,含每条路径 statSync)0.43 ms;热路径(命中缓存,只做签名)
0.0002 ms。正常每轮走的是热路径,可忽略。
完整方法与口径说明见 docs/benchmark.md(英文)。
与其他上下文类扩展共存
Pi 的上下文类扩展不少:pi-zip、billion-context、context-mode、pi-cache-optimizer、
magic-context、pi-smart-compact、pi-hot-compact、pi-context-prune。
它们都不碰压缩的文件清单,而本扩展也不碰别的东西,所以是正交的。
pi-zip 从它那边也给出了同样结论:
An extension that only writes Pi's compaction summaries (a
session_before_compacthandler) does not pause pi-zip.
如果同时装两个都改写压缩摘要的扩展,结果不可预测 —— 二选一。
安全性
- 绝不 cancel 压缩。 任何出错都返回
{},Pi 走原生路径,行为不受影响。 - 绝不写你的会话文件。
context钩子每轮在内存里改;session_before_compact只是返回 一个 hook 结果,由 Pi 按正常方式持久化。 - 不联网。 没有遥测、没有 fetch、没有统计上报。
- 卸载即完全回滚。 旧摘要在会话文件里原样保留。
- 失败预算。 连续 2 次失败后本进程停用,交回 Pi。
已知边界(请读)
- 依赖 Pi 内部行为。 三条:
fromHook继承守卫、details不渲染进上下文、context钩子 就地改消息。第一条有文档保证,后两条不是契约。Pi 升级可能改掉。真发生时扩展不会崩, 只会静默失效 —— 所以加载时会自检并 log 告警。升级 Pi 后请看一眼 stderr,或跑/filelist。 - 不影响 Pi 的压缩触发点。 Pi 用自己的条目投影(
agent-session.js:422)决定何时压缩, 不看改写后的 prompt。缩短摘要让真实 prompt 变小,但不会移动 Pi 的触发线。 接管压缩本身会缓解这点(新摘要本来就小)。 - 有意丢信息。 已删除的路径从清单消失,清单有条数上限。摘要正文仍会提到关键文件。
要完整清单就设
tool: true+archiveSensitive: true,用session_files检索。 style: "index"+tool: true作者没在生产环境跑过;indexRecent上限只是离线量出来的。- 存档过滤不追溯。 旧条目保持原样。
开发
npm test # 纯逻辑,不需要会话数据
npm run test:plugin # 对着 mock 的 Pi 跑完整钩子链(不调模型)
npm run privacy # 防止把个人路径 / 会话 ID 发出去
npm run check # 上面三个全跑
test/plugin-load.mjs 用 mock 的 compact() 和一次性 HOME 加载扩展,
所以既不读开发者自己的配置,也不调模型。用 PLUGIN_PKG=/path/to/pkg 可以指向另一份副本测。
想在自己机器上量一遍,见 docs/verify-it-works.md。
许可
MIT,见 LICENSE。