pi-context-guard-jev 0.1.0 β†’ 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ # Reference
2
+
3
+ Everything you can see, call and configure. How it works inside: [DESIGN.md](DESIGN.md).
4
+
5
+ ## Requirements
6
+
7
+ - Pi β‰₯ 1.0.3
8
+ - An OpenRouter credential (`OPENROUTER_API_KEY` or `/login`) for the default classifier,
9
+ `openrouter/~typesafe/jev-latest`. Any classifier model in Pi's catalog can be set with the `model`
10
+ setting, but only the default has been tested.
11
+
12
+ If the model or credential is missing, the extension does nothing and the status bar says so.
13
+
14
+ ## Status bar
15
+
16
+ | Text | Meaning |
17
+ |---|---|
18
+ | `πŸ›‘ 0 saved` | On and ready; nothing is distilled on this branch yet |
19
+ | `πŸ›‘ 0 Β· Ξ£βˆ’79k` | A compaction dropped the earlier edits from context: nothing is kept out right now, about 79k tokens were saved on this branch in total |
20
+ | `πŸ›‘ distilling…` | Jev is judging the outputs of the run that just finished, or of a mid-run checkpoint |
21
+ | `πŸ›‘ βˆ’4.2k Β· 1` | β‰ˆ tokens kept out of context Β· items: about 4.2k tokens are currently kept out of context, by 1 distilled or omitted tool output, output without its images, or old exchange |
22
+ | `πŸ›‘ βˆ’4.2k Β· 1 Β· Ξ£βˆ’83k` | The same, after a compaction: Ξ£ is the total saved on this branch, including edits the compaction dropped |
23
+ | `πŸ›‘ guard off` | Turned off with `/guard off` or `"enabled": false` |
24
+ | `πŸ›‘ no openrouter key` | No credential for the classifier's provider (the provider name is filled in) |
25
+ | `πŸ›‘ Jev model not found` | The configured `model` is not in Pi's classifier catalog |
26
+
27
+ The token figure is Pi's usual estimate, characters Γ· 4. It counts what is kept out of the model's context
28
+ **on the current branch right now**: original size minus replacement size, for every distilled output,
29
+ removed image and omitted exchange that is still in the context (an image counts as about width Γ— height Γ· 750
30
+ tokens). It goes down after `/tree` to a point before an edit, and after compaction removes distilled
31
+ results; then Ξ£ shows the total of every pass on the branch so far. It is not a measured token count.
32
+
33
+ ## `/guard` command
34
+
35
+ | Command | Effect |
36
+ |---|---|
37
+ | `/guard` or `/guard status` | Shows whether the guard is on, the model, the savings on this branch (distilled outputs and omitted exchanges), Jev runs and mid-run checkpoints, requests and cost, whether checkpoints and exchange pruning are on, and the last run's or checkpoint's result for each item (large, small, exchange), including Jev traces such as `kw.15 focus=chunk_4:.52 keep 9/29` or `p.06` |
38
+ | `/guard on` / `/guard off` | Turns the guard on or off and saves `enabled` to the settings file |
39
+ | `/guard reload` | Re-reads the settings file and shows any warnings again |
40
+
41
+ Settings are read at session start and on `/guard reload`.
42
+
43
+ ## `recall` tool
44
+
45
+ Every distilled output and omitted exchange starts with `[context-guard]` and ends with
46
+ `recall({"entryId":"…"})`. For an omitted exchange, recall returns the whole exchange as a transcript
47
+ (prompt, replies, tool calls and results, custom messages). The model can call:
48
+
49
+ | Parameter | Meaning |
50
+ |---|---|
51
+ | `entryId` (required) | The entry id from the header |
52
+ | `pattern` | JavaScript regex (≀ 500 chars, 1 s limit); returns only matching lines, prefixed with their line numbers |
53
+ | `offset` | First line to return, 1-based. When `pattern` is set, this counts matching lines. |
54
+ | `limit` | Most lines to return |
55
+
56
+ Like Pi's `read` tool, the output is capped at 2,000 lines and 50 KB, and a note says how to continue.
57
+
58
+ ## Configuration
59
+
60
+ The settings file is `~/.pi/agent/context-guard.json` (inside Pi's agent directory), or the path in
61
+ `PI_CONTEXT_GUARD_CONFIG`. Every key is optional. Values that are missing, the wrong type or out of range
62
+ fall back to the default.
63
+
64
+ | Key | Default | Meaning |
65
+ |---|---|---|
66
+ | `enabled` | `true` | Master switch (`/guard on` and `/guard off` write it) |
67
+ | `model` | `"openrouter/~typesafe/jev-latest"` | Classifier as `provider/id`; the first `/` separates the provider |
68
+ | `minResultChars` | `4000` | Tool results from this size are chunked; shorter ones are judged whole (see `smallResultMinChars`) |
69
+ | `minRunChars` | `8000` | Distill a run's large outputs only if they add up to at least this (small outputs and exchanges are judged regardless) |
70
+ | `keepWholeThreshold` | `0.7` | Keep a segment untouched if P(`keep_whole`) is at least this |
71
+ | `focusWholeThreshold` | `0.6` | Keep a segment untouched if `focus` picks "whole" with at least this probability |
72
+ | `chunkKeepThreshold` | `0.6` | Keep a chunk if P(chunk needed) is at least this |
73
+ | `noneThreshold` | `0.5` | Remove a segment entirely if `focus` picks "none" with at least this probability and no chunk passes |
74
+ | `maxKeepRatio` | `0.6` | Skip the edit if the kept chunks are more than this fraction of the original |
75
+ | `keepCitedFiles` | `false` | Also keep grep chunks for files that the final answer names (see DESIGN.md for why it is off) |
76
+ | `timeoutMs` | `8000` | Total time budget for all Jev requests of one run; unfinished parts are kept |
77
+ | `concurrency` | `6` | Jev requests in parallel |
78
+ | `maxSegmentChars` | `32000` | Most characters of one output per Jev request (keeps the worst-case request under Jev's 32k-token context, see [JEV.md](JEV.md)) |
79
+ | `maxChunksPerSegment` | `40` | Most chunks per Jev request |
80
+ | `excludeTools` | `["edit", "write"]` | Tools whose results are never distilled |
81
+ | `distillErrors` | `true` | Also distill error results, such as the log of a failing test or build; Jev is told the call failed, and that its details are usually no longer needed once the agent got past it |
82
+ | `historyExchanges` | `3` | Earlier exchanges (prompt + last assistant text) sent to Jev so it judges relevance against the ongoing work; `0` sends no earlier conversation at all (integer 0–10) |
83
+ | `pinAnthropicCache` | `true` | Anthropic cache breakpoints: at the previous user question on every request, and on the first request after context edits a read point (at a cache entry this process wrote) and a write anchor ([CACHE.md](CACHE.md)) |
84
+ | `midRun` | `true` | Mid-run checkpoints during long runs ([RESULTS.md](RESULTS.md#long-autonomous-runs)) |
85
+ | `midRunMinAgeTurns` | `4` | Judge an output mid-run only once it is at least this many turns old (integer 1–100) |
86
+ | `midRunBatchChars` | `60000` | Run a checkpoint only when the not-yet-judged eligible outputs add up to this many characters |
87
+ | `midRunBatchCharsOpenAI` | `60000` | The same, for `openai-codex` models |
88
+ | `midRunChunkKeepThreshold` | `0.6` | Keep a chunk at a checkpoint if P(still needed) is at least this (probe in [JEV.md](JEV.md)) |
89
+ | `midRunBreakEven` | `true` | Skip a checkpoint unless its one-time prompt-cache rewrite is likely to pay off; `false` checkpoints purely for context room |
90
+ | `smallResultMinChars` | `400` | Tool results from this size up to `minResultChars` are judged as whole items; `0` turns this off |
91
+ | `smallKeepThreshold` | `0.45` | Keep a small output if P(still needed) is at least this (probe in [JEV.md](JEV.md)) |
92
+ | `pruneExchanges` | `true` | Judge old exchanges and omit the unrelated ones |
93
+ | `keepRecentExchanges` | `2` | The last this many exchanges before the current prompt are never omitted |
94
+ | `exchangeOmitThreshold` | `0.2` | Omit an exchange only if P(still relevant) is below this (probe in [JEV.md](JEV.md)) |
95
+ | `exchangeBreakEven` | `true` | At run end, omit unrelated exchanges only when the cache rewrite they cause pays off; deferred ones are judged again at later run ends and omitted once their total pays off. `false` always omits them |
96
+ | `exchangeMinSavingChars` | `8000` | A run end omits exchanges only if they save at least this many characters together (when they cost a cache rewrite) |
97
+ | `imageKeepTurns` | `3` | Remove images from tool results once this many assistant messages have followed them (`recall` shows them again); images in user messages are kept; `0` turns this off |
98
+
99
+ Example:
100
+
101
+ ```json
102
+ { "minResultChars": 6000, "excludeTools": ["edit", "write", "bash"] }
103
+ ```
104
+
105
+ ## Data sent to Jev
106
+
107
+ **This extension sends data to a third party, and it is on by default.** After every run that qualifies,
108
+ each Jev request sends the following to OpenRouter, which forwards it to TypeSafe:
109
+
110
+ - the user's question (up to 4,000 characters),
111
+ - the final answer (up to 6,000 characters),
112
+ - the assistant's notes written during the run (up to 2,000 characters),
113
+ - excerpts of the earlier conversation in this session: the latest compaction or branch summary (up to
114
+ 2,000 characters), the first prompt (up to 1,000), and the last 3 earlier exchanges, each one prompt
115
+ (up to 800) and the last assistant text after it (up to 1,200). Tool calls and tool outputs of earlier runs
116
+ are not included in this part. Set `historyExchanges` to `0` to send none of it,
117
+ - the tool name and arguments,
118
+ - the full text of the large tool output being judged, and of small outputs (from 400 characters; up to
119
+ 4,000 characters each). This can include source code, logs or anything else the tools returned,
120
+ - excerpts of old exchanges being judged: each prompt (up to 600 characters), the last assistant text (up to
121
+ 800), the tool calls as short labels (tool name and the command or path, up to 80 characters each) and the
122
+ size. Set `pruneExchanges` to `false` to send none of it.
123
+
124
+ Never sent: tool outputs below `smallResultMinChars` (400 characters), results of excluded tools (`edit`,
125
+ `write`), images (never sent: they are removed by age), the text of outputs with images, old exchanges with entries another extension changed,
126
+ exchanges below 2,000 characters, and the last `keepRecentExchanges` (2) exchanges before the current
127
+ prompt. Large outputs are sent only when the run's large outputs add up to `minRunChars` (8,000); small
128
+ outputs and old exchanges are sent at every run end and every mid-run checkpoint that has any.
129
+
130
+ `/guard off` turns the extension off and saves that choice in the settings file, so it stays off in later
131
+ sessions. See OpenRouter's data policy:
132
+ <https://openrouter.ai/docs/guides/privacy/data-collection>.
133
+
134
+ ## What it never touches
135
+
136
+ - messages of the current run other than tool results, and the last `keepRecentExchanges` exchanges before
137
+ the current prompt
138
+ - compaction and branch summaries, exchanges that did not finish (the last reply has tool calls), and
139
+ messages Pi cannot edit (system messages, `!` shell executions), which stay in place inside an omitted
140
+ exchange
141
+ - images in your own messages; images in tool results younger than `imageKeepTurns` (3) turns
142
+ - the text of tool results that contain images (only the images are removed, by age)
143
+ - error results, if `distillErrors` is `false`
144
+ - results of excluded tools (`edit`, `write` by default)
145
+ - results shorter than `smallResultMinChars` (400), and large results of runs below `minRunChars`
146
+ - aborted or failed runs
147
+ - results that are already distilled, or that another extension already edited
148
+ - single lines too long for one Jev request (kept as they are)
149
+ - tool outputs of earlier runs one by one: earlier runs are only touched as whole exchanges (and their old
150
+ images)
151
+
152
+ ## Limitations
153
+
154
+ - **Lossy by design.** Relevance is judged against the run's question and answer and the earlier
155
+ conversation (the ongoing task), so a follow-up that goes somewhere new may need something that was
156
+ removed. The header and omission lines say what is missing, and `recall` returns it. An earlier task
157
+ that comes back after several unrelated ones may have been omitted as a whole exchange.
158
+ - **Latency.** Settling waits for Jev. This adds about 0.5–1.2 s after each qualifying run (measured), and
159
+ never more than `timeoutMs` (8 s). A mid-run checkpoint pauses the run the same way.
160
+ - **Cost.** Each qualifying run costs a few Jev requests, measured at under $0.001 per run. The first prompt
161
+ after an edit also has a one-time partial prompt-cache miss ([CACHE.md](CACHE.md)).
162
+ - **Steering.** A message sent during a run joins the run's question. Outputs from before it can be distilled,
163
+ but the first edit before it rewrites the whole prompt cache once (the question pin moves to the steering
164
+ message).
@@ -0,0 +1,146 @@
1
+ # Measured results
2
+
3
+ Single runs on Pi 1.0.3, not a benchmark: the main model takes a different path each time. Costs are what Pi
4
+ reports at list price. How to reproduce them: [TESTING.md](TESTING.md).
5
+
6
+ ## Examples
7
+
8
+ Real runs on public code: each example ran once with the guard on and once with it off (`--no-guard`), from
9
+ the same prompts. Reproduce with `node test/e2e/examples.mjs`.
10
+ Examples A–C judge large outputs only (their numbers match `smallResultMinChars: 0`); example D shows the
11
+ default configuration and `exchangeBreakEven: false`.
12
+
13
+ **A. Searching a real codebase.** In a copy of the published Pi package (`dist/` without source maps and
14
+ the minified bundle, plus `docs/`), the agent was asked where Pi decides to auto-compact, starting with
15
+ `rg -n -i compact dist docs`. Pi's bash tool returned the last 50 KB of that search. Jev kept the matches
16
+ in the compaction module, the settings and the decision points in `agent-session.js`, and dropped matches
17
+ in the grep tool, HTML export templates, the SDK and so on. Then the agent was asked "which of those
18
+ settings can a user change, and what are their defaults?".
19
+ - Claude Sonnet 5.5: 50,413 β†’ 16,210 chars (141 of 431 lines kept). The first turn-2 request carried
20
+ **15.5k tokens instead of 28.2k**. The answer was correct (`enabled` true, `reserveTokens` 16384,
21
+ `keepRecentTokens` 20000), without `recall`.
22
+ - GPT-6 Luna: 51,216 β†’ 13,023 chars (107 of 385 lines), and a 5.4k read of `compaction.js` β†’ 1.8k; two
23
+ smaller outputs were left whole. Turn 2 carried **11.5k tokens instead of 34.3k** and was correct, without
24
+ `recall`.
25
+
26
+ **C. Reading a 164 KB source file.** The agent read `dist/core/agent-session.js` (3,535 lines) in
27
+ seven 500-line pieces and explained how a prompt reaches the model. Jev kept 45–164 lines of each of the first
28
+ four pieces and removed the last three completely (each became a one-line stub). The follow-up asked for a
29
+ detail the explanation never mentioned: the exact error thrown when a prompt arrives during compaction.
30
+ That line was among the kept ones, so the agent quoted it exactly without `recall`. The turn-2 request
31
+ carried **9.2k tokens instead of 35.3k**.
32
+
33
+ **B. A failing test run.** In a copy of this repository with one injected off-by-one bug, the agent ran
34
+ the unit tests (a 15.8k-character log, 3 failures among 116 tests) and reported the cause. Jev kept the
35
+ 3 failures with their assertion messages and the failing-test summary, and dropped the passing tests:
36
+ 15.8k β†’ 8.6k chars. Turn 2 ("now fix it") carried **5.7k tokens instead of 7.5k**, and both runs fixed
37
+ the bug. Failing commands are distilled because `distillErrors` is on by default.
38
+
39
+ What the model sees after distilling (example A, Claude; the first lines of the real replacement):
40
+
41
+ ```text
42
+ [context-guard] Distilled the output of bash `rg -n -i compact dist docs`: kept 141 of 431 lines (15.1k of 50.4k chars); the rest was judged not needed for the answer. Full output: recall({"entryId":"1704787f"}).
43
+ [… 79 lines omitted: matches in dist/core/tools/grep.js, dist/core/session-manager.js, dist/core/export-html/template.js, dist/core/export-html/template.css, dist/core/sdk.js and 5 more …]
44
+ dist/core/compaction/compaction.d.ts:25: /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
45
+ dist/core/compaction/compaction.d.ts:28:export interface CompactionSettings {
46
+ dist/core/compaction/compaction.d.ts:33:export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
47
+ dist/core/compaction/compaction.d.ts:54:/** Estimate projected context without trusting usage captured before a later edit or compaction. */
48
+ dist/core/compaction/compaction.d.ts:57: * Check if compaction should trigger based on context usage.
49
+ dist/core/compaction/compaction.d.ts:59:export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
50
+ [… 44 lines omitted: matches in dist/core/compaction/compaction.d.ts, dist/core/compaction/branch-summarization.d.ts, dist/core/compaction/index.d.ts, dist/core/session-manager.d.ts, dist/core/cache-stats.js and 2 more …]
51
+ ```
52
+
53
+ | Example | Model | Tool output before β†’ after | Jev | Next request's context, on / off | Main-model cost of turn 2, on / off | Follow-up |
54
+ |---|---|---|---|---|---|---|
55
+ | A. code search | Claude Sonnet 5.5 | 50.4k β†’ 16.2k chars | 3 requests, 0.8 s, $0.0012 | 15.5k / 28.2k tokens | $0.033 / $0.016 | correct, no `recall` |
56
+ | A. code search | GPT-6 Luna | 56.6k β†’ 14.8k chars (2 outputs; 2 more, 15k, left whole) | 5 requests, 0.6 s, $0.0015 | 11.5k / 34.3k tokens | $0.0012 / $0.0005 | correct, no `recall` |
57
+ | C. 164 KB file | GPT-6 Luna | 163k β†’ 26k chars (7 reads) | 7 requests, 0.9 s, $0.0027 | 9.2k / 35.3k tokens | $0.0007 / $0.0005 | correct, no `recall` |
58
+ | B. failing test log | GPT-6 Luna | 15.8k β†’ 8.6k chars | 1 request, 0.8 s, $0.0004 | 5.7k / 7.5k tokens | $0.0022 / $0.0016 | fixed in both runs |
59
+ | D. five topics | Claude Sonnet 5.5, `exchangeBreakEven: false` | 1 old exchange omitted (8.8k chars) | 2 requests, $0.0003 | 8.0k / 12.8k tokens (prompt 5) | $0.017 / $0.006 (prompt 5's first request) | correct, searched again |
60
+
61
+ **D. Several topics in one session.** In the generated test repo, five prompts in a row: which API
62
+ clients retry on 503; a README paragraph for `StatusBar`; a count of HTTP 500 lines in a log; where
63
+ `StatusBadge` is used; and then "back to the clients that retry on 503: which of them also retry on 502?"
64
+ (`node test/e2e/run-e2e.mjs --scenario topics --turns 5`). The README exchange is under 2,000 characters
65
+ and is never judged; the last 2 exchanges before each prompt are never judged either.
66
+
67
+ | Model, configuration | First exchange at the end of prompt 4 | At the end of prompt 5 | Prompt 5 | Context of prompt 5's first request | Main-model cost | Jev |
68
+ |---|---|---|---|---|---|---|
69
+ | GPT-6 Luna, default | judged unrelated (P = 0.07), **deferred**: too small to pay for the cache rewrite in a 4-prompt session | relevant (P = 0.95), kept | correct, no `recall` | 7.5k, 6,656 read from cache | $0.0018 | $0.0005 |
70
+ | Claude Sonnet 5.5, default | unrelated (P = 0.07), deferred | relevant (P = 0.97), kept | correct, no `recall` | 11.5k, 11,049 read from cache | $0.073 | $0.0003 |
71
+ | Claude Sonnet 5.5, `exchangeBreakEven: false` | unrelated (P = 0.08), **omitted** (8.8k characters, 5 entries) | – | correct, searched again | 8.0k: 1,760 read, 6,280 written | $0.087 | $0.0003 |
72
+ | Claude Sonnet 5.5, guard off | – | – | – | 12.8k | $0.083 | – |
73
+
74
+ In this short session the gate kept the first exchange, which the follow-up then needed. With the gate off,
75
+ the omission made the next request rewrite the cache from the omitted exchange on (it comes before the
76
+ question pin) and the model searched again; the smaller context pays off only in longer sessions.
77
+
78
+ **When it doesn't help.**
79
+
80
+ - **The first request after an edit costs more, not less.** The prompt cache only matches up to the
81
+ first changed message, so the next request writes the shorter history again instead of reading the long
82
+ one from cache. In A that made Claude's turn-2 request cost $0.033 instead of $0.016, and GPT's $0.0012
83
+ instead of $0.0005; OpenAI read nothing from cache on that request (in the fixture runs it read 2,560
84
+ tokens instead of 13,824: a partial miss).
85
+ At the per-token prices in these runs, the smaller context earns that back after about 7 more requests
86
+ (Claude, A), about 3 (GPT, A) or about 1 (GPT, C). Before that, the benefit is a smaller context window,
87
+ not a lower bill. Anthropic requests also get a cache breakpoint at the previous question; in a run with
88
+ 11 sequential tool calls it raised the first request's cache read from 1,693 to 5,810 tokens compared
89
+ with `pinAnthropicCache: false` ([CACHE.md](CACHE.md)).
90
+ - **Small or fully needed outputs are left whole.** In A, GPT's 9k-character read of `agent-session.js` was
91
+ `not-worth` (too much of it was needed) and a 6k narrowed search was judged needed whole.
92
+
93
+ Numbers on the generated StatusBadge test repo (96% removed, `recall` of a removed line) are in
94
+ [TESTING.md](TESTING.md#results-pi-103).
95
+
96
+ ## Long autonomous runs
97
+
98
+ **Measured** (Pi 1.0.3, e2e scenario `long`: "fix all failing tests" in a generated project with 10 bugs,
99
+ the full suite after every fix; single runs, not a benchmark; costs are what Pi reports at list price):
100
+
101
+ | Main model | Configuration | Requests | Peak context | Main-model cost | Checkpoints (Jev) | Suite green |
102
+ |---|---|---|---|---|---|---|
103
+ | GPT-6 Luna | guard off | 32 / 37 | 68.4k / 87.7k | $0.020 / $0.030 | – | yes / yes |
104
+ | GPT-6 Luna | `midRun: false` (run end only) | 26 | 86.3k | $0.022 | – (run end $0.006) | yes |
105
+ | GPT-6 Luna | default | 29 | **45.0k** | $0.021 | 2 at requests 16 and 24, 0.8 s each, removed 62k and 60k chars ($0.006 with run end) | yes |
106
+ | GPT-6 Luna | `smallResultMinChars: 0` (large outputs only) | 31 | 72.4k | $0.024 | 1 at request 19, 1.0 s, removed 154k chars ($0.007 with run end) | yes |
107
+ | Claude Sonnet 5.5 | guard off | 14 | 89.8k | $0.41 | – | yes |
108
+ | Claude Sonnet 5.5 | `smallResultMinChars: 0` | 8 | 65.7k | $0.24 | none: too short to pay off (run end $0.003) | yes |
109
+ | Claude Sonnet 5.5 | forced: `midRunBreakEven: false`, `midRunMinAgeTurns: 2`, `midRunBatchChars: 20000`, `smallResultMinChars: 0` | 12 | 43.3k | $0.34 | 3 ($0.0032 with run end) | yes |
110
+
111
+ Context per request (every 5th request), GPT-6 Luna:
112
+
113
+ | Request | 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 |
114
+ |---|---|---|---|---|---|---|---|---|
115
+ | guard off (37 requests) | 3.0k | 14.6k | 29.3k | 41.4k | 52.1k | 62.5k | 73.3k | 83.4k |
116
+ | default (29 requests) | 3.2k | 17.0k | 35.3k | 41.0k | 39.3k | 28.9k | – | – |
117
+
118
+ At the default run's first checkpoint, Jev distilled four superseded test logs (for example 30,164 β†’ 8,163)
119
+ and replaced 4 of 7 small reads of files the agent had already edited with one-line notes (for example
120
+ 3,386 β†’ 145); the next request carried 20.1k instead of 41.0k tokens. No run needed `recall`.
121
+
122
+ **Trade-offs:**
123
+ - **OpenAI Codex reads nothing from cache after any edit** (2,560 or 0 tokens on the next request, after
124
+ every checkpoint measured, wherever the edit was). Each checkpoint re-sends the context uncached once and
125
+ pays back after about 8–10 later requests. In these 26–37-request runs the main-model cost came out
126
+ about the same with and without checkpoints, plus about $0.005 of Jev per run; the gain is a smaller
127
+ context.
128
+ - **On Anthropic a checkpoint rewrites the cache after the latest entry the next request can read** (at worst after the question; writing costs 12.5Γ— the read price).
129
+ In the forced run, the requests after the three checkpoints read 5.8k / 16.7k / 16.7k tokens and wrote
130
+ 10.9k / 22.8k / 20.0k. After the first checkpoint of a run only the system prompt and question can be
131
+ read, because the run's first output is usually the first one edited. Claude solved the task in 8–14
132
+ requests, too few for a checkpoint to pay off, so with the default break-even rule it never
133
+ checkpointed; forced checkpoints in such short runs cost more than they saved. Checkpoints are meant
134
+ for runs that go on for dozens of requests.
135
+ - **Latency mid-run:** a checkpoint pauses the run for the Jev requests (0.5–2.2 s measured, at most
136
+ `timeoutMs`).
137
+ - Details: [CACHE.md](CACHE.md#mid-run-checkpoints), [DESIGN.md](DESIGN.md#mid-run-checkpoints),
138
+ wording probe in [JEV.md](JEV.md#checkpoint-wording).
139
+
140
+ ## Old images
141
+
142
+ Claude Sonnet 5.5 with `pi-claude-auth`, 2026-10-08: six 1200Γ—800 PNGs, read one per message, then a
143
+ follow-up about the second one. The run end removed the four images that were 3–6 turns old, with no Jev
144
+ request (19.9k chars β‰ˆ 5k tokens). The next request carried 2 images instead of 6: 9.4k input tokens
145
+ instead of about 14.1k. Told not to read the file again, the model called `recall`, got the image back and
146
+ read the code number in it correctly.
package/docs/TESTING.md CHANGED
@@ -13,15 +13,19 @@ These need Node β‰₯ 22.18, which runs the `.ts` files directly. There is no netw
13
13
 
14
14
  | File | Covers |
15
15
  |---|---|
16
- | `run.test.ts` | Run span (including image-only prompts), question/answer/notes, candidate rules (errors, images, excluded tools, short, already-distilled and other-extension-edited results), steering messages; history (last N exchanges, first request, compaction/branch summaries, image-only and steering prompts, clipping, `historyExchanges` 0) |
16
+ | `checkpoint.test.ts` | Mid-run eligibility and age, the memo and candidate rules, later calls, notes, superseded detection (edits, re-reads, reruns), cache anchors (read point only from logged entries of the same model within the TTL, invalidated by later edits and compaction, parallel-call batches never split, early return without pending edits), refresh on read-through (same model, cacheRead clearly past the entry, unchanged prefix; trusted after 5 minutes once refreshed), the run baseline, the Anthropic rewrite estimate (from the trusted read point, else from the question), pending edits, the memo from records, the break-even rule, context size with summaries |
17
+ | `items.test.ts` | Small outputs (size window, candidate rules, age, memo, stub length, superseded), exchange spans (custom messages, completed exchanges only, the current run and the recent exchanges never touched, already omitted, memo; images, foreign edits and foreign omissions skipped, our own markers allowed; system, `!` and custom messages; a compaction that cut an exchange; minimum size), stubs and edits, the small and exchange requests in both phases, batching, the shared limiter, `judgeItems` (parallel limit, failures keep, time budget), `processItems` (stubs, omissions, records with kinds, one `concurrency` across all kinds) |
18
+ | `run.test.ts` | Run span (including image-only prompts), question/answer/notes, candidate rules (errors, images, excluded tools, short, already-distilled and other-extension-edited results), steering messages (continue the run, join the question); history (last N exchanges, first request, compaction/branch summaries, image-only and steering prompts, clipping, `historyExchanges` 0) |
17
19
  | `chunk.test.ts` | grep detection, grouping by file, context lines and `--`, `-digits-` file names, split/merge rules, plain-text windows, segment limits |
18
- | `decide.test.ts` | Request shape, with and without `earlier_conversation` (state order, omitted parts, history wording vs. the unchanged single-run wording); the decision order (error, missing answers, `keep_whole`, strong/weak `whole`, chunks, `none`); `citedFiles` |
20
+ | `decide.test.ts` | Checkpoint request shape and wording; run-end request shape, with and without `earlier_conversation` (state order, omitted parts, history wording vs. the unchanged single-run wording); the decision order (error, missing answers, `keep_whole`, strong/weak `whole`, chunks, `none`); `citedFiles` |
19
21
  | `distill.test.ts` | Skips, edits, `removed`, `not-worth`, failures, time budget (including a classifier that never answers and late answers), parent abort, concurrency limit, multi-segment outputs, oversize segments never sent, `keepCitedFiles`, usage sums |
20
22
  | `render.test.ts` | Labels, verbatim chunks with omission lines, `read` offsets, file lists, full removal |
21
- | `cache-pin.test.ts` | API-key and OAuth payloads, trailing system messages, TTL selection and `ttl-conflict`, string content, tool-result-only messages, over-budget restore |
22
- | `stats.test.ts` | Savings from the projection, run records, status texts and colours, cost format |
23
+ | `cache-pin.test.ts` | Question pin: API-key and OAuth payloads, trailing system messages, TTL selection and `ttl-conflict`, string content, tool-result-only messages, over-budget restore. Anchors: question > read > write priority, read anchor inside a batch of tool_result blocks, breakpoint budget (identity and tools breakpoints dropped, then write, then read; the question survives a foreign breakpoint), not found / last message / already marked, normalized tool ids, TTL order, `markedToolResults` |
24
+ | `stats.test.ts` | Savings from the projection (images by pixels), run records (checkpoints counted), lifetime total and `compacted`, status texts (`πŸ›‘ 0 Β· Ξ£βˆ’79k`) and colours, cost format |
25
+ | `images.test.ts` | Old images: age rule, keep-turns 0/1/3, stub text and sizes, user images / already removed / foreign-edited results skipped |
26
+ | `size.test.ts` | Image sizes from PNG/JPEG/GIF/WebP headers, image tokens by pixels (not base64 length), thinking and tool-call sizes, `contextChars` with screenshots |
23
27
  | `recall.test.ts` | Original output, pattern (length cap, 4,000-char match window, catastrophic-regex timeout), offset/limit, line and byte caps, oversized lines |
24
- | `config.test.ts` | Defaults, validation and ranges (incl. `historyExchanges` 0–10 integer), `provider/id` parsing, load/save round trip |
28
+ | `config.test.ts` | Defaults, validation and ranges (incl. `historyExchanges` 0–10 integer and the `midRun*` settings), `provider/id` parsing, load/save round trip |
25
29
  | `extension.test.ts` | The real entry point (see below) |
26
30
 
27
31
  `extension.test.ts` loads `src/index.ts` through **Pi's own extension loader** (`loadExtensions` from the
@@ -29,7 +33,31 @@ These need Node β‰₯ 22.18, which runs the `.ts` files directly. There is no netw
29
33
  `PI_CODING_AGENT_DIR` and `PI_CONTEXT_GUARD_CONFIG` to a temporary directory, so `/guard on|off` never touches the real
30
34
  `~/.pi/agent/context-guard.json`. It checks that the handlers, tool and command are registered, that drafts
31
35
  are appended after other extensions' drafts, that unfinished runs and a missing model are ignored, that the
32
- pin applies only to `anthropic-messages`, that `recall` works, and that `/guard off|on` persists.
36
+ pin applies only to `anthropic-messages`, that `recall` works, and that `/guard off|on` persists. For
37
+ `turn_end` it checks that the final turn is skipped, that a batch below `midRunBatchChars` and a checkpoint
38
+ that would not break even are skipped, that a due checkpoint appends edits and a `mid-run` record after other
39
+ drafts (never `continue`), that nothing is judged twice once the record is committed, that drafts a later
40
+ handler dropped (or Pi rejected) are judged again, that an unanswered output (error) is asked again, that the
41
+ memo follows `/tree`, and that run end judges outputs kept mid-run again. On a `pi-claude-auth`-shaped
42
+ payload (system prompt moved into the first user message) it checks the question pin on every request; on
43
+ the first request after edits a write anchor, and a read point only at an entry this process sent earlier
44
+ (also on a warming replay); a read point after run-end edits; and no read point for another model, after
45
+ the TTL (mocked clock), after a reload, for an anchor dropped for the budget, or for history made with
46
+ pinning off. Through `message_end` it checks that a response that read through a logged entry keeps it
47
+ usable past 5 minutes, and that a short read or another model's response does not. For whole items it
48
+ checks that run end omits an unrelated old exchange (stub on the prompt, `null` for the rest) and stubs a
49
+ small output, that the next run judges the next eligible exchange, that `recall` on the stub returns the
50
+ whole exchange, that `pruneExchanges: false` and `smallResultMinChars: 0` turn them off, and that the
51
+ break-even rule drops exchanges from a checkpoint batch that would not pay off with them. The run-end
52
+ exchange gate: a small omission is deferred and the accumulated batch is omitted at a later run end; a big
53
+ early exchange in a short session is deferred, and omitted with `exchangeBreakEven: false`; on OpenAI Codex
54
+ an exchange rides along when the pass edits something else and is deferred when it does not. Old images: run
55
+ end removes images 3+ turns old with no Jev request (also with the model missing), `recall` returns them as
56
+ image blocks (a pattern search only names them), nothing is removed twice; a checkpoint fills its batch with
57
+ old images alone and still obeys break-even, `imageKeepTurns: 0` turns it off; images of an earlier run are
58
+ deferred by the run-end gate and removed with `exchangeBreakEven: false`; `recall` of an omitted exchange
59
+ attaches user images and names tool-result images. The fake session uses Pi's projection shape (an omitted
60
+ entry stays with no messages).
33
61
 
34
62
  Test through Pi's loader (or `pi -e`), not a plain Node import of the extension. Plain Node imports of
35
63
  `@earendil-works/pi-coding-agent` can fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`, even though Pi loads the
@@ -41,7 +69,7 @@ This uses a real Pi, a real main model and real Jev requests. **It costs money o
41
69
  main-model requests per turn, plus a few Jev requests (under $0.001 per distilled run).
42
70
 
43
71
  ```bash
44
- node test/e2e/run-e2e.mjs --model <provider/id> [--ext <path>]... [--scenario badge|long|history] \
72
+ node test/e2e/run-e2e.mjs --model <provider/id> [--ext <path>]... [--scenario badge|sequential|history|long|topics] \
45
73
  [--config '<JSON>'] [--no-guard] [--turns N] [--thinking level]
46
74
  ```
47
75
 
@@ -49,7 +77,7 @@ node test/e2e/run-e2e.mjs --model <provider/id> [--ext <path>]... [--scenario ba
49
77
  |---|---|---|
50
78
  | `--model` | (required) | Main model, e.g. `openai-codex/gpt-6-luna` or `anthropic/claude-sonnet-5-5` |
51
79
  | `--ext` | none | Extra extension to load; can be repeated (e.g. an auth extension) |
52
- | `--scenario` | `badge` | `badge`: 3 turns about `StatusBadge` usages. `long`: 2 turns with 10+ sequential tool calls, for the cache pin. `history`: 3 turns for the earlier-conversation feature (below). |
80
+ | `--scenario` | `badge` | `badge`: 3 turns about `StatusBadge` usages. `sequential`: 2 turns with 10+ sequential tool calls, for the question pin. `history`: 3 turns for the earlier-conversation feature. `long`: one autonomous "fix all failing tests" run, for mid-run checkpoints. `topics`: 5 prompts on different topics, the last a follow-up on the first, for old exchanges (both below; use `--turns 5`). |
53
81
  | `--config` | `{}` | context-guard settings for this run, e.g. `'{"pinAnthropicCache":false}'` |
54
82
  | `--no-guard` | off | Control run without context-guard |
55
83
  | `--turns` | `3` | Turns to run (capped by the scenario) |
@@ -80,7 +108,10 @@ How it works:
80
108
  | turn 2 first request reads cache | `cacheRead` > 0 on the first request after the edit |
81
109
  | turn 3 quotes the removed line exactly | The model quoted line 3 of `src/api/jobStatus.ts` from the "first search" |
82
110
  | turn 3 used recall (line was distilled away) | Only when that line is missing from turn 3's payload: the model must have called `recall` |
83
- | long: turn 1 produced context edits / 10+ tool calls / 10+ model requests | The long scenario really was long and sequential |
111
+ | sequential: turn 1 produced context edits / 10+ tool calls / 10+ model requests | The sequential scenario really was long and sequential |
112
+ | long: the suite is green afterwards | `npm test` passes in the fixture after the run |
113
+ | long: at least one mid-run checkpoint (forced) | Only with `midRunBreakEven: false` in `--config`: a `mid-run` record was written (a default run may rightly make none) |
114
+ | long: request after the checkpoint at request N reads the cached prefix | Anthropic: the first request after each checkpoint reads at least what the run's first request read and wrote (system prompt and question) |
84
115
  | history: turn 2 produced context edits | The broad search in turn 2 was distilled |
85
116
  | history: turn 3 names exactly the clients that retry on 429 | The answer names the 9 clients whose fixture file has `=== 429`; a client named only to say it does *not* retry is allowed |
86
117
  | anthropic requests stay within 4 breakpoints | For `anthropic/…` models: the pin never exceeds the limit |
@@ -93,6 +124,59 @@ summary has a `history` block: `keptApiRetryLines` / `kept429Lines` (src/api ret
93
124
  distilled text), the turn-2 Jev results and ms, turn 3's tool calls and whether it used `recall`, and the
94
125
  clients turn 3 named. Results are in [JEV.md](JEV.md#earlier-conversation-historyexchanges).
95
126
 
127
+ **`long` scenario.** `test/e2e/make-long-fixture.mjs` generates a small Node project (`ledgerly`: money
128
+ in integer cents, business-day calendars, CSV, pricing, a ledger report; 182 tests). Ten bugs sit in
129
+ different modules, in up to three layers: an import-time `ReferenceError` hides a whole test file, and two
130
+ tests fail first because of one bug and then, once it is fixed, because of another. A full `npm test` prints
131
+ 12k characters when green and 25–30k with the failures, so every rerun is a large, quickly superseded tool
132
+ output. The prompt asks the agent to fix one bug at a time and run the plain suite after every fix, without
133
+ asking anything. (`--fixed` writes the bug-free version, `--bugs 1,4,…` a subset.) GPT-6 Luna needs 26–37
134
+ requests; Claude Sonnet 5.5 needs 8–14, because it batches several fixes per command and pipes test output
135
+ into `grep`/`head`. Load `--ext test/e2e/plain-test-runs.ts` for Claude to strip a pipe after `npm test`
136
+ (it logs each rewrite to stderr), so its suite runs return full logs like GPT's.
137
+
138
+ The summary gets a `long` block: per-request contexts (input + cacheRead + cacheWrite) and a sparkline,
139
+ totals of input/cacheRead/cacheWrite/output and the main-model cost Pi reports, every `context-guard`
140
+ record with its phase, saved characters, Jev ms and cost, and the request before and after it (context,
141
+ cacheRead, cacheWrite), whether the suite is green, `recall` calls, and repeated non-suite tool calls (lost
142
+ outputs searched again). Results are in [RESULTS.md](RESULTS.md#long-autonomous-runs) and in
143
+ [CACHE.md](CACHE.md#mid-run-checkpoints).
144
+
145
+ **`topics` scenario.** Five prompts in the generated StatusBadge repo: which API clients retry on 503; a
146
+ README paragraph for `StatusBar`; a count of HTTP 500 log lines; where `StatusBadge` is used; and "back to
147
+ the API clients that retry on 503: which of them also retry on 502?". The summary gets a `topics` block:
148
+ the context of each prompt's first request and its usage, the exchange results (phase, prompt, outcome,
149
+ P), the small-output results, the last prompt's tool calls and whether it used `recall`, and whether its
150
+ answer names exactly the clients whose file has both `=== 503` and `=== 502` (a crude check: it fails on
151
+ answers that also list clients to say they do not qualify). Results are in [RESULTS.md](RESULTS.md#examples) (example D) and in
152
+ [CACHE.md](CACHE.md#old-exchanges).
153
+
154
+ **API-shape probe.** `CG_OMIT_AT_TURN=2 node test/e2e/run-e2e.mjs --model … --no-guard --ext
155
+ test/e2e/omit-probe.ts` omits the session's first exchange after turn 2 the way the guard does, so turn 3
156
+ sends a stub user message right before the next user message, without the omitted assistant and
157
+ tool-result messages. Both GPT-6 Luna and Claude Sonnet 5.5 (`pi-claude-auth`) accepted it and answered.
158
+
159
+ **Old images, live** (2026-10-08, Claude Sonnet 5.5 with `pi-claude-auth`, ad-hoc): six 1200Γ—800 PNGs read
160
+ one per message, then a follow-up about the second one. Run end removed the four images 3–6 turns old with
161
+ no Jev request (record `requests: 0`, 19.9k chars β‰ˆ 5k tokens); the next request carried 2 images instead
162
+ of 6 (input 14.1k β†’ 9.5k tokens). Told not to read the file again, the model called
163
+ `recall({"entryId":…})`, got the image back and read the code in it correctly.
164
+
165
+ **Session replay.** `node test/e2e/replay-session.ts <session.jsonl> [--from N] [--all] [--summary]` rebuilds Pi's
166
+ projection at every turn of a real session file and prints what the mid-run gates decide (run start, eligible
167
+ large and small outputs, old exchanges, batch size, break-even) and the run-end candidates. No model calls,
168
+ nothing leaves the machine. Without the live cache log it uses the question pin, so its break-even is the
169
+ conservative case.
170
+
171
+ **Whole-item probe.** `node test/e2e/probe-items.mjs [--reps 2] [--sessions <topics no-guard run dir>]…`
172
+ sends the hand-written exchanges and small outputs in `test/e2e/probe-data/` (and the `topics` exchanges)
173
+ to Jev and prints P per item and the accuracy per threshold (results in [JEV.md](JEV.md#old-exchanges)).
174
+
175
+ **Anthropic TTL experiment.** `node test/e2e/ttl-probe.mjs [--arms refresh,control,ttl1h,idle,idle1h]`
176
+ runs `test/e2e/ttl-probe.ts` with Claude: does a cache entry stay alive while later requests read a longer
177
+ prefix, and does it expire when nothing reads it for 6 minutes? About 9 minutes and roughly $0.9 at list price for all five arms.
178
+ Results: [CACHE.md](CACHE.md#do-cache-entries-on-the-read-path-stay-alive-measured).
179
+
96
180
  With the guard on, the badge scenario has 9 checks, plus 1 for Anthropic models. Most guard checks are
97
181
  skipped with `--no-guard`.
98
182
 
@@ -118,30 +202,37 @@ the runner disables installed extensions:
118
202
 
119
203
  ```bash
120
204
  node test/e2e/run-e2e.mjs --model anthropic/claude-sonnet-5-5 --ext ~/.pi/agent/npm/node_modules/pi-claude-auth
121
- node test/e2e/run-e2e.mjs --model anthropic/claude-sonnet-5-5 --ext ~/.pi/agent/npm/node_modules/pi-claude-auth --scenario long
122
- node test/e2e/run-e2e.mjs --model anthropic/claude-sonnet-5-5 --ext ~/.pi/agent/npm/node_modules/pi-claude-auth --scenario long --config '{"pinAnthropicCache":false}'
205
+ node test/e2e/run-e2e.mjs --model anthropic/claude-sonnet-5-5 --ext ~/.pi/agent/npm/node_modules/pi-claude-auth --scenario sequential
206
+ node test/e2e/run-e2e.mjs --model anthropic/claude-sonnet-5-5 --ext ~/.pi/agent/npm/node_modules/pi-claude-auth --scenario sequential --config '{"pinAnthropicCache":false}'
123
207
  node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna
124
208
  node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna --no-guard
125
209
  node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna --scenario history
126
210
  node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna --scenario history --config '{"historyExchanges":0}'
211
+ # mid-run checkpoints (up to 4 in parallel)
212
+ node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna --scenario long --no-guard
213
+ node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna --scenario long --config '{"midRun":false}'
214
+ node test/e2e/run-e2e.mjs --model openai-codex/gpt-6-luna --scenario long
215
+ node test/e2e/run-e2e.mjs --model anthropic/claude-sonnet-5-5 --ext ~/.pi/agent/npm/node_modules/pi-claude-auth --ext test/e2e/plain-test-runs.ts --scenario long
127
216
  ```
128
217
 
129
- ### Results so far (Pi 1.0.3)
218
+ ### Results (Pi 1.0.3)
130
219
 
131
220
  | Run | Result |
132
221
  |---|---|
133
222
  | GPT-6 Luna, badge | 36,113 β†’ 1,501 chars, 2 Jev requests, 550 ms, $0.00083; turn 3 used `recall` with a regex; 9/9 checks pass |
134
223
  | Claude Sonnet 5.5, badge | 36,113 β†’ 3,066 chars, 2 Jev requests, 1.18 s, $0.00084; turn 2 cacheRead 5,777 / cacheWrite 2,036; turn 3 used `recall`; 10/10 checks pass |
135
- | Claude Sonnet 5.5, long, with / without pin | first request after the run: cacheRead 5,810 / 1,693, cacheWrite 8,913 / 13,068 |
224
+ | Claude Sonnet 5.5, sequential, with / without pin | first request after the run: cacheRead 5,810 / 1,693, cacheWrite 8,913 / 13,068 |
136
225
  | GPT-6 Luna, badge, with / without guard | turn 2 cacheRead 2,560 / 13,824 |
137
- | GPT-6 Luna, badge, after the history feature | 36,113 β†’ ~2.3k chars; 9/9 checks pass (an earlier run in parallel with 4 others had one OpenAI cache miss on turn 2: 8/9) |
226
+ | GPT-6 Luna, badge, with history (default) | 36,113 β†’ ~2.3k chars; 9/9 checks pass (a run in parallel with 4 others had one OpenAI cache miss on turn 2: 8/9) |
138
227
  | GPT-6 Luna / Claude Sonnet 5.5, history on / off | See [JEV.md](JEV.md#earlier-conversation-historyexchanges): 2 rounds Γ— 4 runs |
228
+ | GPT-6 Luna / Claude Sonnet 5.5, long (mid-run checkpoints) | See [RESULTS.md](RESULTS.md#long-autonomous-runs); all suites green, no `recall` |
229
+ | GPT-6 Luna / Claude Sonnet 5.5, topics, guard on / off | See [RESULTS.md](RESULTS.md#examples), example D |
139
230
 
140
231
  See [CACHE.md](CACHE.md) for what the cache numbers mean.
141
232
 
142
233
  ## Real-world examples (`examples.mjs`)
143
234
 
144
- The README's "Examples (measured)" section comes from `test/e2e/examples.mjs`. It runs the same prompts on
235
+ The examples in [RESULTS.md](RESULTS.md#examples) come from `test/e2e/examples.mjs`. It runs the same prompts on
145
236
  real public code twice, with context-guard on and with it off (`--no-guard`), and compares the first
146
237
  request of the follow-up turn.
147
238
 
@@ -159,7 +250,7 @@ node test/e2e/examples.mjs --jobs 2
159
250
 
160
251
  Each run gets its own workspace, session id and `PI_CONTEXT_GUARD_CONFIG` file (defaults). The Pi package is
161
252
  public (npm); nothing private is sent to OpenRouter. Example B also records the test log's size
162
- (`testLogChars`, which must be β‰₯ 8,000 to be a fair example; it was 14.4k).
253
+ (`testLogChars`, which must be β‰₯ 8,000 to be a fair example; it was 14.4–15.8k).
163
254
 
164
255
  Output: `test/e2e/out/examples-<stamp>/` with `results.md` (the table), `results.json`, and one directory per
165
256
  run (`A-claude-on`, …) holding `summary.json` (per turn: tool calls, tool-result sizes, edits with kept/total
@@ -171,17 +262,15 @@ model saw.
171
262
  Pi from list prices (most of it Claude in example A; GPT runs used a subscription). About 5 minutes with 4
172
263
  parallel jobs.
173
264
 
174
- **Findings of the first full run** (details in the README):
175
-
176
- - Next-request context, on / off: A Claude 15.5k / 28.2k tokens, A GPT 11.5k / 34.3k, C GPT 9.2k / 35.3k;
177
- all follow-ups correct, none needed `recall`.
178
- - The first request after an edit cost more with the guard on (cache rewrite): A Claude $0.033 / $0.016.
179
- - B was not distilled at all: the failing test command is an error result, which context-guard then never
180
- edited. Fixed by `distillErrors` (default on); a rerun distilled the log 15.8k β†’ 8.6k chars, keeping the
181
- 3 failures with their assertion messages, and turn 2 still fixed the bug.
182
- - In C, `read` outputs carry Pi's two-line "[Showing lines …]" note, so omission ranges at the end of a piece
183
- named two line numbers past the file lines (e.g. "lines 466–502" of a 1–500 read). Fixed: ranges are now
184
- clamped to the file lines and a removed note gets no omission line.
265
+ **Findings** (details in [RESULTS.md](RESULTS.md#examples)):
266
+
267
+ - Next-request context, on / off: A Claude 15.5k / 28.2k tokens, A GPT 11.5k / 34.3k, C GPT 9.2k / 35.3k,
268
+ B GPT 5.7k / 7.5k; all follow-ups correct, none needed `recall`.
269
+ - The first request after an edit costs more with the guard on (cache rewrite): A Claude $0.033 / $0.016.
270
+ - B: the failing test log (an error result, distilled because `distillErrors` is on) went 15.8k β†’ 8.6k
271
+ chars, keeping the 3 failures with their assertion messages.
272
+ - C: `read` outputs end with Pi's "[Showing lines …]" note; omission ranges are clamped to the file lines
273
+ and a removed note gets no omission line.
185
274
 
186
275
  ## Manual TUI check (tmux)
187
276
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-context-guard-jev",
3
- "version": "0.1.0",
4
- "description": "Keep big tool outputs from bloating your Pi context: after each run, the Jev classifier keeps only the lines your work still needs, verbatim and recallable, with savings in the status bar. Claude + OpenAI, cache-aware.",
3
+ "version": "0.3.1",
4
+ "description": "Pi extension that removes tool output the agent no longer needs from its context. The Jev classifier keeps the needed lines verbatim; stale small outputs, unrelated old exchanges and old screenshots become one-line notes. Everything can be recalled.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "alandarev",