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.
@@ -152,19 +152,23 @@ export function toolCallArguments(snapshot: ActiveContextSnapshot, assistantInde
152
152
  return calls.get(id);
153
153
  }
154
154
 
155
- /**
156
- * Anchor budgets for a stage-identified brief. They exist to keep the composed brief
157
- * inside the hard 1200-character brief cap with the generic prose trimmed away: an
158
- * argument list, a leading label from the result head, and the result TAIL, which is
159
- * where a staged chain puts its keys and a report puts its conclusion.
160
- */
161
155
  export const IDENTIFIED_BRIEF_ARGUMENT_CHARS = 240;
162
156
  export const IDENTIFIED_BRIEF_VALUE_CHARS = 96;
157
+ // A composed stage-identified brief is held to the ONE policy cap, maxBriefChars
158
+ // (Shane 2026-08-14: "2k brief is more appropriate"). The head keeps the
159
+ // result's leading paragraph and gets exactly what remains of the cap after the
160
+ // parts that identify the fold (call signatures, tail anchor, fixed text), so
161
+ // the paragraph takes every character the contract can give it. The 1,100
162
+ // sub-cap this replaced held 100 below a separate 1,200 supplied maximum as
163
+ // headroom for commit-time gap absorption's appended sentence; the absorption
164
+ // eligibility law (gate 136) removed that hazard, and both smaller numbers went
165
+ // with it. Gate 134 pins the paragraph surviving.
166
+ // The head bound for a result with NO terminated opening paragraph: its first
167
+ // line, exactly the pre-paragraph behaviour.
163
168
  export const IDENTIFIED_BRIEF_HEAD_CHARS = 160;
164
169
  export const IDENTIFIED_BRIEF_TAIL_CHARS = 120;
165
170
  export const IDENTIFIED_BRIEF_CALLS_CHARS = 400;
166
171
 
