pi-fold 1.0.4 → 2.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.
@@ -562,16 +562,6 @@ function mcpServerForTool(toolName, fallback) {
562
562
  : fallback;
563
563
  }
564
564
 
565
- /**
566
- * MCP tools are recognized by their name, and by nothing else.
567
- *
568
- * This was a host-supplied predicate whose default returned false for everything, so a
569
- * conventionally named MCP tool was never classified unless a host wrote the predicate:
570
- * a documented feature whose shipped state guaranteed it never ran. `mcp__server__tool`
571
- * is the convention every MCP host follows, so it is the rule. A name outside the
572
- * convention is deliberately not classified as MCP, and its oversized results fall to
573
- * the ordinary tool-result path.
574
- */
575
565
  function conventionalMcpTool(toolName) {
576
566
  return mcpServerForTool(toolName, null) !== null;
577
567
  }
@@ -1,35 +1,20 @@
1
1
  import { registerActiveContext } from "./active-context.ts";
2
2
  import { registerEvidenceIngestion } from "./evidence.js";
3
- import { createSummarizeContextSpan } from "./summarizer.js";
4
3
 
5
- /**
6
- * THE public surface: five options, and every name outside them is refused.
7
- *
8
- * Unknown keys used to spread through silently, which made every deletion a no-op rather
9
- * than an error and handed a deployment the opposite of what it asked for. A caller that
10
- * passes a name believes it bought behavior, so a name this package does not sell is a
11
- * throw with the replacement named in it.
12
- */
13
4
  const PUBLIC_OPTIONS = Object.freeze([
14
- "thresholds", "summarizer", "providerInputBudget", "blacklistAutoFoldTools", "guidance",
5
+ "thresholds", "providerInputBudget", "blacklistAutoFoldTools", "guidance",
15
6
  ]);
16
7
 
17
- /**
18
- * Both spellings of the auto-fold list were ALLOW-lists, and the list runs the other way
19
- * now. Forwarding one silently would be the worst possible outcome of a rename: a set of
20
- * tools a deployment named to make foldable would become the exact set it barred.
21
- */
8
+ const GENERATOR_DELETED = "the model brief generator is deleted (2026-08-14): across 15 sealed " +
9
+ "generator runs, 75 percent of its 1,186 upgraded briefs were never consulted or never became " +
10
+ "visible, and the no-generator build scored the same for less. Every fold carries its " +
11
+ "deterministic brief; sessions already holding model briefs keep them on load";
12
+
22
13
  const INVERTED_AUTO_FOLD_LIST = "renamed blacklistAutoFoldTools, and the sense is INVERTED: " +
23
14
  "every completed tool batch folds unmarked, and the list names the exceptions whose " +
24
15
  "results must stay raw. An allow-list moved across verbatim would bar exactly the tools " +
25
16
  "it meant to permit";
26
17
 
