pi-fold 1.0.3 → 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.
@@ -35,7 +35,6 @@ import {
35
35
  } from "./persistence.ts";
36
36
  import { currentTurnRefKeys } from "./transcript.ts";
37
37
  import {
38
- EPOCH_MAX_TOPUP_MARKS,
39
38
  ESTIMATED_BYTES_PER_TOKEN,
40
39
  MAX_WEDGE_ABSORB_TOKENS,
41
40
  ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES,
@@ -65,60 +64,23 @@ import {
65
64
  resultCall,
66
65
  } from "./selection.ts";
67
66
 
68
- /**
69
- * Two-phase fold scheduling.
70
- *
71
- * A provider prefix cache is positional: editing the middle of the window
72
- * invalidates every byte after the edit, so per-turn folding pays a full uncached
73
- * prefix over and over. Nothing here changes what a fold MEANS. A fold decision
74
- * becomes a MARK, which moves no projection byte, and a later COMMIT applies every
75
- * pending mark through the same preparation and commit machinery an immediate fold
76
- * uses, in one rewrite.
77
- *
78
- * Every function is deterministic: transcript ordinals and positions only, never a
79
- * wall clock and never randomness.
80
- */
81
-
82
- /** The empty read, shared and frozen, so a state with no marks allocates nothing. */
83
67
  const NO_PENDING_MARKS: readonly PendingMark[] = Object.freeze([]);
84
68
 
85
- /**
86
- * The pending marks as a READ VIEW, not a copy.
87
- *
88
- * Every caller here reads: membership tests, accounting sums, and filters that build
89
- * a fresh array anyway. The copy belongs on the WRITE path, and `withPendingMarks`
90
- * already takes it, so taking a second one on every read bought nothing and cost a
91
- * great deal: `markSpanRefs` calls this once per mark and is itself called once per
92
- * mark by the accounting, eligibility, staleness, current-turn and absorption
93
- * readings, which made a deep clone of the whole array quadratic in the mark count
94
- * per pass. Profiled at 21 percent of a 120-turn session, the single largest cost in
95
- * the runtime, ahead of every projection and hash.
96
- *
97
- * The view is safe because the state is replaced rather than edited: no code path
98
- * mutates `state.pendingMarks` in place, and the readonly type is what keeps it so.
99
- */
100
69
  export function pendingMarks(state: Pick<ActiveContextState, "pendingMarks">): readonly PendingMark[] {
101
70
  return state.pendingMarks ?? NO_PENDING_MARKS;
102
71
  }
103
72
 
104
- /**
105
- * Property ORDER matters here: the state digest is a stable stringify, so an
106
- * appended key would replay as digest drift. Rebuild the tail of the record in the
107
- * same order `parseActiveContextState` produces.
108
- */
109
73
  export function withPendingMarks(
110
74
  state: ActiveContextState,
111
75
  marks: readonly PendingMark[],
112
76
  ): ActiveContextState {
113
77
  const next = marks.length ? parsePendingMarks(clone(marks) as PendingMark[]) : [];
114
78
  const head = { ...state };
115
- delete head.surfacing;
116
79
  delete head.pendingMarks;
117
80
  delete head.advisory;
118
81
  delete head.prepared;
119
82
  return {
120
83
  ...head,
121
- ...(state.surfacing?.length ? { surfacing: clone(state.surfacing) } : {}),
122
84
  ...(next.length ? { pendingMarks: next } : {}),
123
85
  ...(state.advisory ? { advisory: clone(state.advisory) } : {}),
124
86
  ...(state.prepared ? { prepared: clone(state.prepared) } : {}),
@@ -131,7 +93,6 @@ export interface MarkAddition {
131
93
  reason: string | null;
132
94
  }
133
95
 
134
- /** Idempotent by mark key, bounded, and never silently dropping the oldest mark. */
135
96
  export function addPendingMark(state: ActiveContextState, mark: PendingMark): MarkAddition {
136
97
  const marks = pendingMarks(state);
137
98
  if (marks.some((existing) => pendingMarkKey(existing) === pendingMarkKey(mark))) {
@@ -151,43 +112,15 @@ export function estimatedTokens(sizeInBytes: number): number {
151
112
  return Math.max(0, Math.ceil(sizeInBytes / ESTIMATED_BYTES_PER_TOKEN));
152
113
  }
153
114
 
154
- /** Transcript-position ordering used for marks; identical to the surfacing ordinal. */
155
115
  export function markOrdinal(snapshot: Pick<ActiveContextSnapshot, "mapped">): number {
156
116
  return snapshot.mapped.length;
157
117
  }
158
118
 
159
119
  export interface MarkSpanRefs {
160
- /** Every evidence ref the mark's parts resolve to against the state in hand. */
161
120
  refs: EvidenceRef[];
162
- /**
163
- * The first part naming a fold the state does not hold, or null when the span reads
164
- * in full. `pending` is the difference between a defer and a drop: a mark still
165
- * standing carries the id its committed fold will have, so that fold is about to
166
- * exist, and where no mark names it nothing ever will.
167
- */
168
121
  unresolved: { foldId: string; pending: boolean } | null;
169
122
  }
170
123
 
171
- /**
172
- * THE ONE READING OF A MARK'S SPAN, AND IT ANSWERS RATHER THAN THROWS.
173
- *
174
- * A mark's parts name folds by id. Between the mark and the commit the state can stop
175
- * holding one: `reboundary` dissolves a root while consulting only `fold.parentId`, so
176
- * nothing there consults the pending marks, and the mark is left naming a fold that is
177
- * gone. `candidateSourceRefs` answers that with `Missing candidate child`, which is
178
- * right for a candidate being prepared and wrong for every reading that merely reports
179
- * on a mark: accounting, eligibility, staleness, the current-turn guard, the claimed
180
- * keys and wedge absorption all ran that throw out of a status call or a commit pass,
181
- * on state the runtime itself produced (Shane 2026-08-10).
182
- *
183
- * So the readings share this, and it is total. Raw parts resolve; a held child is
184
- * flattened; an unheld child is REPORTED, with the refs either side of it still
185
- * collected, because a partial span is the honest reading of a partly resolvable mark
186
- * and a guard that can see half the evidence is better than one that sees none. A
187
- * repeated fold on the path reads as unresolved and not pending: a corrupt forest costs
188
- * one mark the ordinary drop rather than hanging the caller, which is the same stop the
189
- * apply order's `placing` set carries.
190
- */
191
124
  export function markSpanRefs(state: ActiveContextState, mark: PendingMark): MarkSpanRefs {
192
125
  const byId = new Map(state.folds.map((fold) => [fold.id, fold] as const));
193
126
  const marks = pendingMarks(state);
@@ -230,11 +163,6 @@ export function markSpanRefs(state: ActiveContextState, mark: PendingMark): Mark
230
163
  return { refs, unresolved };
231
164
  }
232
165
 
233
- /**
234
- * Estimated bytes a mark would remove from the projection. Fold marks use a
235
- * placeholder estimate rather than a trial fold: the number is presentational, so
236
- * paying a full preparation per mark on every status call is not worth exactness.
237
- */
238
166
  export function markFreedBytes(
239
167
  snapshot: ActiveContextSnapshot,
240
168
  state: ActiveContextState,
@@ -257,14 +185,6 @@ export function markFreedBytes(
257
185
  return Math.max(0, bytes(source) - placeholder);
258
186
  }
259
187
 
260
- /**
261
- * Whether a pending mark covers any evidence the current excursion just gathered.
262
- *
263
- * Read off whatever the span resolves to. A mark naming an unheld fold still answers
264
- * here, on the parts that DO resolve, so a span holding both fresh raw evidence and a
265
- * child that has not landed is guarded on the fresh evidence instead of throwing the
266
- * whole commit.
267
- */
268
188
  export function markTouchesCurrentTurn(
269
189
  state: ActiveContextState,
270
190
  mark: PendingMark,
@@ -274,24 +194,6 @@ export function markTouchesCurrentTurn(
274
194
  return markSpanRefs(state, mark).refs.some((ref) => currentTurn.has(objectRefKey(ref)));
275
195
  }
276
196
 
277
- /**
278
- * The real staleness of a mark: the earliest window index its span covers, in branch
279
- * order.
280
- *
281
- * A mark's `ordinal` is the transcript position at MARK time, so across epochs it
282
- * carries when the DECISION was made and nothing about the age of what the decision
283
- * covers, and inside one epoch every mark shares one value. Ordering a release by it
284
- * therefore fell through to comparing mark ids, which are content hashes. Measured on the
285
- * sealed rep-3 state 2026-08-10: the bounded release held back the marks covering window
286
- * indices 101 and 111 and surrendered the one covering 117, the newest material on the
287
- * table, so the two marks the guard kept were not the two it exists to keep.
288
- *
289
- * A span that no longer maps sorts past the newest entry: there is no staleness to read
290
- * on material that has left the branch, and releasing such a mark frees no window byte.
291
- * A span that does not resolve in full sorts there too, for the same reason and by the
292
- * same number: while a named fold is missing there is no start to read, and the apply
293
- * order places such a mark off its naming rather than off this reading.
294
- */
295
197
  export function markSpanStart(
296
198
  snapshot: ActiveContextSnapshot,
297
199
  state: ActiveContextState,
@@ -308,71 +210,48 @@ export function markSpanStart(
308
210
  return indices.length ? Math.min(...indices) : snapshot.mapped.length;
309
211
  }
310
212
 
311
- /**
312
- * How many of the newest guarded marks the pressure waiver still protects. The agent's
313
- * MOST RECENT reads are the ones an in-flight excursion is about to work from, so they
314
- * are the last thing surrendered; everything older is fair game once the window is the
315
- * bigger risk.
316
- */
317
213
  export const GUARD_WAIVER_PROTECTED_MARKS = 2;
318
214
 
319
- /**
320
- * How many marks the guard must be holding before the pressure waiver releases any.
321
- * Without a floor the waiver degrades into exactly the per-pass folding epoch
322
- * scheduling exists to prevent: the batch drains, one new mark is guarded on the next
323
- * pass, and releasing that one mark buys a whole prefix invalidation. Below the fence
324
- * the waiver only ever releases a BATCH. The fence itself ignores this floor.
325
- */
326
215
  export const GUARD_WAIVER_MINIMUM_MARKS = 4;
327
216
 
328
- /**
329
- * Survivability outranks the guard.
330
- *
331
- * The current-turn guard is correct while there is room to wait, and fatal once there
332
- * is not: a workload that never emits a terminal assistant message has ONE turn, so
333
- * the boundary never advances and every mark is guarded forever. Measured 2026-08-06
334
- * (rep 11): 58 assistant messages, all stopReason "toolUse", ZERO terminal ones, so
335
- * the automatic commit applied nothing all run; the only marks that ever landed came
336
- * from the agent's own two explicit commits. The window reached 359,625 tokens and the
337
- * next request went out at ~458k estimated and was rejected by the provider.
338
- *
339
- * So: the guard holds in full while the commit has other work. It is waived only when
340
- * it would STARVE the commit -- no other mark could be applied -- and pressure has
341
- * reached the backstop, and then only for the OLDEST guarded marks. At the hard fence
342
- * every guarded mark is waived, because the only useful action left is the fold that
343
- * keeps the request transmissible.
344
- */
345
217
  export function guardWaiverCount(input: {
346
218
  snapshot: ActiveContextSnapshot;
347
219
  ratio: number | null;
348
220
  guardedMarks: number;
349
- /** Marks this commit can apply WITHOUT the waiver. While any exist, the guard holds. */
350
221
  otherApplicableMarks: number;
222
+ /**
223
+ * THE BOUNDARY IS ITS OWN SIGNAL, and a better one than the occupancy anchor.
224
+ *
225
+ * The anchor exists to answer "is this session in enough trouble to spend the open
226
+ * turn's own evidence": a proxy, because nothing else was asking. At a compaction
227
+ * boundary Pi has already answered it. Cancelling the compaction and then applying
228
+ * nothing hands Pi's own threshold a byte-identical projection, which is worse than
229
+ * letting it compact, because compaction would at least have shed something.
230
+ *
231
+ * Measured on the sol-20260813 smoke: the boundary fired, cancelled the compaction and
232
+ * retained all seven marks at 0.20 occupancy, freeing nothing. The full run reaches the
233
+ * anchor 12,576 tokens past its own trigger, so it self-corrects there and this is
234
+ * about what happens in between; a smoke, or any session whose budget it never
235
+ * approaches, never corrects at all.
236
+ *
237
+ * Everything else about the waiver is unchanged, and deliberately: the newest marks
238
+ * still stay raw, a small guarded set still holds whole, and other applicable work
239
+ * still takes precedence over spending the open turn.
240
+ */
241
+ boundary?: boolean;
351
242
  }): number {
352
- const { snapshot, ratio, guardedMarks, otherApplicableMarks } = input;
243
+ const { snapshot, ratio, guardedMarks, otherApplicableMarks, boundary } = input;
353
244
  if (guardedMarks <= 0 || typeof ratio !== "number" || !Number.isFinite(ratio)) return 0;
354
245
  if (ratio >= hardFenceRatio(snapshot)) return guardedMarks;
355
246
  const occupancy = budgetOccupancy(snapshot, ratio);
356
- if (occupancy === null || occupancy < snapshot.policy.refoldRatio || otherApplicableMarks > 0) return 0;
247
+ if (occupancy === null || otherApplicableMarks > 0) return 0;
248
+ if (occupancy < snapshot.policy.refoldRatio && !boundary) return 0;
357
249
  if (guardedMarks < GUARD_WAIVER_MINIMUM_MARKS) return 0;
358
250
  return Math.max(1, guardedMarks - GUARD_WAIVER_PROTECTED_MARKS);
359
251
  }
360
252
 
361
253
  export type MarkEligibility = "eligible" | "protected" | "unfulfillable";
362
254
 
363
- /**
364
- * Whether a pending mark can be applied right now.
365
- *
366
- * "protected" is a WAITING state, not a failure: the span is fresh or the agent
367
- * protected it, and both conditions expire. "unfulfillable" is terminal: the evidence
368
- * or the fold the mark names has left the branch, so no later commit can honour it.
369
- * Collapsing the two is what made a refusal look like a drop.
370
- *
371
- * A mark naming a fold the state does not hold is judged by the same two words, and
372
- * the classification is the whole answer: a fold another pending mark is about to mint
373
- * is a span still WAITING to be readable, and a fold nothing will mint has left the
374
- * branch exactly as lost evidence has. No third word (Shane 2026-08-10).
375
- */
376
255
  export function markEligibility(
377
256
  snapshot: ActiveContextSnapshot,
378
257
  state: ActiveContextState,
@@ -403,25 +282,15 @@ export interface MarkAccounting {
403
282
  freedTokens: number;
404
283
  freedBudgetShare: number;
405
284
  rewriteTokens: number;
406
- /** Marks a commit could apply right now. */
407
285
  eligibleMarks: number;
408
- /** Marks whose span is still fresh or protected, waiting rather than lost. */
409
286
  retainedMarks: number;
410
287
  eligibleFreedBytes: number;
411
288
  eligibleFreedTokens: number;
412
- /** The ROI signal: eligible marked mass as a share of the truthful window. */
413
289
  eligibleFreedBudgetShare: number;
414
- /** Raw peek mass no reclamation can take: pinned reads and protected results. */
415
290
  pinnedBytes: number;
416
- /** How many peek results that mass is spread over. */
417
291
  pinnedResults: number;
418
292
  }
419
293
 
420
- /**
421
- * What a commit of the current marks would cost and free. `rewriteTokens` is the
422
- * prefix a commit invalidates: everything from the earliest marked message to the
423
- * end of the window, which is exactly what a positional cache must re-send.
424
- */
425
294
  export function markAccounting(
426
295
  snapshot: ActiveContextSnapshot,
427
296
  state: ActiveContextState,
@@ -474,22 +343,6 @@ export function markAccounting(
474
343
  };
475
344
  }
476
345
 
477
- /**
478
- * THE commit trigger. There is exactly one.
479
- *
480
- * Occupancy of the serving budget reaches maxTarget and a commit fires. Nothing else
481
- * fires one automatically: the user command and the fence/recovery lane are the only
482
- * other paths into a commit, and both are authority rather than economics.
483
- *
484
- * Two arms used to sit here. The eligible-share arm asked whether accumulated marks
485
- * were worth a rewrite and fired 164 commits from ordinal 17 in rep 15, each a fresh
486
- * cache rebuild bought with marks that had not yet earned one; worse, it could fire
487
- * BELOW maxTarget, which is the quiet-runtime law it was sitting underneath. The
488
- * pressure arm read a window ratio against the refold rung, which after
489
- * re-denomination is simply a second line above this one. Both are gone. Marking is
490
- * doorless below the commit line and costs nothing, so what a pass accumulates is never a
491
- * reason to commit; crossing the band top is.
492
- */
493
346
  export function epochCommitDue(snapshot: ActiveContextSnapshot, ratio: number | null): boolean {
494
347
  const occupancy = budgetOccupancy(snapshot, ratio);
495
348
  return occupancy !== null && occupancy >= snapshot.thresholds.maxTarget;
@@ -505,8 +358,6 @@ export function foldMarkFor(input: {
505
358
  const { candidate } = input;
506
359
  return {
507
360
  mark: "fold",
508
- // The same identity the committed fold will get, so a repeated decision is
509
- // inert rather than a second fold of the same span.
510
361
  id: foldIdFor(candidate.kind, candidate.parts),
511
362
  kind: candidate.kind,
512
363
  parts: clone(candidate.parts),
@@ -517,25 +368,18 @@ export function foldMarkFor(input: {
517
368
  };
518
369
  }
519
370
 
520
- /** The deterministic brief a ladder decision would have folded with. */
521
371
  export function ladderBrief(
522
372
  snapshot: ActiveContextSnapshot,
523
373
  state: ActiveContextState,
524
374
  candidate: FoldCandidate,
525
375
  ): string {
526
376
  if (candidate.kind === "tool-result") return automaticToolBrief(snapshot, candidate);
527
- if (candidate.kind === "consolidation") return deterministicConsolidationBrief(candidate, state);
377
+ if (candidate.kind === "consolidation") {
378
+ return deterministicConsolidationBrief(candidate, state, snapshot.toolName, snapshot);
379
+ }
528
380
  return deterministicChapterCandidateBrief(snapshot, candidate);
529
381
  }
530
382
 
531
- /**
532
- * The brief and provenance an automatic mark carries.
533
- *
534
- * A warmed chapter brief the runtime already paid a model call for is REUSED when the
535
- * prepared fold is exactly this candidate. Under epoch scheduling every automatic fold
536
- * arrives through a mark, so a preparation whose brief the mark path ignored would be a
537
- * model call spent on nothing.
538
- */
539
383
  export function automaticMarkBrief(
540
384
  snapshot: ActiveContextSnapshot,
541
385
  state: ActiveContextState,
@@ -548,11 +392,6 @@ export function automaticMarkBrief(
548
392
  return { brief: ladderBrief(snapshot, state, candidate), briefProvenance: { kind: "deterministic" } };
549
393
  }
550
394
 
551
- /**
552
- * Turn one automatic rung selection into a mark. Prepared chapters are excluded on
553
- * purpose: that rung only fires at the hard provider fence, which is already inside
554
- * a commit epoch, and deferring it would strand a model-generated brief.
555
- */
556
395
  export function ladderSelectionMark(input: {
557
396
  snapshot: ActiveContextSnapshot;
558
397
  state: ActiveContextState;
@@ -574,24 +413,6 @@ export function ladderSelectionMark(input: {
574
413
  });
575
414
  }
576
415
 
577
- /**
578
- * PEEK COPIES ARE EPHEMERAL BY CONTRACT.
579
- *
580
- * A peek returns a fold's exact stored source, so the copy sits in the window as raw
581
- * mass beside that fold's own placeholder: the same bytes held twice, with the second
582
- * copy re-retrievable forever from the id the agent already used. So a completed peek
583
- * read is reclaimed at the NEXT commit rather than aging through the ladder, and the
584
- * window being append-only is why the reclaim can only happen at a commit boundary.
585
- *
586
- * Two survival paths, and only two. Expanding the fold it peeked means the agent asked
587
- * for those bytes in place, and pinning is the veto: a pin holds the copy raw through
588
- * every commit, exactly as it holds any other evidence, and lifting it hands the copy
589
- * back to the next commit. Both are read here at marking time, and a pin made AFTER the
590
- * mark exists still vetoes, because a protected mark waits instead of applying.
591
- *
592
- * These marks are minted at the last-call exposure as well as at the commit, so the
593
- * pending disposal is visible during the gated round the pin has to fit inside.
594
- */
595
416
  export function ephemeralPeekMarks(input: {
596
417
  snapshot: ActiveContextSnapshot;
597
418
  state: ActiveContextState;
@@ -619,7 +440,6 @@ export function ephemeralPeekMarks(input: {
619
440
  .map(({ index }) => snapshot.mapped[index].ref!);
620
441
  const peeked = peekedSourceFoldIds(snapshot, refs);
621
442
  if (!peeked) continue;
622
- // The agent committed to what it peeked: leave the read in the raw window.
623
443
  if (peeked.some((id) => state.expanded.includes(id) || peekedFoldProtected(snapshot, state, id))) continue;
624
444
  if (refs.some((ref) => claimed.has(objectRefKey(ref))) ||
625
445
  refs.length > snapshot.policy.maxFoldSourceRefs ||
@@ -633,8 +453,6 @@ export function ephemeralPeekMarks(input: {
633
453
  candidate,
634
454
  brief: automaticToolBrief(snapshot, candidate),
635
455
  briefProvenance: { kind: "deterministic" },
636
- // A peek is an agent-initiated read; attributing its disposal to the ladder
637
- // would deflate the agent-vs-ladder mark ratio the adjudication reads.
638
456
  origin: "agent",
639
457
  ordinal: input.ordinal,
640
458
  }));
@@ -655,7 +473,6 @@ function peekedFoldProtected(
655
473
  : refsProtected(refs, state, snapshot);
656
474
  }
657
475
 
658
- /** Every evidence key already spoken for by a fold or a pending fold mark. */
659
476
  export function claimedRefKeys(state: ActiveContextState): Set<string> {
660
477
  const keys = new Set<string>();
661
478
  for (const fold of state.folds) {
@@ -668,35 +485,13 @@ export function claimedRefKeys(state: ActiveContextState): Set<string> {
668
485
  return keys;
669
486
  }
670
487
 
671
- /** What is still on the table after the marks in hand: the steering number and its parts. */
672
488
  export interface UnmarkedRemainder {
673
- /** Tool results in a MEMBER batch that no fold and no pending mark owns. */
674
489
  spans: number;
675
490
  tokens: number;
676
- /** Unmarked member tokens as a share of the member tool mass. The steering number. */
677
491
  share: number;
678
- /** The largest few, by reclaim value, so the next batch is one read away. */
679
492
  candidates: Array<{ id: string; tokens: number }>;
680
493
  }
681
494
 
682
- /**
683
- * The unmarked remainder, as an AGGREGATE plus a bounded head.
684
- *
685
- * An exhaustive list is what the status index already is, and re-rendering it is what
686
- * this build removed from the projection. What an agent needs to decide the NEXT batch
687
- * is one percentage and the few largest names; everything else is a total.
688
- *
689
- * ONE DEFINITION OF STALE, AND IT IS THE SELECTOR'S OWN. The scope is class membership:
690
- * every result inside a batch `automaticToolBatches` admits, which is the same
691
- * enumeration `selectAutomaticToolBatch` picks its proposal out of. So the last call and
692
- * the deferred-commit record announce mass a commit can actually take, by construction
693
- * rather than by two filters kept in step. Counting whole-window tool results instead
694
- * announced material no rung could ever propose: rep 3 named 280k tokens while every
695
- * selector had nothing, which reads as a runtime refusing work it had just described.
696
- *
697
- * Membership is not ownership: the denominator is every member result, taken or not, so
698
- * the share reads as the fraction of reachable tool mass still unspoken for.
699
- */
700
495
  export function unmarkedRemainder(
701
496
  snapshot: ActiveContextSnapshot,
702
497
  state: ActiveContextState,
@@ -729,24 +524,12 @@ export function unmarkedRemainder(
729
524
  };
730
525
  }
731
526
 
732
- /** Every fold id already spoken for by a pending refold mark. */
733
527
  export function markedFoldIds(state: ActiveContextState): Set<string> {
734
528
  const ids = new Set<string>();
735
529
  for (const mark of pendingMarks(state)) if (mark.mark === "refold") ids.add(mark.id);
736
530
  return ids;
737
531
  }
738
532
 
739
- /**
740
- * THE COUNT LAW, AS MARKS. Every parent the eligible-root count owes right now, all of
741
- * them, in the epoch that notices.
742
- *
743
- * Separate from the top-up on purpose. The top-up is pressure arithmetic: it stops the
744
- * moment the marks in hand reach the freeing target, which is the right rule for how
745
- * DEEP a commit cuts and the wrong rule entirely for a law that says a count of eligible
746
- * roots at or above the width is a state that must not persist. So this runs at every
747
- * commit epoch and it runs first, whatever the pressure and whatever the top-up would
748
- * have proposed (Shane 2026-08-10).
749
- */
750
533
  export function consolidationMarks(input: {
751
534
  snapshot: ActiveContextSnapshot;
752
535
  state: ActiveContextState;
@@ -760,22 +543,12 @@ export function consolidationMarks(input: {
760
543
  }));
761
544
  }
762
545
 
763
- /**
764
- * Agent judgment leads; automation guarantees the floor. If the marks the agent made
765
- * would free less than the target share of the window, automation adds the stalest
766
- * unprotected eligible SPANS until they do: completed tool batches and raw narrative
767
- * chapters. Placeholders are not on that list any more; a placeholder gets a parent from
768
- * the count law above, which is not pressure arithmetic and does not stop at a target.
769
- */
770
546
  export function topUpMarks(input: {
771
547
  snapshot: ActiveContextSnapshot;
772
548
  state: ActiveContextState;
773
549
  ordinal: number;
774
- /** Freeing target as a share of the SERVING BUDGET. Required: there is no second default. */
775
550
  targetShare: number;
776
- /** Evidence keys the top-up may never propose, e.g. the current excursion's reads. */
777
551
  excludeRefKeys?: ReadonlySet<string>;
778
- /** Measure progress against ELIGIBLE mass, so pinned or protected marks never count. */
779
552
  eligibleOnly?: boolean;
780
553
  }): PendingFoldMark[] {
781
554
  const { snapshot } = input;
@@ -789,7 +562,7 @@ export function topUpMarks(input: {
789
562
  return input.eligibleOnly ? accounting.eligibleFreedBudgetShare : accounting.freedBudgetShare;
790
563
  };
791
564
  let share = progress(state);
792
- for (let attempt = 0; attempt < EPOCH_MAX_TOPUP_MARKS && share < target; attempt += 1) {
565
+ while (share < target) {
793
566
  const candidate = selectAutomaticSpan(snapshot, state, claimed);
794
567
  if (!candidate) break;
795
568
  const mark = foldMarkFor({
@@ -808,15 +581,8 @@ export function topUpMarks(input: {
808
581
  return marks;
809
582
  }
810
583
 
811
- /** One sliver a commit swallowed into its later neighbour, reported never inferred. */
812
584
  export interface AbsorbedWedge {
813
- /** The mark that grew backward to cover it. */
814
585
  intoMarkId: string;
815
- /**
816
- * The id that mark carried BEFORE it grew. A mark's id is derived from its span, so
817
- * growing the span mints a new one; without this the trail from the decision the
818
- * agent made to the fold that carries it is broken, and a mark appears to vanish.
819
- */
820
586
  fromMarkId: string;
821
587
  startId: string;
822
588
  endId: string;
@@ -824,28 +590,10 @@ export interface AbsorbedWedge {
824
590
  tokens: number;
825
591
  }
826
592
 
827
- /**
828
- * Wedge absorption. THE ANTI-LCM PIN LIVES HERE.
829
- *
830
- * A sliver of stale raw content hugging a fold boundary is a crumb nothing will ever
831
- * reclaim: it is below the minimum chapter size, so no chapter rung will take it, and
832
- * it is not a tool batch, so no tool rung will either. Absorbing it costs nothing
833
- * because it happens INSIDE a commit the epoch has already paid for -- it grows a
834
- * pending mark backward rather than performing a mutation of its own.
835
- *
836
- * What it must never do is erode deliberate non-sequential curation. An agent holding
837
- * folds at 10:20, 40:55 and 60:70 with raw spans between them chose that shape, and a
838
- * mechanism that swallowed those gaps would delete the curation element outright. So
839
- * the threshold is tiny, measured in the session's own calibrated tokens, and NOT
840
- * pressure-scaled: above it, a gap stays raw permanently no matter how full the window
841
- * gets or how ragged the projection looks.
842
- */
843
593
  export function absorbWedgeMarks(input: {
844
594
  snapshot: ActiveContextSnapshot;
845
595
  state: ActiveContextState;
846
- /** The session's measured serialized chars per token; never a fixed constant. */
847
596
  charsPerToken: number;
848
- /** Evidence absorption may never touch, e.g. the current excursion's own reads. */
849
597
  excludeRefKeys?: ReadonlySet<string>;
850
598
  }): { state: ActiveContextState; absorbed: AbsorbedWedge[] } {
851
599
  const { snapshot } = input;
@@ -863,9 +611,7 @@ export function absorbWedgeMarks(input: {
863
611
  .filter((index): index is number => index !== undefined);
864
612
  return indices.length ? { start: Math.min(...indices), end: Math.max(...indices) } : null;
865
613
  };
866
- // One pass, oldest first. Each absorption rewrites exactly one mark, and a mark that
867
- // has already grown is not a candidate to grow again in the same commit.
868
- for (let guard = 0; guard < MAX_PENDING_MARKS; guard += 1) {
614
+ for (;;) {
869
615
  const marks = pendingMarks(state);
870
616
  const occupied = [
871
617
  ...orderedRoots(state, snapshot).map((root) => ({ start: root.start, end: root.end })),
@@ -886,7 +632,6 @@ export function absorbWedgeMarks(input: {
886
632
  if (gapStart < 0 || gapEnd < gapStart) continue;
887
633
  if (absorbed.some((item) => item.intoMarkId === mark.id)) continue;
888
634
  const gapTokens = Math.ceil(spanBytes(snapshot, gapStart, gapEnd + 1) / charsPerToken);
889
- // The whole discriminator, in one line: above this, the gap is the agent's.
890
635
  if (gapTokens > MAX_WEDGE_ABSORB_TOKENS) continue;
891
636
  const gapRefs = [];
892
637
  let usable = true;
@@ -897,15 +642,19 @@ export function absorbWedgeMarks(input: {
897
642
  }
898
643
  if (!usable || !gapRefs.length) continue;
899
644
  if (refsProtected(gapRefs, state, snapshot)) continue;
900
- // Structural safety: the grown span must still be a valid closed chapter, which
901
- // is what keeps every tool call paired with its result in the projection.
902
645
  if (!chapterRangeIsUnitAligned(snapshot, gapStart, interval.end)) continue;
903
646
  const parts = partsForRange(snapshot, state, gapStart, interval.end, new Set<FoldKind>(["tool-result"]));
904
647
  if (!parts) continue;
648
+ // ELIGIBILITY, not truncation (Shane 2026-08-14): the grown fold's brief
649
+ // must name what it absorbed, and a brief already near the policy cap has
650
+ // no room for that sentence. Slicing dropped exactly the words that made
651
+ // the absorption honest, so a wedge whose truthful suffix does not fit is
652
+ // simply not absorbed; the gap stays raw for a later commit.
653
+ const absorbSuffix = ` It also holds ${gapRefs.length} short adjacent entry(s) absorbed at commit.`;
654
+ if (mark.brief.length + absorbSuffix.length > snapshot.policy.maxBriefChars) continue;
905
655
  const grown = foldMarkFor({
906
656
  candidate: { kind: "chapter", parts, sourceRefs: candidateSourceRefs(parts, state) },
907
- brief: `${mark.brief} It also holds ${gapRefs.length} short adjacent entry(s) absorbed at commit.`
908
- .slice(0, snapshot.policy.maxBriefChars),
657
+ brief: `${mark.brief}${absorbSuffix}`,
909
658
  briefProvenance: mark.briefProvenance,
910
659
  origin: mark.origin,
911
660
  ordinal: mark.ordinal,
@@ -941,7 +690,6 @@ export interface RefusedMark {
941
690
  id: string;
942
691
  origin: MarkOrigin;
943
692
  reason: string;
944
- /** Whether the mark survives as pending rather than being discarded. */
945
693
  retained: boolean;
946
694
  }
947
695
 
@@ -950,38 +698,16 @@ export interface CommitEpochResult {
950
698
  applied: AppliedMark[];
951
699
  refused: RefusedMark[];
952
700
  retained: PendingMark[];
953
- /** Guarded marks the pressure waiver released into this commit, oldest material first. */
954
701
  waived: PendingMark[];
955
702
  }
956
703
 
957
- /**
958
- * Apply every pending mark. This is the ONE mutation of the epoch: it reuses
959
- * prepareFold/commitPreparedFold exactly as the immediate path does, so a committed
960
- * mark and an immediate fold produce the same fold record.
961
- */
962
704
  export async function commitPendingMarks(input: {
963
705
  snapshot: ActiveContextSnapshot;
964
706
  state: ActiveContextState;
965
707
  generation: number;
966
708
  now?: () => number;
967
- /**
968
- * Keep marks the commit could not apply YET. A mark is a standing decision; dropping
969
- * it because the span happened to be fresh at commit time silently discards the
970
- * agent's judgment and leaves nothing for the next commit to act on.
971
- */
972
709
  retainIneligible?: boolean;
973
- /**
974
- * Never fold evidence the CURRENT excursion gathered. A commit that runs between
975
- * an agent's read and its use of that read hands the agent a window where its own
976
- * just-gathered evidence is a placeholder. Such marks stay pending for the next
977
- * commit rather than being applied or dropped.
978
- */
979
710
  guardCurrentTurn?: boolean;
980
- /**
981
- * How many guarded marks the pressure waiver releases anyway, oldest material first. The
982
- * guard protects an in-flight excursion; it may never cost the session its ability
983
- * to send a request at all. See `guardWaiverCount`.
984
- */
985
711
  guardWaiver?: number;
986
712
  }): Promise<CommitEpochResult> {
987
713
  const marks = pendingMarks(input.state);
@@ -990,13 +716,6 @@ export async function commitPendingMarks(input: {
990
716
  const guardReasons = new Map<string, string>();
991
717
  const held = new Set(input.state.folds.map((fold) => fold.id));
992
718
  const deferred: PendingMark[] = [];
993
- /**
994
- * Whether the mark names a fold some other standing mark is about to mint. Until that
995
- * fold exists there is no eligibility worth judging: the question is about material
996
- * the child owns, and the apply loop is where it gets asked, after the child has
997
- * landed. A mark naming a fold NOBODY will mint is not this case; it is judged
998
- * normally, and the apply loop drops it by name.
999
- */
1000
719
  const awaitingMintedChild = (mark: PendingMark): boolean =>
1001
720
  markSpanRefs(input.state, mark).unresolved?.pending === true;
1002
721
  const currentTurn = input.guardCurrentTurn
@@ -1009,11 +728,6 @@ export async function commitPendingMarks(input: {
1009
728
  if (markTouchesCurrentTurn(input.state, mark, currentTurn)) guarded.push(mark);
1010
729
  else applicable.push(mark);
1011
730
  }
1012
- // Oldest MATERIAL first: the newest reads are the ones the excursion is about to
1013
- // use, so they are the last evidence the waiver surrenders. Staleness is a property
1014
- // of the span, never of when the mark was proposed; see `markSpanStart`. The id
1015
- // comparison is the final tiebreak between marks covering the same start, and
1016
- // nothing else.
1017
731
  const guardedStart = new Map(guarded.map((mark) =>
1018
732
  [pendingMarkKey(mark), markSpanStart(input.snapshot, input.state, mark)] as const));
1019
733
  guarded.sort((left, right) =>
@@ -1038,10 +752,6 @@ export async function commitPendingMarks(input: {
1038
752
  if (input.retainIneligible) {
1039
753
  const applicable: PendingMark[] = [];
1040
754
  for (const mark of marks) {
1041
- // A mark awaiting a child another mark will mint is not judged here. Its
1042
- // eligibility is a question about material the child fold owns, and that fold is
1043
- // what this commit is about to create. The apply loop asks it once the child has
1044
- // landed, and answers by name when it does not.
1045
755
  if (!awaitingMintedChild(mark) &&
1046
756
  markEligibility(input.snapshot, input.state, mark) === "protected") retained.push(mark);
1047
757
  else applicable.push(mark);
@@ -1059,23 +769,6 @@ export async function commitPendingMarks(input: {
1059
769
  "span is still fresh or protected; the mark stays pending until it is eligible",
1060
770
  retained: true,
1061
771
  }));
1062
- // CHILDREN BEFORE THE SPANS THAT ABSORB THEM, AND TRANSCRIPT ORDER IS NOT THAT ORDER.
1063
- //
1064
- // The invariant is real: a span whose parts name another mark's fold must find that
1065
- // fold in the forest when it applies. `foldMarkFor` gives a mark the id its committed
1066
- // fold will carry, so the naming is exact and readable off the parts. What it is NOT is
1067
- // geometry. A parent CONTAINS its child, so the earliest window index a parent covers
1068
- // is at or before its child's, and ordering oldest material first applies the parent
1069
- // FIRST in exactly the case the invariant exists for. The cost is not a reordering:
1070
- // `candidateSourceRefs` throws `Missing candidate child` on the unresolved part, the
1071
- // apply loop catches it, and the mark is refused with `retained: false`, so the parent
1072
- // applied one place too early loses the agent's decision outright rather than waiting.
1073
- //
1074
- // So the dependency is the law and the sort key is the tiebreak. Among marks with no
1075
- // such tie, real transcript order: the earliest window index the span covers, oldest
1076
- // material first, id last. The `ordinal` this used to sort on is the transcript position
1077
- // at MARK time, one value shared by every mark an epoch proposes, so within an epoch it
1078
- // decided nothing and the order fell through to comparing content hashes.
1079
772
  const minting = new Map(marks.flatMap((mark) =>
1080
773
  mark.mark === "fold" && !held.has(mark.id) ? [[mark.id, mark] as const] : []));
1081
774
  const applyStart = new Map(marks.map((mark) =>
@@ -1088,9 +781,6 @@ export async function commitPendingMarks(input: {
1088
781
  const placing = new Set<string>();
1089
782
  const place = (mark: PendingMark): void => {
1090
783
  const key = pendingMarkKey(mark);
1091
- // `placing` is the cycle stop. Fold ids are hashes OF the parts, so a cycle would
1092
- // need a hash to name its own ancestor; the guard is here so a corrupt state loses
1093
- // one mark to the ordinary refusal rather than hanging the commit.
1094
784
  if (placed.has(key) || placing.has(key)) return;
1095
785
  placing.add(key);
1096
786
  if (mark.mark === "fold") {
@@ -1117,15 +807,6 @@ export async function commitPendingMarks(input: {
1117
807
  continue;
1118
808
  }
1119
809
  const parts = clone(mark.parts);
1120
- // THE ANSWER TO A NAME THE STATE CANNOT RESOLVE, AND IT IS TWO ANSWERS.
1121
- //
1122
- // A mark still standing will mint the fold this one names, so the span becomes
1123
- // readable at the next commit: that is a DEFER, and the mark stays pending. This
1124
- // is the child a guard held while its parent was applicable, which the epoch used
1125
- // to discard outright. Nothing standing names it and it is a DROP: `reboundary`
1126
- // dissolved the fold, or the mark that would have minted it was itself refused,
1127
- // and no later commit can honour the decision. Same two words the eligibility
1128
- // reading uses, and the same receipt shape a dropped brief upgrade gets.
1129
810
  const span = markSpanRefs(state, mark);
1130
811
  if (span.unresolved) {
1131
812
  const { foldId, pending } = span.unresolved;
@@ -1169,9 +850,6 @@ export async function commitPendingMarks(input: {
1169
850
  generation: input.generation,
1170
851
  });
1171
852
  applied.push({ mark: "fold", id: mark.id, origin: mark.origin, foldId: prepared.id });
1172
- // A warmed preparation the commit just materialized through its mark is spent:
1173
- // leaving it standing would hold a prepared fold whose id already exists in the
1174
- // forest, and every later drift check would refuse it by name.
1175
853
  if (state.prepared && state.folds.some((fold) => fold.id === state.prepared!.id)) {
1176
854
  state = clearPrepared(state);
1177
855
  }
@@ -1185,9 +863,6 @@ export async function commitPendingMarks(input: {
1185
863
  });
1186
864
  }
1187
865
  }
1188
- // Deferrals rejoin the pending set. A mark the loop could not read yet is a standing
1189
- // decision exactly as a guard-held one is, and `retained` is what the commit record
1190
- // counts as deferred, so both live in the same list.
1191
866
  if (deferred.length) {
1192
867
  state = withPendingMarks(state, [...pendingMarks(state), ...deferred]);
1193
868
  retained.push(...deferred);
@@ -1195,7 +870,6 @@ export async function commitPendingMarks(input: {
1195
870
  return { state, applied, refused, retained, waived };
1196
871
  }
1197
872
 
1198
- /** The scheduling block reported by the status action. */
1199
873
  export function schedulingStatus(input: {
1200
874
  snapshot: ActiveContextSnapshot;
1201
875
  state: ActiveContextState;
@@ -1203,16 +877,11 @@ export function schedulingStatus(input: {
1203
877
  }): Record<string, unknown> {
1204
878
  const accounting = markAccounting(input.snapshot, input.state);
1205
879
  return {
1206
- // A literal, not a setting: epoch is the only scheduler there is.
1207
880
  mode: "epoch",
1208
881
  ...accounting,
1209
- // The declared policy, reported never mutable.
1210
882
  thresholds: { ...input.snapshot.thresholds },
1211
883
  budgetTokens: input.snapshot.budgetTokens,
1212
884
  commitDue: epochCommitDue(input.snapshot, input.ratio),
1213
- // One trigger, reported as one line. The eligible share is still reported above in
1214
- // the accounting because it is what a commit WOULD free; it is no longer a reason
1215
- // to fire one.
1216
885
  commitTrigger: {
1217
886
  mode: "band-top",
1218
887
  commitOccupancy: input.snapshot.thresholds.maxTarget,