pi-fold 3.0.0 → 4.0.0
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.
- package/README.md +76 -212
- package/extensions/active-context.ts +457 -362
- package/extensions/evidence.js +1 -5
- package/extensions/index.js +8 -3
- package/extensions/json.ts +23 -1
- package/extensions/lib/canonical.ts +34 -0
- package/extensions/lib/curation.ts +141 -9
- package/extensions/lib/editor-ui.ts +133 -25
- package/extensions/lib/folding.ts +181 -17
- package/extensions/lib/instrumentation.ts +0 -6
- package/extensions/lib/measurement.ts +84 -4
- package/extensions/lib/persistence.ts +181 -9
- package/extensions/lib/policy.ts +80 -28
- package/extensions/lib/scheduling.ts +105 -18
- package/extensions/lib/selection.ts +112 -39
- package/extensions/lib/tool-surface.ts +6 -4
- package/extensions/lib/transcript.ts +65 -14
- package/extensions/settings.ts +123 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# pi-fold
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/pi-fold) [](https://doi.org/10.5281/zenodo.22142456)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The full write-up, with interactive versions of every mechanism on this page and the complete measurement story, is at [shaneconner.com/projects/pi-fold](https://shaneconner.com/projects/pi-fold/).
|
|
5
|
+
**Rotate stale text out of the context window without losing it, and spend the prefix cache once per commit instead of continuously.** The oldest part of a session collapses into short briefs while the exact originals stay on disk, one tool call away. Folding is automatic and needs no agent participation. The shipped shape is deterministic briefs paired with a durable memory store, which is the cheapest configuration measured that kept recall intact.
|
|
8
6
|
|
|
9
7
|

|
|
10
8
|
|
|
11
|
-
*
|
|
9
|
+
*Marks outline spans as they complete and move nothing; the next commit applies every standing mark in one rewrite. Expanding a brief brings the exact messages back byte for byte. Illustrative animation drawn on the 2.x surface, where the agent laid the marks; since 3.0 the runtime lays them.*
|
|
12
10
|
|
|
13
11
|
## Install
|
|
14
12
|
|
|
@@ -16,256 +14,122 @@ The full write-up, with interactive versions of every mechanism on this page and
|
|
|
16
14
|
pi install npm:pi-fold
|
|
17
15
|
```
|
|
18
16
|
|
|
19
|
-
Node 22 or later, Pi 0.83 or later.
|
|
20
|
-
|
|
21
|
-
pi-fold serves the working tier of a four-tier memory stack. [pi-canon](https://github.com/shaneconner/pi-canon) is a separate, optional package serving the long-term tiers, the journal episodic and the canon semantic; the two compose but neither requires the other.
|
|
22
|
-
|
|
23
|
-
## What it is
|
|
24
|
-
|
|
25
|
-
pi-fold registers one tool, `pi_fold_context`, and gives the session's own agent nine actions over its transcript: `status`, `peek`, `brief`, `expand`, `refold`, `pin`, `unpin`, `reboundary`, and `unmark`. It also registers `/fold`, `/fold-editor`, `/fold-status` and `/fold-settings` for the human.
|
|
26
|
-
|
|
27
|
-
A fold takes a contiguous span of session entries and replaces it, in the window only, with a short brief. The entries themselves go to a fold store, byte for byte, addressed by their SHA-256 hash. The brief carries a handle, the handle resolves to the original, and expansion restores the exact bytes after verifying the hash. A fold is therefore a claim the runtime can check, not a claim the reader has to trust.
|
|
28
|
-
|
|
29
|
-
Everything else follows from one invariant, and it is the reason several otherwise reasonable features are not here.
|
|
30
|
-
|
|
31
|
-
**A provider prefix cache is positional.** It replays the longest byte-identical prefix of the prompt, so one changed byte at offset K discards everything cached after K. The cost of a mutation has nothing to do with its size: deleting a single character in the middle of a large window and rewriting a hundred thousand tokens near the end of it bill the same way, because both invalidate the same suffix.
|
|
32
|
-
|
|
33
|
-
**So the runtime may move a byte in the window only at a moment it is already rewriting the window**, which is a commit or a fold. Everything else is a pure append at the tail, and an appended byte is never later altered, shortened, removed, or repositioned. Stated plainly: never mutate the window, and never show something and then take it back. The second half is the one that costs features. Anything that displays state and then updates it in place is out, however useful the display was, because updating in place is a mid-prefix write with better manners.
|
|
34
|
-
|
|
35
|
-
Pinning is the other half of governing a window. `pin` holds entries raw through every fold, automatic or marked, so it is how an agent keeps a span expanded on purpose. It is tracked as a claim beside the marks and reported with them, and it frees nothing: the commit still takes its drop from whatever is left, so pinned mass makes the rest of the window fold sooner. A pinned share cap stops that from becoming a window nothing can reclaim.
|
|
36
|
-
|
|
37
|
-
**Marks are free, because a mark moves nothing.** The runtime stages the marks itself: the moment a tool batch or a chapter closes, the frontier cuts it into a pending fold mark, event by event, up to eight cuts per pass, stalest first. A mark lives in durable session state, outside the window; the projection stays byte-identical and the cached prefix survives. What the agent owns is the words. Once enough cut folds are waiting, one bounded notice names them, and `brief` writes the agent's own sentence onto a pending fold for free, because the brief reaches the window only when the commit writes that placeholder for the first time. A supplied brief is kept verbatim; a fold left unbriefed commits with a deterministic brief the runtime writes from the span itself, and the provenance records which happened.
|
|
38
|
-
|
|
39
|
-
Curation is correctable until the commit. `reboundary` with ids re-cuts a span into exactly one fold, merging adjacent cuts when the span covers several and splitting one when the span sits inside it; with a single id it returns that fold's span to raw. `unmark` withdraws a pending mark entirely, and `brief` may be rewritten on a pending fold any number of times at no cost. A standing fold's brief is never rewritten, because rewriting it rewrites the projection at that fold's placeholder and costs the whole prefix cache from there on.
|
|
17
|
+
Node 22 or later, Pi 0.83 or later. MIT licensed.
|
|
40
18
|
|
|
41
|
-
|
|
19
|
+
Folding does not require a memory store, but every best result measured came from pairing it with one. The campaigns used [pi-canon](https://github.com/shaneconner/pi-canon), installed separately. The two began as a single working setup and were split into separate extensions for modularity, and the experiments are what established that they work better as complements than either does alone. Only that pairing has been measured. Any durable long-term memory store is expected to compose the same way, though that is a suspicion rather than a result.
|
|
42
20
|
|
|
43
|
-
|
|
21
|
+
## What it does
|
|
44
22
|
|
|
45
|
-
|
|
23
|
+
Compaction replaces the transcript with a summary and discards the originals. Folding replaces stale spans with briefs and keeps the originals retrievable.
|
|
46
24
|
|
|
47
|
-
|
|
25
|
+
<picture>
|
|
26
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/shaneconner/pi-fold/main/media/fold-vs-compaction-dark.svg">
|
|
27
|
+
<img alt="Compaction turns the transcript into a summary and discards the originals, so the session starts over. pi-fold turns it into a brief and keeps the exact originals on disk, one peek or expand away, so the session keeps going." src="https://raw.githubusercontent.com/shaneconner/pi-fold/main/media/fold-vs-compaction-light.svg">
|
|
28
|
+
</picture>
|
|
48
29
|
|
|
49
|
-
|
|
30
|
+
A fold takes a contiguous span of session entries and replaces it, in the window only, with a short brief. The entries go to a fold store byte for byte, addressed by their SHA-256 hash. The brief carries a handle, the handle resolves to the original, and expansion restores the exact bytes after verifying the hash.
|
|
50
31
|
|
|
51
|
-
|
|
32
|
+
The package registers one tool, `pi_fold_context`, with nine actions over the transcript: `status`, `peek`, `brief`, `expand`, `refold`, `pin`, `unpin`, `reboundary` and `unmark`. A session that never calls the tool still folds; it degrades into lossless hierarchical compaction.
|
|
52
33
|
|
|
53
|
-
##
|
|
34
|
+
## Results
|
|
54
35
|
|
|
55
|
-
|
|
36
|
+
One 64-stage assignment over the curl C repository, run as a single agentic turn. Same model, same 251,520-token serving budget, every run. After all 64 stages landed, one withheld message asked sixteen questions about material from across the whole session.
|
|
56
37
|
|
|
57
|
-
|
|
38
|
+
| Condition | Correct of 16 | Wrong | Cost | Fresh input | Cached input | Wall clock |
|
|
39
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
40
|
+
| Compaction | 4 | 3 | $151.24 | 7.64M | 160.2M | 6.9h |
|
|
41
|
+
| Compaction + memory store | 14 | 0 | $201.70 | 9.65M | 231.7M | 9.0h |
|
|
42
|
+
| pi-fold, deterministic briefs | 11.3 avg | 3.5 avg | $93.65 | 5.74M | 91.2M | 4.6h |
|
|
43
|
+
| **pi-fold + memory store** | **14** | **0** | **$100.85** | **5.57M** | **102.2M** | **5.2h** |
|
|
44
|
+
| pi-fold, agent-written briefs | 3 | 9 | $111.96 | 7.12M | 108.8M | 5.4h |
|
|
58
45
|
|
|
59
|
-
|
|
46
|
+
*Fresh input is what the provider had to read for the first time; cached input is what it replayed. The deterministic-briefs row averages four runs that scored 14, 14, 9 and 8.*
|
|
60
47
|
|
|
61
|
-
|
|
48
|
+
Compare the two store-paired rows. Both scored 14 correct with none wrong, and folding cost half. That gap is request count rather than price: compaction needed roughly twice the provider requests for the same work, plus summarizer calls that never show up in its own ledger.
|
|
62
49
|
|
|
63
|
-
|
|
50
|
+
Compaction alone abstained nine times and was wrong three, so its dominant failure was declining rather than inventing, but it did invent. And the fold's deterministic runs are bimodal rather than average: two answered 14 with nothing wrong, two lost old material, and cost did not predict which. The store was tried once on each arm and both came back with nothing wrong, which is suggestive, not a rate.
|
|
64
51
|
|
|
65
|
-
|
|
52
|
+
Agent-written briefs scored worst of anything measured, which is why that invitation ships off.
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
| --- | ---: | ---: | :--- |
|
|
69
|
-
| Total tokens | 9,598,126 | 16,011,169 | 0.60x |
|
|
70
|
-
| Fresh input | 1,249,415 | 1,322,539 | 0.95x |
|
|
71
|
-
| Cache read | 8,309,760 | 14,602,240 | 0.57x |
|
|
72
|
-
| Output | 38,951 | 86,390 | 0.45x |
|
|
73
|
-
| Billed calls | 69 | 114 | 0.61x |
|
|
74
|
-
| Cost, Pi's own ledger | $11.57 | $21.00 | 0.55x, a 44.9% saving |
|
|
75
|
-
| Pooled cache share | 0.869 | 0.917 | inverted, see below |
|
|
76
|
-
| Peak request context | 236,861 | 369,024 | threshold 272,000 |
|
|
77
|
-
| Long-context surcharge | $0.00 | $4.50 on 17 requests | 21.4% of native's bill |
|
|
78
|
-
| Compactions | 0 | 3 | |
|
|
79
|
-
| Agent file reads | 27, 0.06 MB | 117, 0.93 MB | 4.3x the reads, 15x the bytes |
|
|
80
|
-
| Wall clock | 25.2 min | 42.7 min | 0.59x |
|
|
54
|
+
Four papers with figure sources, redacted per-request ledgers and campaign logs are on Zenodo: [design and trace evaluation](https://doi.org/10.5281/zenodo.21856873), [working memory under context shedding](https://doi.org/10.5281/zenodo.21980746), [ephemeral retrieval](https://doi.org/10.5281/zenodo.22142454), and [rotation and retention](https://doi.org/10.5281/zenodo.22142456), which is the campaign above. Narrative versions on [Medium](https://medium.com/@shane.conner/a-compaction-summary-is-one-record-doing-a-stores-job-3f46212ef059), interactive versions at [shaneconner.com](https://shaneconner.com/projects/pi-fold/), and the full chronology in [the experiment log](https://github.com/shaneconner/pi-fold/blob/main/docs/fold_vs_compaction/experiment-log.md).
|
|
81
55
|
|
|
82
|
-
|
|
56
|
+
## Limits
|
|
83
57
|
|
|
84
|
-
|
|
58
|
+
- **Native compaction is strong and is the right default for most sessions.** It needs no installation and no tool surface. This is an alternative for workflows where the numbers above matter, not a replacement.
|
|
59
|
+
- **The benchmark is narrow by construction.** It was built to reproduce specific things compaction handles badly, one to four runs per condition, and it says nothing about production behavior.
|
|
60
|
+
- **Folding is not a memory store.** It rotates transcript source out of the window; durable placement of what a session learned is a separate job, and the pairing is where the measured value sat.
|
|
85
61
|
|
|
86
|
-
|
|
87
|
-
| --- | ---: | ---: | :--- |
|
|
88
|
-
| Total tokens | 11,435,313 | 16,970,754 | 0.67x |
|
|
89
|
-
| Fresh input | 1,489,599 | 1,407,978 | 1.06x |
|
|
90
|
-
| Cache read | 9,895,936 | 15,510,016 | 0.64x |
|
|
91
|
-
| Output | 49,778 | 52,760 | 0.94x |
|
|
92
|
-
| Billed calls | 82 | 108 | 0.76x |
|
|
93
|
-
| Cost, Pi's own ledger | $13.89 | $21.18 | 0.66x, a 34.4% saving |
|
|
94
|
-
| Pooled cache share | 0.869 | 0.917 | inverted, see below |
|
|
95
|
-
| Peak request context | 236,861 | 368,639 | threshold 272,000 |
|
|
96
|
-
| Long-context surcharge | $0.00 | $4.81 on 20 requests | |
|
|
97
|
-
| Fold events / compactions | 6 fold events, 0 compactions | 3 compactions | |
|
|
98
|
-
| Agent file reads | 39, 3.84 MB | 108, 4.62 MB | 2.8x the reads |
|
|
99
|
-
| Wall clock | 34.2 min | 32.4 min | 1.06x, pi-fold slower |
|
|
100
|
-
|
|
101
|
-
**Where the cost numbers come from.** Cost is Pi's own per-request ledger accounting, not a rate card chosen for the writeup. Every billed record's cost equals that record's own usage priced at the tier Pi applied: base rates of $5.00/M input, $0.50/M cache read and $30.00/M output, and a long-context tier of $10.00/M, $1.00/M and $45.00/M on any request whose context exceeds the model's 272,000-token window. That reproduces the ledger to the cent on both arms, including pi-fold's complete-run total of $13.89. Reasoning tokens sit inside output and are not billed separately. Native's three compaction summarizations are themselves billed calls, $0.58 at stage 56 and $0.54 on the complete run.
|
|
102
|
-
|
|
103
|
-
**Caveats, because the numbers rest on them.**
|
|
104
|
-
|
|
105
|
-
- **Fresh input is essentially identical.** 0.95x at stage 56 and 1.06x on the complete pairing: neither runtime consistently reads less new material. The 6.4 million token gap at stage 56 is re-sent accumulated context, 6.29 million of it cache read alone, not a difference in how much material was consumed.
|
|
106
|
-
- **No wall-clock claim on the complete pairing.** pi-fold was 5.6% slower there, 34.2 minutes against 32.4. The wall-clock win belongs to the stage-56 pairing only.
|
|
107
|
-
- **The cache share runs backwards, and that is the useful finding.** Native holds the higher pooled share, 0.917 against 0.869, while spending 67% more tokens and billing 82% more dollars, because cache share measures how cheap each prompt was rather than how much prompt was needed.
|
|
108
|
-
- **Part of the cost gap is a pricing tier, not token volume.** At flat base rates native's stage-56 bill would be $16.51, a 29.9% saving rather than 44.9%, and its complete-run bill $16.38, a 15.2% saving. The tier is real money a native run really pays, but quote the flat-rate figures when the argument is about tokens rather than price schedules.
|
|
109
|
-
- **One run per arm.** Native never finished; the supervisor lost the worker at the stage-57 request, so the stage-56 cut excludes native's final 6 messages and $0.57 of ledger cost. The complete pairing ran on different days. The stage-56 pairing is same-day and is the stronger claim.
|
|
110
|
-
- **The agent never called the tool.** All 62 folds in that run were the automatic ladder's; every commit records zero agent marks. So these are the fallback's numbers, produced by the part of the design that is closest to prior work, with agent curation, the distinguishing claim, unexercised.
|
|
111
|
-
- **The reread story is a count, not a duplicate-payload metric.** It rests on file read count and bytes from disk, both in the tables above.
|
|
112
|
-
|
|
113
|
-
Full method, artifact hashes and per-iteration history: [docs/fold_vs_compaction/experiment-log.md](https://github.com/shaneconner/pi-fold/blob/main/docs/fold_vs_compaction/experiment-log.md). Queued work: [docs/next-steps.md](https://github.com/shaneconner/pi-fold/blob/main/docs/next-steps.md).
|
|
114
|
-
|
|
115
|
-
### Transcript-only hidden mass, protocol v4
|
|
62
|
+
## Configuration
|
|
116
63
|
|
|
117
|
-
|
|
64
|
+
`registerPiFold(pi)` works with no arguments, and the defaults are the configuration the campaign above actually ran: deterministic briefs, the invitation off, the tool-call diet at half the window, and a 0.80/0.20 band. Nothing here needs tuning.
|
|
118
65
|
|
|
119
|
-
|
|
120
|
-
| --- | ---: | ---: |
|
|
121
|
-
| Completed 64 stages | 2 of 2 | 0 of 2 |
|
|
122
|
-
| Join endpoints recorded correctly | 8 of 8 | 0 of 8; all eight recorded `unknown` |
|
|
123
|
-
| Ordinary probes | 40 of 42 | not available from two incomplete runs |
|
|
124
|
-
| Withheld end-block cells | 60 of 60 | not reached |
|
|
66
|
+
The one thing worth adding is a long-term memory store. Rotation and retention are different jobs: folding gets stale material out of the window, a store keeps what the session learned. A handoff summary tries to do both in one record, written once under time pressure, bounded by the window it has to fit in. Folding is the rotation half and only that half.
|
|
125
67
|
|
|
126
|
-
|
|
68
|
+
Five options, and every other name is refused.
|
|
127
69
|
|
|
128
|
-
|
|
70
|
+
| Option | Default | Effect |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| `thresholds` | `{ maxTarget: 0.80, minTarget: 0.20, consolidateAfter: 10, minFoldChars: 8000 }` | The band, set whole or not at all. The first two are proportions of the serving budget. `consolidateAfter` is how many visible roots a parent is owed. `minFoldChars` is the size a fold has to reach to be worth making, and the floor a gap between folds has to clear to stay raw. Validated atomically; an impossible policy is refused by name rather than clamped. |
|
|
73
|
+
| `providerInputBudget` | the transport's descriptor | The tokens this deployment may actually put in a request, already net of any output reservation. Every ratio, fence and budget on this page is a share of this one number. |
|
|
74
|
+
| `blacklistAutoFoldTools` | `new Set()` | The exception list. Every completed tool batch is foldable without an agent mark; name a tool here and its results stay raw. |
|
|
75
|
+
| `toolFoldThreshold` | `0.50` | The tool-call diet: a share in `[0, 1)` naming the oldest fraction of the window whose tool results are clipped in view. `0` turns it off. |
|
|
76
|
+
| `postFoldNotice` | `false` | The invitation switch. False is the shape that won the campaign above: no carrier invites a brief and every fold ships the runtime's own words. `true` appends a standing invitation to improve a pending brief. |
|
|
129
77
|
|
|
130
|
-
|
|
78
|
+
Four commands ship for the human: `/fold-status`, `/fold` (commit every staged mark now), `/fold-editor` (the interactive map of the window) and `/fold-settings`.
|
|
131
79
|
|
|
132
80
|
## How it works
|
|
133
81
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
The cutting is the runtime's. As units complete, the frontier stages each one as a pending fold mark, at the level of events rather than turns: a completed tool batch is proposable the moment its results land, and an incomplete one never is. The agent's actions ride on top: it briefs the cut folds in its own words, expands any fold whose detail is needed again, dissolves a mis-cut boundary with `reboundary`, withdraws a mark with `unmark`, and pins entries that must stay raw. Curation the agent cannot fix afterwards is curation it will not risk making, which is why `reboundary` is part of the ordinary surface rather than an extra.
|
|
137
|
-
|
|
138
|
-
Every fold obeys the same structural rules. Folds nest rather than partially overlap. Chapter folds align to closed user, assistant and tool-batch units, consolidations take two or more adjacent folds, and pinned or blacklisted evidence never folds. The frontier keeps its own cuts bite-sized, because nobody vouched for a bigger boundary, and a fold that hides more than it says is a fold nobody reads back. A span someone names, through `reboundary` or the editor, is taken whole at any size: a person or the agent cut that boundary on purpose, and a large fold stays cheap to read because peek takes an offset and a byte count, states the middle it omitted, and reads any child fold by id.
|
|
139
|
-
|
|
140
|
-
The automatic law folds only completed tool batches and structurally closed chapters, absorbs only the tool-result folds inside a chapter's own span, and treats fold placeholders as ordinary span material once the window carries `consolidateAfter` unpinned folds, which keeps the brief index tidy long before pressure matters. Briefs are bounded at 2,000 characters each, one cap for supplied and runtime-written briefs alike, so the visible index normally costs a low single-digit share of the window. Automatic foldability is membership, not position: a span folds because it is a completed tool batch or older material a chapter can compose, and what holds it back is the agent's pins and the blacklist. Openness is structural and event-level: an incomplete tool batch is never proposable, and the routine commit cuts stalest first and stops at its aim, so the newest events stay raw for exactly as long as there is room for them.
|
|
82
|
+
### The cache invariant
|
|
141
83
|
|
|
142
|
-
|
|
84
|
+
**A provider prefix cache is positional.** It replays the longest byte-identical prefix of the prompt, so one changed byte at offset K discards everything cached after K. The cost of a mutation has nothing to do with its size.
|
|
143
85
|
|
|
144
|
-
|
|
86
|
+
**So the runtime may move a byte in the window only at a moment it is already rewriting the window**, which is a commit or a fold. Everything else is a pure append, and an appended byte is never later altered, shortened, removed or repositioned. Never mutate the window, and never show something and then take it back. The second half is the one that costs features: anything that displays state and then updates it in place is out, because updating in place is a mid-prefix write with better manners.
|
|
145
87
|
|
|
146
|
-
|
|
88
|
+
### Marks and commits
|
|
147
89
|
|
|
148
|
-
|
|
90
|
+
<picture>
|
|
91
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/shaneconner/pi-fold/main/media/marks-and-commits-dark.svg">
|
|
92
|
+
<img alt="A completed unit becomes a pending mark that leaves the window and its cached prefix untouched. Marks repeat for free below maxTarget; at maxTarget one commit applies every standing mark in a single rewrite. From the brief, peek appends the exact bytes and expand restores them in place, both after verifying the SHA-256." src="https://raw.githubusercontent.com/shaneconner/pi-fold/main/media/marks-and-commits-light.svg">
|
|
93
|
+
</picture>
|
|
149
94
|
|
|
150
|
-
|
|
95
|
+
A mark moves nothing. The moment a tool batch or chapter closes, the runtime cuts it into a pending mark, up to eight cuts per pass, stalest first. The mark lives in durable state outside the window, so the projection stays byte-identical and the cached prefix survives. Marks accumulate for free until one commit applies all of them in a single rewrite.
|
|
151
96
|
|
|
152
|
-
|
|
97
|
+
What the agent owns is the words. `brief` writes its own sentence onto a pending fold for free, because that brief reaches the window only when the commit writes the placeholder for the first time. A supplied brief is kept verbatim; an unbriefed fold commits with the deterministic brief the runtime writes from the span itself, and the provenance records which happened. Curation stays correctable until the commit: `reboundary` re-cuts a span, `unmark` withdraws a pending mark, and a pending brief can be rewritten any number of times at no cost. A standing fold's brief is never rewritten, because that rewrites the projection at its placeholder.
|
|
153
98
|
|
|
154
|
-
|
|
99
|
+
### The band
|
|
155
100
|
|
|
156
|
-
|
|
101
|
+
A commit fires on one condition: occupancy reaching `maxTarget` of the serving budget. It applies marks stalest first and stops at `minTarget`, so the newest events stay raw and a mark past the bound waits for the next epoch. Below `maxTarget` the window is quiet, because a commit is an epoch transition that risks the whole prefix cache and the right cadence is the fewest commits that keep the window inside its budget.
|
|
157
102
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Protection is unchanged. The frontier never cuts protected evidence, and a pending mark that has become protected refuses at commit with a message naming it, leaving the rest of the epoch to apply. `pi_fold_context {"action":"status"}` reports the pending marks under `automatic.scheduling` with their origin, the estimated share of the serving budget a commit would free, and the estimated prefix tokens it would rewrite.
|
|
169
|
-
|
|
170
|
-
## Reclaiming peek output
|
|
171
|
-
|
|
172
|
-
A `peek` returns a fold's exact stored source, bounded by the chapter cap. That copy then sits in the window as raw evidence while the fold it came from sits beside it as a placeholder: the same bytes, held twice, and the copy is the one automatic folding cannot take. A runtime that does not classify a peek batch as a completed read measures the pressure at every rung and finds nothing eligible. On a workload that peeks freely this is the whole failure: a measured 64-stage session ended with 14 raw peek results holding 1.9M characters, 82% of everything still unfolded, while the runtime had already folded 53 of the 56 stage results and had no supply left.
|
|
173
|
-
|
|
174
|
-
So a peek result is classified as a completed read, unconditionally. The peek batch is eligible for the same tool rung under the same protections: protected evidence is still refused, and the fold record is the ordinary reversible one, so the source recovers exactly. Nothing about the commit's thresholds changes; only what counts as foldable does. The cost is that a peeked span the agent is about to work from can fold back before it is used, which means peeking again to read it; the duplicate window it otherwise leaves behind is the larger bill.
|
|
175
|
-
|
|
176
|
-
## What the invariant cost
|
|
177
|
-
|
|
178
|
-
Three separate features shipped that each rewrote the window mid-prefix, and each carried a code comment asserting the edit was tail-local and therefore safe. In all three cases the comment was the marker of the bug, and it was also the reason the bug survived review: the reasoning looked done, so nobody redid it.
|
|
179
|
-
|
|
180
|
-
Peek reclamation was the costliest. It removed duplicate bytes from the window after a peek and deferred the removal until a later assistant message existed, so the edit would land behind the tail. That deferral is what makes it look tail-local and what guarantees it is not: waiting for a later message is precisely the condition under which the window has already grown over the site of the edit. Measured 2026-08-07, two such reclamations cost 100k fresh tokens. A peek is now append-only, and its duplicate bytes are reclaimed by the tool-fold rung at the next commit, the one moment a rewrite is already being paid for.
|
|
181
|
-
|
|
182
|
-
The ephemeral surfacing slate went the same way, and it deserves its own note because the idea was not the problem. It scored the fold set against the live window and offered the agent a short slate of folds worth peeking at or expanding, and the selection was useful. What failed was the delivery: the slate was shown on one pass and withdrawn on the next so a recomputed slate could take its place, which is a show-then-retract. Its bytes occupy prefix positions in one request and different bytes occupy them in the next, so the cached prefix dies. The delivery was deleted first, and the selector followed on its own measurement: re-homed to the commit boundary as one persisted suggestion per epoch, the sealed corpus showed 86 surfacings with 7 taken, all seven on earlier builds, so the subsystem was deleted whole. What remains is the browsable index the briefs already are, and the agent's `status` and `peek` reads over it.
|
|
183
|
-
|
|
184
|
-
Anticipatory guidance went with them: pressure milestones, the live advisory, curation reminders, the last-call notice. Guidance about a coming event has to arrive before the event, so it can never ride a rewrite the runtime was already paying for; it has to create one. What is left in the window is a bounded receipt of what the runtime just did, built only when a commit produced one, landing inside the freeze, never rendered twice.
|
|
185
|
-
|
|
186
|
-
Earlier releases read those runs as evidence about agents: eleven runs at a voluntary fold share of 0.00, and three runs built to invite curation producing 0, 1 and 0 voluntary folds. That reading is withdrawn. Two things made marking impossible rather than unattractive in those runs. The ladder marked on every measurement, so by the time any invitation reached the agent there was nothing unclaimed left to name, which the records show directly: the unmarked remainder was zero at every measured crossing. And the invitation was anchored to the serving budget rather than to the commit threshold, which on a real deployment puts it above the threshold rather than below it, so it arrived after the commit was already due. Both are fixed below. The numbers stand as measurements of that design; they are not evidence about what an agent will do, and the question is open until it is measured on a runtime where marking is possible.
|
|
103
|
+
```
|
|
104
|
+
272,000 the transport's descriptor
|
|
105
|
+
─────────────────────────────────────────────────────
|
|
106
|
+
~250k peak: inflow continues while the epoch computes
|
|
107
|
+
217,600 maxTarget 0.80 ← the commit fires here
|
|
108
|
+
⋮ marks accumulate, free, quiet
|
|
109
|
+
54,400 minTarget 0.20 ← the commit stops here
|
|
110
|
+
─────────────────────────────────────────────────────
|
|
111
|
+
```
|
|
187
112
|
|
|
188
|
-
|
|
113
|
+
Every commit re-reads roughly `minTarget` of the budget uncached, so a shallower cut buys less runway for nearly the same bill: the rewrite tax is `minTarget / (maxTarget - minTarget)`, 0.33 at 0.80/0.20 against 1.0 at 0.80/0.40.
|
|
189
114
|
|
|
190
|
-
|
|
115
|
+
### Peek and expand
|
|
191
116
|
|
|
192
|
-
|
|
117
|
+
`peek` returns a fold's exact SHA-256-verified source as a tool result at any depth, with the ancestors still collapsed. It appends and never edits the prefix, so checking what a fold holds costs nothing a commit has to pay for. It takes `offset` and `bytes` for a bounded slice and reads any child fold by id, so a large fold has a narrow read.
|
|
193
118
|
|
|
194
|
-
|
|
119
|
+
A peek is ephemeral by default: the bytes ride the window until the model's next message, then their place holds a one-line placeholder and the reply that used them is the surviving trace. Peeking again is lossless and costs one append. The default follows what the agent chose: on the run measured for it, six of seven peeks asked for ephemeral at first exposure. `expand` is the commitment, restoring the source in place until the fold is refolded. Looking is not the same as taking.
|
|
195
120
|
|
|
196
|
-
|
|
121
|
+
### Clipping, pins and the compaction path
|
|
197
122
|
|
|
198
|
-
|
|
123
|
+
Tool results get a second, lighter treatment at the same commit. Results in the oldest `toolFoldThreshold` share of the projected window, half of it by default, are clipped in view: the identifying head stays, the rest becomes a marker naming the entry id that peeks it back, and the stored bytes are untouched, so a later fold of that span still encodes the full result.
|
|
199
124
|
|
|
200
|
-
|
|
125
|
+
Briefs are bounded at 2,000 characters each, so the visible index normally costs a low single-digit share of the window. Folds nest rather than partially overlap, and collapsed folds double as a browsable index: `status` with `detail: "tree"` lists every fold in transcript order with its depth and parent. `pin` holds entries raw through every fold and frees nothing, so pinned mass makes the rest of the window fold sooner; a pinned-share cap stops that from becoming a window nothing can reclaim.
|
|
201
126
|
|
|
202
|
-
|
|
127
|
+
Pi's automatic compaction stays enabled underneath and is intercepted by kind. A threshold pass is cancelled, because the fold commit fires first. An overflow pass becomes an automated recovery: the session branches back to the last message before the error, and the retried pass folds and commits before transmitting. Manual `/compact` passes through untouched as the escape hatch.
|
|
203
128
|
|
|
204
|
-
|
|
129
|
+
Evidence ingestion writes read-only artifacts under the session directory's `pi-fold-evidence/` at mode `0444`, under a 512 MB session cap. It is always on: the artifacts are what an oversized result folds against, so a deployment that stopped writing them would keep its folds and lose what makes them lossless.
|
|
205
130
|
|
|
206
|
-
|
|
131
|
+
## Prior work
|
|
207
132
|
|
|
208
|
-
|
|
209
|
-
| --- | --- | --- |
|
|
210
|
-
| `thresholds` | `{ maxTarget: 0.80, minTarget: 0.40, consolidateAfter: 10, minFoldChars: 8000 }` | The band, set whole or not at all. The first two are proportions of the serving budget: fold at `maxTarget`, fold down to `minTarget`. Against a 272,000-token descriptor the defaults hold the session in roughly a 100k to 250k band: the trigger fires at 80% and the session runs one epoch's inflow past it before the commit lands, and the aim leaves about 100k of the freshest context raw across the whole cycle. `consolidateAfter` is a positive integer count of visible roots per parent owed. `minFoldChars` is the size in characters a fold has to reach to be worth making, and the same number is the floor a gap between two folds has to clear to stay raw: anything smaller is absorbed by the fold beside it. Characters rather than tokens because a token means a different amount of text on every wire. Validated atomically at registration, and an impossible policy is refused by name rather than clamped. User policy only: no agent action can read it back as a mutable surface, though status reports the values. |
|
|
211
|
-
| `providerInputBudget` | the transport's descriptor | Machine surface, off the `/fold-settings` screen as of 2.1: the tokens this deployment may actually put into a request, already net of whatever output reservation it holds back. Every ratio, fence and budget divides by it directly, and pi-fold subtracts nothing from a declared value. It exists for a harness that needs runs comparable across descriptor changes. Shares otherwise apply to the model's own window: the runtime falls back to the transport's descriptor and estimates the reservation out of it, and the descriptor is reported in status either way, so the gap stays auditable. |
|
|
212
|
-
| `blacklistAutoFoldTools` | `new Set()` | The exception list. Every completed tool batch is foldable by the runtime on its own, without an agent mark; name a tool here and its results stay raw instead. Empty by default, because foldability is not the protection: pins are, and they apply to a blacklisted tool and an ordinary one alike. Pass a `ReadonlySet<string>`. Blacklisting is not free: the mass stays in the window until a chapter fold takes it, and a closed unit that alone exceeds the chapter cap is reclaimed whole rather than in pieces. |
|
|
213
|
-
|
|
214
|
-
### Migrating from 2.x
|
|
215
|
-
|
|
216
|
-
Version 3.0 moves the cutting from the agent to the runtime, on the first live session's measurement: a session that never closes its turn never handed the old design a boundary to fold at, so the guard that waited for one held everything while occupancy climbed. Boundaries are now events, not turns.
|
|
217
|
-
|
|
218
|
-
- The `fold` action is deleted from the tool surface. The runtime stages every completed unit itself; the agent annotates with `brief`, corrects with `reboundary` and `unmark`, and protects with `pin`.
|
|
219
|
-
- Remove `guidance`. The option is refused by name: it switched the copy that taught the agent to choose spans, and the agent no longer chooses spans. The post-fold notice that replaced it is not optional, because a fold the agent is never told about is one it can never brief.
|
|
220
|
-
- `thresholds.freshTail` is gone: fresh-tail protection is deleted, because openness is structural per event and the commit's depth bound already stops at the aim. A settings file carrying the key is migrated by dropping it, with every tuned value kept.
|
|
221
|
-
- The default `thresholds` are `maxTarget: 0.80` and `minTarget: 0.40`, which against a 272,000-token descriptor holds a session in roughly a 100k to 250k band.
|
|
222
|
-
- Existing sessions remain readable, folds and briefs included.
|
|
223
|
-
|
|
224
|
-
### Migrating from 1.x
|
|
225
|
-
|
|
226
|
-
Version 2.0 makes the measured deletions explicit instead of accepting options that no longer control the runtime.
|
|
227
|
-
|
|
228
|
-
- Remove `summarizer` and `summarizeContextSpan`. The model brief generator is deleted; every new fold carries a deterministic or agent-supplied brief. Passing either name is refused with the deletion reason.
|
|
229
|
-
- Remove `guidance.thresholdNotices`. `guidance` now contains only `actionResponses`.
|
|
230
|
-
- The default `thresholds.minTarget` is now `0.40`. A host supplying the whole threshold object keeps its declared value.
|
|
231
|
-
- Existing sessions remain readable. Fold records already carrying model-authored briefs keep those briefs and their provenance on load.
|
|
232
|
-
|
|
233
|
-
### Projection candidate records
|
|
234
|
-
|
|
235
|
-
`registerPiFold` returns `{ projectionCandidates }`, a provider a host may call with a Pi context. It emits one record per visible collapsed fold. The fields and current values are:
|
|
236
|
-
|
|
237
|
-
| Field | Emitted value |
|
|
238
|
-
| --- | --- |
|
|
239
|
-
| `version` | `1` |
|
|
240
|
-
| `key` | `"projection:<fold-id>"` |
|
|
241
|
-
| `kind` | `"projection"` |
|
|
242
|
-
| `domain` | `"system"` |
|
|
243
|
-
| `horizon` | `"working"` |
|
|
244
|
-
| `source_id` | Fold ID. |
|
|
245
|
-
| `source_version` | Exact source SHA-256. |
|
|
246
|
-
| `route` | `{ tool: "pi_fold_context", arguments: { action: "expand", id: <fold-id> } }` |
|
|
247
|
-
| `token_cost` | Estimated replacement tokens, rounded up with a minimum of 1. |
|
|
248
|
-
| `expansion_cost` | Estimated exact-source tokens, rounded up with a minimum of 1. |
|
|
249
|
-
| `rank` | Zero-based position among emitted candidates. |
|
|
250
|
-
| `score` | Saved-byte fraction, capped at 1. |
|
|
251
|
-
| `raw_score` | Exact bytes saved by the fold. |
|
|
252
|
-
| `confidence` | `"exact"` |
|
|
253
|
-
| `freshness` | `"current"` |
|
|
254
|
-
| `locked_owner` | `false` |
|
|
255
|
-
| `collapse_key` | `"projection:<fold-id>"` |
|
|
256
|
-
| `generator` | `"projection-model"`, `"projection-deterministic"`, or `"projection-supplied"` from brief provenance. |
|
|
257
|
-
| `generator_version` | `"memory-slate-generators-v2"` |
|
|
258
|
-
| `recency` | `null` |
|
|
259
|
-
|
|
260
|
-
## Status
|
|
261
|
-
|
|
262
|
-
Stable. The option surface is settled and the append-only projection invariant is enforced by the gate suite, which is the contract: `tests/verify.mjs` covers the runtime and `scripts/verify_pi_context_experiment.mjs` covers the experiment harness. New behavior lands with its gate.
|
|
263
|
-
|
|
264
|
-
## Development
|
|
133
|
+
Two recent lines inform this one. Lossless hierarchical compaction, exemplified by LCM ([arXiv:2605.04050](https://arxiv.org/abs/2605.04050)) with a walkthrough at [losslesscontext.ai](https://www.losslesscontext.ai/), builds a summary DAG over older messages with lossless pointers to every original. Agentic context management, exemplified by [Letta](https://github.com/letta-ai/letta) and by [arXiv:2510.11967](https://arxiv.org/abs/2510.11967), gives the agent tools over its own memory. See also [arXiv:2607.00692](https://arxiv.org/abs/2607.00692).
|
|
265
134
|
|
|
266
|
-
|
|
267
|
-
npm ci
|
|
268
|
-
npm run lint
|
|
269
|
-
npm test
|
|
270
|
-
node scripts/verify_pi_context_experiment.mjs
|
|
271
|
-
```
|
|
135
|
+
What this one adds is an efficiency result, arrived at by running the mechanism repeatedly and following the bill. Rotation is the part every approach here shares. The cost sits in where the rewrites land, because a prefix cache charges by position rather than by size, so each fold is staged as a mark that moves nothing and one commit spends the cache for all of them at once. The same campaigns settled the recommended shape: deterministic briefs paired with a durable memory store, which held recall at the lowest cost and wall clock measured. Agent control over boundaries and brief text stays available, and the invitation to use it ships off.
|