27
- /**
28
- * Options that left the surface, each answered by name. Deployment identity is hardwired:
29
- * this is a branded package with one deployment, not a framework a host rebrands, and a
30
- * consumer that moved `entryTypePrefix` stranded its own durable folds under a namespace
31
- * nothing would read back.
32
- */
33
18
  const REFUSED_OPTIONS = Object.freeze({
34
19
  toolName: "the deployment identity is hardwired to pi-fold: the tool is pi_fold_context",
35
20
  toolLabel: "the deployment identity is hardwired to pi-fold",
@@ -52,11 +37,11 @@ const REFUSED_OPTIONS = Object.freeze({
52
37
  providerTotalWindow: "renamed providerInputBudget, and it is ALREADY NET: pass the tokens " +
53
38
  "the deployment may actually fill, not the total window the runtime then subtracts a " +
54
39
  "guessed output reservation from",
55
- summarizeContextSpan: "choose a brief generator with summarizer (\"session\" or " +
56
- "{ provider, model, effort })",
40
+ summarizer: GENERATOR_DELETED,
41
+ summarizeContextSpan: GENERATOR_DELETED,
57
42
  });
58
43
 
59
- export function registerPiFold(pi, options = {}, loadHostModule) {
44
+ export function registerPiFold(pi, options = {}) {
60
45
  for (const name of Object.keys(options)) {
61
46
  if (Object.hasOwn(REFUSED_OPTIONS, name)) {
62
47
  throw new Error(`${name} is no longer an option: ${REFUSED_OPTIONS[name]}`);
@@ -66,13 +51,8 @@ export function registerPiFold(pi, options = {}, loadHostModule) {
66
51
  `${PUBLIC_OPTIONS.join(", ")}`);
67
52
  }
68
53
  }
69
- const { summarizer = "session", ...activeContextOptions } = options;
70
- const summarizeContextSpan = createSummarizeContextSpan(summarizer, loadHostModule);
71
- // Evidence first, then the runtime. Ingestion is unconditional: the artifacts are the
72
- // exact-recovery anchors an oversized tool result folds against, so a deployment that
73
- // turned them off kept the folds and lost what makes them lossless.
74
54
  registerEvidenceIngestion(pi);
75
- return registerActiveContext(pi, { ...activeContextOptions, summarizeContextSpan });
55
+ return registerActiveContext(pi, options);
76
56
  }
77
57
 
78
58
  export default function piFold(pi, options) {
@@ -8,7 +8,6 @@ export interface EvidenceRef {
8
8
  sha256: string;
9
9
  }
10
10
 
11
- // Capture trust-boundary intrinsics before mutable prototypes can influence validation.
12
11
  const ArrayConstructor = Array;
13
12
  const arrayIsArray = Array.isArray;
14
13
  const arrayPrototype = Array.prototype;
@@ -95,7 +94,6 @@ function safeJson(value: unknown, ancestors: unknown[]): string | undefined {
95
94
  }
96
95
  }
97
96
 
98
- /** Preserve JSON property order without invoking getters, toJSON, coercion, iterators, or mutable prototypes. */
99
97
  export function stableStringify(value: unknown): string {
100
98
  const serialized = safeJson(value, new ArrayConstructor<unknown>());
101
99
  if (typeof serialized !== "string") throw new Error("Value is not JSON-serializable");
@@ -122,7 +120,6 @@ export function ownDataProperty(value: unknown, key: PropertyKey): unknown {
122
120
  return descriptor && "value" in descriptor ? descriptor.value : undefined;
123
121
  }
124
122
 
125
- /** Copy a dense own-data array without invoking inherited accessors, iterators, or methods. */
126
123
  export function denseOwnArrayValues(value: unknown): unknown[] | null {
127
124
  if (!arrayIsArray(value) || isProxy(value) || getPrototypeOf(value) !== arrayPrototype) return null;
128
125
  const lengthDescriptor = getOwnPropertyDescriptor(value, "length");
@@ -202,7 +199,9 @@ export function objectRefKey(ref: EvidenceRef): string {
202
199
  }
203
200
 
204
201
  export function sameObjectIdentity(left: EvidenceRef, right: EvidenceRef): boolean {
205
- return objectRefKey(left) === objectRefKey(right);
202
+ return left.sessionId === right.sessionId &&
203
+ left.entryId === right.entryId &&
204
+ left.role === right.role;
206
205
  }
207
206
 
208
207
  export function evidenceValue(message: unknown): unknown {
@@ -216,31 +215,6 @@ export function evidenceValue(message: unknown): unknown {
216
215
  };
217
216
  }
218
217
 
219
- /**
220
- * Evidence digests, kept against the message OBJECT.
221
- *
222
- * A message is canonicalized and hashed whole to produce its digest, and the messages
223
- * this runs on are transcript entries: tool results in the tens of kilobytes. Nothing
224
- * cached the result, so every reading that wanted to confirm a ref rehashed the entire
225
- * message from scratch. `exactMapped` does exactly that on every lookup, and the
226
- * selection, surfacing, ordering and accounting passes call it once per ref, per fold,
227
- * several times a turn. Replayed against the sealed sol-20260812 rep-2 session at its
228
- * 85 percent point, that single recomputation was 76 percent of all CPU, and it grows
229
- * with the transcript because the transcript is what it rehashes.
230
- *
231
- * A WeakMap on the object is the same shape `MAPPED_BY_KEY` and `BRANCH_POSITIONS`
232
- * already use one line over, and the same rule gate 113 settled for entry evidence: the
233
- * session is append-only, so keep the derivation against the object and let a REPLACED
234
- * object miss rather than serve a stale answer. Entries are never edited in place; they
235
- * are appended, and a changed message is a different object, which misses and rehashes.
236
- *
237
- * What this no longer detects, stated plainly: a message mutated IN PLACE after it was
238
- * first hashed now keeps its original digest. That case was already half invisible,
239
- * because the ref a snapshot carries was itself computed from that same object at
240
- * mapping time, so a mutation that beat the digest would have to be caught by this
241
- * recomputation alone. No path in this package edits a persisted message, and the
242
- * runtime clones before it stores.
243
- */
244
218
  const EVIDENCE_DIGESTS = new WeakMap<object, string>();
245
219
 
246
220
  export function evidenceSha256(message: unknown): string {
@@ -18,16 +18,9 @@ export function bytes(value: unknown): number {
18
18
  export function boundedUtf8(text: string, maximumBytes: number): string {
19
19
  const buffer = Buffer.from(text, "utf8");
20
20
  if (buffer.length <= maximumBytes) return text;
21
- // A multi-byte sequence cut by the bound decodes to one trailing replacement
22
- // character; dropping it keeps the slice an exact prefix of the source.
23
21
  return buffer.subarray(0, maximumBytes).toString("utf8").replace(/\uFFFD$/, "");
24
22
  }
25
23
 
26
- /**
27
- * The suffix of `text` beginning at a byte offset. A multi-byte sequence cut by the
28
- * offset decodes to one leading replacement character; dropping it keeps the slice an
29
- * exact suffix of the source, which is what makes a paged peek losslessly rejoinable.
30
- */
31
24
  export function utf8Slice(text: string, offsetBytes: number): string {
32
25
  if (offsetBytes <= 0) return text;
33
26
  const buffer = Buffer.from(text, "utf8");
@@ -75,7 +68,6 @@ export function contentText(message: unknown): string {
75
68
  return text.join("\n");
76
69
  }
77
70
 
78
- /** Pi's exact one-entry projection, kept local so this service has no second transcript owner. */
79
71
  export function sessionEntryMessages(entry: Record<string, unknown>): unknown[] {
80
72
  if (entry.type === "message" && entry.message && typeof entry.message === "object") {
81
73
  const message = entry.message as Record<string, unknown>;
@@ -1,9 +1,3 @@
1
- import { objectRefKey } from "../json.ts";
2
- import {
3
- boundedUtf8,
4
- bytes,
5
- messageRole,
6
- } from "./canonical.ts";
7
1
  import { boundReceiptText } from "./measurement.ts";
8
2
  import {
9
3
  contextBrand,
@@ -11,140 +5,8 @@ import {
11
5
  CONTEXT_RECEIPT_BLOCK_BYTES,
12
6
  DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN,
13
7
  MAX_CONTEXT_RECEIPTS,
14
- MAX_LAST_CALL_TEXT_BYTES,
15
- MAX_SURFACING_LINE_BYTES,
16
- MAX_THRESHOLD_NOTICE_TEXT_BYTES,
17
- } from "./policy.ts";
18
- import type {
19
- ActiveContextSnapshot,
20
- ActiveContextState,
21
8
  } from "./policy.ts";
22
9
 
23
- /**
24
- * Guided curation.
25
- *
26
- * The ladder's commit triggers are correct and silent. Silent is the problem: the
27
- * agent learns that its evidence became placeholders by reading a placeholder, which
28
- * is the one moment it can no longer do anything about it. So a commit is ANNOUNCED
29
- * before it happens, early enough that reacting is still cheap, and the announcement
30
- * carries what is about to happen, what it costs, and the actions that change it.
31
- *
32
- * Everything here is deterministic: transcript ordinals, measured tokens and byte
33
- * counts only. No wall clock and no randomness.
34
- */
35
-
36
- export interface StaleToolMass {
37
- /** Serialized bytes of tool results outside the fresh tail and outside every fold. */
38
- bytes: number;
39
- /** How many tool results that mass is spread over. */
40
- results: number;
41
- }
42
-
43
- /**
44
- * Tool-result mass a commit could actually reach: outside the fresh tail, outside any
45
- * protection, and not already owned by a fold. Occupancy alone would announce commits
46
- * that have nothing to fold; this is the second signal that says the announcement is
47
- * about to be worth something.
48
- */
49
- export function staleToolMass(
50
- snapshot: ActiveContextSnapshot,
51
- state: ActiveContextState,
52
- ): StaleToolMass {
53
- const owned = new Set(state.folds.flatMap((fold) =>
54
- fold.parts.flatMap((part) => part.kind === "raw" ? [objectRefKey(part.ref)] : [])));
55
- let total = 0;
56
- let results = 0;
57
- for (const item of snapshot.mapped) {
58
- if (!item.ref || messageRole(item.message) !== "toolResult") continue;
59
- if (snapshot.toolProtectedIndices.has(item.index)) continue;
60
- if (owned.has(objectRefKey(item.ref))) continue;
61
- total += bytes(item.message);
62
- results += 1;
63
- }
64
- return { bytes: total, results };
65
- }
66
-
67
- export interface CurationSignals {
68
- /** Measured provider tokens as a share of the truthful serving budget; null unmeasured. */
69
- occupancy: number | null;
70
- /** The trigger line this occupancy is read against: thresholds.maxTarget. */
71
- maxTarget: number;
72
- occupancyTokens: number | null;
73
- /** THE serving budget: one resolved value, the same one the fence and estimator use. */
74
- budgetTokens: number;
75
- window: number;
76
- /** Stale tool mass as a share of the serving budget, in the session's own tokens. */
77
- staleToolShare: number;
78
- staleToolTokens: number;
79
- staleToolResults: number;
80
- /** Marks a commit could apply right now. */
81
- eligibleFolds: number;
82
- }
83
-
84
- /**
85
- * Both signals are measured against ONE resolved serving budget, handed in by the
86
- * caller that owns the capacity accounting. Nothing here re-derives a window from a
87
- * descriptor: rep 15's trigger and gate ran the whole run against a 272,000-token
88
- * per-request descriptor while the fence used the truthful 383,616, because the budget
89
- * was computed twice from two different places.
90
- */
91
- export function curationSignals(input: {
92
- snapshot: ActiveContextSnapshot;
93
- state: ActiveContextState;
94
- usedTokens: number | null;
95
- budgetTokens: number;
96
- /** The resolved serving WINDOW the budget was taken from, reported for audit. */
97
- window: number;
98
- /** The session's own measured serialized chars per token; never a fixed constant. */
99
- charsPerToken: number;
100
- eligibleFolds: number;
101
- }): CurationSignals {
102
- const mass = staleToolMass(input.snapshot, input.state);
103
- const charsPerToken = Number.isFinite(input.charsPerToken) && input.charsPerToken > 0
104
- ? input.charsPerToken
105
- : 4;
106
- const staleToolTokens = Math.ceil(mass.bytes / charsPerToken);
107
- return {
108
- maxTarget: input.snapshot.thresholds.maxTarget,
109
- occupancy: input.usedTokens === null || input.budgetTokens <= 0
110
- ? null
111
- : input.usedTokens / input.budgetTokens,
112
- occupancyTokens: input.usedTokens,
113
- budgetTokens: input.budgetTokens,
114
- window: input.window,
115
- staleToolShare: input.budgetTokens > 0 ? staleToolTokens / input.budgetTokens : 0,
116
- staleToolTokens,
117
- staleToolResults: mass.results,
118
- eligibleFolds: input.eligibleFolds,
119
- };
120
- }
121
-
122
- /**
123
- * ONE signal. Occupancy of the serving budget reaches maxTarget, and a commit is due.
124
- *
125
- * The second signal is gone with the announcement it guarded. It existed because the
126
- * trigger ANNOUNCED a commit before running one, and announcing a commit that has
127
- * nothing stale to fold is a false statement in the window; the announcement was
128
- * deleted when its cache cost was measured, and the AND-condition outlived its reason.
129
- * A commit with nothing eligible now simply applies nothing and says so, which the
130
- * reclaim floor and the eligibility accounting already report. Stale mass is still
131
- * measured and still reported; it is no longer a condition on the trigger.
132
- */
133
- export function curationTriggerFires(signals: CurationSignals): boolean {
134
- return signals.occupancy !== null &&
135
- Number.isFinite(signals.occupancy) &&
136
- signals.occupancy >= signals.maxTarget;
137
- }
138
-
139
- /**
140
- * The awareness block a mark call answers with.
141
- *
142
- * The projection is byte-frozen between fold events, so this tool result is the ONLY
143
- * cache-free place left to tell the agent where it stands. It carries three things and
144
- * stops: what is now held, what is still on the table as an aggregate with a bounded
145
- * head of the largest names, and the one percentage worth steering by. It is hard
146
- * bounded on the receipt block's principle: awareness that becomes bloat is bloat.
147
- */
148
10
  export function markAwarenessText(input: {
149
11
  held: ReadonlyArray<{ id: string; kind: string; tokens: number }>;
150
12
  remainder: { spans: number; tokens: number; share: number; candidates: ReadonlyArray<{ id: string; tokens: number }> };
@@ -168,16 +30,6 @@ export function markAwarenessText(input: {
168
30
  ].join("\n"), CONTEXT_MARK_RESPONSE_BYTES, `[${brand} marks] Held; the remainder is unavailable this pass.`);
169
31
  }
170
32
 
171
- /**
172
- * The rider: the one post-commit carrier, composed at a fold commit and delivered
173
- * INSIDE the rewrite that commit already paid for, beside the receipt. Its wording
174
- * follows the curation rule (report what is happening, name the actions), its numbers
175
- * are decision inputs only: pending agent marks with what they free, up to three
176
- * completed-unit anchors, and the pinned share against its cap. Raw occupancy,
177
- * distance-to-commit and unmarked-share percentages stay OUT: they are pressure
178
- * readouts, not decisions, and the pre-commit last call is where they are both
179
- * accurate and free.
180
- */
181
33
  export function contextRiderText(input: {
182
34
  toolName: string;
183
35
  brandNoun?: string;
@@ -185,12 +37,9 @@ export function contextRiderText(input: {
185
37
  eligibleMarks: number;
186
38
  freedTokens: number;
187
39
  eligibleFreedTokens: number;
188
- /** Up to three completed-unit entry ids the agent could mark next. */
189
40
  anchors: string[];
190
41
  pinnedShare: number;
191
42
  maxPinnedShare: number;
192
- /** The surfacing slate, already bounded and already logged; null on a silent pass. */
193
- suggestion?: string | null;
194
43
  }): string {
195
44
  const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
196
45
  const anchors = input.anchors.length
@@ -198,10 +47,7 @@ export function contextRiderText(input: {
198
47
  : "none";
199
48
  const pinnedPercent = Math.round(input.pinnedShare * 100);
200
49
  const capPercent = Math.round(input.maxPinnedShare * 100);
201
- // The slate carries its OWN bound and is joined after this text is bounded, so the
202
- // carrier's overhead is its bound plus the line's, and neither can eat the other: a
203
- // long rider must never truncate the one sentence a suggestion consists of.
204
- return joinSurfacing(boundReceiptText(
50
+ return boundReceiptText(
205
51
  [
206
52
  `[${brand} notice] A fold commit just landed; the next one will batch every pending mark ` +
207
53
  "into one rewrite, and marks are free until then.",
@@ -219,93 +65,9 @@ export function contextRiderText(input: {
219
65
  ].join("\n"),
220
66
  2_048,
221
67
  `[${brand} notice] Post-commit curation details are unavailable this pass.`,
222
- ), input.suggestion);
223
- }
224
-
225
- /** One carrier, one optional slate line, appended last and bounded on its own. */
226
- export function joinSurfacing(text: string, suggestion?: string | null): string {
227
- if (!suggestion) return text;
228
- return `${text}\n${boundedUtf8(suggestion, MAX_SURFACING_LINE_BYTES)}`;
229
- }
230
-
231
- /** The ruled last-call sentence (Shane, 2026-08-09 13:23), verbatim. */
232
- export const LAST_CALL_WORDING =
233
- "Fold commit triggered, please add or edit marks, pin or unpin context based on foreseeable relevance.";
234
-
235
- /**
236
- * The pre-commit last-call.
237
- *
238
- * It rides the commit boundary: the band-top trigger has fired, the rewrite is one
239
- * round away, so this is the single moment telemetry is both accurate and free. It
240
- * carries the ruled wording, the three numbers the ruling names (occupancy against the
241
- * commit line, unmarked foldable mass the ladder can reach, pending marks), the verbs,
242
- * and the one-round law. Never a question: continuing the task is the stated default,
243
- * and the commit proceeds regardless.
244
- */
245
- export function lastCallText(input: {
246
- signals: CurationSignals;
247
- unmarked: { spans: number; tokens: number };
248
- pendingMarks: number;
249
- /** Peek copies this exposure marked for reclaim, so the pin that vetoes one is timely. */
250
- peekReclaims?: number;
251
- /** The surfacing slate, already bounded and already logged; null on a silent pass. */
252
- suggestion?: string | null;
253
- toolName: string;
254
- brandNoun?: string;
255
- }): string {
256
- const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
257
- const { signals } = input;
258
- const occupancy = signals.occupancy === null ? "unmeasured" : `${Math.round(signals.occupancy * 100)}%`;
259
- const peekReclaims = input.peekReclaims ?? 0;
260
- return joinSurfacing(boundReceiptText([
261
- `[${brand} last call] ${LAST_CALL_WORDING}`,
262
- `Occupancy is ${occupancy} of the ${signals.budgetTokens}-token serving budget, at or past the ` +
263
- `${Math.round(signals.maxTarget * 100)}% commit line. Unmarked foldable mass: ` +
264
- `${input.unmarked.spans} span(s), about ${input.unmarked.tokens} tokens. ` +
265
- `Pending marks: ${input.pendingMarks}.`,
266
- // Stated only when there is one to veto: a standing sentence about zero peek copies
267
- // is bytes every exposure pays for and no round ever acts on.
268
- peekReclaims > 0
269
- ? `Peek copies reclaimed by this commit: ${peekReclaims}. Each duplicates a fold you can peek ` +
270
- "again at any time, and its placeholder names that fold, so nothing becomes unreachable. " +
271
- "Pin one to keep the copy raw."
272
- : "",
273
- `Marks: ${input.toolName} ` +
274
- "{\"action\":\"fold\",\"marks\":[{\"ids\":[\"<start>\",\"<end>\"],\"brief\":\"<factual brief>\"}]} " +
275
- "adds or widens several in one call. Pins: {\"action\":\"protect\",\"ids\":[\"<entry-id>\"]} holds " +
276
- "entries raw through every fold, and {\"action\":\"unprotect\"} releases them.",
277
- "This is one round: the commit proceeds on the pass after your next response with whatever marks " +
278
- "exist. Continuing the task is the default; nothing here needs a reply.",
279
- ].filter(Boolean).join("\n"), MAX_LAST_CALL_TEXT_BYTES,
280
- `[${brand} last call] ${LAST_CALL_WORDING}`), input.suggestion);
281
- }
282
-
283
- /**
284
- * One occupancy waypoint, stated once. Informatory and quiet: below the commit line
285
- * nothing folds automatically, so the only useful sentence is where the window stands
286
- * and that marks and pins made now shape the eventual commit.
287
- */
288
- export function thresholdNoticeText(input: {
289
- share: number;
290
- occupancyTokens: number;
291
- budgetTokens: number;
292
- maxTarget: number;
293
- toolName: string;
294
- brandNoun?: string;
295
- }): string {
296
- const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
297
- return boundReceiptText(
298
- `[${brand} notice] Context occupancy crossed ${Math.round(input.share * 100)}% of the ` +
299
- `${input.budgetTokens}-token serving budget (${input.occupancyTokens} tokens). Nothing folds before ` +
300
- `the fold commit at ${Math.round(input.maxTarget * 100)}%; marks and pins made now shape it: ` +
301
- `${input.toolName} {"action":"fold","marks":[{"ids":["<start>","<end>"],"brief":"<factual brief>"}]} ` +
302
- 'or {"action":"protect","ids":["<entry-id>"]}.',
303
- MAX_THRESHOLD_NOTICE_TEXT_BYTES,
304
- `[${brand} notice] Context occupancy crossed ${Math.round(input.share * 100)}% of the serving budget.`,
305
68
  );
306
69
  }
307
70
 
308
- /** One automatic context action, reported back as status rather than as advice. */
309
71
  export interface ContextReceipt {
310
72
  kind: string;
311
73
  ordinal: number;
@@ -313,20 +75,14 @@ export interface ContextReceipt {
313
75
  foldsCommitted: number;
314
76
  foldsCreated: number;
315
77
  freedTokens: number;
316
- /** Measured occupancy either side of the action, in tokens. The impact, concretely. */
317
78
  occupancyBefore: number | null;
318
79
  occupancyAfter: number | null;
319
- /** What the action actually folded, split the way an agent thinks about it. */
320
80
  spansFolded: number;
321
81
  toolResultsFolded: number;
322
- /** Bite-sized splitting, when an oversized span became sequential folds. */
323
82
  splitFolds: number;
324
83
  splitFromChars: number;
325
- /** Short stale spans a commit absorbed into their later neighbour fold. */
326
84
  absorbedWedges: number;
327
- /** Overflow recovery, when this action rebuilt a request the provider would reject. */
328
85
  recovered: boolean;
329
- /** Mass the agent's pins held out of this commit's reach, in bytes. */
330
86
  protectedBytes: number;
331
87
  note: string | null;
332
88
  }
@@ -352,7 +108,6 @@ export function contextReceipt(input: Partial<ContextReceipt> & { kind: string;
352
108
  };
353
109
  }
354
110
 
355
- /** Newest last, oldest evicted: a receipt block that can never grow into its own problem. */
356
111
  export function withReceipt(
357
112
  receipts: readonly ContextReceipt[],
358
113
  receipt: ContextReceipt,
@@ -394,11 +149,6 @@ export function receiptLine(receipt: ContextReceipt): string {
394
149
  return parts.filter(Boolean).join(" ");
395
150
  }
396
151
 
397
- /**
398
- * The receipt block. Informatory, never exhortative: it reports what the runtime did,
399
- * what it cost, and the verbs that correct it. Hard-bounded, because a report about
400
- * bloat that becomes bloat has argued against itself.
401
- */
402
152
  export function receiptBlockText(input: {
403
153
  receipts: readonly ContextReceipt[];
404
154
  toolName: string;
@@ -413,11 +163,6 @@ export function receiptBlockText(input: {
413
163
  "{\"action\":\"rebrief\",\"id\":\"<fold-id>\",\"brief\":\"<factual brief>\"} replaces a brief that " +
414
164
  "does not describe what you needed; {\"action\":\"reboundary\",\"id\":\"<fold-id>\"} returns a " +
415
165
  "mis-cut fold to raw so you can fold the span you meant.";
416
- // The verbs are the only part of this block an agent can act on, and they sit last, so
417
- // a plain tail truncation drops them precisely when the report runs long -- which is
418
- // when a commit did the most and correcting it matters most. Reserve them, and spend
419
- // what remains on receipt lines newest-first: an older action that no longer fits is
420
- // the right thing to lose, and its structured record is still in the ring.
421
166
  const fixed = Buffer.byteLength(header, "utf8") + Buffer.byteLength(verbs, "utf8") + 1;
422
167
  if (fixed > CONTEXT_RECEIPT_BLOCK_BYTES) {
423
168
  return `[${brand} actions] Recent automatic context actions are unavailable this pass.`;
@@ -427,8 +172,6 @@ export function receiptBlockText(input: {
427
172
  for (let index = input.receipts.length - 1; index >= 0; index -= 1) {
428
173
  const line = receiptLine(input.receipts[index]);
429
174
  if (Buffer.byteLength(line, "utf8") + 1 > remaining) {
430
- // Not even the newest line fits whole: carry as much of it as the reservation
431
- // leaves rather than reporting an action with no detail at all.
432
175
  if (!kept.length && remaining > 1) kept.push(boundReceiptText(line, remaining - 1, ""));
433
176
  break;
434
177
  }