此前截断分散在多层且互相冲突:Pi bash 保留尾部 50KB,RTK 兜底截断又只留 开头 12000 字符(砍掉报错与完整输出路径),pi-chrome 截开头 30K,web_fetch 各 provider 不一致。新扩展统一负责"新结果给模型看多少": - capture.ts 最先加载暂存原文,index.ts 最后加载(system-prompt 之前) - 超过 24K 字符时原文落盘 <agentDir>/my-pi/pi-overflow/(700/600、原子写), 优先取 bash fullOutputPath 完整文件;模型看到 头 + 中段信号行 + 尾 的有界 视图,行号基于落盘原文、结尾永远可见 - overflow_query:按 id 正则搜索、行区间、jsonPath 只读查询,id 校验防穿越 - 收编:RTK truncate 默认关闭、删除 pi-chrome truncateText、web_fetch 默认 每页 20000(上限 100000)、web_search max_results 限定 1–20 - pi-compaction 遮蔽带 details.overflow.id 的结果时在占位符提示 overflow_query - 登记 tools.jsonc / routing / 权限 allow / tool-search 常驻与分组 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pi-compaction
Pressure-aware context masking and threshold compaction for Pi.
Responsibilities
Pi context management is layered; this extension owns the middle two layers:
- L1 — entry truncation (future
pi-overflow): truncate new tool results as they are written. Not implemented here. - L2 — observation masking (this extension): under real context pressure, replace old tool results outside a protected recent window with deterministic one-line placeholders via Pi
context_editentries. Originals stay in the session untouched and are recoverable withcontext_tree_query. - L3 — whole-context summary (this extension): when the session idles (
agent_settled) above the summary threshold, triggerctx.compactonce per leaf. - Pi native threshold compaction is the final safety net and is never blocked; this extension no longer cancels
session_before_compactbased on its own thresholds.
Additionally it:
- publishes one effective-context snapshot for normal provider requests and Codex/SubAPI native compaction;
- maintains a masked-results ledger so
/context,/compaction-status, the footer, and AutoReview see the same placeholders the model sees.
Context Mode remains responsible for preventing large external data from entering the session. RTK remains responsible for ordinary tool-output cleanup.
Configuration
Configuration lives in the installed bundle's config/pi-compaction.json (the bundle repository's config/ directory is the single source of truth; pi update refreshes the installed copy). The extension never reads settings.json and never writes config at runtime:
{
"enabled": true,
"masking": {
"enabled": true,
"triggerTokens": 200000,
"triggerRatio": 0.6,
"targetTokens": 100000,
"targetRatio": 0.35,
"minClearTokens": 50000,
"keepRecentTurns": 10,
"minEntryTokens": 256,
"excludeTools": ["ask_user_question"]
},
"summary": {
"triggerTokens": 400000,
"triggerRatio": 0.9,
"thinking": "low"
},
"native": {
"enabled": true
}
}
All thresholds are absolute token counts constrained by a context-window ratio: effectiveThreshold = min(tokens, floor(ratio × contextWindow)). Masking fires when currentTokens ≥ triggerEff and clears the oldest eligible results until currentTokens - cleared ≤ targetEff; if the clearable volume stays under min(minClearTokens, triggerEff - targetEff) nothing is masked, so masking is rare, batched, and hysteretic. masking.target is normalized below masking.trigger and summary.trigger above masking.trigger when a config would violate that ordering. Invalid or missing fields fall back to defaults in memory.
Masking never touches the most recent keepRecentTurns assistant turns, user/assistant messages, excludeTools, SKILL.md reads, entries already masked, or results smaller than minEntryTokens.
Native providers
Native compaction is enabled by the native.enabled config key and uses the model selected in the session when compaction starts. It does not select a separate summarization model.
| Provider | API | Native transport |
|---|---|---|
openai-codex |
openai-codex-responses |
Existing Codex Responses endpoint and JWT/account-ID authentication |
sub-api |
openai-responses |
Public Responses endpoint and resolved gateway headers or API key |
For SubAPI, keep the existing models.json API as openai-responses. The adapter requires an explicit HTTP(S) baseUrl: for example, https://gateway.example/v1 becomes https://gateway.example/v1/responses; an existing /responses suffix is not duplicated. It preserves configured headers and merges the native compaction beta feature, without decoding the API key or adding a ChatGPT account ID. No gateway hostname or credentials are bundled.
SubAPI preserves a non-empty explicit Authorization header (case-insensitively) ahead of the API key. Without it, a non-blank API key supplies Bearer authentication; if no key is available, a non-empty cf-aig-authorization header also supports proxy-managed authentication. Header-only credentials do not create a dummy Bearer token. Missing or blank credentials fail before any network request and return control to Pi. Official Codex still requires its API key/JWT even when explicit authentication headers are present.
Both transports send the current projection as ResponseItems, append compaction_trigger, and parse the SSE compaction item. SubAPI must support Sub2API native remote compaction v2 on its public Responses endpoint. This is not a client for the separate /v1/responses/compact endpoint; supporting only that endpoint or ordinary Responses chat is insufficient.
Request interception also restores SubAPI native checkpoints into subsequent Responses input. Checkpoints retain a bounded portable summary for switching models/providers, and their version 1 format is unchanged. Native authentication, transport or response failures return control to Pi's normal compaction fallback. Other provider/API pairs and native.enabled: false retain the existing portable-summary path. Setting native.enabled to false does not discard an already-created native checkpoint.
The gateway must have a compatible deployed version, model and available upstream account. Offline tests cover the protocol and lifecycle; they do not establish live gateway availability. No settings migration, provider rename or Pi core modification is required.
Session format
The extension writes no segment-summary or other custom entries for masking: L2 uses Pi's native context_edit entries ({ type: "context_edit", targetId, replacement }), which alter only future model context. The raw session entries — and therefore UI, exports, and the evidence ledger — are unchanged; every masked result remains recoverable through context_tree_query. Masking placeholders are deterministic single lines starting with [pi-compaction] Earlier .
Compaction entries still carry pi-compaction version 1 details; native checkpoints use the same kind/version. This implementation intentionally does not read legacy context-prune-*, openai-codex-native-compaction, or older segment-summary entries. Start a new session after switching to this extension.
Interfaces
context_tree_query: recover the original output of a tool result that pi-compaction cleared from context, by tool call ID (1–20 ids per call, bounded output). Use it when a[pi-compaction] Earlierplaceholder lacks evidence needed for the current task.- pi-overflow linkage: when the masked tool result carries
details.overflow.id(pi-overflow stored the oversized original), the placeholder ends withFull original stored: overflow_query {"id":"…"}. pi-compaction only reads that public field and never imports pi-overflow internals. /compaction-status: show raw/effective tokens, reclaimed tokens, confidence, segments, masked results, and generation.- Service
my-pi.pi-compaction.service.v1:getSnapshot,getMetrics, andquery(sessionId, ids)—queryreturns a placeholder record only for tool call IDs that are currently masked, so consumers such as the permission system's AutoReview see the same evidence the model does.
Performance model
The first session hydration scans the branch once. Appended turns use delta projection and immutable cached snapshots; the context hook performs no model call, no session write, and returns the message list unchanged except on the native-checkpoint path, where post-checkpoint context_edit entries are applied during projection. Masking drafts are produced in turn_end in one batch and only when both the trigger and the minClear hysteresis pass, so the hot path stays allocation-free for unprompted turns.