pi-fold 1.0.4 → 3.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.
@@ -28,7 +28,6 @@ import {
28
28
  orderedRoots,
29
29
  refsInOrder,
30
30
  refsProtected,
31
- toolRefsProtected,
32
31
  } from "./measurement.ts";
33
32
  import {
34
33
  childFoldIds,
@@ -53,8 +52,7 @@ import {
53
52
  MAX_FOLD_SPAN_CHARS,
54
53
  PEEK_DEFAULT_MAX_BYTES,
55
54
  PEEK_HEAD_SHARE,
56
- STATUS_DIET_SUGGESTIONS,
57
- zoneBytes,
55
+ STATUS_DIET_INDEX_ROWS,
58
56
  } from "./policy.ts";
59
57
  import type {
60
58
  ActiveContextSnapshot,
@@ -85,49 +83,29 @@ import {
85
83
  export function selectAutomaticChapter(
86
84
  snapshot: ActiveContextSnapshot,
87
85
  state: ActiveContextState,
88
- maximumSourceRefs: number = snapshot.policy.maxFoldSourceRefs,
89
86
  claimed: ReadonlySet<string> = new Set<string>(),
90
87
  ): FoldCandidate | null {
91
- // Every structurally closed unit is a candidate. What keeps a chapter off the newest
92
- // material is `refsProtected` below -- the fresh byte tail and the agent's pins -- and
93
- // what keeps it off the running excursion's own evidence is the guard adjudicated at
94
- // the commit. There is no position filter here: a unit is composable or it is not.
95
88
  const units = chapterUnits(snapshot);
96
- // An automatic chapter is raw material, always. Letting a chapter swallow placeholders
97
- // once the root count reached `consolidateAfter` was the old approximation of
98
- // consolidation, and the count law replaces it outright: a placeholder gets a parent
99
- // when the count says it must, under a fold that says what it is, not as a side effect
100
- // of which chapter happened to be composable that pass (Shane 2026-08-10).
101
89
  const allowedChildren = NO_FOLD_KINDS;
102
90
  for (let unitIndex = 0; unitIndex < units.length; unitIndex += 1) {
103
91
  const first = units[unitIndex];
104
92
  let best: FoldCandidate | null = null;
105
- const turnStarts = new Set<number>();
106
93
  for (let endIndex = unitIndex; endIndex < units.length; endIndex += 1) {
107
94
  const unit = units[endIndex];
108
95
  if (endIndex > unitIndex && unit.start !== units[endIndex - 1].end) break;
109
- turnStarts.add(unit.turnStart);
110
- if (turnStarts.size > snapshot.policy.maxChapterTurns) break;
111
96
  const coherentSegment = endIndex > unitIndex || first.end - first.start > 1;
112
97
  if (!coherentSegment) continue;
113
98
  const parts = partsForRange(snapshot, state, first.start, unit.end - 1, allowedChildren);
114
99
  if (!parts || parts.some((part) => part.kind === "fold" && state.expanded.includes(part.foldId))) continue;
115
100
  const refs = candidateSourceRefs(parts, state);
116
- if (refs.length > maximumSourceRefs) break;
117
101
  if (claimed.size && refs.some((ref) => claimed.has(objectRefKey(ref)))) continue;
118
102
  if (refsProtected(refs, state, snapshot)) continue;
119
103
  const size = bytes(encodedFoldSource(snapshot, state, parts, "chapter"));
120
- // The chapter cap bounds what the rung COMPOSES, never what it may reclaim: a
121
- // single closed unit that alone exceeds it is still accepted (rep 19: six status
122
- // results, each above the cap, were exactly the mass nothing could fold).
123
- if (size > snapshot.policy.maxChapterChars && endIndex > unitIndex) break;
124
- // Bite-sized by construction. A multi-unit chapter never grows past the cap, so
125
- // the ladder cannot build the 60kB fold whose tail hid the answer (rep 6); a
126
- // SINGLE unit that alone exceeds it is still accepted, because there is no
127
- // interior boundary to stop at and refusing would leave the biggest span in the
128
- // window as the one thing nothing may reclaim.
129
104
  const biteSized = size <= MAX_FOLD_SPAN_CHARS || endIndex === unitIndex;
130
- if (size >= snapshot.policy.minChapterChars && biteSized) best = { kind: "chapter", parts, sourceRefs: refs };
105
+ // ACCUMULATE UNTIL OVER, never cut to hit the number. The walk grows the span one
106
+ // whole unit at a time and records the first one that carries it past the floor,
107
+ // so a fold is always at least minFoldChars and always ends on a message boundary.
108
+ if (size >= snapshot.thresholds.minFoldChars && biteSized) best = { kind: "chapter", parts, sourceRefs: refs };
131
109
  if (size > MAX_FOLD_SPAN_CHARS) break;
132
110
  }
133
111
  if (best) return best;
@@ -135,26 +113,12 @@ export function selectAutomaticChapter(
135
113
  return null;
136
114
  }
137
115
 
138
- /**
139
- * ONE automatic selection law for stale material.
140
- *
141
- * There is no chapter rung and no consolidation rung any more, each with its own
142
- * trigger: automation proposes the stalest eligible SPAN, and what the span contains
143
- * decides the fold's kind. Raw narrative folds as a chapter; a parent the root count
144
- * requires folds as a consolidation. Nothing here is pressure-scaled: the epoch's own
145
- * commit trigger decides WHEN, and this decides WHAT.
146
- *
147
- * One candidate, because that is what a single inline rung can apply. The count law
148
- * itself is not rationed this way: the commit epoch takes EVERY group the count requires
149
- * in one pass, and this is the leftover route by which the stalest of them can also ride
150
- * an epoch's own rewrite.
151
- */
152
116
  export function selectAutomaticStaleSpan(
153
117
  snapshot: ActiveContextSnapshot,
154
118
  state: ActiveContextState,
155
119
  claimed: ReadonlySet<string> = new Set<string>(),
156
120
  ): FoldCandidate | null {
157
- const chapter = selectAutomaticChapter(snapshot, state, snapshot.policy.maxFoldSourceRefs, claimed);
121
+ const chapter = selectAutomaticChapter(snapshot, state, claimed);
158
122
  const folds = selectAutomaticConsolidations(snapshot, state)[0] ?? null;
159
123
  if (!chapter || !folds) return chapter ?? folds;
160
124
  const at = (candidate: FoldCandidate): number =>
@@ -162,10 +126,6 @@ export function selectAutomaticStaleSpan(
162
126
  return at(folds) < at(chapter) ? folds : chapter;
163
127
  }
164
128
 
165
- /**
166
- * What the commit epoch proposes: a completed read-only tool batch if one is stale, and
167
- * otherwise the stalest span the one law composes. This is the whole automatic path.
168
- */
169
129
  export function selectAutomaticSpan(
170
130
  snapshot: ActiveContextSnapshot,
171
131
  state: ActiveContextState,
@@ -185,14 +145,6 @@ export type AutomaticRungSelection =
185
145
 
186
146
  export interface AutomaticRungSelectionOptions {
187
147
  toolOnly?: boolean;
188
- summarizerAvailable?: boolean;
189
- failedPreparationIds?: ReadonlySet<string>;
190
- /**
191
- * Evidence and folds already spoken for by something that is not yet a fold, i.e.
192
- * a pending epoch mark. Excluding them makes each eligible turn select NEW stale
193
- * content instead of re-selecting the batch a mark already covers, which is the
194
- * only way marks accumulate below the commit threshold.
195
- */
196
148
  claimed?: ReadonlySet<string>;
197
149
  claimedFoldIds?: ReadonlySet<string>;
198
150
  }
@@ -216,7 +168,7 @@ export function selectAutomaticRung(
216
168
  const claimedFoldIds = options.claimedFoldIds ?? new Set<string>();
217
169
  if (!options.toolOnly && state.prepared) {
218
170
  if (!Number.isFinite(ratio) || ratio < hardFenceRatio(snapshot)) return null;
219
- const candidate = selectAutomaticChapter(snapshot, state, snapshot.policy.maxFoldSourceRefs, claimed);
171
+ const candidate = selectAutomaticChapter(snapshot, state, claimed);
220
172
  return candidate ? { kind: "prepared-chapter", candidate } : null;
221
173
  }
222
174
  const tool = selectAutomaticToolBatch(snapshot, state, claimed)[0] ?? null;
@@ -224,24 +176,12 @@ export function selectAutomaticRung(
224
176
  if (options.toolOnly || !Number.isFinite(ratio)) return null;
225
177
  const refold = selectAutomaticRefold(snapshot, state, claimedFoldIds);
226
178
  if (refold) return { kind: "refold", foldId: refold };
227
- // One law decides the span; only the CHAPTER kind still has a preparation path in
228
- // front of it, because only a chapter brief is worth a model call.
229
179
  const span = selectAutomaticStaleSpan(snapshot, state, claimed);
230
180
  if (span?.kind === "consolidation") return { kind: "consolidation", candidate: span };
231
181
  const chapter = span;
232
182
  if (!chapter) return null;
233
- const preparationFailed = options.failedPreparationIds?.has(
234
- automaticPreparationId(chapter, state),
235
- ) ?? false;
236
- if (ratio >= hardFenceRatio(snapshot)) {
237
- return !options.summarizerAvailable || preparationFailed
238
- ? { kind: "chapter", candidate: chapter }
239
- : { kind: "chapter-prepare", candidate: chapter };
240
- }
241
- if ((options.summarizerAvailable && ratio >= snapshot.policy.warmRatio) ||
242
- ((!options.summarizerAvailable || preparationFailed) && ratio >= snapshot.policy.prepareRatio)) {
243
- return { kind: "chapter-prepare", candidate: chapter };
244
- }
183
+ if (ratio >= hardFenceRatio(snapshot)) return { kind: "chapter", candidate: chapter };
184
+ if (ratio >= snapshot.policy.prepareRatio) return { kind: "chapter-prepare", candidate: chapter };
245
185
  return null;
246
186
  }
247
187
 
@@ -250,12 +190,10 @@ export function foldCandidatesDetail(
250
190
  state: ActiveContextState,
251
191
  ratio: number | null,
252
192
  options: {
253
- summarizerAvailable?: boolean;
254
193
  generation?: number;
255
194
  measurementFresh?: boolean;
256
195
  automaticFailure?: boolean;
257
196
  preparing?: boolean;
258
- failedPreparationIds?: ReadonlySet<string>;
259
197
  } = {},
260
198
  ): Record<string, unknown> {
261
199
  const measuredRatio = ratio !== null && Number.isFinite(ratio) ? ratio : Number.NaN;
@@ -264,10 +202,7 @@ export function foldCandidatesDetail(
264
202
  const staleSpan = selectAutomaticStaleSpan(snapshot, state);
265
203
  const consolidation = staleSpan?.kind === "consolidation" ? staleSpan : null;
266
204
  const chapter = staleSpan?.kind === "chapter" ? staleSpan : null;
267
- const selection = selectAutomaticRung(snapshot, state, measuredRatio, {
268
- summarizerAvailable: options.summarizerAvailable,
269
- failedPreparationIds: options.failedPreparationIds,
270
- });
205
+ const selection = selectAutomaticRung(snapshot, state, measuredRatio);
271
206
  let wouldFireNow: string | null = null;
272
207
  let blockedBy: string | null = null;
273
208
  if (options.measurementFresh === false) blockedBy = "measurement-stale";
@@ -296,9 +231,6 @@ export function foldCandidatesDetail(
296
231
  } : null,
297
232
  wouldFireNow,
298
233
  blockedBy,
299
- // The one position that still cuts anything, plus the count law read as the
300
- // arithmetic it is: the roots it counts, the width it divides by, and how many
301
- // parents that count owes right now.
302
234
  zones: {
303
235
  freshBoundary: snapshot.freshBoundary,
304
236
  budgetTokens: snapshot.budgetTokens,
@@ -369,25 +301,6 @@ export function encodedFoldSource(
369
301
  }));
370
302
  }
371
303
 
372
- /**
373
- * What a generator reads to brief a PARENT: its children at depth one.
374
- *
375
- * `encodedFoldSource` renders a parent's children through `renderFold`, which yields their
376
- * placeholders, so a parent's source was the children's briefs. Briefing that produces a
377
- * summary of summaries, and by the second rung the material itself is three descriptions
378
- * away. So each child is opened here instead: its own brief for orientation, then its
379
- * contents, with ITS children left as placeholders. Depth one is the whole rule. It bounds
380
- * the read at one level per rung whatever the ladder's height, and it is the same view an
381
- * agent gets from expanding the parent, so a brief written from it describes what expanding
382
- * would actually return.
383
- *
384
- * When the opened children do not fit, the LARGEST collapses back to brief-only first, and
385
- * again until the rest fit. Size is what decides, not position: dropping in span order
386
- * would spend the whole budget on an early giant and close every child behind it, while
387
- * taking the giant out first buys back the most room for the fewest subjects lost. A
388
- * collapsed child still appears, in span order, carrying its brief and saying it is
389
- * collapsed, so the generator can tell a child it read from a child it only heard about.
390
- */
391
304
  export function consolidationSourceText(
392
305
  snapshot: ActiveContextSnapshot,
393
306
  state: ActiveContextState,
@@ -417,8 +330,6 @@ export function consolidationSourceText(
417
330
  ? { collapsed: "brief only: this child did not fit expanded" }
418
331
  : { contents: entry.messages }),
419
332
  })));
420
- // Largest first, and only as many as the budget forces: each collapse is a subject the
421
- // brief must describe from one sentence instead of from its contents.
422
333
  const bySize = entries
423
334
  .filter((entry) => entry.foldId !== null)
424
335
  .map((entry) => ({ index: entry.index, size: bytes(entry.messages) }))
@@ -459,17 +370,6 @@ export function boundedOrientation(
459
370
  return { beforeRefs: before, beforeText, afterRefs: after.refs, afterText: after.text };
460
371
  }
461
372
 
462
- /**
463
- * What is wrong with a generated brief, said in words its author can act on, or null.
464
- *
465
- * Length is a criterion we set, so a brief that misses it is answered rather than cut.
466
- * Truncating a long brief silently loses whatever the generator put last, which on a group
467
- * is the last children it covered; the cure is to hand the complaint back and let the
468
- * generator spend the budget itself. Coverage cannot be checked the same way: no test tells
469
- * whether a sentence stands for a child, so coverage is instructed in the request and
470
- * defended by the deterministic index underneath, and what is checked here is the pair of
471
- * failures that were silent before.
472
- */
473
373
  export function briefContractComplaint(
474
374
  brief: string,
475
375
  maxBriefChars: number,
@@ -488,240 +388,44 @@ export function briefContractComplaint(
488
388
  return null;
489
389
  }
490
390
 
491
- /**
492
- * One brief from the generator, with one chance to cure a criterion it missed.
493
- *
494
- * The no-retry rule this lane was built with holds for what it was written about: a
495
- * generator that fails on a span fails on it again, and looping would spend the session's
496
- * calls proving it. A contract complaint is the other case. The request stated a limit, the
497
- * answer missed it, and the complaint names exactly what to change, so the second ask is
498
- * new information rather than the same ask twice. It is bounded at one, and only a
499
- * complaint earns it: transport faults, timeouts and attribution drift still fail on the
500
- * first answer. Both attempts sit inside the caller's timeout, so a brief costs no more
501
- * wall clock than it did.
502
- */
503
- export async function generatedBrief(input: {
504
- summarize: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
505
- request: Record<string, unknown>;
506
- ctx?: unknown;
507
- maxBriefChars: number;
508
- toolName: string;
509
- }): Promise<{ brief: string; provenance: BriefProvenance; cured: boolean }> {
510
- let complaint: string | null = null;
511
- for (let attempt = 0; attempt < 2; attempt += 1) {
512
- const result = await input.summarize(
513
- complaint === null ? input.request : { ...input.request, cure: complaint },
514
- input.ctx,
515
- );
516
- const brief = typeof result?.brief === "string" ? result.brief.trim() : "";
517
- const provenance = generatorAttribution(result);
518
- complaint = briefContractComplaint(brief, input.maxBriefChars, input.toolName);
519
- if (complaint === null) return { brief, provenance, cured: attempt > 0 };
520
- }
521
- throw new Error(`Model context brief usefulness contract drift after one cure: ${complaint}`);
522
- }
523
-
524
- /**
525
- * Attribution is the harness's own wiring, not the generator's judgment, so it is never
526
- * handed back to be cured: a summarizer that cannot say which model wrote a brief is broken
527
- * in a way a second ask cannot mend. Shared by the single and batched paths so the two
528
- * cannot drift.
529
- */
530
- function generatorAttribution(result: Record<string, unknown> | null | undefined): BriefProvenance {
531
- const digest = result?.launchContractDigest;
532
- if (typeof result?.provider !== "string" || !result.provider ||
533
- typeof result?.model !== "string" || !result.model ||
534
- typeof result?.effort !== "string" || !result.effort || result.toolCalls !== 0 ||
535
- (digest !== undefined && (typeof digest !== "string" || !/^[a-f0-9]{64}$/.test(digest)))) {
536
- throw new Error("Model context brief attribution, zero-tool, or digest contract drift");
537
- }
538
- return {
539
- kind: "model",
540
- provider: result.provider,
541
- model: result.model,
542
- effort: result.effort,
543
- ...(typeof digest === "string" ? { launchContractDigest: digest } : {}),
544
- };
545
- }
546
-
547
- /**
548
- * Many briefs from one call, with one chance to cure the ones that missed.
549
- *
550
- * The lane used to send one request per fold, two at a time, which meant most folds never
551
- * reached a generator at all: the 2026-08-11 rep committed 87 folds and applied 14 model
552
- * briefs, the rest keeping the deterministic sentence because the queue was full. A commit
553
- * folds its spans together, so they are briefed together, and coverage stops competing with
554
- * throughput.
555
- *
556
- * The cure is per-span and the retry carries only the spans that failed, so one bad brief
557
- * does not make the model rewrite eleven good ones. A span still failing after its cure
558
- * keeps its deterministic brief and is named in the error, which is the same outcome a
559
- * single-span failure has always had; the difference is that its neighbours still land.
560
- */
561
- export async function generatedBriefs(input: {
562
- summarize: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
563
- request: Record<string, unknown>;
564
- spans: Array<Record<string, unknown>>;
565
- ctx?: unknown;
566
- maxBriefChars: number;
567
- toolName: string;
568
- }): Promise<{
569
- briefs: Array<{ brief: string; provenance: BriefProvenance } | null>;
570
- cured: number;
571
- complaints: string[];
572
- }> {
573
- const settled = new Array<{ brief: string; provenance: BriefProvenance } | null>(input.spans.length)
574
- .fill(null);
575
- let pending = input.spans.map((span, index) => ({ span, index }));
576
- let cured = 0;
577
- let complaints: string[] = [];
578
- for (let attempt = 0; attempt < 2 && pending.length; attempt += 1) {
579
- const asked = pending;
580
- const result = await input.summarize({
581
- ...input.request,
582
- spans: asked.map((item) => item.span),
583
- ...(complaints.length
584
- ? {
585
- cure: asked.map((item, at) =>
586
- `Brief ${at + 1}: ${complaints[at] ?? "did not meet the stated criteria."}`).join(" "),
587
- }
588
- : {}),
589
- }, input.ctx);
590
- const provenance = generatorAttribution(result);
591
- const written = Array.isArray(result?.briefs) ? result.briefs : [];
592
- const failed: typeof pending = [];
593
- const next: string[] = [];
594
- for (let at = 0; at < asked.length; at += 1) {
595
- const brief = typeof written[at] === "string" ? (written[at] as string).trim() : "";
596
- const complaint = briefContractComplaint(brief, input.maxBriefChars, input.toolName);
597
- if (complaint === null) {
598
- settled[asked[at].index] = { brief, provenance };
599
- if (attempt > 0) cured += 1;
600
- continue;
601
- }
602
- failed.push(asked[at]);
603
- next.push(complaint);
604
- }
605
- pending = failed;
606
- complaints = next;
607
- }
608
- return { briefs: settled, cured, complaints };
609
- }
610
-
611
391
  export async function prepareFold(input: {
612
392
  candidate: FoldCandidate;
613
393
  snapshot: ActiveContextSnapshot;
614
394
  state: ActiveContextState;
615
395
  generation: number;
616
396
  brief?: string;
617
- /**
618
- * Provenance for a supplied brief. A full record round-trips a brief whose
619
- * generator already ran (a pending mark carrying a model brief); the string
620
- * forms stay the shorthand for the two model-free kinds.
621
- */
622
397
  briefProvenance?: "supplied" | "deterministic" | BriefProvenance;
623
- summarize?: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
624
- onSummarizerFailure?: (error: unknown) => void;
625
398
  ctx?: unknown;
626
399
  signal?: AbortSignal;
627
400
  now?: () => number;
628
401
  }): Promise<PreparedFold> {
629
402
  const { candidate, snapshot, state } = input;
630
- const protectedSource = candidate.kind === "tool-result"
631
- ? toolRefsProtected(candidate.sourceRefs, state, snapshot)
632
- : refsProtected(candidate.sourceRefs, state, snapshot);
633
- if (!candidate.sourceRefs.length || candidate.sourceRefs.length > snapshot.policy.maxFoldSourceRefs ||
403
+ const protectedSource = refsProtected(candidate.sourceRefs, state, snapshot);
404
+ if (!candidate.sourceRefs.length ||
634
405
  refsInOrder(snapshot, candidate.sourceRefs) === null || protectedSource) {
635
406
  throw new Error("Fold source is not exact, stale, and unprotected");
636
407
  }
637
408
  const sourceText = encodedFoldSource(snapshot, state, candidate.parts, candidate.kind);
638
- const groupChildren = candidate.parts.filter((part) => part.kind === "fold").length;
639
- const readableSource = groupChildren
640
- ? consolidationSourceText(snapshot, state, candidate.parts)
641
- : sourceText;
642
409
  const orientation = boundedOrientation(snapshot, candidate.sourceRefs);
643
410
  const sourceSha256 = sha256Text(sourceText);
644
411
  const beforeSha256 = sha256Text(orientation.beforeText);
645
412
  const afterSha256 = sha256Text(orientation.afterText);
646
- const candidateId = sha256Value({
647
- kind: candidate.kind,
648
- parts: candidate.parts.map(normalizedPart),
649
- sourceSha256,
650
- beforeSha256,
651
- afterSha256,
652
- });
653
413
 
654
414
  let brief: string;
655
415
  let provenance: BriefProvenance;
656
416
  if (input.brief !== undefined) {
657
- if (!usefulBrief(input.brief, snapshot.policy.maxBriefChars, snapshot.toolName)) {
658
- throw new Error(`Supplied brief must be non-structural and at most ${snapshot.policy.maxBriefChars} characters`);
659
- }
417
+ const complaint = briefContractComplaint(
418
+ input.brief.trim(), snapshot.policy.maxBriefChars, snapshot.toolName);
419
+ if (complaint !== null) throw new Error(`Supplied brief rejected. ${complaint}`);
660
420
  brief = input.brief.trim();
661
421
  provenance = typeof input.briefProvenance === "object"
662
422
  ? clone(input.briefProvenance)
663
423
  : { kind: input.briefProvenance ?? "supplied" };
664
424
  } else {
665
- let modelBrief: { brief: string; provenance: BriefProvenance } | null = null;
666
- if (input.summarize) {
667
- const controller = new AbortController();
668
- const relayAbort = (): void => controller.abort();
669
- input.signal?.addEventListener("abort", relayAbort, { once: true });
670
- let timeout: ReturnType<typeof setTimeout> | undefined;
671
- try {
672
- if (bytes(readableSource) > snapshot.policy.maxSourceChars) {
673
- throw new Error("Fold source exceeds the bounded model-summary request");
674
- }
675
- const timed = new Promise<never>((_resolve, reject) => {
676
- timeout = setTimeout(() => {
677
- controller.abort();
678
- reject(new Error(`Context brief exceeded ${snapshot.policy.briefTimeoutMs}ms`));
679
- }, snapshot.policy.briefTimeoutMs);
680
- });
681
- const generated = await Promise.race([generatedBrief({
682
- summarize: input.summarize,
683
- request: {
684
- candidateId,
685
- sourceRefs: clone(candidate.sourceRefs),
686
- // What the generator READS, which for a group is its children opened one level
687
- // rather than the placeholders `sourceText` renders. The fold's own identity
688
- // stays `sourceSha256` above: what a span IS does not change with how it is read.
689
- sourceText: readableSource,
690
- sourceSha256: sha256Text(readableSource),
691
- children: groupChildren,
692
- beforeRefs: clone(orientation.beforeRefs),
693
- beforeText: orientation.beforeText,
694
- beforeSha256,
695
- afterRefs: clone(orientation.afterRefs),
696
- afterText: orientation.afterText,
697
- afterSha256,
698
- maxBriefChars: snapshot.policy.maxBriefChars,
699
- signal: controller.signal,
700
- },
701
- ctx: input.ctx,
702
- maxBriefChars: snapshot.policy.maxBriefChars,
703
- toolName: snapshot.toolName,
704
- }), timed]);
705
- modelBrief = { brief: generated.brief, provenance: generated.provenance };
706
- } catch (error) {
707
- if (input.signal?.aborted) throw error;
708
- input.onSummarizerFailure?.(error);
709
- } finally {
710
- clearTimeout(timeout);
711
- input.signal?.removeEventListener("abort", relayAbort);
712
- }
713
- }
714
- if (modelBrief) {
715
- brief = modelBrief.brief;
716
- provenance = modelBrief.provenance;
717
- } else {
718
- // By kind, not by assumption: a group that loses its generator falls back to the
719
- // index of its children, and only a chapter falls back to a chapter's brief.
720
- brief = candidate.kind === "consolidation"
721
- ? deterministicConsolidationBrief(candidate, state)
722
- : deterministicChapterCandidateBrief(snapshot, candidate);
723
- provenance = { kind: "deterministic" };
724
- }
425
+ brief = candidate.kind === "consolidation"
426
+ ? deterministicConsolidationBrief(candidate, state, snapshot.toolName, snapshot)
427
+ : deterministicChapterCandidateBrief(snapshot, candidate);
428
+ provenance = { kind: "deterministic" };
725
429
  }
726
430
 
727
431
  const id = foldIdFor(candidate.kind, candidate.parts);
@@ -869,13 +573,12 @@ export function preparedFoldError(input: {
869
573
  if (prepared.branchSha256 !== branchSha256(snapshot, [
870
574
  ...prepared.beforeRefs, ...prepared.sourceRefs, ...prepared.afterRefs,
871
575
  ])) return "branch drift";
872
- const toolSource = prepared.fold.kind === "tool-result";
873
- if (indices.some((index) => toolSource
874
- ? snapshot.toolProtectedIndices.has(index)
875
- : snapshot.protectedIndices.has(index))) return "fresh-tail drift";
876
- if (toolSource
877
- ? toolRefsProtected(prepared.sourceRefs, state, snapshot)
878
- : refsProtected(prepared.sourceRefs, state, snapshot)) return "source became protected";
576
+ // One protected set now: an entry is unfoldable because its turn has not completed or
577
+ // because it carries no evidence ref, and both are validity, not recency. The branch on
578
+ // fold kind went with fresh-tail protection: it read `toolRefsProtected` on one side,
579
+ // and once that collapsed onto `refsProtected` both arms were the same call.
580
+ if (indices.some((index) => snapshot.protectedIndices.has(index))) return "protection drift";
581
+ if (refsProtected(prepared.sourceRefs, state, snapshot)) return "source became protected";
879
582
  if (Object.prototype.hasOwnProperty.call(input, "ratio")) {
880
583
  if (input.ratio === null || typeof input.ratio !== "number" || !Number.isFinite(input.ratio)) {
881
584
  return "current pressure unavailable";
@@ -910,9 +613,6 @@ export function commitPreparedFold(input: {
910
613
  if (error) throw new Error(`Prepared active-context fold discarded: ${error}`);
911
614
  const nested = stateWithNestedFold(input.state, input.prepared.fold);
912
615
  const next = clearPrepared({ ...nested, revision: input.state.revision + 1 });
913
- // A fold is not committed unless its provider projection preserves every
914
- // existing tool-call/output pair atomically. This keeps malformed provider
915
- // context non-constructible even if a future selector regresses.
916
616
  projectActiveContext(input.snapshot, next);
917
617
  return next;
918
618
  }
@@ -975,12 +675,10 @@ export function protectEvidence(
975
675
  });
976
676
  const byKey = new Map(state.protected.map((ref) => [objectRefKey(ref), ref]));
977
677
  for (const ref of refs) protect ? byKey.set(objectRefKey(ref), ref) : byKey.delete(objectRefKey(ref));
978
- // Atomic: the whole resolved set pins or nothing does. A partial pin that reports
979
- // success would leave part of the requested span silently reclaimable.
980
678
  if (byKey.size > MAX_ACTIVE_PROTECTED) {
981
679
  throw new Error(
982
680
  `Protecting ${ids.join(", ")} would hold ${byKey.size} refs, over the ${MAX_ACTIVE_PROTECTED}-ref cap; ` +
983
- "unprotect something first",
681
+ "unpin something first",
984
682
  );
985
683
  }
986
684
  return clearPrepared({
@@ -1031,9 +729,7 @@ export function renderFold(
1031
729
  const refs = flattenFoldRefs(fold, state);
1032
730
  const indices = refsInOrder(snapshot, refs);
1033
731
  if (!indices) return null;
1034
- const mustReveal = state.expanded.includes(fold.id) || (fold.kind === "tool-result"
1035
- ? toolRefsProtected(refs, state, snapshot)
1036
- : refsProtected(refs, state, snapshot));
732
+ const mustReveal = state.expanded.includes(fold.id) || (refsProtected(refs, state, snapshot));
1037
733
  if (!mustReveal) {
1038
734
  const first = snapshot.messages[indices[0]] as Record<string, unknown>;
1039
735
  const text = foldPlaceholder(fold, state, snapshot);
@@ -1105,13 +801,6 @@ export function assertProjectionPreservesToolLinkage(source: unknown[], projecte
1105
801
  }
1106
802
  }
1107
803
 
1108
- /**
1109
- * Peek mass the ladder cannot take: results whose evidence the agent protected
1110
- * outright. This is the number that made rep 7's starvation invisible. The mass sat
1111
- * raw in the window, the ROI trigger's eligibility arithmetic could never reach its
1112
- * threshold over it, and no accounting field said so while the window grew to 235k
1113
- * tokens.
1114
- */
1115
804
  export function pinnedPeekMass(
1116
805
  snapshot: ActiveContextSnapshot,
1117
806
  state: ActiveContextState,
@@ -1170,13 +859,6 @@ export function projectActiveContext(
1170
859
  index += 1;
1171
860
  }
1172
861
  }
1173
- // A peek result is APPEND-ONLY. It used to be rewritten in place once a model call
1174
- // had seen it, on the theory that the edit was tail-local; rep 22 disproved that.
1175
- // The result is only tail-local when it ARRIVES, and reclamation deliberately waits
1176
- // until a later assistant message exists, by which point the window has grown over
1177
- // it and the edit lands mid-prefix. Two peeks cost 100k fresh tokens that way. The
1178
- // duplicate bytes are still reclaimed, by the tool-fold rung at the next commit,
1179
- // which is the one moment a rewrite is already being paid for.
1180
862
  assertProjectionPreservesToolLinkage(snapshot.messages, output);
1181
863
  return output;
1182
864
  }
@@ -1187,9 +869,7 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
1187
869
  const refs = flattenFoldRefs(fold, state);
1188
870
  const allSourceIds = refs.map((ref) => ref.entryId);
1189
871
  const sourceIds = allSourceIds.slice(0, 64);
1190
- const blocked = fold.kind === "tool-result"
1191
- ? toolRefsProtected(refs, state, snapshot)
1192
- : refsProtected(refs, state, snapshot);
872
+ const blocked = refsProtected(refs, state, snapshot);
1193
873
  const projection = state.expanded.includes(fold.id) ? "expanded" : "folded";
1194
874
  return {
1195
875
  id: fold.id,
@@ -1205,9 +885,6 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
1205
885
  sourceCount: allSourceIds.length,
1206
886
  sourceIdsTruncated: sourceIds.length < allSourceIds.length,
1207
887
  sourceSha256: fold.sourceSha256,
1208
- // A paged row without the brief is a chronological id and nothing else: the
1209
- // agent that paged the tree to find WHICH fold holds something got the one
1210
- // field that answers it withheld, while the tree detail carried it all along.
1211
888
  brief: foldBrief(fold, state),
1212
889
  sourceChars: fold.sourceChars,
1213
890
  actions: {
@@ -1216,12 +893,11 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
1216
893
  : { action: "refold", id: fold.id },
1217
894
  expand: { action: "expand", id: fold.id },
1218
895
  refold: { action: "refold", id: fold.id },
1219
- protect: { action: "protect", ids: [fold.id] },
896
+ pin: { action: "pin", ids: [fold.id] },
1220
897
  },
1221
898
  };
1222
899
  }
1223
900
 
1224
- /** Every fold, nested ones included, in transcript order with each parent before its children. */
1225
901
  export function orderedFoldTree(state: ActiveContextState, snapshot: ActiveContextSnapshot): ActiveFold[] {
1226
902
  const byId = foldMap(state);
1227
903
  const ordered: ActiveFold[] = [];
@@ -1260,12 +936,6 @@ export function foldTreeDetail(
1260
936
  }));
1261
937
  }
1262
938
 
1263
- /**
1264
- * One compact row per collapsed fold: what is behind it, where its span starts and
1265
- * ends, and the brief that carries whatever key material the fold captured. This is
1266
- * the answer to "which fold has X" that agents were paying 56-83k chars per peek to
1267
- * reconstruct, and it costs a line.
1268
- */
1269
939
  export function foldIndexRow(
1270
940
  fold: ActiveFold,
1271
941
  state: ActiveContextState,
@@ -1294,7 +964,6 @@ export function foldIndexRow(
1294
964
  };
1295
965
  }
1296
966
 
1297
- /** Every mapped source id to the fold that now holds it, so a lookup never needs a read. */
1298
967
  export function foldSourceMap(
1299
968
  state: ActiveContextState,
1300
969
  snapshot: ActiveContextSnapshot,
@@ -1304,7 +973,6 @@ export function foldSourceMap(
1304
973
  for (const fold of orderedFoldTree(state, snapshot)) {
1305
974
  for (const ref of flattenFoldRefs(fold, state)) entries.push([ref.entryId, fold.id]);
1306
975
  }
1307
- // Deepest wins: the innermost fold is the one that actually holds the source.
1308
976
  const byEntry = new Map(entries);
1309
977
  const all = [...byEntry.entries()];
1310
978
  return { entries: all.slice(0, limit), total: all.length, truncated: all.length > limit };
@@ -1315,8 +983,7 @@ export function activeContextStatus(
1315
983
  state: ActiveContextState,
1316
984
  offset = 0,
1317
985
  limit = 40,
1318
- maximumChapterSourceRefs = Number.MAX_SAFE_INTEGER,
1319
- statusOptions: { diet?: boolean; suggestions?: number } = {},
986
+ statusOptions: { diet?: boolean; indexRows?: number } = {},
1320
987
  ): Record<string, unknown> {
1321
988
  const roots = orderedRoots(state, snapshot).map((item) => item.fold.id);
1322
989
  const ordered = orderedFoldTree(state, snapshot);
@@ -1330,26 +997,22 @@ export function activeContextStatus(
1330
997
  stale: !snapshot.protectedIndices.has(item.index),
1331
998
  protected: snapshot.protectedIndices.has(item.index) || protectedKeys.has(objectRefKey(item.ref)),
1332
999
  staleToolResult: item.ref.role === "toolResult" &&
1333
- !snapshot.toolProtectedIndices.has(item.index) && !protectedKeys.has(objectRefKey(item.ref)) &&
1000
+ !snapshot.protectedIndices.has(item.index) && !protectedKeys.has(objectRefKey(item.ref)) &&
1334
1001
  resultCall(snapshot, item.index, true) !== null,
1335
1002
  }] : []);
1336
1003
  const selectedObjects = objects.slice(offset, offset + limit);
1337
- const eligibleChapter = selectAutomaticChapter(snapshot, state, maximumChapterSourceRefs);
1004
+ const eligibleChapter = selectAutomaticChapter(snapshot, state);
1338
1005
  const eligibleSourceIds = eligibleChapter?.sourceRefs.map((ref) => ref.entryId) ?? [];
1339
1006
  const eligibleEndpoints = eligibleSourceIds.length
1340
1007
  ? [...new Set([eligibleSourceIds[0], eligibleSourceIds.at(-1)!])]
1341
1008
  : [];
1342
- // The index that used to ride every status call grew from 12.6k to 86.5k chars in
1343
- // one measured session, injected on every request that asked for state. On the diet
1344
- // the default payload carries counts, the source map and the folds worth acting on;
1345
- // the full tree and the object list move behind an explicit paged query.
1346
1009
  const index = statusOptions.diet
1347
1010
  ? visibleCollapsedFolds(state, snapshot)
1348
1011
  .map((fold) => foldIndexRow(fold, state, snapshot))
1349
1012
  .sort((left, right) =>
1350
1013
  Number(right.reclaimableBytes) - Number(left.reclaimableBytes) ||
1351
1014
  String(left.id).localeCompare(String(right.id)))
1352
- .slice(0, statusOptions.suggestions ?? STATUS_DIET_SUGGESTIONS)
1015
+ .slice(0, statusOptions.indexRows ?? STATUS_DIET_INDEX_ROWS)
1353
1016
  : null;
1354
1017
  const sourceMap = statusOptions.diet ? foldSourceMap(state, snapshot) : null;
1355
1018
  return {
@@ -1391,30 +1054,17 @@ export function activeContextStatus(
1391
1054
  endId: eligibleSourceIds.at(-1),
1392
1055
  action: { action: "fold", ids: eligibleEndpoints, brief: "<factual brief, at most 1000 characters>" },
1393
1056
  } : null,
1394
- rawTailMinimumBytes: zoneBytes(snapshot.thresholds.freshTail, snapshot.budgetTokens),
1395
1057
  currentTurnRequiresBoundary: false,
1396
1058
  actions: {
1397
1059
  status: { action: "status", offset, limit },
1398
1060
  fold: { action: "fold", ids: ["<source-or-fold-id>"], brief: "<optional factual brief, at most 1000 characters>" },
1399
- protect: { action: "protect", ids: ["<source-or-fold-id>"] },
1061
+ pin: { action: "pin", ids: ["<source-or-fold-id>"] },
1400
1062
  },
1401
1063
  };
1402
1064
  }
1403
1065
 
1404
- /** Room reserved for the one-line continuation marker a truncated page carries. */
1405
1066
  const STATUS_CONTINUATION_RESERVE_BYTES = 400;
1406
1067
 
1407
- /**
1408
- * Enforce the hard status page cap on one fully assembled status payload.
1409
- *
1410
- * Measured 2026-08-07 (rep 19): one status answer serialized to 526KB, 6.2x the
1411
- * remaining headroom, and aborted the request. The paging affordance existed;
1412
- * nothing forced it. This function is the force: every listing in the payload is
1413
- * truncated at a unit boundary (whole rows, never mid-unit) until the serialized
1414
- * page fits, and a one-line continuation marker names the next offset. Trim order
1415
- * spends the diagnostics first and the listing the caller asked for last, and the
1416
- * requested listing always keeps at least one unit so paging always progresses.
1417
- */
1418
1068
  export function boundStatusPayload(
1419
1069
  payload: Record<string, unknown>,
1420
1070
  detail: string | null,
@@ -1426,9 +1076,6 @@ export function boundStatusPayload(
1426
1076
  const target = cap - STATUS_CONTINUATION_RESERVE_BYTES;
1427
1077
  const fits = (): boolean => size() <= target;
1428
1078
  const omitted: string[] = [];
1429
- // keepEnd keeps the NEWEST units of a time-ordered ledger; a paged listing keeps
1430
- // its head so the continuation offset is the next unread unit. Binary search for
1431
- // the largest keep that fits; a stage that cannot fit alone trims to its floor.
1432
1079
  const shrink = (owner: unknown, key: string, label: string, keepEnd = false, minKeep = 0): number => {
1433
1080
  if (!isPlainRecord(owner) || fits()) return 0;
1434
1081
  const original = ownValue(owner, key);
@@ -1452,16 +1099,22 @@ export function boundStatusPayload(
1452
1099
  };
1453
1100
  const automatic = ownValue(payload, "automatic");
1454
1101
  const instrumentation = isPlainRecord(automatic) ? ownValue(automatic, "instrumentation") : undefined;
1455
- const surfacing = isPlainRecord(automatic) ? ownValue(automatic, "surfacing") : undefined;
1102
+ const lastAction = isPlainRecord(automatic) ? ownValue(automatic, "lastAutomaticAction") : undefined;
1103
+ const lastEpoch = isPlainRecord(lastAction) ? ownValue(lastAction, "epoch") : undefined;
1456
1104
  shrink(instrumentation, "projectionRecords", "projection record(s)", true);
1457
1105
  shrink(instrumentation, "cacheObservations", "cache observation(s)", true);
1458
1106
  shrink(instrumentation, "events", "context event(s)", true);
1459
- shrink(surfacing, "log", "surfacing log row(s)", true);
1460
1107
  if (shrink(payload, "sourceMap", "source map row(s)") > 0) payload.sourceMapTruncated = true;
1461
1108
  shrink(payload, "topFolds", "top fold row(s)");
1109
+ // The last epoch's receipt lists grow with commit depth (a deep epoch applies dozens
1110
+ // of marks). They are the commit's own receipt, so they outlast
1111
+ // every other diagnostic, but they still page away before any listing row the
1112
+ // caller actually asked for: on the detail pages, where none of the diagnostics
1113
+ // above exist, they are the mass that made one fold row miss the cap.
1114
+ const receiptRowsDropped =
1115
+ shrink(lastEpoch, "applied", "applied receipt row(s)", true) +
1116
+ shrink(lastEpoch, "absorbed", "absorbed receipt row(s)", true) > 0;
1462
1117
  const rowLabel = (key: string): string => key === "folds" ? "fold row(s)" : key === "objects" ? "object row(s)" : "tree row(s)";
1463
- // The listing the caller asked for goes last and never below one unit. Its
1464
- // machine-readable next offset moves back to the first unit the page dropped.
1465
1118
  let continueAt: number | null = null;
1466
1119
  if (detail === "folds" || detail === "objects") {
1467
1120
  const rideKey = detail === "folds" ? "objects" : "folds";
@@ -1478,9 +1131,17 @@ export function boundStatusPayload(
1478
1131
  else payload.nextOffset = continueAt;
1479
1132
  }
1480
1133
  if (!omitted.length) return payload;
1481
- const resume = continueAt === null
1482
- ? "the full lists stay reachable through the paged status details"
1134
+ // A dropped receipt row has no page: the epoch receipt is a record of the last
1135
+ // action, not a listing, and claiming it "stays reachable" pointed the agent at
1136
+ // a page that does not exist (2026-08-14 external review). The span-to-fold
1137
+ // mapping it summarizes stays complete in the folds and tree details, so the
1138
+ // continuation says exactly that instead.
1139
+ const pageable = continueAt === null
1140
+ ? "the fold, object and tree lists stay reachable through the paged status details"
1483
1141
  : `continue at ${JSON.stringify({ action: "status", detail, offset: continueAt })}`;
1142
+ const resume = receiptRowsDropped
1143
+ ? `${pageable}; the epoch receipt keeps only its newest rows and the trimmed rows have no page`
1144
+ : (continueAt === null ? "the full lists stay reachable through the paged status details" : pageable);
1484
1145
  payload.continuation =
1485
1146
  `Status pages are bounded to ${cap} bytes; omitted ${omitted.join(", ")}; ${resume}.`;
1486
1147
  return payload;
@@ -1495,9 +1156,7 @@ export function visibleCollapsedFolds(
1495
1156
  const visit = (fold: ActiveFold): void => {
1496
1157
  const refs = flattenFoldRefs(fold, state);
1497
1158
  if (!foldInterval(fold, state, snapshot)) return;
1498
- const protectedSource = fold.kind === "tool-result"
1499
- ? toolRefsProtected(refs, state, snapshot)
1500
- : refsProtected(refs, state, snapshot);
1159
+ const protectedSource = refsProtected(refs, state, snapshot);
1501
1160
  if (!state.expanded.includes(fold.id) && !protectedSource) {
1502
1161
  output.push(fold);
1503
1162
  return;
@@ -1559,7 +1218,6 @@ export interface FoldSourceRequest {
1559
1218
  projectEntry?: (entry: Record<string, unknown>) => unknown[];
1560
1219
  }
1561
1220
 
1562
- /** One exact message per ref identity and digest, read off the immutable session entries. */
1563
1221
  function exactSessionMessages(input: FoldSourceRequest): Map<string, unknown> {
1564
1222
  const projectEntry = input.projectEntry ?? sessionEntryMessages;
1565
1223
  const exact = new Map<string, unknown>();
@@ -1586,13 +1244,6 @@ function exactMessageFor(
1586
1244
  return clone(message);
1587
1245
  }
1588
1246
 
1589
- /**
1590
- * One fold's ENTIRE original source, at every depth.
1591
- *
1592
- * This is the rescue read: restoration and the recovery audit both need the bytes as the
1593
- * session first wrote them, with nothing standing in for anything. Retrieval inside a
1594
- * session is the other function, and it serves one level.
1595
- */
1596
1247
  export function recoverFoldMessages(input: FoldSourceRequest): unknown[] {
1597
1248
  const fold = input.state.folds.find((item) => item.id === input.foldId);
1598
1249
  if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
@@ -1600,7 +1251,6 @@ export function recoverFoldMessages(input: FoldSourceRequest): unknown[] {
1600
1251
  return flattenFoldRefs(fold, input.state).map((ref) => exactMessageFor(exact, ref, input.sessionId));
1601
1252
  }
1602
1253
 
1603
- /** A child fold as its parent's stored span holds it: the placeholder, not the bytes under it. */
1604
1254
  export function storedChildPlaceholder(child: ActiveFold, state: ActiveContextState): Record<string, unknown> {
1605
1255
  return {
1606
1256
  placeholder: "fold",
@@ -1614,18 +1264,6 @@ export function storedChildPlaceholder(child: ActiveFold, state: ActiveContextSt
1614
1264
  };
1615
1265
  }
1616
1266
 
1617
- /**
1618
- * One fold's stored span, AS STORED: one level.
1619
- *
1620
- * A fold's span is what it took in, and what it took in was already whatever the window
1621
- * held: raw entries, and placeholders for folds that were there first. So the honest read
1622
- * of a parent is that same span, children still placeheld. Reading it as the original
1623
- * bytes at every depth would hand back a whole session's transcript for one call and
1624
- * undo the nesting the fold performed.
1625
- *
1626
- * The verbatim floor is untouched by this. A leaf's stored span IS its original bytes, so
1627
- * the bytes are always exactly one peek away from any id the read hands back.
1628
- */
1629
1267
  export function foldStoredSpan(input: FoldSourceRequest): unknown[] {
1630
1268
  const fold = input.state.folds.find((item) => item.id === input.foldId);
1631
1269
  if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
@@ -1639,21 +1277,12 @@ export function foldStoredSpan(input: FoldSourceRequest): unknown[] {
1639
1277
  });
1640
1278
  }
1641
1279
 
1642
- /**
1643
- * Ephemeral point read of one fold's stored span. It returns the same SHA-256-verified
1644
- * span expansion restores, ONE level: raw entries verbatim, child folds still placeheld.
1645
- * It is bounded for one model call and changes no projection: the fold stays collapsed and
1646
- * the durable state is untouched. Any id in the result peeks in one hop, so the verbatim
1647
- * floor is a hop away at every depth rather than a payload nobody asked for.
1648
- */
1649
1280
  export function peekFoldSource(input: {
1650
1281
  foldId: string;
1651
1282
  state: ActiveContextState;
1652
1283
  entries: Array<Record<string, unknown>>;
1653
1284
  sessionId: string;
1654
- /** Explicit widening. Absent, a peek returns the bounded index view. */
1655
1285
  maximumBytes?: number;
1656
- /** Byte offset into the exact stored source; the narrowing half of admission control. */
1657
1286
  offset?: number;
1658
1287
  toolName?: string;
1659
1288
  projectEntry?: (entry: Record<string, unknown>) => unknown[];
@@ -1672,9 +1301,6 @@ export function peekFoldSource(input: {
1672
1301
  const offset = Math.min(Math.max(0, input.offset ?? 0), sourceBytes);
1673
1302
  const budget = input.maximumBytes ?? PEEK_DEFAULT_MAX_BYTES;
1674
1303
  const window = offset > 0 ? utf8Slice(source, offset) : source;
1675
- // Offset zero is the INDEX read and takes head AND tail, because chain keys and
1676
- // conclusions live at the end of a source and a head-only slice silently omits
1677
- // exactly that region. An explicit offset is paging and stays contiguous.
1678
1304
  const view = offset > 0
1679
1305
  ? { text: boundedUtf8(window, budget), omittedBytes: 0, contiguous: true }
1680
1306
  : boundedHeadTail(window, budget);
@@ -1685,8 +1311,6 @@ export function peekFoldSource(input: {
1685
1311
  : null;
1686
1312
  const truncated = view.omittedBytes > 0 || nextOffset !== null;
1687
1313
  const children = childFoldIds(fold);
1688
- // The index view: every nested fold's brief in FULL. A brief is the navigable unit,
1689
- // so truncating it is the one economy that costs the read its purpose.
1690
1314
  const index = descendantIndexRows(fold, input.state);
1691
1315
  return {
1692
1316
  version: 1,
@@ -1706,15 +1330,13 @@ export function peekFoldSource(input: {
1706
1330
  truncated,
1707
1331
  view: offset > 0 ? "slice" : (view.omittedBytes > 0 ? "index" : "complete"),
1708
1332
  ...(index.length ? { index } : {}),
1709
- // Narrower and WIDER reads that are always constructible, so a refusal or a
1710
- // truncation can name one.
1711
1333
  children,
1712
1334
  narrower: {
1713
1335
  ...(nextOffset === null ? {} : { slice: { action: "peek", id: fold.id, offset: nextOffset, bytes: returnedBytes } }),
1714
1336
  ...(children.length ? { child: { action: "peek", id: children[0] } } : {}),
1715
1337
  },
1716
1338
  ...(truncated
1717
- ? { wider: { action: "peek", id: fold.id, bytes: Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.maxChapterChars) } }
1339
+ ? { wider: { action: "peek", id: fold.id, bytes: Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.maxSourceChars) } }
1718
1340
  : {}),
1719
1341
  lifetime: "these bytes stay in the window exactly as returned, like any other tool result, and " +
1720
1342
  `nothing rewrites this result in place. It is a COPY of fold ${fold.id}'s stored source, so the ` +
@@ -1728,17 +1350,13 @@ export function peekFoldSource(input: {
1728
1350
  "placeheld. Peek a child id to read its own span; the fold stayed collapsed and no projection changed."
1729
1351
  : "Complete exact source; the fold stayed collapsed and no projection changed.",
1730
1352
  source: returned,
1731
- // Serialized AFTER the source on purpose: a reader that just consumed a bounded slice
1732
- // decides its next action at the end of the payload, and a notice buried before ten
1733
- // thousand source bytes is a notice unread (measured: a run concluded a staged chain
1734
- // was finished because the chain key lived in the unshown tail of a truncated peek).
1735
1353
  ...(truncated
1736
1354
  ? {
1737
1355
  truncationReminder: `STOP: ${view.omittedBytes || sourceBytes - returnedBytes} of ${sourceBytes} ` +
1738
1356
  "bytes were NOT shown" +
1739
1357
  (view.omittedBytes ? " (the middle; the head and tail are both above)" : " (everything after this slice)") +
1740
1358
  `. Widen with {"action":"peek","id":"${fold.id}","bytes":${
1741
- Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.maxChapterChars)}}` +
1359
+ Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.maxSourceChars)}}` +
1742
1360
  (nextOffset === null ? "" : ` or read on with {"action":"peek","id":"${fold.id}","offset":${nextOffset}}`) +
1743
1361
  ".",
1744
1362
  }
@@ -1746,16 +1364,10 @@ export function peekFoldSource(input: {
1746
1364
  };
1747
1365
  }
1748
1366
 
1749
- /** The marker a head+tail view puts where the omitted middle was. */
1750
1367
  export function headTailMarker(omittedBytes: number): string {
1751
1368
  return omittedBytes > 0 ? `\n... [${omittedBytes} exact source bytes omitted] ...\n` : "";
1752
1369
  }
1753
1370
 
1754
- /**
1755
- * A bounded view that keeps the head AND the tail. A head-only bound drops exactly the
1756
- * region a staged chain keeps its key in; the omitted size is stated where the reading
1757
- * ends so "there is more" is never an inference.
1758
- */
1759
1371
  export function boundedHeadTail(
1760
1372
  source: string,
1761
1373
  budget: number,
@@ -1776,7 +1388,6 @@ export function boundedHeadTail(
1776
1388
  };
1777
1389
  }
1778
1390
 
1779
- /** Every descendant fold, in transcript order, with its brief in full. */
1780
1391
  export function descendantIndexRows(
1781
1392
  fold: ActiveFold,
1782
1393
  state: ActiveContextState,