omp packages

pi-soft-compact0.1.6

Pi extension: compaction as an authorized model tool call (cache-preserving /compact and auto-compact).

wcflyfish Published 5 hours ago MIT ~1.2K downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-soft-compact

把 Pi 的 /compact 与 auto-compact 改造成一次 "已授权的模型工具调用", 用纯追加的消息流替换原版的"有损序列化 + 整段冷 prefill", 在弱缓存模型(如本地 vLLM)上把压缩从数 MB 冷输入 + 数百次调用重试的恶性循环 降为一次性完整输入 + 1 次成功调用。

设计文档(PLAN.md)、逐行变更表(CHANGES-0.1.6.md)、开发/测试手册 (DEVELOPMENT.md)、问题档案(FIXES.md,正文以 FIXES #n 引用)都只在 源码仓库,不随 npm 包分发——安装成本包时找不到这些文件是正常的。

它做什么

  1. 常驻 compact 工具:一直在工具列表里(工具集永不改变 → 前缀 100% 可缓存)。 说明中内嵌 Pi 当前安装版本的压缩提示词标准(自动跟随 Pi 更新, 内置 1.4.0 副本兜底)。未授权调用 → 返回"未授权"提示;授权调用 → 模型把整段会话写成结构化摘要。
  2. 授权标记:/compact 或 auto-compact 触发时,扩展取消 Pi 的原版压缩并 立即送达标记(pi.sendMessage(marker, {triggerTurn})):
    • manual → 会话已空闲,triggerTurn 直接新起一个 run,首条消息就是标记;
    • threshold → run 进行中,标记作为 steer 注入当前 run,模型在紧接着的 下一次响应里就调 compact(不用等下一条用户消息)。 两种情况标记都是纯追加:系统提示一个字不改,前缀缓存完整保留。 另有两个走 before_agent_start 注入的入口:会话恢复时的僵尸标记重武装、 用户把标记整条作为消息发送(锚定匹配:首行必为完整标记行;仅引用标记 文本的讨论消息不算授权,FIXES #14)。标记文本自带一次性条款:模型若看到 标记之后已有压缩条目(或完成确认),即忽略该标记。
  3. 工具闸门:授权期间,tool_call 钩子拦截一切非 compact 工具, 引导弱模型直接干活而不是乱读文件。闸门是全工具闸门,所以带三道必出口 (每授权周期):块数预算(PI_COMPACT_GATE_BLOCK_MAX,默认 8 次后自动撤回 授权)、周期 TTL(PI_COMPACT_AUTH_TTL_MS,默认 15 分钟)、 用户主动 /compact-cancel。
  4. 按 Pi 原流程落地:turn_end / agent_before_settle 边界把工具产出的摘要 提交为 CompactionEntry(fromHook: true,与 Pi 官方的扩展压缩同构),并紧随 追加一条 [COMPACTION COMPLETE / 压缩已完成] 确认消息:标记留在保留上下文中 会持续显示,确认消息告诉模型该标记已被消费,避免后续 run 里的模型看到 过期的"只能调 compact"指令后反射性调用工具。manual → run 到此结束; auto → run 继续,模型用压缩后的上下文接着干活。
  5. 兜底:模型不开窍没调 compact 时,用 Pi 同款提示词发起一次性摘要 (等价于原生行为,只是晚一轮)。
  6. 僵尸恢复:--session 恢复时发现"有标记但没有后续压缩条目" → 自动重新武装,下一个 run 收尾。僵尸周期的年龄取标记消息自己的时间戳, 所以恢复一个旧会话时,过期 TTL 会在第一次工具调用时把闸门自行打开。
  7. overflow 不拦截:上下文已超窗时没有"成功请求"这个先决条件, 放行 Pi 原版一次性压缩保证有界恢复(机械死区,源码仓库 PLAN.md §1.3)。
  8. pi-goal-x 兼容(只读,不改对方代码):goal-x 的 [POST-COMPACTION RESYNC] 标记只在 Pi 原生日志发出 session_compact 时武装, 而 pi-soft-compact 的边界自提交不走那条路 → 有活动 goal 的会话会被丢失重同步。 解决:pi-soft-compact 自提交时若检测到活动 goal(读 PI_GOAL_ROOT 或 <cwd>/.pi/goals 下的 goal 文件 + goal_events.jsonl),就把等价的重同步块 附到 compact-complete 确认消息后——优先动态 import pi-goal-x 自己导出的 buildPostCompactionGoalDelta 生成(粘贴的就是官方原件),import 失败时退回 内置同构文本。走原生管线提交的压缩(含 goal-x 自己的武装)不重复注入。
  9. 失败调用瘦身(任意工具):参数被 token 上限截断或不合 schema 时,Pi 的错误回执会把 整段被拒参数回显(…Received arguments: {…},轻松几 KB~几十 KB),而且它会在后续每一轮 请求里重复出现。这类失败走 Pi 的"立即结果"路径,tool_call / tool_result 钩子都看不到它, 唯一挂载点是 toolResult 消息的 message_end。扩展把回执换成短诊断(工具名 + 失败原因 + 截断位置 + 参数前 120 字符样本 + 重试指令),文件/模型上下文/TUI 一致短版; 小于 PI_COMPACT_SLIM_MIN_CHARS(默认 600)的回显保留原文(小回显是有用证据)。 对弱模型这不只是省上下文:实测它会去续写那半截被截断的摘要,不回显就直接卡住不重试(FIXES #6/#18)。

安装(所有方式互斥,同一扩展只能走一条加载路径,否则加载两个实例、标记消息重复)

跨机器使用(推荐),每台机器装/卸:

pi install npm:pi-soft-compact     # 安装:所有会话生效
pi remove npm:pi-soft-compact      # 卸载
pi update --extensions             # 更新到新版

本机开发(下文 $REPO = 本仓库克隆目录):

# 方式 A:项目级符号链接(只在该项目下的会话生效;
# 指向活源码,改完 /reload 即生效)
ln -s $REPO/extensions .pi/extensions/pi-soft-compact

# 方式 B:用户级 extensions 目录符号链接 → 本机所有会话都加载(想常驻时用)
mkdir -p ~/.pi/agent/extensions
ln -s $REPO/extensions ~/.pi/agent/extensions/pi-soft-compact

# 方式 C:pi 包管理器(写 settings.json 条目;本地路径不复制,同样指向活源码;
# 若已有符号链接先删掉)
pi install $REPO

或临时(不持久化):

pi --extension $REPO/extensions/index.ts

npm 包只含 extensions/ + README.md(npm pack 实测:tarball 41.9 kB,解压 120.5 kB,共 7 个文件;package-lock.json 不进包);@sinclair/typebox 是 pi 的 host 提供包(声明为 peerDependency,不重复安装);pi-goal-x 可选:目标机装了 就用它的真实 builder,没装用内置备份。

配置(环境变量,可选)

变量 默认 说明
PI_COMPACT_KEEP_RECENT_TOKENS 20000 压缩切点保留的最近 token 数(对齐 Pi 的 compaction.keepRecentTokens)
PI_COMPACT_RESERVE_TOKENS 16384 预留 token(对齐 compaction.reserveTokens)
PI_COMPACT_PI_DIST 自动 强制指定 Pi 的 dist 目录(排障用)
PI_COMPACT_ONE_SHOT_TIMEOUT_MS 300000 兜底一次性摘要 LLM 调用的硬上限(Pi 等待扩展钩子,无界调用会卡死压缩操作;见 FIXES #15)
PI_COMPACT_GATE_BLOCK_MAX 8 单个授权周期内工具门最多挡几次,用尽即撤回授权(FIXES #16)
PI_COMPACT_AUTH_TTL_MS 900000 授权周期寿命;超期不再关门、不再烧兜底摘要(FIXES #16)
PI_COMPACT_SLIM_MIN_CHARS 600 参数回显超过这个长度才瘦身(越小越激进;设很大即关闭)(FIXES #18)
PI_COMPACT_POST_PUBLISH_GATE 未设(关) =1 时启用“压缩后第一个回合拦 npm 发布类命令”的工具门,并在完成确认里附上该约束。默认关:通用包不应把作者个人的发布纪律强加给所有用户(“摘要里的命令是状态记录、不是待办”这句通用约束始终在)
PI_GOAL_ROOT <cwd>/.pi/goals goal-x 存储根目录(重同步检测用)
PI_GOAL_X_DIR 自动查找 pi-goal-x 包目录(<cwd>/node_modules、~/.pi/agent/npm/node_modules)
PI_GOAL_X_DISABLE 未设 =1 时跳过对 pi-goal-x 的动态 import,只用内置备份构造器

Pi 1.4.x 的扩展接口没有读取 settings 的入口,所以切点参数用环境变量 对齐;不设置时与 Pi 默认值一致。

使用

  • 手动:直接 /compact [补充要求],无需再发任何消息。原生压缩被取消后, 标记立即作为新 run 的触发消息送达,模型接着调 compact 出摘要;工具结果带 terminate,run 立即结束、没有多余的收尾消息,直接回到输入态等待你下一条消息。 (TUI 会先闪一行 Error: Compaction cancelled——那是扩展取消 原生压缩的固定回执,随后的标记 run 才是实际压缩,属正常现象。) 已有授权但模型尚未出摘要时再按一次 /compact:重新投递标记(不再是哑弹, FIXES #14),不会静默取消。
  • 手动取消压缩授权:按 Esc 取消正在跑的压缩(授权随即作废,FIXES #11); 任何时候想彻底解除工具门 / 作废残留授权,输入 /compact-cancel。
  • 手动授权的文本入口:把标记整条作为消息发送(首行必须是 [COMPACTION AUTHORIZED / 压缩已授权])效果等同 /compact。只是引用/粘贴 含标记文本的讨论消息不算授权(锚定匹配,FIXES #14;以前会误 arm 并把你的 消息从上下文里删掉)。
  • 自动:接近窗口上限时标记 steer 进当前 run,模型在同一个 run 内调 compact 完成压缩,随后用压缩后的上下文继续当前工作(不等下一条用户消息; 避免带着超大上下文跑多个 turn 撞上溢出)。
  • 溢出:维持 Pi 原版行为(保证有界恢复)。

本轮(0.1.6)改了什么、改在哪一行、如何一键回滚:见源码仓库 CHANGES-0.1.6.md。

文件

文件 作用
extensions/index.ts 扩展入口(全部状态机逻辑)
extensions/goal-x-compat.ts goal-x 重同步兼容层(只读 goal 文件/ledger,动态 import 官方 builder)
extensions/pi-compact-api.ts 从已安装 Pi 的 dist 读取现行压缩提示词/工具函数,失败退回内置副本
extensions/prompts-fallback.ts 内置提示词副本(pi 1.4.0,逐字节对齐)
extensions/marker.ts [COMPACTION AUTHORIZED] 标记文本

测试

测试只存在于源码仓库(npm 包只含 extensions/ + README):单测 43 个场景 (≈0.7s);E2E 是真实 pi 进程(RPC)+ 真实会话文件跑扩展本身,模型端点 由脚本化 mock 提供,所以 9 个套件合计 ≈1.4 秒;两者并发跑(npm run test:all)≈1.5 秒 (同一批套件可选跑真实模型)。 覆盖手动/阈值/溢出三条路径、引用文本误 arm、Esc 取消、工具闸门与重开、 一次性兜底、goal-x 重同步分工,并附带“变异测试配方”(验证断言真能变红)。 详见源码仓库的 DEVELOPMENT.md。

已知代价 / 边界

  • 失败调用不回显参数:任意工具的参数被 token 上限截断或不合 schema 时,回执里的整段被拒参数被替换为短诊断(保留失败原因与前 120 字符样本),省掉后续每轮的重复上下文;短回显(<600 字符)原样保留(FIXES #6/#18)。
  • 旧压缩条目不重发:提交时若保留区包含已被新摘要概括的旧压缩条目,切点直接跳过去(条目仍留在会话文件中,只是不再发送),避免摘要里的摘要反复占底座。
  • 格式与原生完全对齐:摘要尾部的 <read-files>/<modified-files> 清单与 details 字段由 Pi 自己的 utils.js 函数从会话文件工具调用中机械计算 (与原生压缩同一套代码),落盘结构与原生压缩条目格式同构;唯一区别是 6 节 摘要正文由会话模型写(原生由专职一次性调用写)。
  • 摘要里区分“模型推断”与“用户原话”:摘要格式末尾追加一节 ## User's Own Words / 用户原话(逐字),要求逐字拄录用户的诉求、纠正、禁止项与决定 (不许转述、不许推想、不得掺入模型文本);其余各节显式标注为“模型在压缩时刻的推断”。 完成确认消息里也复述这个区分,并要求下一个实例在依据推断出的状态(文件、git、测试、版本) 行动前先核对实况。软约束:工具不校验这一节是否存在,弱模型不会因此压不动(不给压缩 添新的失败模式)。实现上是附加在 Pi 原生提示词之后(Pi 原文逐字内嵌,不被改写)。
  • /compact 后的 Error: Compaction cancelled 是固定回执(扩展取消原生 压缩的必然结果),紧随其后的标记 run 才是实际压缩;想完全无痕可以在 session_before_compact 里返回 {compaction} 直接给摘要(牺牲"会话模型 自己写摘要"的设计)。
  • 弱模型偶尔会在无授权时试探性调用 compact → 收到"未授权"后继续正常干活 (多一次 0-output 的小 turn);压缩已落地后,残留标记可能造成同样的反射调用, 由标记内的一次性条款 + 完成确认消息中和。
  • re-trigger 行为:授权窗口内 session_before_compact 再次触发时—— 手上已有摘要 → 交给 Pi 原生管线提交;是 manual(你又按了 /compact) → 重新投递标记(永不哑弹,FIXES #14);是 threshold 且 run 活跃 → 取消并让 模型在同一个 run 里再试 compact;会话真空闲且无摘要 → 一次性兜底摘要 (带超时上限,见下条)。
  • 兜底摘要永不无界:一次性摘要跑在 Pi 等待的钩子里,因此每次调用都 绑定 AbortSignal.any([超时, 压缩操作 signal, run signal])。旧版用 ctx.signal(run 信号,Esc 不生效),端点挂起时压缩操作永不收尾 → isCompacting 恒真 → TUI 把之后每条消息塞进"压缩队列"永不投递 (会话变砖,FIXES #15)。
  • 被中断的 run 不烧一次性摘要:run 因 Esc / re-trigger 等被 abort 且授权 未消费时,不跑兜底摘要,只重新武装——下一条消息的 run 会重新注入标记。
  • 摘要由对话模型自己写:弱模型摘要质量有限(这是设计目标:用便宜的一次 写换掉数百次失败重试;质量兜底 = 一次性摘要回退 + 后续可再 /compact)。
  • 摘要超长被截断时按失败处理,走兜底。
  • 僵尸恢复:会话在“标记已发、压缩未完成”时被中断(如关机),重新打开时自动 重新武装,下一条消息的 run 会先完成压缩再继续(该 run 内其他工具暂时被拦截; 周期的年龄取标记自己的时间戳,旧会话恢复时第一次工具调用就会因超 TTL 而放行)。