pi-fold 3.0.0 → 3.1.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.
@@ -6,10 +6,12 @@ import {
6
6
  stableStringify,
7
7
  } from "./json.ts";
8
8
  import {
9
+ contentText,
9
10
  bytes,
10
11
  clone,
11
12
  emptyActiveContextState,
12
13
  ownValue,
14
+ serializedPrefixHolds,
13
15
  sessionEntryMessages,
14
16
  uniqueMessageDigestAnchor,
15
17
  } from "./lib/canonical.ts";
@@ -70,6 +72,7 @@ import {
70
72
  deriveFoldParents,
71
73
  flattenFoldRefs,
72
74
  foldIdFor,
75
+ foldRecordRef,
73
76
  makeFoldRecordEntry,
74
77
  makeStateCheckpoint,
75
78
  makeStateDelta,
@@ -104,6 +107,7 @@ import {
104
107
  ESTIMATED_BYTES_PER_TOKEN,
105
108
  entryTypeNamespace,
106
109
  MAX_FOLD_SPAN_CHARS,
110
+ DEFAULT_TOOL_FOLD_THRESHOLD,
107
111
  MAX_PINNED_SHARE,
108
112
  PEEK_DEFAULT_MAX_BYTES,
109
113
  peekIsEphemeral,
@@ -112,17 +116,22 @@ import {
112
116
  RETIRED_TOOL_ACTIONS,
113
117
  AUTO_FOLD_BLACKLIST_DEFAULT,
114
118
  USER_RESCUE_MAX_SOURCE_CHARS,
119
+ MEMORY_BODY_CHARS,
120
+ MEMORY_KEY_CHARS,
121
+ MEMORY_KEYS_MAX,
122
+ WORKING_MEMORY_TOOL_ACTIONS,
115
123
  } from "./lib/policy.ts";
116
124
  import type {
117
125
  ActiveContextSnapshot,
118
126
  ActiveContextState,
119
127
  ActiveContextThresholds,
120
128
  ActiveContextToolAction,
129
+ BriefProvenance,
121
130
  FoldCandidate,
122
131
  FoldKind,
123
132
  FoldRecordEntry,
124
- MarkOrigin,
125
133
  PreparedFold,
134
+ WorkingMemoryEntry,
126
135
  } from "./lib/policy.ts";
127
136
  import {
128
137
  absorbWedgeMarks,
@@ -131,6 +140,7 @@ import {
131
140
  import {
132
141
  contextReceipt,
133
142
  foldNoticeText,
143
+ memoryTocText,
134
144
  receiptBlockText,
135
145
  withReceipt,
136
146
  } from "./lib/curation.ts";
@@ -142,6 +152,7 @@ import {
142
152
  commitCoverage,
143
153
  windowClaims,
144
154
  commitPendingMarks,
155
+ toolClipAdditions,
145
156
  consolidationMarks,
146
157
  ephemeralPeekMarks,
147
158
  epochCommitDue,
@@ -149,6 +160,7 @@ import {
149
160
  foldMarkFor,
150
161
  ladderBrief,
151
162
  markAccounting,
163
+ markRewriteTokens,
152
164
  markFreedBytes,
153
165
  unmarkedRemainder,
154
166
  markClaimingRef,
@@ -211,38 +223,6 @@ export * from "./lib/scheduling.ts";
211
223
  export * from "./lib/selection.ts";
212
224
  export * from "./lib/transcript.ts";
213
225
 
214
- export interface BatchedMarkRequest {
215
- ids: string[];
216
- brief?: string;
217
- }
218
-
219
- export function batchedMarkRequests(params: Record<string, unknown>): BatchedMarkRequest[] {
220
- const batched = params.marks;
221
- if (batched === undefined) {
222
- return [{
223
- ids: stringIds(params.ids),
224
- ...(typeof params.brief === "string" && params.brief.trim() ? { brief: params.brief } : {}),
225
- }];
226
- }
227
- const values = denseOwnArrayValues(batched);
228
- if (!values || !values.length || values.length > 64) {
229
- throw new Error("marks must be a dense array of 1-64 {ids, brief} objects");
230
- }
231
- return values.map((value, index) => {
232
- if (!value || typeof value !== "object" || Array.isArray(value)) {
233
- throw new Error(`marks[${index}] must be an object with ids and an optional brief`);
234
- }
235
- const brief = ownValue(value, "brief");
236
- if (brief !== undefined && (typeof brief !== "string" || !brief.trim())) {
237
- throw new Error(`marks[${index}].brief must be a nonempty string`);
238
- }
239
- return {
240
- ids: stringIds(ownValue(value, "ids")),
241
- ...(typeof brief === "string" && brief.trim() ? { brief } : {}),
242
- };
243
- });
244
- }
245
-
246
226
  export function registerActiveContext(pi: any, options: {
247
227
  toolName?: string;
248
228
  toolLabel?: string;
@@ -252,6 +232,28 @@ export function registerActiveContext(pi: any, options: {
252
232
  blacklistAutoFoldTools?: ReadonlySet<string>;
253
233
  providerInputBudget?: number;
254
234
  thresholds?: ActiveContextThresholds;
235
+ /** THE TOOL-CALL DIET's share point (2026-08-24; default 0.50 since 2026-08-28, Shane,
236
+ * on the sol-20260826-full2 verdict): tool results in the oldest toolFoldThreshold
237
+ * share of the projected window are clipped IN VIEW at each commit, identified head
238
+ * kept, full bytes peek-recoverable behind the entry id the marker names. A share in
239
+ * [0, 1), where 0 turns the diet off; 0.50 is what every fold repetition from 3 onward
240
+ * ran, so the default is the value that was measured rather than the absence of one. */
241
+ toolFoldThreshold?: number;
242
+ /** THE INVITATION SWITCH (public since 2026-08-27; DEFAULT FALSE since 2026-08-28,
243
+ * Shane, on the sol-20260826-full2 verdict). false is the deterministic shape the
244
+ * campaign measured and the one it recommends: no carrier ever invites a brief and
245
+ * every fold goes out with the runtime's own words, which drew 14, 14, 9 and 8 correct
246
+ * against the invited condition's 3. true restores the standing invitation for the
247
+ * agent to improve a brief. Either way the agent verbs stay on the tool, so an agent
248
+ * that finds a fold can still annotate what it reads. */
249
+ postFoldNotice?: boolean;
250
+ /** THE WORKING MEMORY (Shane, 2026-08-26): a session-scoped ordered dictionary the
251
+ * agent maintains beside the fold index. `remember` writes or removes an entry,
252
+ * `recall` reads bodies on demand, and the projection carries one table of contents
253
+ * that refreshes at each commit. Off when absent: the digest channel earned its
254
+ * place in a measured gap (running tallies lost 0/6 against native's 6/6 while the
255
+ * index won everything else), and it must stay separable from the index it is not. */
256
+ workingMemory?: boolean;
255
257
  }): {
256
258
  projectionCandidates: (ctx: any) => Array<Record<string, unknown>>;
257
259
  } {
@@ -260,6 +262,24 @@ export function registerActiveContext(pi: any, options: {
260
262
  const brandNoun = options.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN;
261
263
  const entryTypePrefix = options.entryTypePrefix ?? DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX;
262
264
  const blacklistAutoFoldTools = options.blacklistAutoFoldTools ?? AUTO_FOLD_BLACKLIST_DEFAULT;
265
+ if (options.postFoldNotice !== undefined && typeof options.postFoldNotice !== "boolean") {
266
+ throw new Error("postFoldNotice must be a boolean: true keeps the post-fold invitation " +
267
+ "notice, false ships every fold with the runtime's deterministic brief and no " +
268
+ "carrier inviting the agent to improve it");
269
+ }
270
+ const postFoldNotice = options.postFoldNotice ?? false;
271
+ if (options.workingMemory !== undefined && typeof options.workingMemory !== "boolean") {
272
+ throw new Error("workingMemory must be a boolean: it enables the session-scoped " +
273
+ "working-memory dictionary and its two actions, remember and recall");
274
+ }
275
+ const workingMemory = options.workingMemory === true;
276
+ const toolFoldThreshold = options.toolFoldThreshold ?? DEFAULT_TOOL_FOLD_THRESHOLD;
277
+ if (typeof toolFoldThreshold !== "number" || !Number.isFinite(toolFoldThreshold) ||
278
+ toolFoldThreshold < 0 || toolFoldThreshold >= 1) {
279
+ throw new Error("toolFoldThreshold must be a share in [0, 1): it names the oldest " +
280
+ "fraction of the projected window whose tool results are clipped in view at each " +
281
+ "commit, and 0 turns the diet off");
282
+ }
263
283
  for (const removed of ["foldScheduling", "foldPeekResults", "toolActions"]) {
264
284
  if (Object.hasOwn(options, removed)) {
265
285
  throw new Error(`${removed} is no longer an option: epoch scheduling, peek foldability ` +
@@ -325,6 +345,7 @@ export function registerActiveContext(pi: any, options: {
325
345
  const milestoneProjectionType = `${entryTypePrefix}-milestone`;
326
346
  const advisoryProjectionType = `${entryTypePrefix}-advisory`;
327
347
  const foldNoticeProjectionType = `${entryTypePrefix}-fold-notice`;
348
+ const memoryTocProjectionType = `${entryTypePrefix}-memory-toc`;
328
349
  const receiptProjectionType = `${entryTypePrefix}-receipts`;
329
350
  const curationProjectionType = `${entryTypePrefix}-curation`;
330
351
  const entryNamespace = entryTypeNamespace(entryTypePrefix);
@@ -332,7 +353,9 @@ export function registerActiveContext(pi: any, options: {
332
353
  const nativeReceiptEntryType = `${entryNamespace}-native-compaction-receipt`;
333
354
  const contextEventEntryType = `${entryNamespace}-context-event`;
334
355
  const nativeDecisionEntryType = `${entryNamespace}-native-compaction-decision`;
335
- const allowedToolActions: readonly ActiveContextToolAction[] = ACTIVE_CONTEXT_TOOL_ACTIONS;
356
+ const allowedToolActions: readonly ActiveContextToolAction[] = workingMemory
357
+ ? [...ACTIVE_CONTEXT_TOOL_ACTIONS, ...WORKING_MEMORY_TOOL_ACTIONS]
358
+ : ACTIVE_CONTEXT_TOOL_ACTIONS;
336
359
  const allowedToolActionSet = new Set<string>(allowedToolActions);
337
360
 
338
361
  type AutomaticFailureState = {
@@ -573,10 +596,9 @@ export function registerActiveContext(pi: any, options: {
573
596
  };
574
597
 
575
598
  const PREFIX_MUTATING_KINDS: ReadonlySet<string> = new Set([
576
- "context.commit", "context.fold", "context.absorb", "context.split", "context.recovery",
599
+ "context.commit", "context.fold", "context.absorb", "context.recovery",
577
600
  ]);
578
601
  const CACHE_ACTION_REQUEST_CLASS: Readonly<Record<string, string>> = Object.freeze({
579
- fold: "after-mark",
580
602
  peek: "after-peek",
581
603
  expand: "after-expand",
582
604
  refold: "after-refold",
@@ -590,8 +612,6 @@ export function registerActiveContext(pi: any, options: {
590
612
  folds_committed: receipt.foldsCommitted,
591
613
  folds_created: receipt.foldsCreated,
592
614
  freed_tokens: receipt.freedTokens,
593
- split_folds: receipt.splitFolds,
594
- split_from_chars: receipt.splitFromChars,
595
615
  absorbed_wedges: receipt.absorbedWedges,
596
616
  recovered: receipt.recovered,
597
617
  protected_bytes: receipt.protectedBytes,
@@ -897,15 +917,28 @@ export function registerActiveContext(pi: any, options: {
897
917
  if (next.folds.length > MAX_ACTIVE_FOLD_RECORDS) {
898
918
  throw new Error("Active-context fold closure exceeds persistence limit");
899
919
  }
920
+ // AN ALREADY-DURABLE FOLD NEEDS A DIGEST, NOT A RECORD (2026-08-28). This built the
921
+ // whole entry for every fold on every persist just to compare one hash and continue:
922
+ // `makeFoldRecordEntry` canonicalizes, hashes, then `parseFoldRecordEntry` re-validates,
923
+ // clones the fold again, recomputes `foldIdFor`, hashes a second time and stringifies
924
+ // for the wire cap. Replaying a real sealed session, 98.2 percent of 4,947 calls hit
925
+ // the `continue`, and 126 of 135 persists appended nothing while rebuilding all of it.
926
+ // `foldRecordRef` is the same digest by construction (`makeFoldRecordEntry` stores
927
+ // `canonicalFoldRecord`, and `stateFromFoldRefs` already matches records to refs on
928
+ // this equality at persistence.ts:809); gate 152 pins it. Equal digests mean
929
+ // byte-identical canonical bytes to a record that already passed
930
+ // `parseFoldRecordEntry`, so skipping the rebuild cannot reach a different verdict.
931
+ // Measured 85.0ms to 19.6ms at 400 folds.
900
932
  for (const fold of next.folds) {
901
- const record = makeFoldRecordEntry(fold, sessionId);
902
- const existing = persistence.persistedFoldRecords.get(record.foldId);
933
+ const ref = foldRecordRef(fold);
934
+ const existing = persistence.persistedFoldRecords.get(ref.id);
903
935
  if (existing) {
904
- if (existing.recordSha256 !== record.recordSha256) {
905
- throw new Error(`Conflicting durable active-context fold ${record.foldId}`);
936
+ if (existing.recordSha256 !== ref.sha256) {
937
+ throw new Error(`Conflicting durable active-context fold ${ref.id}`);
906
938
  }
907
939
  continue;
908
940
  }
941
+ const record = makeFoldRecordEntry(fold, sessionId);
909
942
  await pi.appendEntry(foldRecordEntryType, record);
910
943
  persistence.persistedFoldRecords.set(record.foldId, record);
911
944
  if (!sessionIdentityStillValid(ctx, sessionId, generationAtStart)) {
@@ -914,13 +947,20 @@ export function registerActiveContext(pi: any, options: {
914
947
  }
915
948
  }
916
949
  const wire = persistence.persistedWireVersion === 2 ? makeStateDelta(persistence.persisted, next) : makeStateCheckpoint(next);
917
- if (persistence.persistedWireVersion === 2 && persistence.persistedStateSha256 !== semanticStateSha256(persistence.persisted)) {
950
+ // THE WIRE CARRIES BOTH DIGESTS IT JUST DERIVED (2026-08-28). `makeStateDelta` writes
951
+ // `baseStateSha256` from `persistence.persisted` and `stateSha256` from `next`, and
952
+ // nothing mutates either state between that call and here, so re-hashing two whole
953
+ // states was pure repetition at about 7ms per durable write. The drift check keeps its
954
+ // teeth: `baseStateSha256` is still derived from `persistence.persisted`, so a
955
+ // persisted digest that has drifted from the state it claims to describe still raises.
956
+ // Gate 152 pins both equalities.
957
+ if (wire.kind === "delta" && persistence.persistedStateSha256 !== wire.baseStateSha256) {
918
958
  throw new Error("Active-context durable base digest drift");
919
959
  }
920
960
  await pi.appendEntry(stateEntryType, wire);
921
961
  persistence.persisted = clone(next);
922
962
  persistence.persistedWireVersion = 2;
923
- persistence.persistedStateSha256 = semanticStateSha256(next);
963
+ persistence.persistedStateSha256 = wire.stateSha256;
924
964
  persistence.state = lifecycle.shuttingDown ? null : clone(next);
925
965
  if (!sessionIdentityStillValid(ctx, sessionId, generationAtStart)) {
926
966
  if (ctx && !contextSessionMatches(ctx, sessionId)) load(ctx);
@@ -1148,7 +1188,6 @@ export function registerActiveContext(pi: any, options: {
1148
1188
 
1149
1189
  const persistManual = async (
1150
1190
  next: ActiveContextState,
1151
- action: Exclude<ActiveContextToolAction, "status">,
1152
1191
  ctx: any,
1153
1192
  ): Promise<void> => {
1154
1193
  const stateAtEntry = persistence.state ? clone(persistence.state) : null;
@@ -1393,25 +1432,36 @@ export function registerActiveContext(pi: any, options: {
1393
1432
  };
1394
1433
  };
1395
1434
 
1396
- const projectedTokenReading = (projected: unknown[]): {
1435
+ // THE SERIALIZATION TRAVELS, THE READING DOES NOT (2026-08-28). Six whole-window
1436
+ // serializations rode every provider request and four were the same string from the same
1437
+ // unmutated array. Only the TEXT is threaded, never a finished reading: each site still
1438
+ // re-reads `measurements.projectionAnchor` and `projectionCharsPerToken()` at its own
1439
+ // time, so every emitted number stays what it was. That matters on the commit path, where
1440
+ // `enforceProjectionBudget` awaits `attemptAutomaticCommit` and an `attributionChanged`
1441
+ // can null the anchor between the last measurement and the read: threading a reading would
1442
+ // freeze `basis` at "measured" where today it flips to "unmeasured". Roughly 50ms per
1443
+ // request at a 467KB window, 100ms at 1MB, all of it blocking main-loop CPU. Honest
1444
+ // framing: six become three, not one.
1445
+ const projectedTokenReading = (projected: unknown[], serialized?: string): {
1397
1446
  tokens: number;
1398
1447
  basis: ProjectionReadingBasis;
1399
1448
  chars: number;
1400
1449
  anchorTokens: number | null;
1401
1450
  deltaChars: number | null;
1451
+ text: string;
1402
1452
  } => {
1403
- const text = stableStringify(projected);
1453
+ const text = serialized ?? stableStringify(projected);
1404
1454
  const chars = Buffer.byteLength(text, "utf8");
1405
1455
  const charsPerToken = projectionCharsPerToken();
1406
1456
  const estimate = Math.ceil(chars / charsPerToken);
1407
1457
  const anchor = measurements.projectionAnchor;
1408
1458
  if (!anchor || anchor.generation !== lifecycle.generation ||
1409
1459
  anchor.sessionId !== persistence.state?.sessionId) {
1410
- return { tokens: estimate, basis: "unmeasured", chars, anchorTokens: null, deltaChars: null };
1460
+ return { tokens: estimate, basis: "unmeasured", chars, anchorTokens: null, deltaChars: null, text };
1411
1461
  }
1412
1462
  const separator = text.length > anchor.head.length ? text[anchor.head.length] : "";
1413
1463
  if (!text.startsWith(anchor.head) || (separator !== "," && separator !== "]")) {
1414
- return { tokens: estimate, basis: "rewritten", chars, anchorTokens: anchor.tokens, deltaChars: null };
1464
+ return { tokens: estimate, basis: "rewritten", chars, anchorTokens: anchor.tokens, deltaChars: null, text };
1415
1465
  }
1416
1466
  const deltaChars = chars - anchor.chars;
1417
1467
  return {
@@ -1420,6 +1470,7 @@ export function registerActiveContext(pi: any, options: {
1420
1470
  chars,
1421
1471
  anchorTokens: anchor.tokens,
1422
1472
  deltaChars,
1473
+ text,
1423
1474
  };
1424
1475
  };
1425
1476
 
@@ -1450,7 +1501,7 @@ export function registerActiveContext(pi: any, options: {
1450
1501
  estimatorErrorShare() * estimate + expectedInflowTokens(),
1451
1502
  ));
1452
1503
 
1453
- const projectionExceedsBudget = (projected: unknown[], ctx: any): {
1504
+ const projectionExceedsBudget = (projected: unknown[], ctx: any, serialized?: string): {
1454
1505
  tokens: number;
1455
1506
  sizeTokens: number;
1456
1507
  basis: ProjectionReadingBasis;
@@ -1458,12 +1509,13 @@ export function registerActiveContext(pi: any, options: {
1458
1509
  marginTokens: number;
1459
1510
  over: boolean;
1460
1511
  crowded: boolean;
1512
+ text: string;
1461
1513
  } => {
1462
1514
  const capacity = currentCapacity(ctx);
1463
1515
  const budgetTokens = Number.isFinite(capacity.budgetTokens) && capacity.budgetTokens > 0
1464
1516
  ? capacity.budgetTokens
1465
1517
  : Number.POSITIVE_INFINITY;
1466
- const reading = projectedTokenReading(projected);
1518
+ const reading = projectedTokenReading(projected, serialized);
1467
1519
  const tokens = reading.tokens;
1468
1520
  const sizeTokens = Math.ceil(reading.chars / projectionCharsPerToken());
1469
1521
  const marginTokens = Number.isFinite(budgetTokens)
@@ -1477,6 +1529,7 @@ export function registerActiveContext(pi: any, options: {
1477
1529
  marginTokens,
1478
1530
  over: tokens > budgetTokens,
1479
1531
  crowded: tokens + marginTokens > budgetTokens,
1532
+ text: reading.text,
1480
1533
  };
1481
1534
  };
1482
1535
 
@@ -1626,9 +1679,13 @@ export function registerActiveContext(pi: any, options: {
1626
1679
  * and it does not: the rebuild reads the marks, not its own last text.
1627
1680
  */
1628
1681
  const appendFoldNotice = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
1682
+ // The deterministic experiment condition: cuts stage and commits land exactly as
1683
+ // ever, but nothing invites a brief, so briefs stay the runtime's own.
1684
+ if (!postFoldNotice) return projected;
1629
1685
  if (!persistence.state) return projected;
1630
1686
  const held = pendingMarks(persistence.state);
1631
- const unbriefed = held.filter((mark) => mark.briefProvenance?.kind !== "supplied");
1687
+ const unbriefed = held.filter((mark) =>
1688
+ mark.briefProvenance?.kind !== "supplied" && mark.briefProvenance?.kind !== "augmented");
1632
1689
  // BATCHED, AND APPENDED ONCE. A notice per cut is noise, and noise is how guidance
1633
1690
  // gets ignored; the frontier cuts often and this speaks only when a batch has built
1634
1691
  // up. `carrierAdmitted` is what makes it an APPEND rather than a rewrite: once the
@@ -1649,6 +1706,7 @@ export function registerActiveContext(pi: any, options: {
1649
1706
  id: mark.id,
1650
1707
  kind: mark.kind,
1651
1708
  tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
1709
+ brief: typeof mark.brief === "string" ? mark.brief : undefined,
1652
1710
  })),
1653
1711
  pending: held.length,
1654
1712
  toolName,
@@ -1673,6 +1731,42 @@ export function registerActiveContext(pi: any, options: {
1673
1731
  return projected;
1674
1732
  };
1675
1733
 
1734
+ /**
1735
+ * THE TABLE OF CONTENTS IS ONE COPY PER COMMIT (Shane, 2026-08-26). It is admitted
1736
+ * once per frozen projection, exactly as the fold notice above, and that admission
1737
+ * rule IS the refresh law: between commits the copy stays buried at its index, so a
1738
+ * quiet pass moves no byte and costs no occupancy anchor, and the tool result of every
1739
+ * `remember` carries the live key list in the meantime. A commit rewrites the
1740
+ * projection, the freeze drops, the body filter strips the old copy, and the next pass
1741
+ * seats a fresh one at the tail with the latest keys. Nothing here re-renders in
1742
+ * place, because a carrier that moves on a quiet pass is the exact defect the fold
1743
+ * notice's comment records.
1744
+ */
1745
+ const appendMemoryToc = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
1746
+ if (!workingMemory) return projected;
1747
+ if (!persistence.state) return projected;
1748
+ const held = persistence.state.memory ?? [];
1749
+ if (!held.length) return projected;
1750
+ if (!carrierAdmitted("memory-toc")) return projected;
1751
+ const entries = [...held].sort((left, right) => right.ordinal - left.ordinal);
1752
+ const text = memoryTocText({
1753
+ entries: entries.map((entry) => ({ key: entry.key, chars: entry.body.length })),
1754
+ toolName,
1755
+ brandNoun,
1756
+ });
1757
+ projected.push({
1758
+ role: "custom",
1759
+ customType: memoryTocProjectionType,
1760
+ content: text,
1761
+ display: false,
1762
+ details: { source: activeContextSource(entryTypePrefix), ephemeral: true },
1763
+ timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
1764
+ ? ownValue(snapshot.messages.at(-1), "timestamp")
1765
+ : 0,
1766
+ });
1767
+ return projected;
1768
+ };
1769
+
1676
1770
  const enforceBandTop = async (
1677
1771
  snapshot: ActiveContextSnapshot,
1678
1772
  ctx: any,
@@ -1711,10 +1805,10 @@ export function registerActiveContext(pi: any, options: {
1711
1805
  snapshot: ActiveContextSnapshot,
1712
1806
  projected: unknown[],
1713
1807
  ctx: any,
1714
- ): Promise<{ projected: unknown[]; aborted: boolean }> => {
1808
+ ): Promise<{ projected: unknown[]; aborted: boolean; text: string }> => {
1715
1809
  let measured = projectionExceedsBudget(projected, ctx);
1716
1810
  const rejected = curation.pendingRejection !== null;
1717
- if (!measured.crowded && !rejected) return { projected, aborted: false };
1811
+ if (!measured.crowded && !rejected) return { projected, aborted: false, text: measured.text };
1718
1812
  const trigger = measured;
1719
1813
  let reduced = projected;
1720
1814
  let attempts = 0;
@@ -1818,9 +1912,11 @@ export function registerActiveContext(pi: any, options: {
1818
1912
  }));
1819
1913
  curation.pendingRejection = null;
1820
1914
  }
1821
- if (!measured.over) return { projected: reduced, aborted: false };
1915
+ // `measured` is recomputed on `reduced` at every turn of the loop above, so its text is
1916
+ // the serialization of the array both of these exits return.
1917
+ if (!measured.over) return { projected: reduced, aborted: false, text: measured.text };
1822
1918
  abortOverBudgetProjection(measured.tokens, measured.budgetTokens, ctx, attempts);
1823
- return { projected: reduced, aborted: true };
1919
+ return { projected: reduced, aborted: true, text: measured.text };
1824
1920
  };
1825
1921
 
1826
1922
  const startPreparation = (snapshot: ActiveContextSnapshot, ratio: number | null, ctx: any): void => {
@@ -1975,7 +2071,7 @@ export function registerActiveContext(pi: any, options: {
1975
2071
  const customType = ownValue(message, "customType");
1976
2072
  return customType !== milestoneProjectionType && customType !== advisoryProjectionType &&
1977
2073
  customType !== receiptProjectionType && customType !== curationProjectionType &&
1978
- customType !== foldNoticeProjectionType;
2074
+ customType !== foldNoticeProjectionType && customType !== memoryTocProjectionType;
1979
2075
  });
1980
2076
  const withdrawn = withdrawConsumedEphemeralPeeks(body);
1981
2077
  if (withdrawn.size && freeze.body) {
@@ -1984,25 +2080,35 @@ export function registerActiveContext(pi: any, options: {
1984
2080
  if (freeze.projection) carryWithdrawalIntoFreeze(freeze.projection, withdrawn);
1985
2081
  }
1986
2082
  const held = freeze.body?.length ?? 0;
1987
- freeze.active = freeze.projection !== null && body.length >= held &&
1988
- stableStringify(body.slice(0, held)) === freeze.bodyText;
2083
+ // THE PREFIX IS READ OUT OF THE WHOLE SERIALIZATION (2026-08-28). This asked
2084
+ // `stableStringify(body.slice(0, held)) === freeze.bodyText`, and in steady state `held`
2085
+ // is `body.length` minus the couple of messages that just arrived, so it was a second
2086
+ // full serialization of the window plus a slice allocation, beside the one below that
2087
+ // every pass does anyway. `serializedPrefixHolds` carries why that is sound; a false
2088
+ // POSITIVE here splices the previous pass's `freeze.projection` in front of the new tail
2089
+ // and emits stale messages, which is a content bug rather than a slowdown, and gate 154
2090
+ // drives the predicate itself rather than restating it.
2091
+ const bodyText = stableStringify(body);
2092
+ freeze.active = freeze.projection !== null &&
2093
+ serializedPrefixHolds(bodyText, freeze.bodyText, held, body.length);
1989
2094
  const projected = freeze.active
1990
2095
  ? [...freeze.projection!, ...body.slice(held)]
1991
2096
  : [...body];
1992
2097
  if (!freeze.active) freeze.keys.clear();
1993
2098
  freeze.body = body;
1994
- freeze.bodyText = stableStringify(body);
2099
+ freeze.bodyText = bodyText;
1995
2100
  appendReceipts(projected, snapshot);
1996
2101
  appendFoldNotice(projected, snapshot);
2102
+ appendMemoryToc(projected, snapshot);
1997
2103
  return holdFrozen(projected);
1998
2104
  };
1999
- const noteProjection = (projected: unknown[]): void => {
2105
+ const noteProjection = (projected: unknown[], serialized?: string): void => {
2000
2106
  const digests = messageDigests(projected);
2001
2107
  const comparison = compareProjections(instrumentation.previousDigests, digests);
2002
2108
  recordProjection(instrumentation.ledger, comparison, digests);
2003
2109
  instrumentation.previousDigests = digests;
2004
2110
  instrumentation.lastChange = comparison.change;
2005
- const text = stableStringify(projected);
2111
+ const text = serialized ?? stableStringify(projected);
2006
2112
  const divergence = prefixDivergence(instrumentation.previousText, text);
2007
2113
  const previousChars = instrumentation.previousText?.length ?? 0;
2008
2114
  instrumentation.lastPreservedShare = previousChars > 0
@@ -2030,7 +2136,7 @@ export function registerActiveContext(pi: any, options: {
2030
2136
  : latestCacheActionClass
2031
2137
  ? latestCacheActionClass
2032
2138
  : divergence.index === null ? "steady-state" : "after-message";
2033
- const reading = projectedTokenReading(projected);
2139
+ const reading = projectedTokenReading(projected, text);
2034
2140
  emit("context.projection", {
2035
2141
  change: comparison.change,
2036
2142
  previous_count: comparison.previousCount,
@@ -2076,6 +2182,7 @@ export function registerActiveContext(pi: any, options: {
2076
2182
  snapshot: ActiveContextSnapshot;
2077
2183
  candidate: FoldCandidate;
2078
2184
  brief?: string;
2185
+ briefProvenance?: "supplied" | "deterministic" | BriefProvenance;
2079
2186
  ctx: any;
2080
2187
  signal?: AbortSignal;
2081
2188
  maximumSourceChars?: number;
@@ -2093,6 +2200,7 @@ export function registerActiveContext(pi: any, options: {
2093
2200
  state: baseState,
2094
2201
  generation: generationAtStart,
2095
2202
  brief: input.brief,
2203
+ briefProvenance: input.briefProvenance,
2096
2204
  ctx: input.ctx,
2097
2205
  signal: input.signal,
2098
2206
  });
@@ -2226,6 +2334,10 @@ export function registerActiveContext(pi: any, options: {
2226
2334
  });
2227
2335
  state = wedges.state;
2228
2336
  const accounting = markAccounting(snapshot, state);
2337
+ // Derived ONCE, here, against the pre-commit state both readers below report against.
2338
+ // It left `MarkAccounting` because it serializes the whole marked tail and every other
2339
+ // caller of `markAccounting` was paying for a number only this path reads.
2340
+ const rewriteTokens = markRewriteTokens(snapshot, state);
2229
2341
  if (!accounting.pending) {
2230
2342
  const remainder = unmarkedRemainder(snapshot, state, projectionCharsPerToken());
2231
2343
  emit("context.commit", {
@@ -2328,6 +2440,24 @@ export function registerActiveContext(pi: any, options: {
2328
2440
  !closed.applied.some((appliedMark) => appliedMark.id === mark.id)), ...closed.retained],
2329
2441
  };
2330
2442
  }
2443
+ // THE TOOL-CALL DIET RIDES THE SAME TRANSACTION (2026-08-24): clips are selected
2444
+ // against the post-fold state and land in the same revision the commit minted, so
2445
+ // the one paid rewrite carries both and no non-commit pass ever moves a byte. The
2446
+ // full bytes stay in the transcript behind each clip's entry id.
2447
+ let clippedResults = 0;
2448
+ if (toolFoldThreshold > 0) {
2449
+ const clipAdditions = toolClipAdditions({
2450
+ snapshot,
2451
+ state: result.state,
2452
+ threshold: toolFoldThreshold,
2453
+ blacklist: blacklistAutoFoldTools,
2454
+ });
2455
+ if (clipAdditions.length) {
2456
+ clippedResults = clipAdditions.length;
2457
+ result = { ...result, state: { ...result.state,
2458
+ clips: [...(result.state.clips ?? []), ...clipAdditions] } };
2459
+ }
2460
+ }
2331
2461
  persistence.state = result.state;
2332
2462
  const bytesAfter = bytes(projectActiveContext(snapshot, persistence.state));
2333
2463
  const freedBytes = Math.max(0, bytesBefore - bytesAfter);
@@ -2362,9 +2492,10 @@ export function registerActiveContext(pi: any, options: {
2362
2492
  consolidation_marks: consolidationAdded,
2363
2493
  closing_consolidation_marks: closingAdded,
2364
2494
  absorbed_wedges: wedges.absorbed.length,
2495
+ clipped_results: clippedResults,
2365
2496
  freed_bytes: freedBytes,
2366
2497
  freed_tokens: estimatedTokens(freedBytes),
2367
- rewrite_tokens: accounting.rewriteTokens,
2498
+ rewrite_tokens: rewriteTokens,
2368
2499
  pinned_bytes: accounting.pinnedBytes,
2369
2500
  pinned_results: accounting.pinnedResults,
2370
2501
  protected_stale_bytes: pinHeld.bytes,
@@ -2436,7 +2567,7 @@ export function registerActiveContext(pi: any, options: {
2436
2567
  protectedStaleRefs: pinHeld.refs,
2437
2568
  retainedMarks: result.retained.length,
2438
2569
  eligibleMarks: accounting.eligibleMarks,
2439
- estimatedRewriteTokens: accounting.rewriteTokens,
2570
+ estimatedRewriteTokens: rewriteTokens,
2440
2571
  estimatedFreedTokens: accounting.freedTokens,
2441
2572
  freedBudgetShare: accounting.freedBudgetShare,
2442
2573
  sourceBytesSaved: freedBytes,
@@ -2486,8 +2617,6 @@ export function registerActiveContext(pi: any, options: {
2486
2617
  occupancyAfter: occupancyBefore === null ? null : Math.max(0, occupancyBefore - freedTokens),
2487
2618
  spansFolded: epoch ? Number(epoch.foldedSpans ?? 0) : foldIds,
2488
2619
  toolResultsFolded: epoch ? Number(epoch.foldedToolResults ?? 0) : 0,
2489
- splitFolds: Number(action.splitFolds ?? 0),
2490
- splitFromChars: Number(action.splitFromChars ?? 0),
2491
2620
  absorbedWedges: Number(epoch?.absorbedWedges ?? 0),
2492
2621
  recovered: curation.recoveryAttempts > 0,
2493
2622
  protectedBytes: Number(epoch?.protectedStaleBytes ?? 0),
@@ -2785,7 +2914,7 @@ export function registerActiveContext(pi: any, options: {
2785
2914
  }
2786
2915
  const budgeted = await enforceProjectionBudget(snapshot, projected, ctx);
2787
2916
  projected = budgeted.projected;
2788
- const reading = projectedTokenReading(projected);
2917
+ const reading = projectedTokenReading(projected, budgeted.text);
2789
2918
  measurements.lastProjectedChars = reading.chars;
2790
2919
  measurements.lastProjectedEstimate = reading.tokens;
2791
2920
  measurements.lastProjectedEstimateBasis = reading.basis;
@@ -2795,7 +2924,7 @@ export function registerActiveContext(pi: any, options: {
2795
2924
  updateStatus(ctx);
2796
2925
  return { messages: projected };
2797
2926
  }
2798
- noteProjection(projected);
2927
+ noteProjection(projected, budgeted.text);
2799
2928
  updateStatus(ctx);
2800
2929
  return { messages: projected };
2801
2930
  } catch (error) {
@@ -3255,15 +3384,6 @@ export function registerActiveContext(pi: any, options: {
3255
3384
  );
3256
3385
  };
3257
3386
 
3258
- /**
3259
- * THE ONE STAGING PATH FOR FOLD MARKS (2026-08-23). The tool's fold action and the
3260
- * /fold-editor's user marks run THIS code and nothing else: span snapping, claim
3261
- * refusal, brief contract, prepareFold, replacement of a standing mark over the
3262
- * same span. Only the ORIGIN differs -- "agent" for the model, "user" for a human
3263
- * laying marks in the editor -- and origin changes nothing mechanical: a user mark
3264
- * counts toward the commit's coverage, answers with its commit arithmetic, and
3265
- * folds at the epoch exactly as an agent mark does.
3266
- */
3267
3387
  /**
3268
3388
  * THE WITHDRAWAL PATH, shared by the tool's unmark action and the editor's
3269
3389
  * withdraw intent: same refusal for an unknown id, same persist, same status.
@@ -3275,7 +3395,7 @@ export function registerActiveContext(pi: any, options: {
3275
3395
  const removed = before.filter((mark) => requested.has(mark.id));
3276
3396
  const unknown = ids.filter((id) => !before.some((mark) => mark.id === id));
3277
3397
  if (unknown.length) throw new Error(`No pending mark named ${unknown.join(", ")}`);
3278
- await persistManual(withPendingMarks(persistence.state!, kept), "unmark", ctx);
3398
+ await persistManual(withPendingMarks(persistence.state!, kept), ctx);
3279
3399
  updateStatus(ctx);
3280
3400
  return removed as Array<Record<string, unknown>>;
3281
3401
  };
@@ -3305,7 +3425,7 @@ export function registerActiveContext(pi: any, options: {
3305
3425
  "folding keeps entries exactly recoverable without pinning them.");
3306
3426
  }
3307
3427
  }
3308
- await persistManual(nextProtection, pin ? "pin" : "unpin", ctx);
3428
+ await persistManual(nextProtection, ctx);
3309
3429
  emit("context.pin", {
3310
3430
  pin,
3311
3431
  ids: ids.join(","),
@@ -3316,140 +3436,140 @@ export function registerActiveContext(pi: any, options: {
3316
3436
  updateStatus(ctx);
3317
3437
  };
3318
3438
 
3319
- const stageFoldMarks = async (
3320
- requests: BatchedMarkRequest[],
3321
- origin: MarkOrigin,
3439
+ /**
3440
+ * THE ONE STAGING PATH FOR FOLD MARKS (2026-08-23). The /fold-editor's user marks
3441
+ * run THIS code and nothing else: span snapping, claim refusal, brief contract,
3442
+ * prepareFold, replacement of a standing mark over the same span. A user mark is
3443
+ * mechanically an ordinary mark: it counts toward the commit's coverage, answers
3444
+ * with its commit arithmetic, and folds at the epoch exactly as a runtime mark does.
3445
+ */
3446
+ const stageFoldMark = async (
3447
+ ids: string[],
3448
+ supplied: string | undefined,
3322
3449
  signal: AbortSignal,
3323
3450
  ctx: any,
3324
3451
  snapshot: ActiveContextSnapshot,
3325
- ): Promise<{ marks: Array<Record<string, unknown>>; corrections: SpanCorrection[]; staged: ActiveContextState }> => {
3326
- const resolved: Array<{
3327
- candidate: FoldCandidate;
3328
- brief?: string;
3329
- corrections: SpanCorrection[];
3330
- splitFrom: number;
3331
- }> = [];
3332
- for (const request of requests) {
3333
- const snapped = snapFoldCandidate(snapshot, persistence.state, request.ids, { allowProtected: true });
3334
- // AN AGENT'S SPAN IS TAKEN WHOLE (Shane, 2026-08-22). The bite-size cap was
3335
- // applied to manual marks too, and it was the only caller: the ladder never
3336
- // splits, because `selectAutomaticChapter` stops accumulating at the cap on its
3337
- // own. So the split existed solely to second-guess the one judgement the design
3338
- // wants to encourage, and it charged for it twice. One intended fold became five
3339
- // or ten placeholders, and every piece was handed the SAME brief, so each one
3340
- // claimed to describe a span it held a fraction of, while the correction text
3341
- // said "each with its own brief", which was never true.
3342
- //
3343
- // The cap stays for the ladder, where it was earned (2026-08-06 rep 6: one
3344
- // 60,432-byte chapter hid the fact the run needed). The difference is who chose
3345
- // the boundary. A ladder fold is a span nobody vouched for, so it is kept small
3346
- // enough to read back cheaply. An agent fold is a span the agent cut and wrote a
3347
- // brief for, and it stays navigable by a route that did not exist in 2026-08:
3348
- // peek takes offset and bytes, states the omitted middle, and reads any child id,
3349
- // so a large fold has a narrow read. An expand that will not fit is refused with
3350
- // a peek offered instead, so size costs a refusal rather than a window.
3351
- resolved.push({
3352
- candidate: snapped.candidate,
3353
- brief: request.brief,
3354
- corrections: snapped.corrections,
3355
- splitFrom: 0,
3356
- });
3357
- }
3358
- const corrections = resolved.flatMap((item) => item.corrections);
3359
- const marks: Array<Record<string, unknown>> = [];
3360
- let staged = persistence.state!;
3361
- for (const item of resolved) {
3362
- const { candidate } = item;
3363
- const alreadyPrepared = staged.folds.some((fold) =>
3364
- fold.id === foldIdFor(candidate.kind, candidate.parts));
3365
- const deferred = refsProtected(candidate.sourceRefs, staged, snapshot) ||
3366
- (candidate.kind === "tool-result" &&
3367
- refsProtected(candidate.sourceRefs, staged, snapshot));
3368
- // A MARK IS EDITABLE UNTIL IT COMMITS (Shane, 2026-08-21). Marking a span you
3369
- // already marked REPLACES the mark, so a brief written in haste is corrected by
3370
- // writing it again: the span is identical, the id derives from the span, and no
3371
- // byte has moved yet, so the correction costs nothing at all. After the commit
3372
- // the fold is standing and there is no rewrite verb, because rewriting a
3373
- // standing brief rewrites the projection at its placeholder; `reboundary`
3374
- // re-cuts the span instead, which was going to rewrite anyway.
3375
- const replacing = foldIdFor(candidate.kind, candidate.parts);
3376
- const claimed = markClaimingRef(staged, candidate.sourceRefs);
3377
- if (claimed && claimed.markId !== replacing) {
3378
- marks.push({
3379
- id: replacing,
3380
- kind: candidate.kind,
3381
- ok: false,
3382
- deferred: false,
3383
- reason: `Evidence ${claimed.entryId} is already held by pending mark ${claimed.markId}, ` +
3384
- `which folds at the next commit. Unmark ${claimed.markId} first if you want to ` +
3385
- "mark this span differently.",
3386
- });
3387
- continue;
3388
- }
3389
- const suppliedComplaint = item.brief === undefined
3390
- ? null
3391
- : briefContractComplaint(item.brief.trim(), snapshot.policy.maxBriefChars, snapshot.toolName);
3392
- if (suppliedComplaint !== null) {
3393
- marks.push({
3394
- id: foldIdFor(candidate.kind, candidate.parts),
3395
- kind: candidate.kind,
3396
- ok: false,
3397
- deferred: false,
3398
- reason: `Supplied brief rejected. ${suppliedComplaint}`,
3399
- });
3400
- continue;
3401
- }
3402
- const briefed = !deferred && !alreadyPrepared
3403
- ? await prepareFold({
3404
- candidate,
3405
- snapshot,
3406
- state: staged,
3407
- generation: lifecycle.generation,
3408
- brief: item.brief,
3409
- ctx,
3410
- signal,
3411
- })
3412
- : null;
3413
- const mark = foldMarkFor({
3414
- candidate,
3415
- brief: briefed?.fold.brief ?? item.brief ?? ladderBrief(snapshot, staged, candidate),
3416
- briefProvenance: briefed?.fold.provenance ??
3417
- (item.brief ? { kind: "supplied" } : { kind: "deterministic" }),
3418
- origin,
3419
- ordinal: markOrdinal(snapshot),
3420
- });
3421
- // Drop the standing mark over this exact span before adding, so a re-mark is a
3422
- // replacement rather than a duplicate refusal. `claimed` is this same mark by the
3423
- // check above, and the id is derived from the span, so the state after is the
3424
- // state a first mark with the new brief would have produced.
3425
- const replaced = claimed !== null;
3426
- if (replaced) {
3427
- staged = withPendingMarks(staged,
3428
- pendingMarks(staged).filter((standing) => standing.id !== replacing));
3429
- }
3430
- const addition = addPendingMark(staged, mark);
3431
- if (!addition.added) {
3432
- marks.push({ id: mark.id, kind: mark.kind, ok: false, deferred: false, reason: addition.reason });
3433
- continue;
3434
- }
3435
- staged = addition.state;
3436
- marks.push({
3437
- id: mark.id,
3438
- kind: mark.kind,
3439
- ok: true,
3440
- deferred,
3441
- ...(replaced ? { replaced: true } : {}),
3442
- ...(deferred
3443
- ? {
3444
- scheduled: "the span is still in the fresh window; this mark is held and folds at the " +
3445
- "first commit after it ages out",
3446
- }
3447
- : {}),
3448
- brief: mark.brief,
3449
- provenance: normalizeLegacyProvenance(mark.briefProvenance),
3450
- });
3451
- }
3452
- return { marks, corrections, staged };
3452
+ ): Promise<{ mark: Record<string, unknown>; corrections: SpanCorrection[]; staged: ActiveContextState }> => {
3453
+ const snapped = snapFoldCandidate(snapshot, persistence.state, ids, { allowProtected: true });
3454
+ // AN AGENT'S SPAN IS TAKEN WHOLE (Shane, 2026-08-22). The bite-size cap was
3455
+ // applied to manual marks too, and it was the only caller: the ladder never
3456
+ // splits, because `selectAutomaticChapter` stops accumulating at the cap on its
3457
+ // own. So the split existed solely to second-guess the one judgement the design
3458
+ // wants to encourage, and it charged for it twice. One intended fold became five
3459
+ // or ten placeholders, and every piece was handed the SAME brief, so each one
3460
+ // claimed to describe a span it held a fraction of, while the correction text
3461
+ // said "each with its own brief", which was never true.
3462
+ //
3463
+ // The cap stays for the ladder, where it was earned (2026-08-06 rep 6: one
3464
+ // 60,432-byte chapter hid the fact the run needed). The difference is who chose
3465
+ // the boundary. A ladder fold is a span nobody vouched for, so it is kept small
3466
+ // enough to read back cheaply. An agent fold is a span the agent cut and wrote a
3467
+ // brief for, and it stays navigable by a route that did not exist in 2026-08:
3468
+ // peek takes offset and bytes, states the omitted middle, and reads any child id,
3469
+ // so a large fold has a narrow read. An expand that will not fit is refused with
3470
+ // a peek offered instead, so size costs a refusal rather than a window.
3471
+ const candidate = snapped.candidate;
3472
+ const corrections = snapped.corrections;
3473
+ let staged = persistence.state!;
3474
+ const alreadyPrepared = staged.folds.some((fold) =>
3475
+ fold.id === foldIdFor(candidate.kind, candidate.parts));
3476
+ const deferred = refsProtected(candidate.sourceRefs, staged, snapshot);
3477
+ // A MARK IS EDITABLE UNTIL IT COMMITS (Shane, 2026-08-21). Marking a span you
3478
+ // already marked REPLACES the mark, so a brief written in haste is corrected by
3479
+ // writing it again: the span is identical, the id derives from the span, and no
3480
+ // byte has moved yet, so the correction costs nothing at all. After the commit
3481
+ // the fold is standing and there is no rewrite verb, because rewriting a
3482
+ // standing brief rewrites the projection at its placeholder; `reboundary`
3483
+ // re-cuts the span instead, which was going to rewrite anyway.
3484
+ const replacing = foldIdFor(candidate.kind, candidate.parts);
3485
+ const claimed = markClaimingRef(staged, candidate.sourceRefs);
3486
+ if (claimed && claimed.markId !== replacing) {
3487
+ return {
3488
+ mark: {
3489
+ id: replacing,
3490
+ kind: candidate.kind,
3491
+ ok: false,
3492
+ deferred: false,
3493
+ reason: `Evidence ${claimed.entryId} is already held by pending mark ${claimed.markId}, ` +
3494
+ `which folds at the next commit. Unmark ${claimed.markId} first if you want to ` +
3495
+ "mark this span differently.",
3496
+ },
3497
+ corrections,
3498
+ staged,
3499
+ };
3500
+ }
3501
+ const suppliedComplaint = supplied === undefined
3502
+ ? null
3503
+ : briefContractComplaint(supplied.trim(), snapshot.policy.agentBriefReserve, snapshot.toolName);
3504
+ if (suppliedComplaint !== null) {
3505
+ return {
3506
+ mark: {
3507
+ id: replacing,
3508
+ kind: candidate.kind,
3509
+ ok: false,
3510
+ deferred: false,
3511
+ reason: `Supplied brief rejected. ${suppliedComplaint}`,
3512
+ },
3513
+ corrections,
3514
+ staged,
3515
+ };
3516
+ }
3517
+ const briefed = !deferred && !alreadyPrepared
3518
+ ? await prepareFold({
3519
+ candidate,
3520
+ snapshot,
3521
+ state: staged,
3522
+ generation: lifecycle.generation,
3523
+ brief: supplied,
3524
+ ctx,
3525
+ signal,
3526
+ })
3527
+ : null;
3528
+ const mark = foldMarkFor({
3529
+ candidate,
3530
+ brief: briefed?.fold.brief ?? supplied ?? ladderBrief(snapshot, staged, candidate),
3531
+ briefProvenance: briefed?.fold.provenance ??
3532
+ (supplied ? { kind: "supplied" } : { kind: "deterministic" }),
3533
+ origin: "user",
3534
+ ordinal: markOrdinal(snapshot),
3535
+ });
3536
+ // Drop the standing mark over this exact span before adding, so a re-mark is a
3537
+ // replacement rather than a duplicate refusal. `claimed` is this same mark by the
3538
+ // check above, and the id is derived from the span, so the state after is the
3539
+ // state a first mark with the new brief would have produced.
3540
+ const replaced = claimed !== null;
3541
+ if (replaced) {
3542
+ staged = withPendingMarks(staged,
3543
+ pendingMarks(staged).filter((standing) => standing.id !== replacing));
3544
+ }
3545
+ const addition = addPendingMark(staged, mark);
3546
+ if (!addition.added) {
3547
+ return {
3548
+ mark: { id: mark.id, kind: mark.kind, ok: false, deferred: false, reason: addition.reason },
3549
+ corrections,
3550
+ staged,
3551
+ };
3552
+ }
3553
+ staged = addition.state;
3554
+ return {
3555
+ mark: {
3556
+ id: mark.id,
3557
+ kind: mark.kind,
3558
+ ok: true,
3559
+ deferred,
3560
+ ...(replaced ? { replaced: true } : {}),
3561
+ ...(deferred
3562
+ ? {
3563
+ scheduled: "the span is still in the fresh window; this mark is held and folds at the " +
3564
+ "first commit after it ages out",
3565
+ }
3566
+ : {}),
3567
+ brief: mark.brief,
3568
+ provenance: normalizeLegacyProvenance(mark.briefProvenance),
3569
+ },
3570
+ corrections,
3571
+ staged,
3572
+ };
3453
3573
  };
3454
3574
 
3455
3575
  const executeAction = async (
@@ -3584,7 +3704,8 @@ export function registerActiveContext(pi: any, options: {
3584
3704
  id: mark.id,
3585
3705
  kind: mark.kind,
3586
3706
  tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
3587
- briefed: mark.briefProvenance?.kind === "supplied",
3707
+ briefed: mark.briefProvenance?.kind === "supplied" ||
3708
+ mark.briefProvenance?.kind === "augmented",
3588
3709
  brief: mark.brief ?? null,
3589
3710
  briefProvenance: mark.briefProvenance ? clone(mark.briefProvenance) : null,
3590
3711
  })),
@@ -3631,7 +3752,41 @@ export function registerActiveContext(pi: any, options: {
3631
3752
  "bytes",
3632
3753
  );
3633
3754
  const target = persistence.state.folds.find((item) => item.id === id);
3634
- if (!target) throw new Error(`Unknown active-context fold ${id}`);
3755
+ // A CLIPPED RESULT ANSWERS TO ITS ENTRY ID (2026-08-24, the tool-call diet): the
3756
+ // clip marker names the entry, and the full bytes are in the transcript rather
3757
+ // than behind a fold, so the read serves them from the mapped message directly,
3758
+ // through the same admission fence and the same slice bounds as a fold peek.
3759
+ if (!target) {
3760
+ const clip = (persistence.state.clips ?? []).find((item) =>
3761
+ item.entryId === id || item.callId === id);
3762
+ if (clip) {
3763
+ const mapped = snapshot.mapped.find((item) => item.ref?.entryId === clip.entryId);
3764
+ if (!mapped) throw new Error(`Clipped result ${id} is no longer on this branch`);
3765
+ const full = contentText(snapshot.messages[mapped.index]);
3766
+ admit({
3767
+ action: "peek",
3768
+ ctx,
3769
+ foldId: clip.entryId,
3770
+ requestedBytes: Math.max(0, Math.min(sliceBytes, full.length - offset)),
3771
+ children: [],
3772
+ });
3773
+ const slice = full.slice(offset, offset + sliceBytes);
3774
+ return toolPayload({
3775
+ id: clip.entryId,
3776
+ source: slice,
3777
+ truncated: offset + slice.length < full.length,
3778
+ sourceChars: full.length,
3779
+ offset,
3780
+ ...(peekIsEphemeral(params) ? {
3781
+ ephemeral: "this result rides your context exactly until your next message; " +
3782
+ "extract what your current task needs into that reply, which takes over its " +
3783
+ "place. Peeking again is lossless and cheap; pass ephemeral false when you " +
3784
+ "need these bytes standing for several turns",
3785
+ } : { durable: "this result stays in your context like any other tool result" }),
3786
+ });
3787
+ }
3788
+ throw new Error(`Unknown active-context fold ${id}`);
3789
+ }
3635
3790
  admit({
3636
3791
  action: "peek",
3637
3792
  ctx,
@@ -3690,7 +3845,12 @@ export function registerActiveContext(pi: any, options: {
3690
3845
  for (const part of [recut]) {
3691
3846
  const durable = persistence.persistedFoldRecords.get(foldIdFor(part.kind, part.parts));
3692
3847
  const { preparedFold, nextState } = await prepareAndCommitExplicit({
3693
- snapshot, candidate: part, brief: durable ? durable.fold.brief : supplied, ctx, signal,
3848
+ // A re-cut carries the durable fold's OWN brief and provenance: those words
3849
+ // were validated when they were first accepted, and re-judging them as a
3850
+ // fresh agent submission would refuse a deterministic or composed brief
3851
+ // against the agent's reserve.
3852
+ snapshot, candidate: part, brief: durable ? durable.fold.brief : supplied,
3853
+ briefProvenance: durable ? durable.fold.provenance : undefined, ctx, signal,
3694
3854
  });
3695
3855
  persistence.state = nextState;
3696
3856
  if (durable && supplied) correctedBriefs[preparedFold.id] = supplied;
@@ -3706,7 +3866,7 @@ export function registerActiveContext(pi: any, options: {
3706
3866
  briefs: { ...(persistence.state.briefs ?? {}), ...correctedBriefs },
3707
3867
  };
3708
3868
  }
3709
- await persistManual(persistence.state, action, ctx);
3869
+ await persistManual(persistence.state, ctx);
3710
3870
  updateStatus(ctx);
3711
3871
  return toolPayload({
3712
3872
  version: 1,
@@ -3744,7 +3904,7 @@ export function registerActiveContext(pi: any, options: {
3744
3904
  if (!Object.keys(briefs).length) delete next.briefs;
3745
3905
  const leases = { ...next.leases };
3746
3906
  delete leases[id];
3747
- await persistManual({ ...next, leases }, action, ctx);
3907
+ await persistManual({ ...next, leases }, ctx);
3748
3908
  updateStatus(ctx);
3749
3909
  return toolPayload({
3750
3910
  version: 1,
@@ -3790,11 +3950,7 @@ export function registerActiveContext(pi: any, options: {
3790
3950
  delete leases[id];
3791
3951
  next = { ...next, leases };
3792
3952
  }
3793
- await persistManual(
3794
- next,
3795
- action,
3796
- ctx,
3797
- );
3953
+ await persistManual(next, ctx);
3798
3954
  updateStatus(ctx);
3799
3955
  return toolPayload({
3800
3956
  version: 1,
@@ -3890,7 +4046,7 @@ export function registerActiveContext(pi: any, options: {
3890
4046
  );
3891
4047
  }
3892
4048
  const text = String(params.brief ?? "").trim();
3893
- const complaint = briefContractComplaint(text, snapshot.policy.maxBriefChars, snapshot.toolName);
4049
+ const complaint = briefContractComplaint(text, snapshot.policy.agentBriefReserve, snapshot.toolName);
3894
4050
  if (complaint !== null) throw new Error(`Supplied brief rejected. ${complaint}`);
3895
4051
  const nextState = {
3896
4052
  ...persistence.state!,
@@ -3900,7 +4056,7 @@ export function registerActiveContext(pi: any, options: {
3900
4056
  : mark),
3901
4057
  };
3902
4058
  persistence.state = nextState;
3903
- await persistManual(nextState, action, ctx);
4059
+ await persistManual(nextState, ctx);
3904
4060
  updateStatus(ctx);
3905
4061
  emit("context.brief", { id, chars: text.length, origin: "agent" });
3906
4062
  const accounting = markAccounting(snapshot, persistence.state!);
@@ -3916,7 +4072,8 @@ export function registerActiveContext(pi: any, options: {
3916
4072
  durableRevision: persistence.state!.revision,
3917
4073
  pendingMarks: accounting.pending,
3918
4074
  unbriefed: pendingMarks(persistence.state!)
3919
- .filter((mark) => mark.briefProvenance?.kind !== "supplied").map((mark) => mark.id),
4075
+ .filter((mark) => mark.briefProvenance?.kind !== "supplied" &&
4076
+ mark.briefProvenance?.kind !== "augmented").map((mark) => mark.id),
3920
4077
  unmarkedSpans: remainder.spans,
3921
4078
  unmarkedTokens: remainder.tokens,
3922
4079
  activation: "durable immediately, and it costs your window nothing: this fold is " +
@@ -3924,14 +4081,104 @@ export function registerActiveContext(pi: any, options: {
3924
4081
  "reaches it only when the commit folds this span. Nothing in your context moved.",
3925
4082
  });
3926
4083
  }
4084
+ if (action === "remember") {
4085
+ // THE DIGEST'S OWN CHANNEL. A working-memory write is durable state outside the
4086
+ // projection, so like `brief` it moves no bytes and costs no prefix cache: the
4087
+ // table of contents the projection carries refreshes at the next commit, and this
4088
+ // result carries the live key list in the meantime.
4089
+ const key = String(params.key ?? "").trim();
4090
+ if (!key) throw new Error("remember needs a key");
4091
+ if (key.length > MEMORY_KEY_CHARS) {
4092
+ throw new Error(`Working-memory keys have a hard ${MEMORY_KEY_CHARS}-character ` +
4093
+ "maximum: the key is a table-of-contents line, not the note. Put the detail " +
4094
+ "in the body.");
4095
+ }
4096
+ const body = String(params.body ?? "").trim();
4097
+ if (body.length > MEMORY_BODY_CHARS) {
4098
+ throw new Error(`Working-memory bodies have a hard ${MEMORY_BODY_CHARS}-character ` +
4099
+ `maximum and this one is ${body.length}. Split the entry, or keep the part ` +
4100
+ "that changes and let the transcript carry the rest: folded material stays " +
4101
+ "losslessly recoverable by peek.");
4102
+ }
4103
+ const held = persistence.state!.memory ?? [];
4104
+ const existing = held.find((entry) => entry.key === key);
4105
+ let nextMemory: WorkingMemoryEntry[];
4106
+ if (!body) {
4107
+ if (!existing) {
4108
+ throw new Error(`No working-memory entry '${key}' to remove. ${held.length
4109
+ ? `The keys are: ${held.map((entry) => entry.key).join(", ")}.`
4110
+ : "Working memory is empty."}`);
4111
+ }
4112
+ nextMemory = held.filter((entry) => entry.key !== key);
4113
+ } else {
4114
+ if (!existing && held.length >= MEMORY_KEYS_MAX) {
4115
+ throw new Error(`Working memory holds its maximum of ${MEMORY_KEYS_MAX} ` +
4116
+ "entries. Merge two entries, or remove one whose facts no longer matter " +
4117
+ `(${toolName} {"action":"remember","key":"<key>","body":""}), then write ` +
4118
+ "this one.");
4119
+ }
4120
+ const ordinal = held.reduce((max, entry) => Math.max(max, entry.ordinal), 0) + 1;
4121
+ nextMemory = existing
4122
+ ? held.map((entry) => entry.key === key ? { key, body, ordinal } : entry)
4123
+ : [...held, { key, body, ordinal }];
4124
+ }
4125
+ const nextState = {
4126
+ ...persistence.state!,
4127
+ revision: persistence.state!.revision + 1,
4128
+ ...(nextMemory.length ? { memory: nextMemory } : {}),
4129
+ };
4130
+ if (!nextMemory.length) delete (nextState as Partial<ActiveContextState>).memory;
4131
+ persistence.state = nextState;
4132
+ await persistManual(nextState, ctx);
4133
+ updateStatus(ctx);
4134
+ const toc = [...nextMemory].sort((left, right) => right.ordinal - left.ordinal)
4135
+ .map((entry) => ({ key: entry.key, chars: entry.body.length }));
4136
+ return toolPayload({
4137
+ version: 1,
4138
+ action,
4139
+ key,
4140
+ ...(body ? { chars: body.length } : { removed: true }),
4141
+ argumentsSha256: executionArgumentsSha256,
4142
+ durableRevision: persistence.state!.revision,
4143
+ entries: toc,
4144
+ activation: body
4145
+ ? "durable immediately, and it costs your window nothing: the body lives in " +
4146
+ "state, this list is the live view, and the table of contents in your " +
4147
+ "context refreshes at the next commit."
4148
+ : "removed. The table of contents in your context refreshes at the next commit.",
4149
+ });
4150
+ }
4151
+ if (action === "recall") {
4152
+ const held = persistence.state!.memory ?? [];
4153
+ if (!held.length) {
4154
+ throw new Error(`Working memory is empty. Write an entry with ${toolName} ` +
4155
+ '{"action":"remember","key":"<key>","body":"..."}.');
4156
+ }
4157
+ const requested = params.keys === undefined && params.key === undefined
4158
+ ? null
4159
+ : (denseOwnArrayValues(params.keys) ?? [params.key ?? params.keys])
4160
+ .map((value) => String(value ?? "").trim()).filter(Boolean);
4161
+ const entries = [...held].sort((left, right) => right.ordinal - left.ordinal);
4162
+ const chosen = requested === null
4163
+ ? entries
4164
+ : entries.filter((entry) => requested.includes(entry.key));
4165
+ if (requested !== null) {
4166
+ const missing = requested.filter((key) => !held.some((entry) => entry.key === key));
4167
+ if (missing.length) {
4168
+ throw new Error(`No working-memory entr${missing.length === 1 ? "y" : "ies"} ` +
4169
+ `${missing.join(", ")}. The keys are: ` +
4170
+ `${entries.map((entry) => entry.key).join(", ")}.`);
4171
+ }
4172
+ }
4173
+ return toolPayload({
4174
+ version: 1,
4175
+ action,
4176
+ argumentsSha256: executionArgumentsSha256,
4177
+ entries: chosen.map((entry) => ({ key: entry.key, chars: entry.body.length, body: entry.body })),
4178
+ });
4179
+ }
3927
4180
  throw new Error(`Unknown ${toolName} action '${action}'`);
3928
4181
  };
3929
- const requestedMarkCount = (params: Record<string, unknown>): number => {
3930
- const batched = denseOwnArrayValues(params?.marks);
3931
- if (batched) return batched.length;
3932
- return params?.ids === undefined && params?.id === undefined ? 0 : 1;
3933
- };
3934
-
3935
4182
  const toolHandler = async (
3936
4183
  toolCallId: string,
3937
4184
  params: Record<string, unknown>,
@@ -3948,7 +4195,6 @@ export function registerActiveContext(pi: any, options: {
3948
4195
  ok,
3949
4196
  error,
3950
4197
  tool_call_id: toolCallId,
3951
- marks_requested: requestedMarkCount(params),
3952
4198
  corrections_applied: corrections.length,
3953
4199
  arguments_sha256: sha256Value(params ?? {}),
3954
4200
  ...(action === "peek" ? { ephemeral: peekIsEphemeral(params) } : {}),
@@ -4234,20 +4480,13 @@ export function registerActiveContext(pi: any, options: {
4234
4480
  onStageMark: async (fromId: string, toId: string, brief?: string): Promise<void> => {
4235
4481
  await runQueued(async () => {
4236
4482
  const fresh = authoritativeSnapshotFor(ctx);
4237
- const request = brief && brief.trim()
4238
- ? { ids: [fromId, toId], brief: brief.trim() }
4239
- : { ids: [fromId, toId] };
4240
- const { marks, staged } = await stageFoldMarks(
4241
- batchedMarkRequests(request), "user",
4483
+ const { mark, staged } = await stageFoldMark(
4484
+ [fromId, toId], brief && brief.trim() ? brief.trim() : undefined,
4242
4485
  new AbortController().signal, ctx, fresh);
4243
- // Refuse BEFORE persisting: an all-refused batch leaves state untouched and
4486
+ // Refuse BEFORE persisting: a refused mark leaves state untouched and
4244
4487
  // persisting it would be a no-op write against the same-state projection.
4245
- if (!marks.some((mark) => mark.ok === true)) {
4246
- const reason = marks.map((mark) => mark.reason).filter(Boolean).join("; ") ||
4247
- "the span was refused";
4248
- throw new Error(reason);
4249
- }
4250
- await persistManual(staged, "fold", ctx);
4488
+ if (mark.ok !== true) throw new Error(String(mark.reason ?? "the span was refused"));
4489
+ await persistManual(staged, ctx);
4251
4490
  updateStatus(ctx);
4252
4491
  });
4253
4492
  safeNotify(ctx, "Staged user mark(s); they apply at the next commit epoch.", "info");
@@ -4291,7 +4530,7 @@ export function registerActiveContext(pi: any, options: {
4291
4530
  label: toolLabel,
4292
4531
  allowedActions: allowedToolActions,
4293
4532
  fullSurface: true,
4294
- maxBriefChars: ACTIVE_CONTEXT_POLICY.maxBriefChars,
4533
+ maxBriefChars: ACTIVE_CONTEXT_POLICY.agentBriefReserve,
4295
4534
  statusDetails: ["fold_candidates", "tree", "folds", "objects"],
4296
4535
  minPeekSliceBytes: PEEK_MIN_SLICE_BYTES,
4297
4536
  defaultPeekBytes: PEEK_DEFAULT_MAX_BYTES,