167
- /** Every scalar argument of a call, in stable key order, bounded per value and in total. */
168
172
  export function identifiedCallArguments(
169
173
  args: unknown,
170
174
  factual: (value: string, maximum: number) => string,
@@ -182,7 +186,6 @@ export function identifiedCallArguments(
182
186
  return oneLine(pairs.join(", "), IDENTIFIED_BRIEF_ARGUMENT_CHARS);
183
187
  }
184
188
 
185
- /** The last characters of a result, which is the region a truncated read never shows. */
186
189
  export function identifiedResultTail(
187
190
  message: unknown,
188
191
  factual: (value: string, maximum: number) => string,
@@ -192,13 +195,6 @@ export function identifiedResultTail(
192
195
  return factual(text.slice(Math.max(0, text.length - IDENTIFIED_BRIEF_TAIL_CHARS)), IDENTIFIED_BRIEF_TAIL_CHARS);
193
196
  }
194
197
 
195
- /**
196
- * The stage-identified brief. The generic prose is trimmed to a few words so the
197
- * character budget buys ANCHORS instead: which call this was, with which arguments,
198
- * what the result opened with, and what it ENDED with. The tail anchor is the point
199
- * of the whole change: a reader deciding which fold holds a chain key can only tell
200
- * from the tail, and a generic sentence never carried one.
201
- */
202
198
  export function stageIdentifiedToolBrief(input: {
203
199
  snapshot: ActiveContextSnapshot;
204
200
  refs: EvidenceRef[];
@@ -216,31 +212,92 @@ export function stageIdentifiedToolBrief(input: {
216
212
  );
217
213
  return args ? `${factualToolName(call.name)}(${args})` : factualToolName(call.name);
218
214
  });
219
- const label = factualValue(
220
- String(contentText(messages[0]) ?? "").split(/\r?\n/).find((line) => line.trim()) ?? "",
221
- IDENTIFIED_BRIEF_HEAD_CHARS,
222
- );
215
+ // THE LEADING PARAGRAPH, not the first line (Shane 2026-08-14). A tool result
216
+ // that opens with a header line and then states, in prose, what the caller must
217
+ // do or know had everything after that header dropped: the head kept ONE line
218
+ // and the header was it. sol-20260814-traps rep 1 is what that costs. A stage
219
+ // result opens "STAGE 09 / read" and carries its audit note, including a code
220
+ // word and a correction WITHDRAWING an earlier one, on the next line. The fold
221
+ // committed at entry 121 with a head of "STAGE 09 / read", the model brief that
222
+ // did carry both facts landed at entry 311, and the answer at entry 185 in
223
+ // between was a fabricated code word present nowhere in the run.
224
+ //
225
+ // Reading to the first blank line is the generic form of that: a result whose
226
+ // payload follows a blank line contributes its whole opening paragraph, and a
227
+ // result that is bulk from line one contributes exactly what it did before. The
228
+ // deterministic brief is what a fold is COMMITTED with, so it has to stand alone
229
+ // the moment the fold appears, and never be the thing an async upgrade rescues.
230
+ // Paragraph-shaped means TERMINATED: an opening block is only kept whole when
231
+ // a blank line ends it before the bulk. A result that is bulk from its first
232
+ // line has no opening prose to keep, and handing its first thousand characters
233
+ // to the brief is noise that inflates every status page and shrinks every
234
+ // fold's reclaimable mass; that result contributes its first line, exactly as
235
+ // before.
236
+ const leadingParagraph = (message: unknown): string | null => {
237
+ const lines = String(contentText(message) ?? "").split(/\r?\n/);
238
+ const kept: string[] = [];
239
+ for (const line of lines) {
240
+ if (line.trim()) kept.push(line.trim());
241
+ else if (kept.length) return kept.join(" ");
242
+ }
243
+ return null;
244
+ };
223
245
  const tail = identifiedResultTail(messages.at(-1), factualValue);
224
- const composed = [
246
+ // THE AGENT'S OWN CLOSING NOTES (Shane 2026-08-14). The batch the fold absorbs
247
+ // runs past its results: the assistant text between the last result and the
248
+ // next batch's work is the agent recording what it just derived, and
249
+ // sol-20260814-traps rep 2 lost trace-a-02 and trace-d-05 because those lines
250
+ // lived nowhere else. Each assistant message contributes its leading
251
+ // paragraph, INCLUDING the one that also opens the next batch: in a
252
+ // pull-shaped session the agent closes batch N and calls batch N+1 in one
253
+ // message (36 of rep 3's 37 assistant messages carry text and the next call
254
+ // together), so the text is collected before the tool-calling break, and
255
+ // ownership stays with batch N because batch N+1's own walk starts after
256
+ // its own results. The walk stops at the first user turn or tool result,
257
+ // and after consuming one tool-calling assistant.
258
+ const resultIndices = refs.map((ref) => exactMapped(snapshot, ref)?.index ?? -1);
259
+ const noteTexts: string[] = [];
260
+ for (let index = Math.max(...resultIndices) + 1; index < snapshot.mapped.length; index += 1) {
261
+ const message = snapshot.mapped[index]?.message;
262
+ const role = messageRole(message);
263
+ if (role !== "assistant") break;
264
+ const paragraphText = assistantNoteText(message);
265
+ if (paragraphText) noteTexts.push(paragraphText);
266
+ const callsTools = (denseOwnArrayValues(ownValue(message, "content")) ?? [])
267
+ .some((part) => ownValue(part, "type") === "toolCall");
268
+ if (callsTools) break;
269
+ }
270
+ // Compose the identifying parts FIRST, then hand the paragraph what is left of
271
+ // the bound. A fixed head allowance either starves the paragraph or outgrows
272
+ // the composed contract; the remainder does neither. The head is seated before
273
+ // the notes: the instruction prose won gate 134 on sealed evidence, so when
274
+ // both are long the head keeps its allowance and the notes take the remainder.
275
+ const assemble = (label: string, noted: string): string => [
225
276
  `Read ${oneLine(signatures.join("; "), IDENTIFIED_BRIEF_CALLS_CHARS)}`,
226
277
  label ? `opens "${label}"` : "",
227
278
  tail ? `ends "${tail}"` : "",
279
+ noted ? `agent noted "${noted}"` : "",
228
280
  calls.length > 1 ? `${calls.length} exact results here` : "exact source here",
229
281
  ].filter(Boolean).join(" · ");
230
- const bounded = oneLine(composed, ACTIVE_CONTEXT_POLICY.maxBriefChars);
282
+ // A present label costs its own wrapper on top of the fixed parts: the
283
+ // ` · opens "` prefix and closing quote are 11 characters that assemble("")
284
+ // does not count, because an empty label drops the whole term.
285
+ const headAllowance = Math.max(0, ACTIVE_CONTEXT_POLICY.maxBriefChars - assemble("", "").length - 11);
286
+ const paragraph = leadingParagraph(messages[0]);
287
+ const label = paragraph !== null
288
+ ? factualValue(paragraph, headAllowance)
289
+ : factualValue(
290
+ String(contentText(messages[0]) ?? "").split(/\r?\n/).find((line) => line.trim()) ?? "",
291
+ IDENTIFIED_BRIEF_HEAD_CHARS,
292
+ );
293
+ // The notes wrapper costs 18 characters the empty form does not count, the
294
+ // same accounting as the label's 11.
295
+ const notesAllowance = Math.max(0, ACTIVE_CONTEXT_POLICY.maxBriefChars - assemble(label, "").length - 18);
296
+ const noted = noteTexts.length ? factualValue(noteTexts.join(" · "), notesAllowance) : "";
297
+ const bounded = oneLine(assemble(label, noted), ACTIVE_CONTEXT_POLICY.maxBriefChars);
231
298
  return usefulBrief(bounded, ACTIVE_CONTEXT_POLICY.maxBriefChars, snapshot.toolName) ? bounded : null;
232
299
  }
233
300
 
234
- /**
235
- * The fold ids a batch of tool results PEEKED, or null if the batch is not one.
236
- *
237
- * A peek result is the one tool output whose bytes the runtime already holds: it is a
238
- * copy of a fold's own stored source. Reclaiming it therefore has an identity the
239
- * ordinary tool-fold path does not have, and that identity has to be derivable from the
240
- * transcript alone, because whichever mechanism claims the copy (the epoch's own
241
- * reclaim marking or the doorless ladder) the record it leaves behind must still point
242
- * back at the source.
243
- */
244
301
  export function peekedSourceFoldIds(
245
302
  snapshot: ActiveContextSnapshot,
246
303
  refs: readonly EvidenceRef[],
@@ -262,14 +319,6 @@ export function peekedSourceFoldIds(
262
319
  return ids.length ? ids : null;
263
320
  }
264
321
 
265
- /**
266
- * The brief a reclaimed peek copy leaves behind: a pointer, not a summary.
267
- *
268
- * The copy's content is the source fold's content, so summarizing it again would mint
269
- * a second description of bytes that already have one. What the agent needs from the
270
- * placeholder is the id it already used once, so re-reading the verbatim content is the
271
- * same one-hop peek it was before the reclaim.
272
- */
273
322
  export function peekReclaimBrief(sourceFoldIds: readonly string[]): string {
274
323
  const named = sourceFoldIds.join(", ");
275
324
  return sourceFoldIds.length === 1
@@ -293,26 +342,19 @@ export function automaticToolBrief(snapshot: ActiveContextSnapshot, candidate: F
293
342
  throw new Error("Automatic tool brief crossed a validated assistant batch");
294
343
  }
295
344
  const first = calls[0];
296
- const factualValue = (value: string, maximum: number): string => value
297
- .replace(new RegExp(snapshot.toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"), "active-context service")
298
- .replace(/[\u0000-\u001f\u007f]+/g, " ")
299
- .replace(/\s+/g, " ")
300
- .trim()
301
- .slice(0, maximum);
345
+ const factualValue = (value: string, maximum: number): string => oneLine(
346
+ value
347
+ .replace(new RegExp(snapshot.toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"), "active-context service")
348
+ .replace(/[\u0000-\u001f\u007f]+/g, " "),
349
+ maximum,
350
+ );
302
351
  const factualBriefValue = (value: string): string => factualValue(value, 120);
303
352
  const factualToolName = (name: string): string => name.toLowerCase() === snapshot.toolName.toLowerCase()
304
353
  ? "active-context status inspection"
305
354
  : factualBriefValue(name);
306
355
  const args = toolCallArguments(snapshot, first.assistantIndex, first.id);
307
- // A peek copy is named by what it copied, before anything else. The stage-identified
308
- // brief below describes a result by its own head and tail, which for a peek copy is a
309
- // second description of a fold that already has one; the pointer is what makes the
310
- // reclaim recoverable, so it comes first and it is derived here rather than at the one
311
- // call site that happens to know a reclaim is under way.
312
356
  const peeked = peekedSourceFoldIds(snapshot, refs);
313
357
  if (peeked) return peekReclaimBrief(peeked);
314
- // Exact anchors before generic prose: one sentence per tool made every fold of that
315
- // tool carry the SAME brief, so no index could answer which fold holds a given stage.
316
358
  const identified = stageIdentifiedToolBrief({ snapshot, refs, calls, factualValue, factualToolName });
317
359
  if (identified) return identified;
318
360
  const targets: string[] = [];
@@ -328,10 +370,28 @@ export function automaticToolBrief(snapshot: ActiveContextSnapshot, candidate: F
328
370
  return `Completed one read-only ${names} batch with ${calls.length} exact results${target}; every stale output remains recoverable from this fold.`;
329
371
  }
330
372
 
331
- /** The narrowest entry in a parent's index that still names a subject rather than a stub. */
332
373
  const MIN_SUBJECT_CHARS = 24;
374
+ const CHAPTER_NOTE_CHARS = 160;
375
+
376
+ // The leading paragraph of an assistant message read as the agent's own note.
377
+ // Unlike a tool result's opening prose, an unterminated block is KEPT: the
378
+ // whole message is the agent's words, so a note with no blank line is a
379
+ // one-paragraph note, not bulk to refuse.
380
+ function assistantNoteText(message: unknown): string {
381
+ const lines = String(contentText(message) ?? "").split(/\r?\n/);
382
+ const kept: string[] = [];
383
+ for (const line of lines) {
384
+ if (line.trim()) kept.push(line.trim());
385
+ else if (kept.length) break;
386
+ }
387
+ return kept.join(" ");
388
+ }
333
389
 
334
- /** Cut to a character budget without splitting a surrogate pair, marked where it cut. */
390
+ // THE one bounding primitive (Shane 2026-08-14). Every deterministic brief cut
391
+ // runs through here so a bound always leaves its mark: "..." means exactly
392
+ // "content continues in the exact source", and a reader never mistakes a cut
393
+ // value for a complete one. oneLine collapses whitespace then delegates;
394
+ // factualValue sanitizes then delegates through oneLine. No other site slices.
335
395
  function boundedSubject(text: string, budget: number): string {
336
396
  if (text.length <= budget) return text;
337
397
  let kept = text.slice(0, Math.max(0, budget - 3)).trimEnd();
@@ -340,48 +400,80 @@ function boundedSubject(text: string, budget: number): string {
340
400
  return `${kept}...`;
341
401
  }
342
402
 
343
- /**
344
- * A parent's fallback brief: an index of its children, one entry each.
345
- *
346
- * This used to concatenate the children's briefs and slice the result at the cap, which
347
- * cannot represent them. Ten children whose briefs may each run to the cap need ten times
348
- * the cap, so the slice landed inside the first child or two and the rest of the group
349
- * left no trace at all. In the 2026-08-11 rep every parent came out at exactly the cap,
350
- * the bottom rung showed five of ten children and every rung above it showed ONE: at rung
351
- * two the first child was itself a capped parent, so it spent the whole budget alone.
352
- *
353
- * The budget is divided instead. Children shorter than an even share hand their remainder
354
- * to the longer ones, so the cut falls on the children that are actually long rather than
355
- * on whoever sorts last, and every child appears whatever its neighbours cost. Coverage is
356
- * the property being defended: this is the brief a group carries when no generator wrote
357
- * it one, and a group that cannot say what a member holds cannot be navigated back into.
358
- */
359
- export function deterministicConsolidationBrief(candidate: FoldCandidate, state: ActiveContextState): string {
403
+ export function deterministicConsolidationBrief(
404
+ candidate: FoldCandidate,
405
+ state: ActiveContextState,
406
+ toolName = DEFAULT_ACTIVE_CONTEXT_TOOL_NAME,
407
+ snapshot?: ActiveContextSnapshot,
408
+ ): string {
360
409
  const byId = foldMap(state);
361
- const subjects = candidate.parts.flatMap((part) => {
362
- if (part.kind !== "fold") return [];
363
- const child = byId.get(part.foldId);
364
- // Through the override map, never off the record: a child upgraded since it committed
365
- // presents the model's brief, and reading the record here would index the sentence
366
- // that brief replaced.
367
- return child ? [foldBrief(child, state).replace(/\s+/g, " ").trim()] : [];
410
+ const escapedToolName = toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
411
+ const sanitize = (value: string): string => value
412
+ .replace(new RegExp(escapedToolName, "gi"), "active-context service")
413
+ .replace(/\s+/g, " ")
414
+ .trim();
415
+ let foldCount = 0;
416
+ let spanStart = Number.MAX_SAFE_INTEGER;
417
+ let spanEnd = -1;
418
+ const seat = (ref: EvidenceRef): void => {
419
+ const item = snapshot ? exactMapped(snapshot, ref) : null;
420
+ if (!item) return;
421
+ spanStart = Math.min(spanStart, item.index);
422
+ spanEnd = Math.max(spanEnd, item.index);
423
+ };
424
+ const childSubjects = candidate.parts.flatMap((part) => {
425
+ if (part.kind === "fold") {
426
+ const child = byId.get(part.foldId);
427
+ if (!child) return [];
428
+ foldCount += 1;
429
+ for (const ref of flattenFoldRefs(child, state)) seat(ref);
430
+ return [sanitize(foldBrief(child, state))];
431
+ }
432
+ seat(part.ref);
433
+ return [];
368
434
  });
435
+ // The agent's own recorded words are the ONLY carrier a derived fact ever
436
+ // has (sol-20260814-traps rep 2 lost trace-a-02 and trace-d-05 exactly here:
437
+ // each lived in one assistant turn between two tool batches, the parent
438
+ // indexed only its children's briefs, and the value reached zero briefs), so
439
+ // every assistant message the group hides contributes its leading paragraph
440
+ // as its OWN subject, sharing the division with the child briefs. The child
441
+ // briefs are not trusted to carry the notes for it: a child's subject is
442
+ // truncated to its division share, and a note past the cut is the rep-2
443
+ // loss again one level up, so a note rides whole or not at all, which the
444
+ // ascending-length division guarantees for anything shorter than its fair
445
+ // share. The span is WALKED rather than read off the candidate's raw parts,
446
+ // because absorption geometry varies: rep 2 held its notes as unclaimed raw
447
+ // parts while denser sessions claim the same assistant text into a child's
448
+ // interval, and both spellings hide the same words. Non-assistant gaps
449
+ // contribute nothing: their facts live in the folds beside them. Without a
450
+ // snapshot the exact text is unreadable and the output is the
451
+ // pre-2026-08-14 bytes exactly.
452
+ const notes: string[] = [];
453
+ if (snapshot && spanEnd >= 0) {
454
+ for (let index = spanStart; index <= spanEnd; index += 1) {
455
+ const item = snapshot.mapped[index];
456
+ if (!item?.message) continue;
457
+ if (messageRole(item.message) !== "assistant") continue;
458
+ const noteText = assistantNoteText(item.message);
459
+ if (noteText) notes.push(sanitize(boundedSubject(noteText, CHAPTER_NOTE_CHARS)));
460
+ }
461
+ }
462
+ const noteCount = notes.length;
463
+ const subjects = [...childSubjects, ...notes];
369
464
  if (!subjects.length) return "Grouped completed context covering no readable folds.";
370
465
  const separator = " | ";
371
- const lead = `Grouped completed context covering ${subjects.length} folds: `;
372
- // Below this width an entry says nothing a reader could act on, so a group too wide to
373
- // give every child that much names as many as it can and then says, in the brief itself,
374
- // how many it could not name. A count is a poor substitute for a subject and a truthful
375
- // one; a slice through the middle of the third child is neither.
466
+ const lead = noteCount
467
+ ? `Grouped completed context covering ${foldCount} folds and ${noteCount} agent notes: `
468
+ : `Grouped completed context covering ${subjects.length} folds: `;
376
469
  const room = ACTIVE_CONTEXT_POLICY.maxBriefChars - lead.length - 1;
377
470
  const named = Math.max(1, Math.min(subjects.length,
378
471
  Math.floor((room + separator.length) / (MIN_SUBJECT_CHARS + separator.length))));
379
472
  const omitted = subjects.length - named;
380
- const tail = omitted ? `${separator}${omitted} more folds in this group` : "";
473
+ // With notes in the division, what the slice drops may be a note rather than
474
+ // a fold, so the tail only claims folds when folds are all it holds.
475
+ const tail = omitted ? `${separator}${omitted} more ${noteCount ? "in this group" : "folds in this group"}` : "";
381
476
  const budget = room - tail.length - separator.length * (named - 1);
382
- // Shortest first, so every subject that fits whole releases what it did not use to the
383
- // subjects still to be measured. `owed` is always the even share of what is left among
384
- // those that remain, which is the largest floor that still leaves room for all of them.
385
477
  const order = subjects.slice(0, named).map((text, index) => ({ text, index })).sort((a, b) =>
386
478
  a.text.length - b.text.length || a.index - b.index);
387
479
  const bounded = new Array<string>(named);
@@ -395,21 +487,11 @@ export function deterministicConsolidationBrief(candidate: FoldCandidate, state:
395
487
  return `${lead}${bounded.join(separator)}${tail}.`.replace(/\s+/g, " ").trim();
396
488
  }
397
489
 
398
- /**
399
- * Every fold kind, as a span may contain it.
400
- *
401
- * The agent path composes with this set, and so does the count law's parent: a fold is a
402
- * fold, whatever kind made it, and a parent may take any of them. What an automatic
403
- * CHAPTER may take is nothing of the sort, at any count: a placeholder reaches an
404
- * automatic fold through exactly one route now, which is the count (Shane 2026-08-10).
405
- */
406
490
  export const ALL_FOLD_KINDS: ReadonlySet<FoldKind> =
407
491
  new Set<FoldKind>(["tool-result", "chapter", "consolidation"]);
408
492
 
409
- /** No placeholder is span material. What an automatic chapter composes is raw entries. */
410
493
  export const NO_FOLD_KINDS: ReadonlySet<FoldKind> = new Set<FoldKind>();
411
494
 
412
- /** The first pinned fold a span would nest, or null when the span swallows no pin. */
413
495
  export function pinnedChildFold(
414
496
  parts: FoldPart[],
415
497
  state: ActiveContextState,
@@ -429,33 +511,12 @@ export function pinnedChildFold(
429
511
  return null;
430
512
  }
431
513
 
432
- /**
433
- * The one refusal nesting keeps.
434
- *
435
- * A span may take in any fold, so the pin is the sole thing that can stop it, and a pin
436
- * is a promise to hold bytes raw. Nesting one would put a fold the agent asked to keep
437
- * visible behind a placeholder, so the span is refused by name with the release valve
438
- * in it, the same texture the pinned-share cap uses.
439
- */
440
514
  export function pinnedNestingRefusal(pinned: ActiveFold, toolName: string): string {
441
515
  return `fold refused: ${pinned.id} is pinned, and nesting it would hide context you asked to keep. ` +
442
516
  `Release it with ${toolName} {"action":"unprotect","ids":["${pinned.id}"]} first, ` +
443
517
  "or name a span that stops at its boundary.";
444
518
  }
445
519
 
446
- /**
447
- * The span you named is already folded, said the way every other collision here is said.
448
- *
449
- * A fold that already owned the evidence used to reach the agent as
450
- * "Evidence <id> has multiple direct fold owners", an invariant message thrown out of the
451
- * tool call with no next move in it. The collision is ordinary: automatic folding and an
452
- * agent curate the same window, so the ladder taking a span the agent was about to name
453
- * is the campaign working, not a race. So it reads like the pin refusal above: name the
454
- * fold holding it, give the two verbs that open it, and name the span that would work.
455
- *
456
- * One sentence covers the three ways a caller can point at folded material: an entry a
457
- * fold directly owns, a span sitting inside one, and a span whose only reading IS one.
458
- */
459
520
  export function foldedSpanRefusal(ownerId: string, subject: string, toolName: string): string {
460
521
  return `fold refused: ${subject} is already folded inside ${ownerId}, so there is nothing ` +
461
522
  `left to fold there. Read it with ${toolName} {"action":"peek","id":"${ownerId}"}, restore it ` +
@@ -509,22 +570,6 @@ export function chapterUnits(snapshot: ActiveContextSnapshot): ChapterUnit[] {
509
570
  .flatMap((segment) => structurallyClosedChapterUnits(snapshot.messages, segment));
510
571
  }
511
572
 
512
- /**
513
- * The automatic jurisdiction, as a list of roots: THE ROOTS THE COUNT COUNTS.
514
- *
515
- * Membership, not position, and not kind. Every visible collapsed root belongs unless the
516
- * agent pinned it or the fresh tail covers it, and an EXPANDED fold is not a placeholder
517
- * at all. One predicate answers all of it, because that is the same question
518
- * `refsProtected` already asks of a span's evidence.
519
- *
520
- * A consolidation counts here like anything else. A consolidated fold is not a different
521
- * species of span, and depth is the point: ten visible parents make a grandparent, and
522
- * under gradual accrual the stalest parent is simply the stalest member of the next group
523
- * of ten, so the oldest context sits a few layers down after a long session. The only
524
- * folds outside the count are the held ones, because a counted fold must be groupable and
525
- * a held one is exactly the fold that cannot be (Shane 2026-08-10). A fold already nested
526
- * inside a parent is not a root and is never counted separately.
527
- */
528
573
  export function unpinnedStaleFolds(
529
574
  snapshot: ActiveContextSnapshot,
530
575
  state: ActiveContextState,
@@ -533,7 +578,6 @@ export function unpinnedStaleFolds(
533
578
  .filter(({ fold }) => !refsProtected(flattenFoldRefs(fold, state), state, snapshot));
534
579
  }
535
580
 
536
- /** Everything a pending mark already speaks for: whole folds by id, raw evidence by key. */
537
581
  export function pendingMarkClaims(
538
582
  state: ActiveContextState,
539
583
  ): { foldIds: Set<string>; refKeys: Set<string> } {
@@ -552,7 +596,6 @@ export function pendingMarkClaims(
552
596
  return { foldIds, refKeys };
553
597
  }
554
598
 
555
- /** Whether the raw material between two roots is material a parent may swallow. */
556
599
  function absorbableGap(
557
600
  snapshot: ActiveContextSnapshot,
558
601
  state: ActiveContextState,
@@ -561,9 +604,6 @@ function absorbableGap(
561
604
  claims: { refKeys: Set<string> },
562
605
  ): boolean {
563
606
  if (from > to) return true;
564
- // No fold kind is admitted, so any root sitting in the gap -- a parent the count
565
- // already made, a fold the agent expanded, a fold the agent pinned -- refuses the
566
- // range outright and splits the run there.
567
607
  const parts = partsForRange(snapshot, state, from, to, NO_FOLD_KINDS);
568
608
  if (!parts) return false;
569
609
  const refs = parts.flatMap((part) => part.kind === "raw" ? [part.ref] : []);
@@ -571,7 +611,6 @@ function absorbableGap(
571
611
  return !refs.some((ref) => claims.refKeys.has(objectRefKey(ref)));
572
612
  }
573
613
 
574
- /** Every tool call in the window, by id, with the assistant and the result that close it. */
575
614
  function windowToolLinkage(snapshot: ActiveContextSnapshot): Map<string, { call: number; result: number }> {
576
615
  const linkage = new Map<string, { call: number; result: number }>();
577
616
  const at = (id: string): { call: number; result: number } => {
@@ -598,15 +637,6 @@ function windowToolLinkage(snapshot: ActiveContextSnapshot): Map<string, { call:
598
637
  return linkage;
599
638
  }
600
639
 
601
- /**
602
- * The parent's span, widened until hiding it keeps every tool call with its result.
603
- *
604
- * A collapsed parent renders as ONE entry, so a span that opens on a tool-result fold
605
- * would hide the result while its call stayed visible, and the projection guard rejects
606
- * that split outright. The widening is the same raw absorption the law already grants
607
- * between children, applied at the edges: it takes the calling assistant in, never a
608
- * fold, and if the material it needs is not absorbable the group does not form.
609
- */
610
640
  function linkageClosedSpan(
611
641
  snapshot: ActiveContextSnapshot,
612
642
  start: number,
@@ -630,7 +660,6 @@ function linkageClosedSpan(
630
660
  return { start: from, end: to };
631
661
  }
632
662
 
633
- /** One group of exactly `consolidateAfter` roots, read as the parent it must become. */
634
663
  function consolidationCandidate(
635
664
  snapshot: ActiveContextSnapshot,
636
665
  state: ActiveContextState,
@@ -647,34 +676,10 @@ function consolidationCandidate(
647
676
  if (refsProtected(raw, state, snapshot)) return null;
648
677
  if (raw.some((ref) => claims.refKeys.has(objectRefKey(ref)))) return null;
649
678
  const sourceRefs = candidateSourceRefs(parts, state);
650
- // THE ONE PHYSICAL BOUND, HANDLED LOUDLY. A record carries at most
651
- // `maxFoldSourceRefs` exact references. A group over that bound is left UNFORMED
652
- // rather than quietly shrunk: shrinking would make the parent a function of the
653
- // record format instead of the count, and the law is the count.
654
679
  if (sourceRefs.length > snapshot.policy.maxFoldSourceRefs) return null;
655
680
  return { kind: "consolidation", parts, sourceRefs };
656
681
  }
657
682
 
658
- /**
659
- * CONSOLIDATION IS A PURE FUNCTION OF THE COUNT (Shane 2026-08-10).
660
- *
661
- * Not a crossing event, not a paced rung, and not one candidate per pass. Count the
662
- * visible unheld roots of every kind; each `consolidateAfter` consecutive ones, stalest
663
- * first, MUST be under a parent, and the remainder is left alone. From a count of n that
664
- * is `n / consolidateAfter` parents, and all of them form in the epoch that notices,
665
- * because a rule that formed one per commit would leave a count the rule says is
666
- * impossible standing for as many commits as it takes to drain.
667
- *
668
- * A parent it made is a root like any other, so the epoch applies the rule to a FIXPOINT:
669
- * ten parents are a group, and the grandparent that follows is how the oldest context
670
- * ends up several layers down after a long session.
671
- *
672
- * The run is where the count is taken. A hold -- a pin, the fresh tail, an expanded fold,
673
- * evidence a pending mark spoke for -- cannot be swallowed, so it splits the run and each
674
- * side counts on its own; a run shorter than the width waits as remainder. Gaps of raw
675
- * context between children fall into the parent, which is what makes the span one
676
- * contiguous range instead of a list of islands.
677
- */
678
683
  export function selectAutomaticConsolidations(
679
684
  snapshot: ActiveContextSnapshot,
680
685
  state: ActiveContextState,
@@ -702,30 +707,12 @@ export function selectAutomaticConsolidations(
702
707
  return groups;
703
708
  }
704
709
 
705
- /** One completed batch the automatic law may take, with the indices it covers. */
706
710
  export interface AutomaticToolBatch {
707
711
  refs: EvidenceRef[];
708
712
  indices: number[];
709
713
  bytes: number;
710
714
  }
711
715
 
712
- /**
713
- * MEMBERSHIP, WRITTEN ONCE: every completed tool batch the automatic law may take,
714
- * stalest first, whether or not anything has claimed it yet.
715
- *
716
- * A batch is a member when it is complete and validated, its tool is not blacklisted
717
- * (`resultCall` runs the batch scan with the deployment's list), its evidence carries
718
- * exact refs in branch order, it is inside the record's reference bound, it is large
719
- * enough to be worth a placeholder, and neither an agent pin nor the fresh byte tail
720
- * covers any part of it. There is no position in that list. A prefix of the window is
721
- * not more foldable than the middle of it, and the middle is not a wall: it is whatever
722
- * the ladder has not needed yet plus whatever the agent pinned (Shane 2026-08-10).
723
- *
724
- * Ownership is deliberately NOT membership. A batch some fold or pending mark already
725
- * speaks for is still a member of the class; it is simply taken. The selector filters on
726
- * ownership because it must propose something new, and the announcement counts the class
727
- * because that is the mass a commit can reach. One definition, two readings of it.
728
- */
729
716
  export function automaticToolBatches(
730
717
  snapshot: ActiveContextSnapshot,
731
718
  state: ActiveContextState,
@@ -756,11 +743,6 @@ export function automaticToolBatches(
756
743
  return batches;
757
744
  }
758
745
 
759
- /**
760
- * The stalest member batch nothing has claimed. `claimed` lets a caller exclude evidence
761
- * already spoken for by something that is not yet a fold (a pending mark), so repeated
762
- * calls walk forward instead of returning the same batch.
763
- */
764
746
  export function selectAutomaticToolBatch(
765
747
  snapshot: ActiveContextSnapshot,
766
748
  state: ActiveContextState,
@@ -779,14 +761,6 @@ export function selectAutomaticToolBatch(
779
761
  return [];
780
762
  }
781
763
 
782
- /**
783
- * The re-fold path.
784
- *
785
- * An expansion is a deliberate act, and the way to make it permanent is a pin: a pinned
786
- * fold is never re-folded, and a leased one is held for the term of its lease. Anything
787
- * else the agent opened is an ordinary member of the foldable class again, so automation
788
- * may return it to its placeholder, stalest and narrowest first.
789
- */
790
764
  export function selectAutomaticRefold(
791
765
  snapshot: ActiveContextSnapshot,
792
766
  state: ActiveContextState,
@@ -835,27 +809,12 @@ export function chapterRangeIsUnitAligned(snapshot: ActiveContextSnapshot, start
835
809
  return units.every((unit, index) => index === 0 || unit.start === units[index - 1].end);
836
810
  }
837
811
 
838
- /**
839
- * Bite-sized folds.
840
- *
841
- * A fold is only navigable if reading one back is cheap. Measured 2026-08-06 (rep 6):
842
- * one 60,432-byte chapter fold hid the fact the run needed in its tail, and every peek
843
- * of it was either truncated short of the answer or too expensive to widen. So a span
844
- * whose content exceeds the cap becomes SEQUENTIAL folds, each with its own brief,
845
- * split only at structurally closed unit boundaries -- never mid-entry.
846
- *
847
- * A single unit that alone exceeds the cap is returned whole: there is no valid
848
- * interior boundary, and refusing to fold it would leave the biggest span in the
849
- * window as the one thing nothing may reclaim. The caller reports the split.
850
- */
851
812
  export function splitCandidateBySize(
852
813
  snapshot: ActiveContextSnapshot,
853
814
  state: ActiveContextState,
854
815
  candidate: FoldCandidate,
855
816
  maxChars: number = MAX_FOLD_SPAN_CHARS,
856
817
  ): FoldCandidate[] {
857
- // A tool-result fold owns exactly one validated assistant batch and a consolidation
858
- // owns whole child folds; neither has an interior boundary to split on.
859
818
  if (candidate.kind !== "chapter") return [candidate];
860
819
  const refs = candidate.sourceRefs.length ? candidate.sourceRefs : candidateSourceRefs(candidate.parts, state);
861
820
  const indices = refs.map((ref) => exactMapped(snapshot, ref)?.index ?? -1);
@@ -887,7 +846,6 @@ export function splitCandidateBySize(
887
846
  return split.length ? split : [candidate];
888
847
  }
889
848
 
890
- /** Exact serialized size of a candidate's whole span, folds and raw entries alike. */
891
849
  export function candidateSpanChars(
892
850
  snapshot: ActiveContextSnapshot,
893
851
  state: ActiveContextState,
@@ -899,12 +857,10 @@ export function candidateSpanChars(
899
857
  return spanBytes(snapshot, Math.min(...indices), Math.max(...indices) + 1);
900
858
  }
901
859
 
902
- /** Exact serialized size of a half-open mapped message range. */
903
860
  export function spanBytes(snapshot: ActiveContextSnapshot, start: number, end: number): number {
904
861
  return bytes(snapshot.messages.slice(start, end));
905
862
  }
906
863
 
907
- /** One auto-snap the runtime applied to a requested span, reported never inferred. */
908
864
  export interface SpanCorrection {
909
865
  from: string[];
910
866
  to: string[];
@@ -916,15 +872,6 @@ export interface SnappedFoldSpan {
916
872
  corrections: SpanCorrection[];
917
873
  }
918
874
 
919
- /**
920
- * Resolve a requested span, correcting it where a correction exists.
921
- *
922
- * A span the agent got slightly wrong -- crossing a fold it forgot about, reaching
923
- * into the turn still in flight, landing mid-unit -- is a span it MEANT, and refusing
924
- * it teaches the agent that curating is a coin flip. So an invalid span SNAPS to the
925
- * nearest valid edge and the correction is stated in the result. Rejection is reserved
926
- * for a request with no valid interpretation at all, and it says exactly what failed.
927
- */
928
875
  export function snapFoldCandidate(
929
876
  snapshot: ActiveContextSnapshot,
930
877
  state: ActiveContextState,
@@ -937,11 +884,6 @@ export function snapFoldCandidate(
937
884
  } catch (error) {
938
885
  directError = error instanceof Error ? error : new Error(String(error));
939
886
  }
940
- // Nearest first, then the other readings of the same intent. A span that cuts into one
941
- // fold has exactly two endpoint corrections -- absorb it, or step past it -- and which
942
- // one survives depends on structure the caller cannot see. A whole fold left inside the
943
- // corrected span is no longer a reason to fail: the span nests it. What still fails is
944
- // a span with no valid frame at all, and a pin the span would swallow.
945
887
  const alternatives = snapSpanAlternatives(snapshot, state, ids);
946
888
  let lastError: Error = directError;
947
889
  for (const snapped of alternatives) {
@@ -954,23 +896,12 @@ export function snapFoldCandidate(
954
896
  lastError = error instanceof Error ? error : new Error(String(error));
955
897
  }
956
898
  }
957
- // A suggestion the caller cannot act on is worse than no suggestion: never name a
958
- // "nearest valid span" this same validation just refused. Nothing snapped, so the
959
- // refusal is the direct one, stated once.
960
899
  throw new Error(
961
900
  `${directError.message}. No corrected reading of that span is constructible`,
962
901
  { cause: lastError },
963
902
  );
964
903
  }
965
904
 
966
- /**
967
- * Every constructible reading of a requested span, nearest intent first.
968
- *
969
- * The three endpoint modes move the EDGES; the fourth reading moves the FRAME, reading
970
- * the span as the whole folds it cuts into. The frame reading survives the nesting law
971
- * because it is still the faithful one where the endpoints cannot snap to closed units:
972
- * the folds themselves already tile the range exactly.
973
- */
974
905
  export function snapSpanAlternatives(
975
906
  snapshot: ActiveContextSnapshot,
976
907
  state: ActiveContextState,
@@ -991,13 +922,6 @@ export function snapSpanAlternatives(
991
922
  });
992
923
  }
993
924
 
994
- /**
995
- * The requested span read as the whole folds it cuts into.
996
- *
997
- * Constructible only when the outward snap lands on a contiguous tiling of two or more
998
- * foldable roots: then the span the agent named IS those folds, and consolidating them
999
- * is the one reading that both covers what was asked for and validates.
1000
- */
1001
925
  export function snapSpanToWholeFolds(
1002
926
  snapshot: ActiveContextSnapshot,
1003
927
  state: ActiveContextState,
@@ -1023,19 +947,13 @@ export function snapSpanToWholeFolds(
1023
947
  };
1024
948
  }
1025
949
 
1026
- /**
1027
- * The correction itself: pull the endpoints out of any fold they cut through, out of
1028
- * the turn still in flight, and onto structurally closed unit boundaries.
1029
- */
1030
950
  export function snapSpanIds(
1031
951
  snapshot: ActiveContextSnapshot,
1032
952
  state: ActiveContextState,
1033
953
  ids: string[],
1034
- /** How an endpoint inside a fold is read: by distance, or always one way. */
1035
954
  mode: "nearest" | "absorb" | "exclude" = "nearest",
1036
955
  ): { ids: string[]; corrections: SpanCorrection[] } | null {
1037
956
  let resolved: Array<{ start: number; end: number; fold?: ActiveFold }>;
1038
- // An unknown id has no nearest valid edge: there is nothing to snap TO.
1039
957
  try { resolved = resolveFoldInputIds(snapshot, state, ids); }
1040
958
  catch { return null; }
1041
959
  const requested = [resolved[0].start, resolved.at(-1)!.end] as [number, number];
@@ -1049,11 +967,6 @@ export function snapSpanIds(
1049
967
  });
1050
968
  };
1051
969
  const units = chapterUnits(snapshot);
1052
- // Each correction can undo another: pulling an endpoint onto a unit boundary can put
1053
- // it back inside a fold, and clamping the width can cut one in half. So the passes run
1054
- // to a FIXED POINT rather than once each. After the first pass an endpoint inside a
1055
- // fold always absorbs it, which is outward and therefore terminating: stepping past a
1056
- // fold here would drop material the agent named, and nesting no longer loses anything.
1057
970
  for (let pass = 0; pass < state.folds.length + 2; pass += 1) {
1058
971
  const passStart = start;
1059
972
  const passEnd = end;
@@ -1088,9 +1001,6 @@ export function snapSpanIds(
1088
1001
  end = endUnit.end - 1;
1089
1002
  note("span ended mid-unit; corrected to the end of its closed user/assistant/tool unit");
1090
1003
  }
1091
- // A chapter spans at most a few closed turns. A longer request is a span the agent
1092
- // meant, so it is CLAMPED to what one fold may hold rather than refused; the
1093
- // remainder stays raw and is the agent's to fold next.
1094
1004
  const covered = units.filter((unit) => unit.start >= start && unit.end <= end + 1);
1095
1005
  if (covered.length) {
1096
1006
  const turns: number[] = [];
@@ -1115,14 +1025,6 @@ export function snapSpanIds(
1115
1025
  return { ids: [entryIdAt(snapshot, start), entryIdAt(snapshot, end)], corrections };
1116
1026
  }
1117
1027
 
1118
- /**
1119
- * Snap a span OUTWARD to whole-fold boundaries.
1120
- *
1121
- * The re-boundary verb operates on folds, so a span that cuts one in half has exactly
1122
- * one sane reading: the agent meant that fold. Outward rather than nearest, because
1123
- * losing a fold the agent did not name is the surprising outcome and re-cutting a
1124
- * slightly larger span is not.
1125
- */
1126
1028
  export function snapToFoldBoundaries(
1127
1029
  snapshot: ActiveContextSnapshot,
1128
1030
  state: ActiveContextState,
@@ -1132,7 +1034,6 @@ export function snapToFoldBoundaries(
1132
1034
  const requested: [number, number] = [resolved[0].start, resolved.at(-1)!.end];
1133
1035
  let [start, end] = requested;
1134
1036
  const corrections: SpanCorrection[] = [];
1135
- // Fixed point: absorbing one fold can pull the span across the next.
1136
1037
  for (let pass = 0; pass < state.folds.length + 1; pass += 1) {
1137
1038
  let moved = false;
1138
1039
  for (const root of orderedRoots(state, snapshot)) {
@@ -1163,39 +1064,22 @@ export function manualFoldCandidate(
1163
1064
  snapshot: ActiveContextSnapshot,
1164
1065
  state: ActiveContextState,
1165
1066
  ids: string[],
1166
- /**
1167
- * Accept a span that is still fresh or protected. Only a caller that will DEFER the
1168
- * fold may pass this: it relaxes eligibility, never structure, so contiguity, unit
1169
- * alignment and batch completeness are still enforced and the commit still refuses
1170
- * to apply a mark whose span is protected at commit time.
1171
- */
1172
1067
  options: { allowProtected?: boolean } = {},
1173
1068
  ): FoldCandidate {
1174
1069
  const blockedTool = (refs: EvidenceRef[]): boolean =>
1175
1070
  !options.allowProtected && toolRefsProtected(refs, state, snapshot);
1176
1071
  const blocked = (refs: EvidenceRef[]): boolean =>
1177
1072
  !options.allowProtected && refsProtected(refs, state, snapshot);
1178
- // The forest as it stands, read once. Building it here can only throw when the STORED
1179
- // forest is already corrupt, which is the loud case and stays loud; every collision
1180
- // between this request and a fold that exists is answered below by name.
1181
1073
  const owners = directFoldOwners(state.folds);
1182
1074
  const bounded = (candidate: FoldCandidate): FoldCandidate => {
1183
1075
  if (candidate.sourceRefs.length > snapshot.policy.maxFoldSourceRefs) {
1184
1076
  throw new Error(`Folds may include at most ${snapshot.policy.maxFoldSourceRefs} exact source references`);
1185
1077
  }
1186
- // Every reading of a requested span leaves through here, so one check covers the
1187
- // tool-batch, consolidation and chapter readings alike. It runs before anything is
1188
- // prepared or persisted: no brief is generated, no state is touched, and the caller
1189
- // gets a span it can act on instead of an invariant it cannot.
1190
1078
  for (const part of candidate.parts) {
1191
1079
  if (part.kind !== "raw") continue;
1192
1080
  const ownerId = owners.get(objectRefKey(part.ref));
1193
1081
  if (ownerId) throw new Error(foldedSpanRefusal(ownerId, part.ref.entryId, snapshot.toolName));
1194
1082
  }
1195
- // A reading that is exactly one fold that already exists is not a fold to make. It
1196
- // wraps a placeholder in a placeholder, reclaims nothing, and buries the fold the
1197
- // caller was pointing at one level deeper, so it is the same refusal: the span is
1198
- // already folded, and here is what opens it.
1199
1083
  const only = candidate.parts.length === 1 ? candidate.parts[0] : null;
1200
1084
  if (only?.kind === "fold") {
1201
1085
  throw new Error(foldedSpanRefusal(only.foldId, "that span", snapshot.toolName));
@@ -1236,10 +1120,6 @@ export function manualFoldCandidate(
1236
1120
  }
1237
1121
  const parts = partsForRange(snapshot, state, start, end, ALL_FOLD_KINDS);
1238
1122
  if (!parts) {
1239
- // Two shapes reach here, and they are different asks. A span INSIDE one fold is
1240
- // material that is already folded, so there is no fold to make and the useful answer
1241
- // names the fold holding it. A span that merely cuts one is a boundary the snap
1242
- // corrects, and it says which fold moved the edge.
1243
1123
  const cut = orderedRoots(state, snapshot).filter((root) => root.start <= end && start <= root.end);
1244
1124
  const container = cut.find((root) => root.start <= start && root.end >= end);
1245
1125
  throw new Error(container
@@ -1254,12 +1134,7 @@ export function manualFoldCandidate(
1254
1134
  }
1255
1135
 
1256
1136
  export function oneLine(value: string, maximum: number): string {
1257
- const text = value.replace(/\s+/g, " ").trim();
1258
- if (text.length <= maximum) return text;
1259
- let bounded = text.slice(0, maximum).trimEnd();
1260
- const finalCode = bounded.charCodeAt(bounded.length - 1);
1261
- if (finalCode >= 0xd800 && finalCode <= 0xdbff) bounded = bounded.slice(0, -1);
1262
- return bounded;
1137
+ return boundedSubject(value.replace(/\s+/g, " ").trim(), maximum);
1263
1138
  }
1264
1139
 
1265
1140
  export function deterministicChapterBrief(
@@ -1271,8 +1146,6 @@ export function deterministicChapterBrief(
1271
1146
  throw new Error("Deterministic chapter brief requires aligned exact evidence");
1272
1147
  }
1273
1148
  const firstUser = messages.find((message) => messageRole(message) === "user");
1274
- const firstAssistant = messages.find((message) =>
1275
- messageRole(message) === "assistant" && contentText(message).trim());
1276
1149
  const toolCounts = new Map<string, number>();
1277
1150
  for (const message of messages) {
1278
1151
  if (messageRole(message) !== "assistant") continue;
@@ -1282,18 +1155,39 @@ export function deterministicChapterBrief(
1282
1155
  if (typeof name === "string" && name) toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1);
1283
1156
  }
1284
1157
  }
1158
+ // EVERY assistant note in the span, not the first line of the first one
1159
+ // (Shane 2026-08-14). A chapter is the first fold that hides its span, and a
1160
+ // value the agent recorded once on a later turn died with the old single
1161
+ // line: sol-20260814-traps rep 2 lost trace-a-02 and trace-d-05 to exactly
1162
+ // that shape, an assistant message between two tool batches whose words
1163
+ // reached zero briefs. Each note is the message's leading paragraph, capped
1164
+ // per note, seated whole in span order until the brief's own bound refuses
1165
+ // the next one, with the count of what did not seat stated rather than cut.
1166
+ const notes = messages.flatMap((message) => {
1167
+ if (messageRole(message) !== "assistant") return [];
1168
+ const note = assistantNoteText(message);
1169
+ return note ? [boundedSubject(note, CHAPTER_NOTE_CHARS)] : [];
1170
+ });
1285
1171
  const ask = oneLine(firstUser ? contentText(firstUser) : "No user ask in this span", 90);
1286
- const assistant = oneLine(firstAssistant ? contentText(firstAssistant).split(/\r?\n/)[0] :
1287
- "No assistant text in this span", 110);
1288
1172
  const tools = oneLine([...toolCounts.entries()]
1289
1173
  .sort(([left], [right]) => left.localeCompare(right))
1290
1174
  .map(([name, count]) => `${count}×${name}`)
1291
1175
  .join(" ") || "no tools", 500);
1176
+ const separator = " | ";
1177
+ const compose = (kept: number): string => {
1178
+ const omitted = notes.length - kept;
1179
+ const shown = kept ? notes.slice(0, kept).join(separator)
1180
+ : (notes.length ? "" : "No assistant text in this span");
1181
+ const tail = omitted ? `${kept ? separator : ""}${omitted} more notes in this span` : "";
1182
+ return `User: ${ask} · Tools: ${tools} · Assistant: ${shown}${tail}`;
1183
+ };
1184
+ let composed = compose(notes.length);
1185
+ for (let kept = notes.length; kept > 0 && composed.length > ACTIVE_CONTEXT_POLICY.maxBriefChars; kept -= 1) {
1186
+ composed = compose(kept - 1);
1187
+ }
1292
1188
  const escapeName = (name: string) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1293
- const composed = `User: ${ask} · Tools: ${tools} · Assistant: ${assistant}`
1294
- .replace(new RegExp(escapeName(toolName), "gi"), "active-context service");
1189
+ composed = composed.replace(new RegExp(escapeName(toolName), "gi"), "active-context service");
1295
1190
  if (usefulBrief(composed, ACTIVE_CONTEXT_POLICY.maxBriefChars, toolName)) return composed;
1296
- // Constant floor-of-the-floor: provably passes usefulBrief (no tool name, no structural pattern).
1297
1191
  return `Folded ${refs.length} exact messages from this span's complete turns.`;
1298
1192
  }
1299
1193