pi-fold 2.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";
@@ -22,6 +24,7 @@ import {
22
24
  descendantIds,
23
25
  encodedFoldSource,
24
26
  foldCandidatesDetail,
27
+ foldIndexRow,
25
28
  foldTreeDetail,
26
29
  peekFoldSource,
27
30
  prepareFold,
@@ -29,7 +32,6 @@ import {
29
32
  projectionSlateCandidates,
30
33
  protectEvidence,
31
34
  requireActiveFold,
32
- selectAutomaticChapter,
33
35
  selectAutomaticRung,
34
36
  setFoldProjectionState,
35
37
  withExpandLease,
@@ -56,7 +58,6 @@ import {
56
58
  providerTokens,
57
59
  refsProtected,
58
60
  stringIds,
59
- toolRefsProtected,
60
61
  toolPayload,
61
62
  } from "./lib/measurement.ts";
62
63
  import type {
@@ -70,8 +71,8 @@ import {
70
71
  clearPrepared,
71
72
  deriveFoldParents,
72
73
  flattenFoldRefs,
73
- foldBrief,
74
74
  foldIdFor,
75
+ foldRecordRef,
75
76
  makeFoldRecordEntry,
76
77
  makeStateCheckpoint,
77
78
  makeStateDelta,
@@ -90,6 +91,7 @@ import {
90
91
  activeContextSource,
91
92
  ACTIVE_CONTEXT_POLICY,
92
93
  ACTIVE_CONTEXT_TOOL_ACTIONS,
94
+ UNBRIEFED_FOLDS_BEFORE_NOTICE,
93
95
  contextBrand,
94
96
  DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN,
95
97
  DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES,
@@ -99,39 +101,46 @@ import {
99
101
  COMMIT_RECLAIM_FLOOR_SHARE,
100
102
  CONTEXT_RECEIPT_BLOCK_BYTES,
101
103
  DEFAULT_CONTEXT_WINDOW,
102
- resolveGuidance,
103
104
  assertThresholdsServable,
104
105
  resolveThresholds,
105
106
  servingBudgetTokens,
106
107
  ESTIMATED_BYTES_PER_TOKEN,
107
108
  entryTypeNamespace,
108
109
  MAX_FOLD_SPAN_CHARS,
110
+ DEFAULT_TOOL_FOLD_THRESHOLD,
109
111
  MAX_PINNED_SHARE,
110
- MAX_WEDGE_ABSORB_TOKENS,
111
112
  PEEK_DEFAULT_MAX_BYTES,
113
+ peekIsEphemeral,
112
114
  PEEK_MIN_SLICE_BYTES,
113
115
  PEEK_READ_ONLY_CONTEXT_ACTIONS,
116
+ RETIRED_TOOL_ACTIONS,
114
117
  AUTO_FOLD_BLACKLIST_DEFAULT,
115
118
  USER_RESCUE_MAX_SOURCE_CHARS,
119
+ MEMORY_BODY_CHARS,
120
+ MEMORY_KEY_CHARS,
121
+ MEMORY_KEYS_MAX,
122
+ WORKING_MEMORY_TOOL_ACTIONS,
116
123
  } from "./lib/policy.ts";
117
124
  import type {
118
- ActiveContextGuidance,
119
125
  ActiveContextSnapshot,
120
126
  ActiveContextState,
121
127
  ActiveContextThresholds,
122
128
  ActiveContextToolAction,
129
+ BriefProvenance,
123
130
  FoldCandidate,
124
131
  FoldKind,
125
132
  FoldRecordEntry,
126
133
  PreparedFold,
134
+ WorkingMemoryEntry,
127
135
  } from "./lib/policy.ts";
128
136
  import {
129
137
  absorbWedgeMarks,
138
+ markSpanRefs,
130
139
  } from "./lib/scheduling.ts";
131
140
  import {
132
141
  contextReceipt,
133
- contextRiderText,
134
- markAwarenessText,
142
+ foldNoticeText,
143
+ memoryTocText,
135
144
  receiptBlockText,
136
145
  withReceipt,
137
146
  } from "./lib/curation.ts";
@@ -140,35 +149,35 @@ import type {
140
149
  } from "./lib/curation.ts";
141
150
  import {
142
151
  addPendingMark,
143
- claimedRefKeys,
152
+ commitCoverage,
153
+ windowClaims,
144
154
  commitPendingMarks,
155
+ toolClipAdditions,
145
156
  consolidationMarks,
146
157
  ephemeralPeekMarks,
158
+ epochCommitDue,
147
159
  estimatedTokens,
148
160
  foldMarkFor,
149
- ladderSelectionMark,
150
161
  ladderBrief,
151
162
  markAccounting,
152
- markEligibility,
163
+ markRewriteTokens,
153
164
  markFreedBytes,
154
165
  unmarkedRemainder,
155
- guardWaiverCount,
156
- markedFoldIds,
166
+ markClaimingRef,
157
167
  markOrdinal,
158
168
  pendingMarks,
159
- markTouchesCurrentTurn,
169
+ refoldMarks,
160
170
  schedulingStatus,
161
171
  topUpMarks,
172
+ frontierMarks,
162
173
  withPendingMarks,
163
174
  } from "./lib/scheduling.ts";
164
175
  import {
165
- candidateSpanChars,
166
176
  manualFoldCandidate,
167
177
  peekedSourceFoldIds,
168
- selectAutomaticToolBatch,
169
178
  snapFoldCandidate,
170
179
  snapToFoldBoundaries,
171
- splitCandidateBySize,
180
+ spanBytes,
172
181
  } from "./lib/selection.ts";
173
182
  import type { SpanCorrection } from "./lib/selection.ts";
174
183
  import {
@@ -187,8 +196,8 @@ import type {
187
196
  ProjectionChange,
188
197
  } from "./lib/instrumentation.ts";
189
198
  import { buildActiveContextCommands, buildActiveContextTool } from "./lib/tool-surface.ts";
199
+ import { buildFoldEditorData, FoldEditorView } from "./lib/editor-ui.ts";
190
200
  import {
191
- currentTurnRefKeys,
192
201
  mapActiveContext,
193
202
  } from "./lib/transcript.ts";
194
203
  import {
@@ -214,38 +223,6 @@ export * from "./lib/scheduling.ts";
214
223
  export * from "./lib/selection.ts";
215
224
  export * from "./lib/transcript.ts";
216
225
 
217
- export interface BatchedMarkRequest {
218
- ids: string[];
219
- brief?: string;
220
- }
221
-
222
- export function batchedMarkRequests(params: Record<string, unknown>): BatchedMarkRequest[] {
223
- const batched = params.marks;
224
- if (batched === undefined) {
225
- return [{
226
- ids: stringIds(params.ids),
227
- ...(typeof params.brief === "string" && params.brief.trim() ? { brief: params.brief } : {}),
228
- }];
229
- }
230
- const values = denseOwnArrayValues(batched);
231
- if (!values || !values.length || values.length > 64) {
232
- throw new Error("marks must be a dense array of 1-64 {ids, brief} objects");
233
- }
234
- return values.map((value, index) => {
235
- if (!value || typeof value !== "object" || Array.isArray(value)) {
236
- throw new Error(`marks[${index}] must be an object with ids and an optional brief`);
237
- }
238
- const brief = ownValue(value, "brief");
239
- if (brief !== undefined && (typeof brief !== "string" || !brief.trim())) {
240
- throw new Error(`marks[${index}].brief must be a nonempty string`);
241
- }
242
- return {
243
- ids: stringIds(ownValue(value, "ids")),
244
- ...(typeof brief === "string" && brief.trim() ? { brief } : {}),
245
- };
246
- });
247
- }
248
-
249
226
  export function registerActiveContext(pi: any, options: {
250
227
  toolName?: string;
251
228
  toolLabel?: string;
@@ -255,7 +232,28 @@ export function registerActiveContext(pi: any, options: {
255
232
  blacklistAutoFoldTools?: ReadonlySet<string>;
256
233
  providerInputBudget?: number;
257
234
  thresholds?: ActiveContextThresholds;
258
- guidance?: Partial<ActiveContextGuidance>;
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;
259
257
  }): {
260
258
  projectionCandidates: (ctx: any) => Array<Record<string, unknown>>;
261
259
  } {
@@ -264,6 +262,24 @@ export function registerActiveContext(pi: any, options: {
264
262
  const brandNoun = options.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN;
265
263
  const entryTypePrefix = options.entryTypePrefix ?? DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX;
266
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
+ }
267
283
  for (const removed of ["foldScheduling", "foldPeekResults", "toolActions"]) {
268
284
  if (Object.hasOwn(options, removed)) {
269
285
  throw new Error(`${removed} is no longer an option: epoch scheduling, peek foldability ` +
@@ -288,6 +304,13 @@ export function registerActiveContext(pi: any, options: {
288
304
  "tools it meant to permit");
289
305
  }
290
306
  }
307
+ if (Object.hasOwn(options, "guidance")) {
308
+ throw new Error("guidance is no longer an option: it switched the copy that taught the " +
309
+ "agent to CHOOSE SPANS, and the agent does not choose spans. The runtime cuts at the " +
310
+ "frontier and the agent annotates what it cut, so the guidance it switched has been " +
311
+ "replaced wholesale by the post-fold notice, which is not optional because a fold the " +
312
+ "agent is never told about is one it can never brief");
313
+ }
291
314
  if (Object.hasOwn(options, "commandPrefix")) {
292
315
  throw new Error("commandPrefix is no longer an option: give whole names with commandNames");
293
316
  }
@@ -317,20 +340,22 @@ export function registerActiveContext(pi: any, options: {
317
340
  }
318
341
  const thresholds = resolveThresholds(options.thresholds);
319
342
  assertThresholdsServable(thresholds, providerInputBudget ?? servingBudgetTokens(DEFAULT_CONTEXT_WINDOW));
320
- const guidance = resolveGuidance(options.guidance);
321
343
  const stateEntryType = `${entryTypePrefix}-state`;
322
344
  const foldRecordEntryType = `${entryTypePrefix}-fold-record`;
323
345
  const milestoneProjectionType = `${entryTypePrefix}-milestone`;
324
346
  const advisoryProjectionType = `${entryTypePrefix}-advisory`;
347
+ const foldNoticeProjectionType = `${entryTypePrefix}-fold-notice`;
348
+ const memoryTocProjectionType = `${entryTypePrefix}-memory-toc`;
325
349
  const receiptProjectionType = `${entryTypePrefix}-receipts`;
326
- const riderProjectionType = `${entryTypePrefix}-rider`;
327
350
  const curationProjectionType = `${entryTypePrefix}-curation`;
328
351
  const entryNamespace = entryTypeNamespace(entryTypePrefix);
329
352
  const providerMeasurementEntryType = `${entryNamespace}-provider-context-measurement`;
330
353
  const nativeReceiptEntryType = `${entryNamespace}-native-compaction-receipt`;
331
354
  const contextEventEntryType = `${entryNamespace}-context-event`;
332
355
  const nativeDecisionEntryType = `${entryNamespace}-native-compaction-decision`;
333
- 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;
334
359
  const allowedToolActionSet = new Set<string>(allowedToolActions);
335
360
 
336
361
  type AutomaticFailureState = {
@@ -353,6 +378,10 @@ export function registerActiveContext(pi: any, options: {
353
378
  };
354
379
 
355
380
  const ladder = {
381
+ /** The provider measurement the last band-top commit ran against; one commit per one. */
382
+ bandTopMeasurement: null as unknown,
383
+ /** The snapshot-and-state key whose frontier scan came back empty; see advanceFoldFrontier. */
384
+ frontierEmptyKey: null as string | null,
356
385
  pendingManual: false,
357
386
  preparing: null as { id: string; controller: AbortController; promise: Promise<void> } | null,
358
387
  lastPreparationError: null as string | null,
@@ -401,7 +430,12 @@ export function registerActiveContext(pi: any, options: {
401
430
  previousText: null as string | null,
402
431
  lastChange: "append" as ProjectionChange,
403
432
  lastPreservedShare: null as number | null,
404
- sinceHandoff: [] as Array<{ seq: number; kind: string }>,
433
+ sinceHandoff: [] as Array<{
434
+ seq: number;
435
+ kind: string;
436
+ action?: string;
437
+ ok?: boolean;
438
+ }>,
405
439
  mutationsSinceHandoff: 0,
406
440
  requests: 0,
407
441
  lastMutationRequest: 0,
@@ -462,6 +496,13 @@ export function registerActiveContext(pi: any, options: {
462
496
  hardFenceReleasedProjectionKeys: new Set<string>(),
463
497
  };
464
498
 
499
+ // EPHEMERAL PEEK (Build 4b): tool call ids whose peek asked for one-read
500
+ // visibility. In-memory ON PURPOSE: a restart forgets the registry and the
501
+ // result stays durable, which is the safe default, and the durable entry
502
+ // always keeps the exact bytes, so folding, peek and rollback read the
503
+ // source unchanged whatever the projection shows.
504
+ const ephemeralPeeks = new Set<string>();
505
+
465
506
  const nativeCompaction = {
466
507
  lastThresholdDecision: null as Record<string, unknown> | null,
467
508
  pendingNativeReceipt: null as NativeCompactionCompletionReceipt | null,
@@ -540,7 +581,12 @@ export function registerActiveContext(pi: any, options: {
540
581
  revision: persistence.state?.revision ?? 0,
541
582
  at: Date.now(),
542
583
  }, payload);
543
- instrumentation.sinceHandoff.push({ seq: record.seq, kind });
584
+ instrumentation.sinceHandoff.push({
585
+ seq: record.seq,
586
+ kind,
587
+ ...(typeof record.action === "string" ? { action: record.action } : {}),
588
+ ...(typeof record.ok === "boolean" ? { ok: record.ok } : {}),
589
+ });
544
590
  const operation = curation.instrumentationQueue.then(async () => {
545
591
  try { await pi.appendEntry(contextEventEntryType, record); }
546
592
  catch { }
@@ -550,8 +596,13 @@ export function registerActiveContext(pi: any, options: {
550
596
  };
551
597
 
552
598
  const PREFIX_MUTATING_KINDS: ReadonlySet<string> = new Set([
553
- "context.commit", "context.fold", "context.absorb", "context.split", "context.recovery",
599
+ "context.commit", "context.fold", "context.absorb", "context.recovery",
554
600
  ]);
601
+ const CACHE_ACTION_REQUEST_CLASS: Readonly<Record<string, string>> = Object.freeze({
602
+ peek: "after-peek",
603
+ expand: "after-expand",
604
+ refold: "after-refold",
605
+ });
555
606
 
556
607
  const deliverReceipt = (receipt: ContextReceipt): void => {
557
608
  curation.receipts = withReceipt(curation.receipts, receipt);
@@ -561,8 +612,6 @@ export function registerActiveContext(pi: any, options: {
561
612
  folds_committed: receipt.foldsCommitted,
562
613
  folds_created: receipt.foldsCreated,
563
614
  freed_tokens: receipt.freedTokens,
564
- split_folds: receipt.splitFolds,
565
- split_from_chars: receipt.splitFromChars,
566
615
  absorbed_wedges: receipt.absorbedWedges,
567
616
  recovered: receipt.recovered,
568
617
  protected_bytes: receipt.protectedBytes,
@@ -868,15 +917,28 @@ export function registerActiveContext(pi: any, options: {
868
917
  if (next.folds.length > MAX_ACTIVE_FOLD_RECORDS) {
869
918
  throw new Error("Active-context fold closure exceeds persistence limit");
870
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.
871
932
  for (const fold of next.folds) {
872
- const record = makeFoldRecordEntry(fold, sessionId);
873
- const existing = persistence.persistedFoldRecords.get(record.foldId);
933
+ const ref = foldRecordRef(fold);
934
+ const existing = persistence.persistedFoldRecords.get(ref.id);
874
935
  if (existing) {
875
- if (existing.recordSha256 !== record.recordSha256) {
876
- 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}`);
877
938
  }
878
939
  continue;
879
940
  }
941
+ const record = makeFoldRecordEntry(fold, sessionId);
880
942
  await pi.appendEntry(foldRecordEntryType, record);
881
943
  persistence.persistedFoldRecords.set(record.foldId, record);
882
944
  if (!sessionIdentityStillValid(ctx, sessionId, generationAtStart)) {
@@ -885,13 +947,20 @@ export function registerActiveContext(pi: any, options: {
885
947
  }
886
948
  }
887
949
  const wire = persistence.persistedWireVersion === 2 ? makeStateDelta(persistence.persisted, next) : makeStateCheckpoint(next);
888
- 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) {
889
958
  throw new Error("Active-context durable base digest drift");
890
959
  }
891
960
  await pi.appendEntry(stateEntryType, wire);
892
961
  persistence.persisted = clone(next);
893
962
  persistence.persistedWireVersion = 2;
894
- persistence.persistedStateSha256 = semanticStateSha256(next);
963
+ persistence.persistedStateSha256 = wire.stateSha256;
895
964
  persistence.state = lifecycle.shuttingDown ? null : clone(next);
896
965
  if (!sessionIdentityStillValid(ctx, sessionId, generationAtStart)) {
897
966
  if (ctx && !contextSessionMatches(ctx, sessionId)) load(ctx);
@@ -1119,7 +1188,6 @@ export function registerActiveContext(pi: any, options: {
1119
1188
 
1120
1189
  const persistManual = async (
1121
1190
  next: ActiveContextState,
1122
- action: Exclude<ActiveContextToolAction, "status">,
1123
1191
  ctx: any,
1124
1192
  ): Promise<void> => {
1125
1193
  const stateAtEntry = persistence.state ? clone(persistence.state) : null;
@@ -1191,7 +1259,6 @@ export function registerActiveContext(pi: any, options: {
1191
1259
  policy: snapshot ? {
1192
1260
  maxTarget: snapshot.thresholds.maxTarget,
1193
1261
  minTarget: snapshot.thresholds.minTarget,
1194
- freshTail: snapshot.thresholds.freshTail,
1195
1262
  hardFenceRatio: hardFenceRatio(snapshot),
1196
1263
  } : null,
1197
1264
  });
@@ -1365,25 +1432,36 @@ export function registerActiveContext(pi: any, options: {
1365
1432
  };
1366
1433
  };
1367
1434
 
1368
- 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): {
1369
1446
  tokens: number;
1370
1447
  basis: ProjectionReadingBasis;
1371
1448
  chars: number;
1372
1449
  anchorTokens: number | null;
1373
1450
  deltaChars: number | null;
1451
+ text: string;
1374
1452
  } => {
1375
- const text = stableStringify(projected);
1453
+ const text = serialized ?? stableStringify(projected);
1376
1454
  const chars = Buffer.byteLength(text, "utf8");
1377
1455
  const charsPerToken = projectionCharsPerToken();
1378
1456
  const estimate = Math.ceil(chars / charsPerToken);
1379
1457
  const anchor = measurements.projectionAnchor;
1380
1458
  if (!anchor || anchor.generation !== lifecycle.generation ||
1381
1459
  anchor.sessionId !== persistence.state?.sessionId) {
1382
- return { tokens: estimate, basis: "unmeasured", chars, anchorTokens: null, deltaChars: null };
1460
+ return { tokens: estimate, basis: "unmeasured", chars, anchorTokens: null, deltaChars: null, text };
1383
1461
  }
1384
1462
  const separator = text.length > anchor.head.length ? text[anchor.head.length] : "";
1385
1463
  if (!text.startsWith(anchor.head) || (separator !== "," && separator !== "]")) {
1386
- return { tokens: estimate, basis: "rewritten", chars, anchorTokens: anchor.tokens, deltaChars: null };
1464
+ return { tokens: estimate, basis: "rewritten", chars, anchorTokens: anchor.tokens, deltaChars: null, text };
1387
1465
  }
1388
1466
  const deltaChars = chars - anchor.chars;
1389
1467
  return {
@@ -1392,6 +1470,7 @@ export function registerActiveContext(pi: any, options: {
1392
1470
  chars,
1393
1471
  anchorTokens: anchor.tokens,
1394
1472
  deltaChars,
1473
+ text,
1395
1474
  };
1396
1475
  };
1397
1476
 
@@ -1422,7 +1501,7 @@ export function registerActiveContext(pi: any, options: {
1422
1501
  estimatorErrorShare() * estimate + expectedInflowTokens(),
1423
1502
  ));
1424
1503
 
1425
- const projectionExceedsBudget = (projected: unknown[], ctx: any): {
1504
+ const projectionExceedsBudget = (projected: unknown[], ctx: any, serialized?: string): {
1426
1505
  tokens: number;
1427
1506
  sizeTokens: number;
1428
1507
  basis: ProjectionReadingBasis;
@@ -1430,12 +1509,13 @@ export function registerActiveContext(pi: any, options: {
1430
1509
  marginTokens: number;
1431
1510
  over: boolean;
1432
1511
  crowded: boolean;
1512
+ text: string;
1433
1513
  } => {
1434
1514
  const capacity = currentCapacity(ctx);
1435
1515
  const budgetTokens = Number.isFinite(capacity.budgetTokens) && capacity.budgetTokens > 0
1436
1516
  ? capacity.budgetTokens
1437
1517
  : Number.POSITIVE_INFINITY;
1438
- const reading = projectedTokenReading(projected);
1518
+ const reading = projectedTokenReading(projected, serialized);
1439
1519
  const tokens = reading.tokens;
1440
1520
  const sizeTokens = Math.ceil(reading.chars / projectionCharsPerToken());
1441
1521
  const marginTokens = Number.isFinite(budgetTokens)
@@ -1449,6 +1529,7 @@ export function registerActiveContext(pi: any, options: {
1449
1529
  marginTokens,
1450
1530
  over: tokens > budgetTokens,
1451
1531
  crowded: tokens + marginTokens > budgetTokens,
1532
+ text: reading.text,
1452
1533
  };
1453
1534
  };
1454
1535
 
@@ -1487,14 +1568,247 @@ export function registerActiveContext(pi: any, options: {
1487
1568
  ctx.abort();
1488
1569
  };
1489
1570
 
1571
+ // THE BAND TOP COMMITS ON ITS OWN (Shane, 2026-08-22).
1572
+ //
1573
+ // `maxTarget` is what the settings panel calls the commit trigger, and until now it
1574
+ // fired nothing. There were two commit sites: the fence below, which measures against
1575
+ // the WHOLE serving budget plus a margin, and the compaction boundary, which is Pi's
1576
+ // own threshold near the top of the window. A live 1M-window session sat at 571,367
1577
+ // tokens against a stated trigger of 516,096 with `folds: 0`, because neither site was
1578
+ // anywhere near. On a 251,520-token budget the fence sits close enough behind the
1579
+ // boundary to hide it; at 1M the two are half a million tokens apart.
1580
+ //
1581
+ // Shane, on the shape of the fix: "if the context is over our max, we commit. I don't
1582
+ // think that should be anything like once it crosses this threshold or anything like
1583
+ // that, it should just be an if statement. That way there isn't much room for error, if
1584
+ // it's over we fold." So there is no latch and no crossing to detect. Every projection
1585
+ // pass asks one question and acts on the answer. Occupancy only moves when the provider
1586
+ // measures, and a second attempt inside one handoff already defers on the mutation
1587
+ // budget, so "one commit per pass while over" is what this produces. A pass that finds
1588
+ // nothing to fold says so in the stream, which is a state worth seeing rather than one
1589
+ // worth suppressing.
1590
+ /**
1591
+ * Cut the raw material that has piled up past the last fold, and say so.
1592
+ *
1593
+ * This is the whole of "the runtime cuts": no commit, no bytes moved, no persistence
1594
+ * boundary. It stages pending marks the same way a commit's own top-up does, through
1595
+ * the same selector and the same brief builder, so a frontier cut and a commit fill are
1596
+ * the same object and the agent cannot tell them apart except by when it heard.
1597
+ */
1598
+ const advanceFoldFrontier = (snapshot: ActiveContextSnapshot): number => {
1599
+ if (!persistence.state) return 0;
1600
+ if (ladder.automaticFailure !== null) return 0;
1601
+ // A SCAN THAT FOUND NOTHING IS NOT RE-RUN UNTIL SOMETHING CHANGES. Exhausting the
1602
+ // selector costs one window scan per staged cut and one more to come back empty, and
1603
+ // once everything foldable is staged EVERY later pass pays that last scan to be told
1604
+ // the same thing. Measured on the 90-projection loop inside gate 50's instrumentation
1605
+ // claim: 137ms of a 460ms pass, 30 percent of the runtime's own per-request cost, for
1606
+ // an answer that cannot have changed.
1607
+ //
1608
+ // The key is the one `authoritativeSnapshotFor` already trusts for the snapshot
1609
+ // itself, plus the two pieces of state the selector reads: the revision, which moves
1610
+ // on every durable write, and the pending-mark count, which moves when the agent
1611
+ // withdraws one. It is only consulted when the last scan staged NOTHING; a scan that
1612
+ // staged something changed the state, so its key is stale by construction.
1613
+ const frontierKey = `${snapshot.messages.length}:${snapshot.mapped.length}:` +
1614
+ `${persistence.state.revision}:${pendingMarks(persistence.state).length}`;
1615
+ if (ladder.frontierEmptyKey === frontierKey) return 0;
1616
+ // CONSOLIDATION IS ANNOUNCED THE SAME WAY (Shane, 2026-08-23). When standing folds
1617
+ // reach `consolidateAfter` the parent that will absorb them is staged HERE rather than
1618
+ // being computed inside the commit, for exactly the reason the frontier exists: a
1619
+ // parent's brief indexes ten children, it is the entry the agent will navigate by, and
1620
+ // the agent knows what mattered in those children while the commit only knows their
1621
+ // order. Staged, it is byte-inert like any other pending mark and rides the same
1622
+ // notice, so there is one mechanism and not two.
1623
+ const marks = [
1624
+ ...frontierMarks({
1625
+ snapshot,
1626
+ state: persistence.state,
1627
+ ordinal: instrumentation.requests,
1628
+ }),
1629
+ ...consolidationMarks({
1630
+ snapshot,
1631
+ state: persistence.state,
1632
+ ordinal: instrumentation.requests,
1633
+ }),
1634
+ ];
1635
+ let state = persistence.state;
1636
+ const staged: ReturnType<typeof frontierMarks> = [];
1637
+ for (const mark of marks) {
1638
+ const addition = addPendingMark(state, mark);
1639
+ if (!addition.added) continue;
1640
+ state = addition.state;
1641
+ staged.push(mark);
1642
+ }
1643
+ // NOTHING STAGED, so this key is answered and the next pass under it can skip the
1644
+ // scan. Read from `staged` rather than from `marks`, because a candidate the pending
1645
+ // set already holds comes back from the selector every pass and is refused every
1646
+ // pass: keying on `marks` left the skip unreachable in exactly the steady state it
1647
+ // was built for, and half its measured saving on the floor.
1648
+ if (!staged.length) {
1649
+ ladder.frontierEmptyKey = frontierKey;
1650
+ return 0;
1651
+ }
1652
+ persistence.state = state;
1653
+ emit("context.frontier", {
1654
+ cut: staged.length,
1655
+ pending_marks: (state.pendingMarks ?? []).length,
1656
+ min_fold_chars: thresholds.minFoldChars,
1657
+ });
1658
+ // NO PERSISTENCE BOUNDARY OF ITS OWN, deliberately. A cut is DERIVED: the mark id
1659
+ // comes from the span, so a restart that lost these re-cuts exactly the same spans
1660
+ // with exactly the same ids, and the only thing that has to survive is a brief the
1661
+ // agent WROTE, which its own action persists along with the marks it rides with.
1662
+ // Persisting here instead cost a durable write on every projection pass, and the
1663
+ // branch entry it appended rebuilt the projection: gates 110 and 111 caught it as an
1664
+ // occupancy anchor that stopped reading the provider's own count.
1665
+ return staged.length;
1666
+ };
1667
+
1668
+ /**
1669
+ * Tell the agent what was cut, once there is enough of it to be worth a turn.
1670
+ *
1671
+ * It is built from LIVE state, so it names what is unbriefed at the pass it speaks on,
1672
+ * and it is admitted ONCE per frozen projection, so a pass that adds material leaves it
1673
+ * buried at its own index rather than moving it to the tail. Those two together mean it
1674
+ * is a snapshot rather than a running tally: cuts staged after it spoke wait for the
1675
+ * next pass that rebuilds the projection anyway, and that is the trade being made. A
1676
+ * carrier that re-renders to stay current has to move, moving costs the occupancy
1677
+ * anchor its provider count, and an anchor is worth more than a fold id arriving one
1678
+ * epoch early. What it must never do is ask twice for a brief the agent has written,
1679
+ * and it does not: the rebuild reads the marks, not its own last text.
1680
+ */
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;
1685
+ if (!persistence.state) return projected;
1686
+ const held = pendingMarks(persistence.state);
1687
+ const unbriefed = held.filter((mark) =>
1688
+ mark.briefProvenance?.kind !== "supplied" && mark.briefProvenance?.kind !== "augmented");
1689
+ // BATCHED, AND APPENDED ONCE. A notice per cut is noise, and noise is how guidance
1690
+ // gets ignored; the frontier cuts often and this speaks only when a batch has built
1691
+ // up. `carrierAdmitted` is what makes it an APPEND rather than a rewrite: once the
1692
+ // carrier is in the frozen projection it is not pushed again, so raw material
1693
+ // arriving afterwards lands after it and the prefix in front of it never moves.
1694
+ //
1695
+ // The first cut of this withdrew the carrier and re-pushed it at the tail every pass,
1696
+ // the way the steward band had to because a band must be able to stand down. That is
1697
+ // exactly wrong for this one: re-pushing moved the notice past each newly arrived
1698
+ // message, so the byte at its old index changed on a pass where nothing else had, and
1699
+ // gates 110 and 111 read the whole projection as rewritten and dropped the occupancy
1700
+ // anchor back to the estimate. A carrier that moves on a quiet pass costs the cache,
1701
+ // which is the one thing this design exists to avoid.
1702
+ if (unbriefed.length < UNBRIEFED_FOLDS_BEFORE_NOTICE) return projected;
1703
+ if (!carrierAdmitted("fold-notice")) return projected;
1704
+ const text = foldNoticeText({
1705
+ unbriefed: unbriefed.map((mark) => ({
1706
+ id: mark.id,
1707
+ kind: mark.kind,
1708
+ tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
1709
+ brief: typeof mark.brief === "string" ? mark.brief : undefined,
1710
+ })),
1711
+ pending: held.length,
1712
+ toolName,
1713
+ brandNoun,
1714
+ });
1715
+ emit("context.frontier", {
1716
+ cut: 0,
1717
+ pending_marks: held.length,
1718
+ unbriefed: unbriefed.length,
1719
+ notice_chars: text.length,
1720
+ });
1721
+ projected.push({
1722
+ role: "custom",
1723
+ customType: foldNoticeProjectionType,
1724
+ content: text,
1725
+ display: false,
1726
+ details: { source: activeContextSource(entryTypePrefix), ephemeral: true },
1727
+ timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
1728
+ ? ownValue(snapshot.messages.at(-1), "timestamp")
1729
+ : 0,
1730
+ });
1731
+ return projected;
1732
+ };
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
+
1770
+ const enforceBandTop = async (
1771
+ snapshot: ActiveContextSnapshot,
1772
+ ctx: any,
1773
+ ): Promise<boolean> => {
1774
+ if (!epochCommitDue(snapshot, measurements.latestRatio)) return false;
1775
+ // ONE COMMIT PER MEASUREMENT, which is what "one commit per crossing" means once the
1776
+ // commit stops taking everything. There was no latch here and none was needed: the
1777
+ // band-top commit folded every span the class law offered, so the next pass found
1778
+ // nothing proposable and deferred. The 2026-08-23 depth bound ends that, on purpose,
1779
+ // and without this the parked window commits a SLICE on every pass at the same
1780
+ // declared occupancy, which is a prefix rewrite per pass and the exact cache cost the
1781
+ // one-commit rule exists to prevent. Occupancy is anchored to the provider's own
1782
+ // count, so a new count is the only evidence that the window actually moved; the
1783
+ // latch is keyed to that measurement and nothing else.
1784
+ if (ladder.bandTopMeasurement !== null &&
1785
+ ladder.bandTopMeasurement === measurements.lastProviderMeasurement) {
1786
+ return false;
1787
+ }
1788
+ ladder.bandTopMeasurement = measurements.lastProviderMeasurement;
1789
+ let action: Record<string, unknown> | null = null;
1790
+ try {
1791
+ // NULL RATIO, deliberately. The band top is routine housekeeping at a stated
1792
+ // threshold, not an emergency, and a null ratio is what arms the depth bound: the
1793
+ // commit takes stalest-first and stops at the aim, so the newest events stay raw
1794
+ // for as long as there is room for them. The fence and the compaction boundary
1795
+ // carry a ratio and no bound, because something has already gone wrong there.
1796
+ action = await attemptAutomaticCommit(snapshot, ctx, "band-top", null);
1797
+ } catch (error) {
1798
+ suspendAutomatic(error, "band-top", ctx);
1799
+ return false;
1800
+ }
1801
+ return action !== null;
1802
+ };
1803
+
1490
1804
  const enforceProjectionBudget = async (
1491
1805
  snapshot: ActiveContextSnapshot,
1492
1806
  projected: unknown[],
1493
1807
  ctx: any,
1494
- ): Promise<{ projected: unknown[]; aborted: boolean }> => {
1808
+ ): Promise<{ projected: unknown[]; aborted: boolean; text: string }> => {
1495
1809
  let measured = projectionExceedsBudget(projected, ctx);
1496
1810
  const rejected = curation.pendingRejection !== null;
1497
- if (!measured.crowded && !rejected) return { projected, aborted: false };
1811
+ if (!measured.crowded && !rejected) return { projected, aborted: false, text: measured.text };
1498
1812
  const trigger = measured;
1499
1813
  let reduced = projected;
1500
1814
  let attempts = 0;
@@ -1503,9 +1817,10 @@ export function registerActiveContext(pi: any, options: {
1503
1817
  attempts += 1;
1504
1818
  // THE EMERGENCY IS THE REQUEST THAT WILL NOT FIT, and only that. A projection at
1505
1819
  // the margin is ordinary work: the fence commits it because it is the pass holding
1506
- // the request, but it is not a recovery, it does not waive the current-turn guard,
1507
- // and it does not buy an exemption from the reclaim floor. Waiving at the margin
1508
- // spends the open turn's own evidence on headroom the boundary was about to buy.
1820
+ // the request, but it is not a recovery and it does not buy an exemption from the
1821
+ // reclaim floor. What separates it from the band top is depth alone: the fence
1822
+ // carries a ratio, so its commit takes everything eligible rather than stopping at
1823
+ // the aim.
1509
1824
  const emergency = measured.over || rejected;
1510
1825
  if (emergency) curation.recoveryAttempts += 1;
1511
1826
  let action: Record<string, unknown> | null = null;
@@ -1597,9 +1912,11 @@ export function registerActiveContext(pi: any, options: {
1597
1912
  }));
1598
1913
  curation.pendingRejection = null;
1599
1914
  }
1600
- 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 };
1601
1918
  abortOverBudgetProjection(measured.tokens, measured.budgetTokens, ctx, attempts);
1602
- return { projected: reduced, aborted: true };
1919
+ return { projected: reduced, aborted: true, text: measured.text };
1603
1920
  };
1604
1921
 
1605
1922
  const startPreparation = (snapshot: ActiveContextSnapshot, ratio: number | null, ctx: any): void => {
@@ -1678,69 +1995,148 @@ export function registerActiveContext(pi: any, options: {
1678
1995
  return projected;
1679
1996
  };
1680
1997
 
1681
- const appendRider = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
1682
- const rider = persistence.state?.rider;
1683
- if (!rider) return projected;
1684
- if (!carrierAdmitted("rider")) return projected;
1685
- projected.push({
1686
- role: "custom",
1687
- customType: riderProjectionType,
1688
- content: rider.text,
1689
- display: false,
1690
- details: {
1691
- source: activeContextSource(entryTypePrefix),
1692
- ephemeral: true,
1693
- riderEpoch: rider.epoch,
1694
- },
1695
- timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
1696
- ? ownValue(snapshot.messages.at(-1), "timestamp")
1697
- : 0,
1698
- });
1699
- return projected;
1700
- };
1701
-
1702
1998
  const holdFrozen = (projected: unknown[]): unknown[] => {
1703
1999
  freeze.projection = [...projected];
1704
2000
  freeze.active = false;
1705
2001
  return projected;
1706
2002
  };
1707
2003
 
2004
+ // An ephemeral peek result rides the projection exactly until the model's
2005
+ // next message, the same answered reading as the directed ask, then its
2006
+ // index holds a one-line placeholder and the reply is the surviving trace.
2007
+ // A message already projection-shaped (a fold's own deterministic brief
2008
+ // placeholder carries the same toolCallId) is never touched: only the raw
2009
+ // result is the ephemeral one.
2010
+ //
2011
+ // Returns what it replaced, keyed by call id, because the freeze has to be
2012
+ // told. Running against the BODY alone was not enough: the freeze compares
2013
+ // this pass's body prefix against the previous one and reuses the previous
2014
+ // PROJECTION when they match, and a withdrawal edits a body index inside
2015
+ // that prefix, so the comparison failed and the whole projection was rebuilt
2016
+ // from the body. The rebuild drops every receipt the freeze had
2017
+ // buried mid-array over the session, which moves divergence back to the
2018
+ // FIRST buried carrier rather than leaving it at the withdrawn index. That
2019
+ // is the opposite of what this mechanism is for: a tail edit was rewriting
2020
+ // the session's whole cached prefix.
2021
+ const withdrawConsumedEphemeralPeeks = (body: unknown[]): Map<string, unknown> => {
2022
+ const withdrawn = new Map<string, unknown>();
2023
+ if (!ephemeralPeeks.size) return withdrawn;
2024
+ for (let index = 0; index < body.length; index += 1) {
2025
+ const message = body[index];
2026
+ const callId = ownValue(message, "toolCallId");
2027
+ if (ownValue(message, "role") !== "toolResult" || typeof callId !== "string" ||
2028
+ !ephemeralPeeks.has(callId)) continue;
2029
+ const details = ownValue(message, "details");
2030
+ if (details && typeof ownValue(details, "projection") === "string") continue;
2031
+ const answered = body.slice(index + 1).some((later) =>
2032
+ ownValue(later, "role") === "assistant");
2033
+ if (!answered) continue;
2034
+ const placeholder = {
2035
+ role: "toolResult",
2036
+ toolCallId: callId,
2037
+ toolName: ownValue(message, "toolName"),
2038
+ content: [{ type: "text", text: "Ephemeral peek consumed; the reply that followed it is the surviving trace." }],
2039
+ isError: false,
2040
+ details: { projection: "ephemeral-peek-consumed" },
2041
+ timestamp: ownValue(message, "timestamp"),
2042
+ };
2043
+ body[index] = placeholder;
2044
+ withdrawn.set(callId, placeholder);
2045
+ }
2046
+ return withdrawn;
2047
+ };
2048
+
2049
+ // Carry a withdrawal into the freeze's own kept arrays so the frozen prefix
2050
+ // stays usable. Matched by CALL ID rather than by position: the projection
2051
+ // interleaves buried carriers, so a body index is not a projection index.
2052
+ // The substitution is identity-preserving on role, call id, tool name and
2053
+ // timestamp, so what changes is the one result's content and nothing else,
2054
+ // and divergence lands at the withdrawn index where the mechanism puts it.
2055
+ const carryWithdrawalIntoFreeze = (messages: unknown[], withdrawn: Map<string, unknown>): void => {
2056
+ for (let index = 0; index < messages.length; index += 1) {
2057
+ const message = messages[index];
2058
+ if (ownValue(message, "role") !== "toolResult") continue;
2059
+ const callId = ownValue(message, "toolCallId");
2060
+ if (typeof callId !== "string") continue;
2061
+ const placeholder = withdrawn.get(callId);
2062
+ if (!placeholder) continue;
2063
+ const details = ownValue(message, "details");
2064
+ if (details && typeof ownValue(details, "projection") === "string") continue;
2065
+ messages[index] = placeholder;
2066
+ }
2067
+ };
2068
+
1708
2069
  const projectWithAdvisory = (snapshot: ActiveContextSnapshot): unknown[] => {
1709
2070
  const body = projectActiveContext(snapshot, persistence.state!).filter((message) => {
1710
2071
  const customType = ownValue(message, "customType");
1711
2072
  return customType !== milestoneProjectionType && customType !== advisoryProjectionType &&
1712
- customType !== receiptProjectionType &&
1713
- customType !== riderProjectionType && customType !== curationProjectionType;
2073
+ customType !== receiptProjectionType && customType !== curationProjectionType &&
2074
+ customType !== foldNoticeProjectionType && customType !== memoryTocProjectionType;
1714
2075
  });
2076
+ const withdrawn = withdrawConsumedEphemeralPeeks(body);
2077
+ if (withdrawn.size && freeze.body) {
2078
+ carryWithdrawalIntoFreeze(freeze.body, withdrawn);
2079
+ freeze.bodyText = stableStringify(freeze.body);
2080
+ if (freeze.projection) carryWithdrawalIntoFreeze(freeze.projection, withdrawn);
2081
+ }
1715
2082
  const held = freeze.body?.length ?? 0;
1716
- freeze.active = freeze.projection !== null && body.length >= held &&
1717
- 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);
1718
2094
  const projected = freeze.active
1719
2095
  ? [...freeze.projection!, ...body.slice(held)]
1720
2096
  : [...body];
1721
2097
  if (!freeze.active) freeze.keys.clear();
1722
2098
  freeze.body = body;
1723
- freeze.bodyText = stableStringify(body);
2099
+ freeze.bodyText = bodyText;
1724
2100
  appendReceipts(projected, snapshot);
1725
- appendRider(projected, snapshot);
2101
+ appendFoldNotice(projected, snapshot);
2102
+ appendMemoryToc(projected, snapshot);
1726
2103
  return holdFrozen(projected);
1727
2104
  };
1728
- const noteProjection = (projected: unknown[]): void => {
2105
+ const noteProjection = (projected: unknown[], serialized?: string): void => {
1729
2106
  const digests = messageDigests(projected);
1730
2107
  const comparison = compareProjections(instrumentation.previousDigests, digests);
1731
2108
  recordProjection(instrumentation.ledger, comparison, digests);
1732
2109
  instrumentation.previousDigests = digests;
1733
2110
  instrumentation.lastChange = comparison.change;
1734
- const text = stableStringify(projected);
2111
+ const text = serialized ?? stableStringify(projected);
1735
2112
  const divergence = prefixDivergence(instrumentation.previousText, text);
1736
2113
  const previousChars = instrumentation.previousText?.length ?? 0;
1737
2114
  instrumentation.lastPreservedShare = previousChars > 0
1738
2115
  ? divergence.identicalChars / previousChars
1739
2116
  : null;
1740
2117
  const charsPerToken = projectionCharsPerToken();
2118
+ const successfulCacheActions = instrumentation.sinceHandoff.filter((event) =>
2119
+ event.kind === "context.attempt" && event.ok === true &&
2120
+ typeof event.action === "string" && CACHE_ACTION_REQUEST_CLASS[event.action]);
2121
+ const latestCacheAction = successfulCacheActions.at(-1) ?? null;
2122
+ const latestCacheActionClass = latestCacheAction?.action
2123
+ ? CACHE_ACTION_REQUEST_CLASS[latestCacheAction.action]
2124
+ : null;
2125
+ const projectionActions = successfulCacheActions.filter((event) =>
2126
+ event.action === "expand" || event.action === "refold");
1741
2127
  const causes = instrumentation.sinceHandoff.filter((event) =>
1742
- PREFIX_MUTATING_KINDS.has(event.kind));
1743
- const reading = projectedTokenReading(projected);
2128
+ PREFIX_MUTATING_KINDS.has(event.kind) || projectionActions.includes(event));
2129
+ const structuralCause = causes.some((event) => event.kind !== "context.attempt");
2130
+ const requestClass = causes.some((event) => event.kind === "context.recovery")
2131
+ ? "after-rollback"
2132
+ : (latestCacheAction?.action === "expand" || latestCacheAction?.action === "refold")
2133
+ ? latestCacheActionClass!
2134
+ : structuralCause
2135
+ ? "after-fold"
2136
+ : latestCacheActionClass
2137
+ ? latestCacheActionClass
2138
+ : divergence.index === null ? "steady-state" : "after-message";
2139
+ const reading = projectedTokenReading(projected, text);
1744
2140
  emit("context.projection", {
1745
2141
  change: comparison.change,
1746
2142
  previous_count: comparison.previousCount,
@@ -1768,14 +2164,14 @@ export function registerActiveContext(pi: any, options: {
1768
2164
  estimated_tokens: Math.ceil(text.length / charsPerToken),
1769
2165
  cause: divergence.index === null
1770
2166
  ? "pure-append"
1771
- : (causes.length ? causes.map((event) => event.kind).join(",") : "unattributed"),
2167
+ : (causes.length ? causes.map((event) => event.kind === "context.attempt"
2168
+ ? `context.attempt:${String(event.action)}`
2169
+ : event.kind).join(",") : "unattributed"),
1772
2170
  cause_event_seqs: causes.map((event) => event.seq).join(","),
1773
2171
  events_since_handoff: instrumentation.sinceHandoff.length,
1774
- request_class: divergence.index === null
1775
- ? "steady-state"
1776
- : (causes.some((event) => event.kind === "context.recovery")
1777
- ? "after-rollback"
1778
- : (causes.length ? "after-fold" : "after-message")),
2172
+ cache_action: latestCacheAction?.action ?? null,
2173
+ cache_action_seq: latestCacheAction?.seq ?? null,
2174
+ request_class: requestClass,
1779
2175
  });
1780
2176
  instrumentation.previousText = text;
1781
2177
  instrumentation.sinceHandoff = [];
@@ -1786,6 +2182,7 @@ export function registerActiveContext(pi: any, options: {
1786
2182
  snapshot: ActiveContextSnapshot;
1787
2183
  candidate: FoldCandidate;
1788
2184
  brief?: string;
2185
+ briefProvenance?: "supplied" | "deterministic" | BriefProvenance;
1789
2186
  ctx: any;
1790
2187
  signal?: AbortSignal;
1791
2188
  maximumSourceChars?: number;
@@ -1803,6 +2200,7 @@ export function registerActiveContext(pi: any, options: {
1803
2200
  state: baseState,
1804
2201
  generation: generationAtStart,
1805
2202
  brief: input.brief,
2203
+ briefProvenance: input.briefProvenance,
1806
2204
  ctx: input.ctx,
1807
2205
  signal: input.signal,
1808
2206
  });
@@ -1852,6 +2250,7 @@ export function registerActiveContext(pi: any, options: {
1852
2250
  let state = persistence.state!;
1853
2251
  let peekAdded = 0;
1854
2252
  let topUpAdded = 0;
2253
+ let refoldAdded = 0;
1855
2254
  let consolidationAdded = 0;
1856
2255
  let closingAdded = 0;
1857
2256
  for (const mark of ephemeralPeekMarks({ snapshot, state, ordinal })) {
@@ -1862,7 +2261,6 @@ export function registerActiveContext(pi: any, options: {
1862
2261
  const addition = addPendingMark(state, mark);
1863
2262
  if (addition.added) { state = addition.state; consolidationAdded += 1; }
1864
2263
  }
1865
- const guarded = currentTurnRefKeys(snapshot);
1866
2264
  const capacity = servingCapacity(snapshot.contextWindow);
1867
2265
  const usedTokens = capacity.usedTokens;
1868
2266
  const budgetTokens = capacity.budgetTokens;
@@ -1879,43 +2277,67 @@ export function registerActiveContext(pi: any, options: {
1879
2277
  });
1880
2278
  return null;
1881
2279
  }
1882
- const freeingTarget = usedTokens === null || budgetTokens <= 0
1883
- ? 0
1884
- : Math.max(0, (usedTokens - thresholds.minTarget * budgetTokens) / budgetTokens);
1885
- if (topUp) {
1886
- for (const mark of topUpMarks({
1887
- snapshot,
1888
- state,
1889
- ordinal,
1890
- eligibleOnly: true,
1891
- targetShare: freeingTarget,
1892
- })) {
2280
+ // ONE DEFINITION OF THE DROP. The fill sizes itself to it, the advisory warns
2281
+ // against it, and the mark response answers "how much more" with it.
2282
+ const freeingTarget = commitCoverage({ snapshot, state, usedTokens, budgetTokens }).targetShare;
2283
+ // ONE COMMIT, SIZED BEFORE IT RUNS (Shane, 2026-08-21). At the threshold we know how
2284
+ // far the window has to come down: `freeingTarget` is the distance from what is used
2285
+ // now to the minTarget floor. The agent's marks count toward that distance first and
2286
+ // the ladder fills the remainder from the stale end, so an agent that marked 30k of a
2287
+ // 100k drop leaves 70k for the fill and both land in the SAME commit.
2288
+ //
2289
+ // Three rules decide the fill, and each replaced something that was tried first:
2290
+ //
2291
+ // The ladder does not STAND DOWN when the agent has marked. Standing down commits at
2292
+ // whatever depth the agent happened to reach, so one small mark commits a barely-moved
2293
+ // window and the next fence pass fires a second commit in the same cycle, which is the
2294
+ // cache cost the one-commit rule exists to prevent. `topUpMarks` measures progress
2295
+ // against the state it is handed, so agent marks are counted rather than displaced,
2296
+ // and `claimedRefKeys` keeps the fill off spans the agent already took.
2297
+ //
2298
+ // Raw stale material is spent BEFORE expanded folds, so an agent reading an expanded
2299
+ // fold keeps it while anything else can cover the drop. This is also the only route
2300
+ // to the refold rung now that nothing stages between commits.
2301
+ //
2302
+ // The fill measures progress over EVERY mark it makes, not just the ones this commit
2303
+ // will apply. Measuring the eligible share alone is what a starved commit needs and it
2304
+ // is wrong here: a mark over protected evidence moves the eligible share by nothing,
2305
+ // so the loop kept marking and walked a whole 24-batch excursion chasing a target it
2306
+ // could not reach that way (the retainer was the current-turn guard then; pins and
2307
+ // protected evidence retain the same way now). Counting everything bounds the fill at
2308
+ // the drop it was asked for. The backstop still reads the eligible share, because
2309
+ // starvation is the one state where the difference is the point.
2310
+ const fill = (targetShare: number, eligibleOnly: boolean): void => {
2311
+ for (const mark of topUpMarks({ snapshot, state, ordinal, eligibleOnly, targetShare })) {
1893
2312
  const addition = addPendingMark(state, mark);
1894
2313
  if (addition.added) { state = addition.state; topUpAdded += 1; }
1895
2314
  }
2315
+ for (const mark of refoldMarks({ snapshot, state, ordinal, eligibleOnly, targetShare })) {
2316
+ const addition = addPendingMark(state, mark);
2317
+ if (addition.added) { state = addition.state; refoldAdded += 1; }
2318
+ }
2319
+ };
2320
+ let backstopFired = false;
2321
+ if (topUp) {
2322
+ fill(freeingTarget, false);
1896
2323
  const reachedShare = markAccounting(snapshot, state).eligibleFreedBudgetShare;
1897
2324
  const shallow = reachedShare < Math.max(commitDepthFloorShare(snapshot), freeingTarget);
1898
2325
  if (shallow && atOrAboveBackstop(snapshot, waiverRatio)) {
1899
- for (const mark of topUpMarks({
1900
- snapshot,
1901
- state,
1902
- ordinal,
1903
- eligibleOnly: true,
1904
- targetShare: 1,
1905
- })) {
1906
- const addition = addPendingMark(state, mark);
1907
- if (addition.added) { state = addition.state; topUpAdded += 1; }
1908
- }
2326
+ backstopFired = true;
2327
+ fill(1, true);
1909
2328
  }
1910
2329
  }
1911
2330
  const wedges = absorbWedgeMarks({
1912
2331
  snapshot,
1913
2332
  state,
1914
2333
  charsPerToken: projectionCharsPerToken(),
1915
- excludeRefKeys: guarded,
1916
2334
  });
1917
2335
  state = wedges.state;
1918
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);
1919
2341
  if (!accounting.pending) {
1920
2342
  const remainder = unmarkedRemainder(snapshot, state, projectionCharsPerToken());
1921
2343
  emit("context.commit", {
@@ -1942,51 +2364,100 @@ export function registerActiveContext(pi: any, options: {
1942
2364
  eligible_marks: accounting.eligibleMarks,
1943
2365
  eligible_freed_share: accounting.eligibleFreedBudgetShare,
1944
2366
  reclaim_floor_share: COMMIT_RECLAIM_FLOOR_SHARE,
2367
+ // THE REMAINDER RIDES EVERY DEFERRAL, not just the empty one. It was on
2368
+ // `nothing-proposable` alone, which was the only starved shape while staging
2369
+ // happened at commit time. The frontier cuts ahead of the commit, so a starved
2370
+ // pass now usually arrives holding a mark it cannot apply and lands HERE instead,
2371
+ // and "below the reclaim floor" without the remainder does not tell a reader
2372
+ // whether the mass is held or simply absent. That is the distinction the whole
2373
+ // record exists to draw.
2374
+ unmarked_stale_spans: unmarkedRemainder(snapshot, state, projectionCharsPerToken()).spans,
1945
2375
  window_tokens: snapshot.contextWindow,
1946
2376
  });
1947
2377
  return null;
1948
2378
  }
1949
2379
  const bytesBefore = bytes(projectActiveContext(snapshot, state));
1950
- const guardWaiver = guardWaiverCount({
1951
- snapshot,
1952
- ratio: waiverRatio,
1953
- boundary: trigger === "compaction-boundary",
1954
- guardedMarks: pendingMarks(state).filter((mark) =>
1955
- markTouchesCurrentTurn(state, mark, guarded)).length,
1956
- otherApplicableMarks: pendingMarks(state).filter((mark) =>
1957
- !markTouchesCurrentTurn(state, mark, guarded) &&
1958
- markEligibility(snapshot, state, mark) === "eligible").length,
1959
- });
2380
+ // THE COMMIT CUTS ONLY AS DEEP AS THE BUDGET ASKS (Shane, 2026-08-23). The frontier
2381
+ // stages as material arrives, so by now the pending set is most of the window; without
2382
+ // this bound the first commit would fold all of it. `freeingTarget` is a share of the
2383
+ // budget and the bound is bytes, so it is converted once, here, through the same ratio
2384
+ // the projection weighs itself with.
2385
+ //
2386
+ // It binds on the ROUTINE path only: a null ratio is the band top, ordinary
2387
+ // housekeeping at a stated threshold, and that is where "only as deep as it needs"
2388
+ // belongs. The fence and the compaction boundary carry a ratio because something has
2389
+ // already gone wrong there, and a lane holding a request that will not fit is not the
2390
+ // place to leave depth on the table. The backstop is exempt for the same reason: it
2391
+ // fires only when the commit is already starving.
2392
+ //
2393
+ // This bound is also what protects the working set since the current-turn guard was
2394
+ // deleted (Shane, 2026-08-23, from the first live session): the cut takes stalest
2395
+ // first and stops at the aim, so the newest events stay raw exactly as long as there
2396
+ // is room for them, in every session shape including one that never closes a turn.
1960
2397
  let result = await commitPendingMarks({
1961
2398
  snapshot,
1962
2399
  state,
1963
2400
  generation: lifecycle.generation,
1964
2401
  retainIneligible: true,
1965
- guardCurrentTurn: true,
1966
- guardWaiver,
2402
+ ...(backstopFired || waiverRatio !== null ? {} : {
2403
+ applyTargetBytes: Math.ceil(freeingTarget * budgetTokens * projectionCharsPerToken()),
2404
+ }),
1967
2405
  });
1968
2406
  for (;;) {
2407
+ // THE CLOSING PASS COMMITS ONLY WHAT IT SEATS (2026-08-23). It rides the same paid
2408
+ // rewrite to give a group its parent, and it runs UNBOUNDED because a parent's whole
2409
+ // span must land together. Handing it the full pending set therefore leaked the depth
2410
+ // cut: the marks the bound had just retained were still pending, the closing commit
2411
+ // carried no bound, and the first band-top commit after the guard deletion folded all
2412
+ // 24 of them to the floor (gate 09's fixture, applied 26 of 24 with deferred 0). The
2413
+ // marks the cut held back are set aside before the closing commit and restored after,
2414
+ // so the only thing an unbounded closing pass can spend is the parents it just made.
1969
2415
  let closing = result.state;
2416
+ const heldBack = pendingMarks(closing);
2417
+ let seeded = withPendingMarks(closing, []);
2418
+ let seededAdded = 0;
1970
2419
  for (const mark of consolidationMarks({ snapshot, state: closing, ordinal })) {
1971
2420
  const addition = addPendingMark(closing, mark);
1972
- if (addition.added) { closing = addition.state; closingAdded += 1; }
2421
+ if (!addition.added) continue;
2422
+ closing = addition.state;
2423
+ const carried = addPendingMark(seeded, mark);
2424
+ if (carried.added) { seeded = carried.state; closingAdded += 1; seededAdded += 1; }
1973
2425
  }
1974
- if (closing === result.state) break;
2426
+ if (!seededAdded) break;
1975
2427
  const closed = await commitPendingMarks({
1976
2428
  snapshot,
1977
- state: closing,
2429
+ state: seeded,
1978
2430
  generation: lifecycle.generation,
1979
2431
  retainIneligible: true,
1980
- guardCurrentTurn: true,
1981
2432
  });
1982
2433
  if (!closed.applied.length) break;
1983
2434
  result = {
1984
2435
  ...closed,
2436
+ state: withPendingMarks(closed.state, [...pendingMarks(closed.state), ...heldBack]),
1985
2437
  applied: [...result.applied, ...closed.applied],
1986
2438
  refused: [...result.refused.filter((mark) => !mark.retained), ...closed.refused],
1987
- waived: [...result.waived, ...closed.waived],
2439
+ retained: [...result.retained.filter((mark) =>
2440
+ !closed.applied.some((appliedMark) => appliedMark.id === mark.id)), ...closed.retained],
1988
2441
  };
1989
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
+ }
1990
2461
  persistence.state = result.state;
1991
2462
  const bytesAfter = bytes(projectActiveContext(snapshot, persistence.state));
1992
2463
  const freedBytes = Math.max(0, bytesBefore - bytesAfter);
@@ -2012,18 +2483,19 @@ export function registerActiveContext(pi: any, options: {
2012
2483
  applied_marks: result.applied.length,
2013
2484
  refused_marks: result.refused.filter((mark) => !mark.retained).length,
2014
2485
  deferred_marks: result.retained.length,
2015
- waived_marks: result.waived.length,
2016
2486
  pending_marks: accounting.pending,
2017
2487
  agent_marks: accounting.agentMarks,
2018
2488
  ladder_marks: accounting.ladderMarks,
2019
2489
  peek_marks: peekAdded,
2020
2490
  topup_marks: topUpAdded,
2491
+ refold_marks: refoldAdded,
2021
2492
  consolidation_marks: consolidationAdded,
2022
2493
  closing_consolidation_marks: closingAdded,
2023
2494
  absorbed_wedges: wedges.absorbed.length,
2495
+ clipped_results: clippedResults,
2024
2496
  freed_bytes: freedBytes,
2025
2497
  freed_tokens: estimatedTokens(freedBytes),
2026
- rewrite_tokens: accounting.rewriteTokens,
2498
+ rewrite_tokens: rewriteTokens,
2027
2499
  pinned_bytes: accounting.pinnedBytes,
2028
2500
  pinned_results: accounting.pinnedResults,
2029
2501
  protected_stale_bytes: pinHeld.bytes,
@@ -2033,36 +2505,6 @@ export function registerActiveContext(pi: any, options: {
2033
2505
  instrumentation.mutationsSinceHandoff += 1;
2034
2506
  instrumentation.lastMutationRequest = instrumentation.requests;
2035
2507
  instrumentation.lastMutationTokens = usedTokens;
2036
- const riderEpoch = commitEvent.seq;
2037
- if (persistence.state.rider?.epoch !== riderEpoch) {
2038
- const postAccounting = markAccounting(snapshot, persistence.state);
2039
- const remainder = unmarkedRemainder(snapshot, persistence.state, projectionCharsPerToken());
2040
- const pinnedMass = explicitProtectedMass(snapshot, persistence.state);
2041
- const pinnedShare = budgetTokens > 0
2042
- ? estimatedTokens(pinnedMass.bytes) / budgetTokens
2043
- : 0;
2044
- const riderText = contextRiderText({
2045
- toolName,
2046
- brandNoun,
2047
- pendingAgentMarks: postAccounting.agentMarks,
2048
- eligibleMarks: postAccounting.eligibleMarks,
2049
- freedTokens: postAccounting.freedTokens,
2050
- eligibleFreedTokens: postAccounting.eligibleFreedTokens,
2051
- anchors: remainder.candidates.slice(0, 3).map((candidate) => candidate.id),
2052
- pinnedShare,
2053
- maxPinnedShare: MAX_PINNED_SHARE,
2054
- });
2055
- persistence.state = { ...persistence.state, rider: { epoch: riderEpoch, text: riderText } };
2056
- emit("context.rider", {
2057
- epoch: riderEpoch,
2058
- chars: riderText.length,
2059
- anchors: Math.min(3, remainder.candidates.length),
2060
- pending_agent_marks: postAccounting.agentMarks,
2061
- eligible_marks: postAccounting.eligibleMarks,
2062
- pinned_share: pinnedShare,
2063
- max_pinned_share: MAX_PINNED_SHARE,
2064
- });
2065
- }
2066
2508
  let foldedToolResults = 0;
2067
2509
  let foldedSpans = 0;
2068
2510
  for (const applied of result.applied) {
@@ -2093,7 +2535,8 @@ export function registerActiveContext(pi: any, options: {
2093
2535
  end_id: wedge.endId,
2094
2536
  entries: wedge.entries,
2095
2537
  tokens: wedge.tokens,
2096
- threshold_tokens: MAX_WEDGE_ABSORB_TOKENS,
2538
+ chars: wedge.chars,
2539
+ threshold_chars: thresholds.minFoldChars,
2097
2540
  });
2098
2541
  }
2099
2542
  const epoch = {
@@ -2105,6 +2548,7 @@ export function registerActiveContext(pi: any, options: {
2105
2548
  ladderMarks: accounting.ladderMarks,
2106
2549
  peekMarks: peekAdded,
2107
2550
  topUpMarks: topUpAdded,
2551
+ refoldMarks: refoldAdded,
2108
2552
  consolidationMarks: consolidationAdded,
2109
2553
  closingMarks: closingAdded,
2110
2554
  absorbedWedges: wedges.absorbed.length,
@@ -2114,8 +2558,6 @@ export function registerActiveContext(pi: any, options: {
2114
2558
  occupancyTokensBefore: usedTokens,
2115
2559
  depthFloorShare: commitDepthFloorShare(snapshot),
2116
2560
  reclaimFloorShare: COMMIT_RECLAIM_FLOOR_SHARE,
2117
- guardWaived: result.waived.length > 0,
2118
- waivedMarks: result.waived.length,
2119
2561
  appliedMarks: result.applied.length,
2120
2562
  refusedMarks: result.refused.filter((mark) => !mark.retained).length,
2121
2563
  deferredMarks: result.retained.length,
@@ -2124,10 +2566,8 @@ export function registerActiveContext(pi: any, options: {
2124
2566
  protectedStaleBytes: pinHeld.bytes,
2125
2567
  protectedStaleRefs: pinHeld.refs,
2126
2568
  retainedMarks: result.retained.length,
2127
- currentTurnRetained: result.retained.filter((mark) =>
2128
- markTouchesCurrentTurn(state, mark, guarded)).length,
2129
2569
  eligibleMarks: accounting.eligibleMarks,
2130
- estimatedRewriteTokens: accounting.rewriteTokens,
2570
+ estimatedRewriteTokens: rewriteTokens,
2131
2571
  estimatedFreedTokens: accounting.freedTokens,
2132
2572
  freedBudgetShare: accounting.freedBudgetShare,
2133
2573
  sourceBytesSaved: freedBytes,
@@ -2177,8 +2617,6 @@ export function registerActiveContext(pi: any, options: {
2177
2617
  occupancyAfter: occupancyBefore === null ? null : Math.max(0, occupancyBefore - freedTokens),
2178
2618
  spansFolded: epoch ? Number(epoch.foldedSpans ?? 0) : foldIds,
2179
2619
  toolResultsFolded: epoch ? Number(epoch.foldedToolResults ?? 0) : 0,
2180
- splitFolds: Number(action.splitFolds ?? 0),
2181
- splitFromChars: Number(action.splitFromChars ?? 0),
2182
2620
  absorbedWedges: Number(epoch?.absorbedWedges ?? 0),
2183
2621
  recovered: curation.recoveryAttempts > 0,
2184
2622
  protectedBytes: Number(epoch?.protectedStaleBytes ?? 0),
@@ -2186,59 +2624,14 @@ export function registerActiveContext(pi: any, options: {
2186
2624
  }));
2187
2625
  };
2188
2626
 
2189
- const applyAutomaticRung = async (
2190
- snapshot: ActiveContextSnapshot,
2191
- ratio: number,
2192
- rungOptions: {
2193
- toolOnly?: boolean;
2194
- waiverRatio?: number;
2195
- } = {},
2196
- ): Promise<Record<string, unknown> | null> => {
2197
- if (!persistence.state || ladder.automaticFailure || ladder.preparing) return null;
2198
- const rungSelectionOptions = { toolOnly: rungOptions.toolOnly };
2199
- const markLadderSelection = (): Record<string, unknown> | null => {
2200
- if (!persistence.state) return null;
2201
- const decision = selectAutomaticRung(snapshot, persistence.state, ratio, {
2202
- ...rungSelectionOptions,
2203
- claimed: claimedRefKeys(persistence.state),
2204
- claimedFoldIds: markedFoldIds(persistence.state),
2205
- });
2206
- const mark = decision
2207
- ? ladderSelectionMark({
2208
- snapshot, state: persistence.state, selection: decision, ordinal: markOrdinal(snapshot),
2209
- })
2210
- : null;
2211
- if (!mark) return null;
2212
- const addition = addPendingMark(persistence.state, mark);
2213
- if (!addition.added) return null;
2214
- persistence.state = addition.state;
2215
- ladder.pendingContextNote =
2216
- `Pending ${mark.mark} mark ${mark.id} recorded; no context bytes moved. ` +
2217
- "It applies at the next commit epoch.";
2218
- ladder.lastAutomaticAction = {
2219
- kind: "mark",
2220
- foldIds: [mark.id],
2221
- sourceIds: [],
2222
- markKind: mark.mark,
2223
- markOrigin: mark.origin,
2224
- pendingMarks: pendingMarks(persistence.state).length,
2225
- sourceBytesSaved: 0,
2226
- };
2227
- return ladder.lastAutomaticAction;
2228
- };
2229
- // MARKING ONLY. Every mutation the rung used to perform inline -- a prepared chapter,
2230
- // a tool fold, a refold, a consolidation, a chapter split -- now travels as a pending
2231
- // mark and lands at the boundary, which is the one point the projection changes.
2232
- // WHAT the rung decides is unchanged; WHEN it takes effect is.
2233
- return markLadderSelection();
2234
- };
2235
2627
  /**
2236
- * The one durable transaction. Whatever it wraps either lands with its state persisted
2237
- * or leaves the state it entered with, and a failure suspends folding by name rather
2238
- * than being retried quietly. Marking and committing both go through it, which is why
2239
- * a commit that vanishes at persistence (gate 122) and a suspension that says nothing
2240
- * (gate 123) are single properties rather than one per caller.
2628
+ * A COMMIT, THROUGH THE SAME TRANSACTION AND THE SAME QUEUE. Two callers reach it: the
2629
+ * compaction boundary, which is the ordinary one, and the projection fence, which is
2630
+ * the emergency one. The fence cannot wait for a boundary because the request it is
2631
+ * holding does not fit, so it commits in place; that is the one exception, and it is
2632
+ * the exception that already existed.
2241
2633
  */
2634
+
2242
2635
  const runAutomaticTransaction = async (
2243
2636
  snapshot: ActiveContextSnapshot,
2244
2637
  ratio: number,
@@ -2291,25 +2684,6 @@ export function registerActiveContext(pi: any, options: {
2291
2684
  ladder.actionQueue = queued.catch(() => undefined);
2292
2685
  return queued;
2293
2686
  };
2294
- const attemptAutomaticRung = (
2295
- snapshot: ActiveContextSnapshot,
2296
- ratio: number,
2297
- ctx: any,
2298
- phase: string,
2299
- rungOptions: {
2300
- toolOnly?: boolean;
2301
- waiverRatio?: number;
2302
- } = {},
2303
- ): Promise<Record<string, unknown> | null> => queueAutomatic(
2304
- snapshot, ratio, ctx, phase, () => applyAutomaticRung(snapshot, ratio, rungOptions),
2305
- );
2306
- /**
2307
- * A COMMIT, THROUGH THE SAME TRANSACTION AND THE SAME QUEUE. Two callers reach it: the
2308
- * compaction boundary, which is the ordinary one, and the projection fence, which is
2309
- * the emergency one. The fence cannot wait for a boundary because the request it is
2310
- * holding does not fit, so it commits in place; that is the one exception, and it is
2311
- * the exception that already existed.
2312
- */
2313
2687
  const attemptAutomaticCommit = (
2314
2688
  snapshot: ActiveContextSnapshot,
2315
2689
  ctx: any,
@@ -2495,14 +2869,11 @@ export function registerActiveContext(pi: any, options: {
2495
2869
  return { messages: event.messages };
2496
2870
  }
2497
2871
  }
2498
- if (selectAutomaticToolBatch(snapshot, persistence.state)[0]) mutationAttempted = true;
2499
- const action = await attemptAutomaticRung(
2500
- snapshot, measurements.latestRatio, ctx, "context",
2501
- );
2502
- if (action) {
2503
- mutationAttempted = true;
2504
- persistedSucceeded = true;
2505
- }
2872
+ // NO EAGER LADDER MARKING (Shane, 2026-08-21): the rung used to stage a mark
2873
+ // here on every measured response, which crowded the agent out of curation
2874
+ // entirely -- by the time an agent looked, every eligible batch was already
2875
+ // marked. The commit epoch fills fresh at commit time (topUpMarks), which is
2876
+ // the only automatic marking there is.
2506
2877
  } else measurements.lastProviderMeasurement = observedMeasurement;
2507
2878
  } else {
2508
2879
  measurements.latestRatio = contextUsageRatio(measurements.lastProviderMeasurement);
@@ -2524,9 +2895,26 @@ export function registerActiveContext(pi: any, options: {
2524
2895
  updateStatus(ctx);
2525
2896
  return { messages: event.messages };
2526
2897
  }
2898
+ // THE FRONTIER CUTS FIRST, and it costs the projection nothing to do so: a pending
2899
+ // mark moves no bytes, so this runs BEFORE the band top and the fence and changes
2900
+ // neither of their inputs. What it changes is WHEN the agent learns a fold exists,
2901
+ // which is now while the material it covers is still in front of it.
2902
+ advanceFoldFrontier(snapshot);
2903
+ // BEFORE the fence, so an ordinary crossing is an ordinary commit and the fence is
2904
+ // left holding only the requests that genuinely will not fit.
2905
+ if (await enforceBandTop(snapshot, ctx)) {
2906
+ mutationAttempted = true;
2907
+ persistedSucceeded = true;
2908
+ try { projected = projectWithAdvisory(snapshot); }
2909
+ catch (error) {
2910
+ suspendAutomatic(error, "projection", ctx);
2911
+ abortUnsafeHardContext(snapshot, ctx);
2912
+ return { messages: event.messages };
2913
+ }
2914
+ }
2527
2915
  const budgeted = await enforceProjectionBudget(snapshot, projected, ctx);
2528
2916
  projected = budgeted.projected;
2529
- const reading = projectedTokenReading(projected);
2917
+ const reading = projectedTokenReading(projected, budgeted.text);
2530
2918
  measurements.lastProjectedChars = reading.chars;
2531
2919
  measurements.lastProjectedEstimate = reading.tokens;
2532
2920
  measurements.lastProjectedEstimateBasis = reading.basis;
@@ -2536,7 +2924,7 @@ export function registerActiveContext(pi: any, options: {
2536
2924
  updateStatus(ctx);
2537
2925
  return { messages: projected };
2538
2926
  }
2539
- noteProjection(projected);
2927
+ noteProjection(projected, budgeted.text);
2540
2928
  updateStatus(ctx);
2541
2929
  return { messages: projected };
2542
2930
  } catch (error) {
@@ -2598,13 +2986,7 @@ export function registerActiveContext(pi: any, options: {
2598
2986
  if (measurements.latestRatio >= hardFenceRatio(measurement, ctx) && ladder.preparing) await ladder.preparing.promise;
2599
2987
  if (!sessionIdentityStillValid(ctx, capturedSessionId, capturedGeneration) ||
2600
2988
  !durableProviderMeasurementMatches(measurement)) return;
2601
- const action = await attemptAutomaticRung(
2602
- authoritativeSnapshotFor(ctx),
2603
- measurements.latestRatio,
2604
- ctx,
2605
- "message-end",
2606
- );
2607
- if (!action && measurementStateChanged && persistence.state && persistence.persisted &&
2989
+ if (measurementStateChanged && persistence.state && persistence.persisted &&
2608
2990
  !sameStateProjection(persistence.state, persistence.persisted)) {
2609
2991
  await persistThroughActionQueue(ctx);
2610
2992
  }
@@ -2629,6 +3011,7 @@ export function registerActiveContext(pi: any, options: {
2629
3011
  model: ownValue(message, "model") ?? null,
2630
3012
  input_tokens: observation.inputTokens,
2631
3013
  cache_read_tokens: observation.cacheReadTokens,
3014
+ cache_write_tokens: observation.cacheWriteTokens,
2632
3015
  total_tokens: providerTokens(message),
2633
3016
  projection_change: observation.change,
2634
3017
  provider_side_miss: observation.providerSideMiss,
@@ -2672,7 +3055,7 @@ export function registerActiveContext(pi: any, options: {
2672
3055
  durableProviderMeasurementMatches(measurements.lastProviderMeasurement)) {
2673
3056
  startPreparation(snapshot, measurements.latestRatio, ctx);
2674
3057
  if (measurements.latestRatio >= hardFenceRatio(snapshot) && ladder.preparing) await ladder.preparing.promise;
2675
- await attemptAutomaticRung(snapshot, measurements.latestRatio, ctx, "turn-end");
3058
+ // No rung here either: turn end stages nothing (see the context-path note).
2676
3059
  }
2677
3060
  ladder.pendingManual = false;
2678
3061
  if (!ladder.automaticFailure) ladder.boundaryFailure = null;
@@ -3001,6 +3384,194 @@ export function registerActiveContext(pi: any, options: {
3001
3384
  );
3002
3385
  };
3003
3386
 
3387
+ /**
3388
+ * THE WITHDRAWAL PATH, shared by the tool's unmark action and the editor's
3389
+ * withdraw intent: same refusal for an unknown id, same persist, same status.
3390
+ */
3391
+ const withdrawMarks = async (ids: string[], ctx: any): Promise<Array<Record<string, unknown>>> => {
3392
+ const requested = new Set(ids);
3393
+ const before = pendingMarks(persistence.state!);
3394
+ const kept = before.filter((mark) => !requested.has(mark.id));
3395
+ const removed = before.filter((mark) => requested.has(mark.id));
3396
+ const unknown = ids.filter((id) => !before.some((mark) => mark.id === id));
3397
+ if (unknown.length) throw new Error(`No pending mark named ${unknown.join(", ")}`);
3398
+ await persistManual(withPendingMarks(persistence.state!, kept), ctx);
3399
+ updateStatus(ctx);
3400
+ return removed as Array<Record<string, unknown>>;
3401
+ };
3402
+
3403
+ /**
3404
+ * THE PIN CHANGE PATH, shared by the tool's pin/unpin actions and the editor's
3405
+ * pin toggle: the pinned-share cap, protectEvidence, the canonical event, persist.
3406
+ */
3407
+ const applyProtectionChange = async (
3408
+ snapshot: ActiveContextSnapshot,
3409
+ ids: string[],
3410
+ pin: boolean,
3411
+ ctx: any,
3412
+ ): Promise<void> => {
3413
+ const refsBefore = persistence.state!.protected.length;
3414
+ const nextProtection = protectEvidence(snapshot, persistence.state!, ids, pin);
3415
+ if (pin) {
3416
+ const capacity = servingCapacity(snapshot.contextWindow);
3417
+ const prospectiveShare = capacity.budgetTokens > 0
3418
+ ? estimatedTokens(explicitProtectedMass(snapshot, nextProtection).bytes) / capacity.budgetTokens
3419
+ : 0;
3420
+ if (prospectiveShare > MAX_PINNED_SHARE) {
3421
+ throw new Error(
3422
+ `protect refused: these pins would hold ${Math.round(prospectiveShare * 100)}% of the ` +
3423
+ `working window raw, past the ${Math.round(MAX_PINNED_SHARE * 100)}% pinned-share cap. ` +
3424
+ 'Release earlier pins with {"action":"unpin","ids":["<entry-id>"]} first; ' +
3425
+ "folding keeps entries exactly recoverable without pinning them.");
3426
+ }
3427
+ }
3428
+ await persistManual(nextProtection, ctx);
3429
+ emit("context.pin", {
3430
+ pin,
3431
+ ids: ids.join(","),
3432
+ id_count: ids.length,
3433
+ protected_refs_before: refsBefore,
3434
+ protected_refs_after: persistence.state!.protected.length,
3435
+ });
3436
+ updateStatus(ctx);
3437
+ };
3438
+
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,
3449
+ signal: AbortSignal,
3450
+ ctx: any,
3451
+ snapshot: ActiveContextSnapshot,
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
+ };
3573
+ };
3574
+
3004
3575
  const executeAction = async (
3005
3576
  params: Record<string, unknown>,
3006
3577
  signal: AbortSignal | undefined,
@@ -3010,6 +3581,11 @@ export function registerActiveContext(pi: any, options: {
3010
3581
  if (!persistence.state) persistence.state = emptyActiveContextState(ctx.sessionManager.getSessionId());
3011
3582
  const executionArgumentsSha256 = sha256Value(params);
3012
3583
  const action = String(params.action ?? "");
3584
+ // A deleted action is refused BY NAME rather than by "not enabled": the two are
3585
+ // different facts, and an agent told a verb is merely off will keep offering it.
3586
+ if (Object.prototype.hasOwnProperty.call(RETIRED_TOOL_ACTIONS, action)) {
3587
+ throw new Error(RETIRED_TOOL_ACTIONS[action]);
3588
+ }
3013
3589
  if (!allowedToolActionSet.has(action)) {
3014
3590
  throw new Error(`${toolName} action '${action}' is not enabled in this runtime`);
3015
3591
  }
@@ -3038,7 +3614,6 @@ export function registerActiveContext(pi: any, options: {
3038
3614
  persistence.state,
3039
3615
  statusOffset,
3040
3616
  statusLimit,
3041
- snapshot.policy.maxFoldSourceRefs,
3042
3617
  { diet: !paged },
3043
3618
  ),
3044
3619
  headroomTokens: currentCapacity(ctx).headroomTokens,
@@ -3056,7 +3631,6 @@ export function registerActiveContext(pi: any, options: {
3056
3631
  hardFenceRatio: hardFenceRatio(snapshot),
3057
3632
  thresholds: { ...snapshot.thresholds },
3058
3633
  zones: {
3059
- freshBoundary: snapshot.freshBoundary,
3060
3634
  budgetTokens: snapshot.budgetTokens,
3061
3635
  },
3062
3636
  responseReserve: currentCapacity(ctx).outputReservation,
@@ -3121,6 +3695,20 @@ export function registerActiveContext(pi: any, options: {
3121
3695
  state: persistence.state,
3122
3696
  ratio: measurements.latestRatio,
3123
3697
  }),
3698
+ // WHAT THE RUNTIME HAS CUT AND NOT YET FOLDED, by name, because the agent needs
3699
+ // an id to brief one and a notice it saw two turns ago is not a reference. Read
3700
+ // from the LIVE state rather than the durable record: a frontier cut is derived
3701
+ // from its span and is not written on its own, so the durable record is behind
3702
+ // by design and only a brief or a commit brings it forward.
3703
+ pendingFolds: pendingMarks(persistence.state).map((mark) => ({
3704
+ id: mark.id,
3705
+ kind: mark.kind,
3706
+ tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
3707
+ briefed: mark.briefProvenance?.kind === "supplied" ||
3708
+ mark.briefProvenance?.kind === "augmented",
3709
+ brief: mark.brief ?? null,
3710
+ briefProvenance: mark.briefProvenance ? clone(mark.briefProvenance) : null,
3711
+ })),
3124
3712
  nativeSummaries: "disabled",
3125
3713
  instrumentation: {
3126
3714
  enabled: true,
@@ -3152,16 +3740,53 @@ export function registerActiveContext(pi: any, options: {
3152
3740
  if (action === "peek") {
3153
3741
  const id = String(params.id ?? "").trim();
3154
3742
  if (!id) throw new Error("peek requires id");
3743
+ if (params.ephemeral !== undefined && typeof params.ephemeral !== "boolean") {
3744
+ throw new Error("peek ephemeral must be a boolean");
3745
+ }
3155
3746
  const offset = boundedInteger(params.offset, 0, 0, 1_000_000_000, "offset");
3156
3747
  const sliceBytes = boundedInteger(
3157
3748
  params.bytes,
3158
3749
  PEEK_DEFAULT_MAX_BYTES,
3159
3750
  PEEK_MIN_SLICE_BYTES,
3160
- snapshot.policy.maxChapterChars,
3751
+ snapshot.policy.maxSourceChars,
3161
3752
  "bytes",
3162
3753
  );
3163
3754
  const target = persistence.state.folds.find((item) => item.id === id);
3164
- 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
+ }
3165
3790
  admit({
3166
3791
  action: "peek",
3167
3792
  ctx,
@@ -3169,40 +3794,24 @@ export function registerActiveContext(pi: any, options: {
3169
3794
  requestedBytes: Math.max(0, Math.min(sliceBytes, target.sourceChars - offset)),
3170
3795
  children: childFoldIds(target),
3171
3796
  });
3172
- const payload = toolPayload(peekFoldSource({
3173
- foldId: id,
3174
- state: persistence.state,
3175
- entries: ctx.sessionManager.getEntries?.() ?? ctx.sessionManager.getBranch(),
3176
- sessionId: ctx.sessionManager.getSessionId(),
3177
- maximumBytes: sliceBytes,
3178
- offset,
3179
- toolName,
3180
- }));
3181
- return payload;
3182
- }
3183
- if (action === "rebrief") {
3184
- const id = String(params.id ?? "").trim();
3185
- if (!id) throw new Error("rebrief requires id");
3186
- const brief = typeof params.brief === "string" ? params.brief.trim() : "";
3187
- if (!brief) throw new Error("rebrief requires a nonempty brief");
3188
- if (brief.length > snapshot.policy.maxBriefChars) {
3189
- throw new Error(`rebrief brief must be at most ${snapshot.policy.maxBriefChars} characters`);
3190
- }
3191
- const fold = requireActiveFold(snapshot, persistence.state, id);
3192
- const previous = foldBrief(fold, persistence.state);
3193
- const briefs = { ...(persistence.state.briefs ?? {}), [id]: brief };
3194
- await persistManual({ ...persistence.state, revision: persistence.state.revision + 1, briefs }, action, ctx);
3195
- updateStatus(ctx);
3196
- return toolPayload({
3197
- version: 1,
3198
- action,
3199
- id,
3200
- brief,
3201
- previousBrief: previous,
3202
- durableRevision: persistence.state.revision,
3203
- activation: "the fold's placeholder and index rows now carry your brief; " +
3204
- "the exact source and its fold record are untouched.",
3797
+ const payload = toolPayload({
3798
+ ...peekFoldSource({
3799
+ foldId: id,
3800
+ state: persistence.state,
3801
+ entries: ctx.sessionManager.getEntries?.() ?? ctx.sessionManager.getBranch(),
3802
+ sessionId: ctx.sessionManager.getSessionId(),
3803
+ maximumBytes: sliceBytes,
3804
+ offset,
3805
+ toolName,
3806
+ }),
3807
+ ...(peekIsEphemeral(params) ? {
3808
+ ephemeral: "this result rides your context exactly until your next message; " +
3809
+ "extract what your current task needs into that reply, which takes over its " +
3810
+ "place. Peeking again is lossless and cheap; pass ephemeral false when you " +
3811
+ "need these bytes standing for several turns",
3812
+ } : { durable: "this result stays in your context like any other tool result" }),
3205
3813
  });
3814
+ return payload;
3206
3815
  }
3207
3816
  if (action === "reboundary") {
3208
3817
  if (params.ids !== undefined) {
@@ -3230,10 +3839,18 @@ export function registerActiveContext(pi: any, options: {
3230
3839
  const recut = manualFoldCandidate(snapshot, persistence.state, snapped.ids, { allowProtected: true });
3231
3840
  const created: Array<Record<string, unknown>> = [];
3232
3841
  const correctedBriefs: Record<string, string> = {};
3233
- for (const part of splitCandidateBySize(snapshot, persistence.state, recut)) {
3842
+ // Re-cut whole, for the same reason a mark is taken whole: the agent named this
3843
+ // boundary, and splitting it hands one brief to several folds that each hold a
3844
+ // fraction of what it describes.
3845
+ for (const part of [recut]) {
3234
3846
  const durable = persistence.persistedFoldRecords.get(foldIdFor(part.kind, part.parts));
3235
3847
  const { preparedFold, nextState } = await prepareAndCommitExplicit({
3236
- 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,
3237
3854
  });
3238
3855
  persistence.state = nextState;
3239
3856
  if (durable && supplied) correctedBriefs[preparedFold.id] = supplied;
@@ -3249,7 +3866,7 @@ export function registerActiveContext(pi: any, options: {
3249
3866
  briefs: { ...(persistence.state.briefs ?? {}), ...correctedBriefs },
3250
3867
  };
3251
3868
  }
3252
- await persistManual(persistence.state, action, ctx);
3869
+ await persistManual(persistence.state, ctx);
3253
3870
  updateStatus(ctx);
3254
3871
  return toolPayload({
3255
3872
  version: 1,
@@ -3287,7 +3904,7 @@ export function registerActiveContext(pi: any, options: {
3287
3904
  if (!Object.keys(briefs).length) delete next.briefs;
3288
3905
  const leases = { ...next.leases };
3289
3906
  delete leases[id];
3290
- await persistManual({ ...next, leases }, action, ctx);
3907
+ await persistManual({ ...next, leases }, ctx);
3291
3908
  updateStatus(ctx);
3292
3909
  return toolPayload({
3293
3910
  version: 1,
@@ -3333,11 +3950,7 @@ export function registerActiveContext(pi: any, options: {
3333
3950
  delete leases[id];
3334
3951
  next = { ...next, leases };
3335
3952
  }
3336
- await persistManual(
3337
- next,
3338
- action,
3339
- ctx,
3340
- );
3953
+ await persistManual(next, ctx);
3341
3954
  updateStatus(ctx);
3342
3955
  return toolPayload({
3343
3956
  version: 1,
@@ -3348,52 +3961,51 @@ export function registerActiveContext(pi: any, options: {
3348
3961
  activation: "durable immediately; projected on the next model call in this same turn",
3349
3962
  });
3350
3963
  }
3351
- if (action === "protect" || action === "unprotect") {
3964
+ if (action === "pin" || action === "unpin") {
3352
3965
  const ids = stringIds(params.ids);
3353
- const refsBefore = persistence.state.protected.length;
3354
- const nextProtection = protectEvidence(snapshot, persistence.state, ids, action === "protect");
3355
- if (action === "protect") {
3356
- const capacity = servingCapacity(snapshot.contextWindow);
3357
- const prospectiveShare = capacity.budgetTokens > 0
3358
- ? estimatedTokens(explicitProtectedMass(snapshot, nextProtection).bytes) / capacity.budgetTokens
3359
- : 0;
3360
- if (prospectiveShare > MAX_PINNED_SHARE) {
3361
- throw new Error(
3362
- `protect refused: these pins would hold ${Math.round(prospectiveShare * 100)}% of the ` +
3363
- `working window raw, past the ${Math.round(MAX_PINNED_SHARE * 100)}% pinned-share cap. ` +
3364
- 'Release earlier pins with {"action":"unprotect","ids":["<entry-id>"]} first; ' +
3365
- "folding keeps entries exactly recoverable without pinning them.");
3366
- }
3367
- }
3368
- await persistManual(nextProtection, action, ctx);
3369
- const refsAfter = persistence.state.protected.length;
3370
- emit("context.protect", {
3371
- protect: action === "protect",
3372
- ids: ids.join(","),
3373
- id_count: ids.length,
3374
- protected_refs_before: refsBefore,
3375
- protected_refs_after: refsAfter,
3966
+ await applyProtectionChange(snapshot, ids, action === "pin", ctx);
3967
+ const refsAfter = persistence.state!.protected.length;
3968
+ // A PIN IS A CLAIM ON THE WINDOW, so it answers with the same picture a mark does.
3969
+ // It never pays the drop, and saying so here is what stops an agent pinning its way
3970
+ // to a coverage it does not have.
3971
+ const pinCapacity = servingCapacity(snapshot.contextWindow);
3972
+ const pinClaims = windowClaims({
3973
+ snapshot,
3974
+ state: persistence.state,
3975
+ budgetTokens: pinCapacity.budgetTokens,
3976
+ charsPerToken: projectionCharsPerToken(),
3977
+ });
3978
+ const pinCoverage = commitCoverage({
3979
+ snapshot,
3980
+ state: persistence.state,
3981
+ usedTokens: pinCapacity.usedTokens,
3982
+ budgetTokens: pinCapacity.budgetTokens,
3376
3983
  });
3377
- updateStatus(ctx);
3378
3984
  return toolPayload({
3379
3985
  version: 1,
3380
3986
  action,
3381
3987
  ids,
3382
3988
  protectedRefs: refsAfter,
3383
- activation: "durable immediately; projected on the next model call in this same turn",
3989
+ pinnedTokens: pinClaims.pinnedTokens,
3990
+ pinnedShare: pinClaims.pinnedShare,
3991
+ maxPinnedShare: MAX_PINNED_SHARE,
3992
+ markedTowardCommitTokens: pinClaims.markedTokens,
3993
+ unclaimedTokens: pinClaims.unclaimedTokens,
3994
+ commitTargetTokens: pinCoverage.targetTokens,
3995
+ unmarkedAgainstCommitTokens: pinCoverage.remainingTokens,
3996
+ activation: action === "pin"
3997
+ ? "durable immediately; projected on the next model call in this same turn. " +
3998
+ "Pinned entries stay RAW and are never folded, automatically or by a mark, so " +
3999
+ "this is how you keep a span expanded. It frees nothing: the next commit still " +
4000
+ "has to reach its drop from what is left, so pinned mass makes the rest of the " +
4001
+ "window fold sooner rather than later."
4002
+ : "durable immediately; projected on the next model call in this same turn. The " +
4003
+ "released entries are foldable again and rejoin the next commit's candidates.",
3384
4004
  });
3385
4005
  }
3386
4006
  if (action === "unmark") {
3387
- const ids = stringIds(params.ids);
3388
- const requested = new Set(ids);
3389
- const before = pendingMarks(persistence.state);
3390
- const kept = before.filter((mark) => !requested.has(mark.id));
3391
- const removed = before.filter((mark) => requested.has(mark.id));
3392
- const unknown = ids.filter((id) => !before.some((mark) => mark.id === id));
3393
- if (unknown.length) throw new Error(`No pending mark named ${unknown.join(", ")}`);
3394
- await persistManual(withPendingMarks(persistence.state, kept), action, ctx);
3395
- updateStatus(ctx);
3396
- const accounting = markAccounting(snapshot, persistence.state);
4007
+ const removed = await withdrawMarks(stringIds(params.ids), ctx);
4008
+ const accounting = markAccounting(snapshot, persistence.state!);
3397
4009
  return toolPayload({
3398
4010
  version: 1,
3399
4011
  action,
@@ -3405,172 +4017,168 @@ export function registerActiveContext(pi: any, options: {
3405
4017
  activation: "the withdrawn marks will never be applied; no context bytes moved",
3406
4018
  });
3407
4019
  }
3408
- if (action === "fold") {
3409
- const requested = batchedMarkRequests(params);
3410
- const resolved: Array<{
3411
- candidate: FoldCandidate;
3412
- brief?: string;
3413
- corrections: SpanCorrection[];
3414
- splitFrom: number;
3415
- }> = [];
3416
- for (const request of requested) {
3417
- const snapped = snapFoldCandidate(snapshot, persistence.state, request.ids, { allowProtected: true });
3418
- const spanChars = candidateSpanChars(snapshot, persistence.state, snapped.candidate);
3419
- const parts = splitCandidateBySize(snapshot, persistence.state, snapped.candidate);
3420
- for (const [index, part] of parts.entries()) {
3421
- resolved.push({
3422
- candidate: part,
3423
- brief: request.brief,
3424
- corrections: index === 0
3425
- ? [
3426
- ...snapped.corrections,
3427
- ...(parts.length > 1
3428
- ? [{
3429
- from: request.ids,
3430
- to: [],
3431
- reason: `span was ${spanChars} chars, over the ${MAX_FOLD_SPAN_CHARS}-char bite-size cap; ` +
3432
- `split into ${parts.length} sequential folds, each with its own brief`,
3433
- }]
3434
- : []),
3435
- ]
3436
- : [],
3437
- splitFrom: parts.length > 1 && index === 0 ? spanChars : 0,
3438
- });
3439
- }
3440
- if (parts.length > 1) {
3441
- emit("context.split", {
3442
- source: "agent",
3443
- span_chars: spanChars,
3444
- parts: parts.length,
3445
- cap_chars: MAX_FOLD_SPAN_CHARS,
3446
- fold_ids: "",
3447
- });
3448
- }
4020
+ if (action === "brief") {
4021
+ // THE ONE EDIT THAT IS FREE. A pending mark is not in the window, so writing its
4022
+ // brief moves no bytes and costs no prefix cache; the brief reaches the projection
4023
+ // only when the commit writes that placeholder for the first time. A STANDING fold
4024
+ // is the opposite case and is refused by name: its placeholder is already in the
4025
+ // window, and rewriting it breaks the cache from that point on, which is the
4026
+ // rebrief deletion of 2026-08-21 read from the other side.
4027
+ const id = String(params.id ?? "").trim();
4028
+ if (!id) throw new Error("brief needs the id of a pending fold");
4029
+ const standing = persistence.state!.folds.find((fold) => fold.id === id);
4030
+ if (standing) {
4031
+ throw new Error(
4032
+ `${id} is a standing fold, not a pending one, so its brief is already in your ` +
4033
+ "window and rewriting it would rewrite the projection from that placeholder on. " +
4034
+ `Read it with ${toolName} {"action":"peek","id":"${id}"}, or re-cut the span with ` +
4035
+ `${toolName} {"action":"reboundary","ids":[...]} if the boundary is wrong.`,
4036
+ );
3449
4037
  }
3450
- const corrections = resolved.flatMap((item) => item.corrections);
3451
- const marks: Array<Record<string, unknown>> = [];
3452
- let staged = persistence.state;
3453
- for (const item of resolved) {
3454
- const { candidate } = item;
3455
- const alreadyPrepared = staged.folds.some((fold) =>
3456
- fold.id === foldIdFor(candidate.kind, candidate.parts));
3457
- const deferred = refsProtected(candidate.sourceRefs, staged, snapshot) ||
3458
- (candidate.kind === "tool-result" &&
3459
- toolRefsProtected(candidate.sourceRefs, staged, snapshot));
3460
- const suppliedComplaint = item.brief === undefined
3461
- ? null
3462
- : briefContractComplaint(item.brief.trim(), snapshot.policy.maxBriefChars, snapshot.toolName);
3463
- if (suppliedComplaint !== null) {
3464
- marks.push({
3465
- id: foldIdFor(candidate.kind, candidate.parts),
3466
- kind: candidate.kind,
3467
- ok: false,
3468
- deferred: false,
3469
- reason: `Supplied brief rejected. ${suppliedComplaint}`,
3470
- });
3471
- continue;
3472
- }
3473
- const briefed = !deferred && !alreadyPrepared
3474
- ? await prepareFold({
3475
- candidate,
3476
- snapshot,
3477
- state: staged,
3478
- generation: lifecycle.generation,
3479
- brief: item.brief,
3480
- ctx,
3481
- signal,
3482
- })
3483
- : null;
3484
- const mark = foldMarkFor({
3485
- candidate,
3486
- brief: briefed?.fold.brief ?? item.brief ?? ladderBrief(snapshot, staged, candidate),
3487
- briefProvenance: briefed?.fold.provenance ??
3488
- (item.brief ? { kind: "supplied" } : { kind: "deterministic" }),
3489
- origin: "agent",
3490
- ordinal: markOrdinal(snapshot),
3491
- });
3492
- const addition = addPendingMark(staged, mark);
3493
- if (!addition.added) {
3494
- marks.push({ id: mark.id, kind: mark.kind, ok: false, deferred: false, reason: addition.reason });
3495
- continue;
3496
- }
3497
- staged = addition.state;
3498
- marks.push({
3499
- id: mark.id,
3500
- kind: mark.kind,
3501
- ok: true,
3502
- deferred,
3503
- ...(deferred
3504
- ? {
3505
- scheduled: "the span is still in the fresh window; this mark is held and folds at the " +
3506
- "first commit after it ages out",
3507
- }
3508
- : {}),
3509
- brief: mark.brief,
3510
- provenance: normalizeLegacyProvenance(mark.briefProvenance),
3511
- });
4038
+ const held = pendingMarks(persistence.state!);
4039
+ const target = held.find((mark) => mark.id === id);
4040
+ if (!target) {
4041
+ const names = held.slice(0, 5).map((mark) => mark.id).join(", ");
4042
+ throw new Error(
4043
+ `No pending fold ${id}. ${held.length
4044
+ ? `The folds waiting for a brief are: ${names}${held.length > 5 ? ", ..." : ""}.`
4045
+ : "Nothing is pending right now, so there is nothing to brief."}`,
4046
+ );
3512
4047
  }
3513
- await persistManual(staged, action, ctx);
4048
+ const text = String(params.brief ?? "").trim();
4049
+ const complaint = briefContractComplaint(text, snapshot.policy.agentBriefReserve, snapshot.toolName);
4050
+ if (complaint !== null) throw new Error(`Supplied brief rejected. ${complaint}`);
4051
+ const nextState = {
4052
+ ...persistence.state!,
4053
+ revision: persistence.state!.revision + 1,
4054
+ pendingMarks: held.map((mark) => mark.id === id
4055
+ ? { ...mark, brief: text, briefProvenance: { kind: "supplied" as const } }
4056
+ : mark),
4057
+ };
4058
+ persistence.state = nextState;
4059
+ await persistManual(nextState, ctx);
3514
4060
  updateStatus(ctx);
3515
- const accounting = markAccounting(snapshot, persistence.state);
3516
- const held = pendingMarks(persistence.state).map((mark) => ({
3517
- id: mark.id,
3518
- kind: mark.kind,
3519
- tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
3520
- }));
3521
- const remainder = unmarkedRemainder(snapshot, persistence.state, projectionCharsPerToken());
3522
- const only = marks.length === 1 && marks[0].ok === true ? marks[0] : null;
4061
+ emit("context.brief", { id, chars: text.length, origin: "agent" });
4062
+ const accounting = markAccounting(snapshot, persistence.state!);
4063
+ const remainder = unmarkedRemainder(snapshot, persistence.state!, projectionCharsPerToken());
3523
4064
  return toolPayload({
3524
4065
  version: 1,
3525
4066
  action,
3526
- scheduling: "epoch",
3527
- ok: marks.some((mark) => mark.ok === true),
3528
- deferredMarks: marks.filter((mark) => mark.deferred === true).length,
3529
- ...(only
3530
- ? {
3531
- id: only.id,
3532
- kind: only.kind,
3533
- brief: only.brief,
3534
- provenance: only.provenance,
3535
- deferred: only.deferred,
3536
- ...(only.scheduled ? { scheduled: only.scheduled } : {}),
3537
- }
3538
- : {}),
3539
- marks,
3540
- corrections,
4067
+ id,
4068
+ kind: target.kind,
4069
+ brief: text,
4070
+ provenance: "agent",
3541
4071
  argumentsSha256: executionArgumentsSha256,
3542
- durableRevision: persistence.state.revision,
4072
+ durableRevision: persistence.state!.revision,
3543
4073
  pendingMarks: accounting.pending,
3544
- eligibleMarks: accounting.eligibleMarks,
3545
- retainedMarks: accounting.retainedMarks,
3546
- estimatedFreedWindowShare: accounting.freedBudgetShare,
3547
- estimatedEligibleWindowShare: accounting.eligibleFreedBudgetShare,
3548
- estimatedRewriteTokens: accounting.rewriteTokens,
3549
- held,
3550
- heldNote: "held until it ages out or the next fold event",
4074
+ unbriefed: pendingMarks(persistence.state!)
4075
+ .filter((mark) => mark.briefProvenance?.kind !== "supplied" &&
4076
+ mark.briefProvenance?.kind !== "augmented").map((mark) => mark.id),
3551
4077
  unmarkedSpans: remainder.spans,
3552
4078
  unmarkedTokens: remainder.tokens,
3553
- unmarkedShare: remainder.share,
3554
- unmarkedCandidates: remainder.candidates,
3555
- ...(guidance.actionResponses ? {
3556
- awareness: markAwarenessText({ held, remainder, toolName, brandNoun }),
3557
- activation: "accepted as pending marks; no context bytes moved, and nothing else in your " +
3558
- "context changed either. They apply together at the next commit epoch, which the runtime " +
3559
- "opens at the fold event. " +
3560
- "A mark over a still-fresh span is held, not refused: it is scheduled, and it folds at the " +
3561
- "first commit after that span ages out of the fresh window. " +
3562
- "Mark several spans in one call: this whole picture comes back with each one.",
3563
- } : {}),
4079
+ activation: "durable immediately, and it costs your window nothing: this fold is " +
4080
+ "pending, so the brief you just wrote is stored outside the projection and " +
4081
+ "reaches it only when the commit folds this span. Nothing in your context moved.",
4082
+ });
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 })),
3564
4178
  });
3565
4179
  }
3566
4180
  throw new Error(`Unknown ${toolName} action '${action}'`);
3567
4181
  };
3568
- const requestedMarkCount = (params: Record<string, unknown>): number => {
3569
- const batched = denseOwnArrayValues(params?.marks);
3570
- if (batched) return batched.length;
3571
- return params?.ids === undefined && params?.id === undefined ? 0 : 1;
3572
- };
3573
-
3574
4182
  const toolHandler = async (
3575
4183
  toolCallId: string,
3576
4184
  params: Record<string, unknown>,
@@ -3587,9 +4195,9 @@ export function registerActiveContext(pi: any, options: {
3587
4195
  ok,
3588
4196
  error,
3589
4197
  tool_call_id: toolCallId,
3590
- marks_requested: requestedMarkCount(params),
3591
4198
  corrections_applied: corrections.length,
3592
4199
  arguments_sha256: sha256Value(params ?? {}),
4200
+ ...(action === "peek" ? { ephemeral: peekIsEphemeral(params) } : {}),
3593
4201
  });
3594
4202
  for (const correction of corrections) {
3595
4203
  const from = denseOwnArrayValues(ownValue(correction, "from")) ?? [];
@@ -3609,6 +4217,7 @@ export function registerActiveContext(pi: any, options: {
3609
4217
  const result = await executeAction(params, signal, ctx);
3610
4218
  const corrections = denseOwnArrayValues(ownValue(ownValue(result, "details"), "corrections"));
3611
4219
  attempt(true, null, (corrections ?? []) as Array<Record<string, unknown>>);
4220
+ if (action === "peek" && peekIsEphemeral(params)) ephemeralPeeks.add(toolCallId);
3612
4221
  return result;
3613
4222
  } catch (error) {
3614
4223
  attempt(false, error instanceof Error ? error.message : String(error), []);
@@ -3642,7 +4251,11 @@ export function registerActiveContext(pi: any, options: {
3642
4251
  if (!lifecycle.latestSnapshot) lifecycle.latestSnapshot = snapshot;
3643
4252
  const divider = args.indexOf(" -- ");
3644
4253
  const selector = (divider >= 0 ? args.slice(0, divider) : args).trim();
3645
- if (selector === "commit") {
4254
+ // BARE /fold IS THE COMMIT (Shane, dogfooding 2026-08-22): staging is the
4255
+ // normal accumulation and applying it is the default verb. The old bare behavior
4256
+ // rescue-folded an auto-offered chapter, which read as the command ignoring the
4257
+ // staged pile; a rescue now needs explicit ids.
4258
+ if (!selector || selector === "commit") {
3646
4259
  const capacity = servingCapacity(snapshot.contextWindow);
3647
4260
  const occupancy = capacity.usedTokens !== null && capacity.budgetTokens > 0
3648
4261
  ? capacity.usedTokens / capacity.budgetTokens
@@ -3665,16 +4278,20 @@ export function registerActiveContext(pi: any, options: {
3665
4278
  committed
3666
4279
  ? `Committed ${ownValue(committed, "applied_marks") ?? 0} mark(s)${topUp ? " with automatic top-up" : ""}. ` +
3667
4280
  "Exact source remains expandable."
3668
- : "Nothing eligible to commit: no pending mark is outside the fresh tail.",
4281
+ : "Nothing to commit: no staged mark could apply (pinned or already-folded spans hold).",
3669
4282
  "info",
3670
4283
  );
3671
4284
  return;
3672
4285
  }
3673
4286
  const supplied = (divider >= 0 ? args.slice(divider + 4) : "").trim() || undefined;
3674
4287
  const ids = selector ? selector.replace(/\.\./g, " ").split(/[\s,]+/).filter(Boolean) : [];
3675
- const candidate = ids.length
3676
- ? manualFoldCandidate(snapshot, persistence.state!, ids)
3677
- : selectAutomaticChapter(snapshot, persistence.state!) ?? selectAutomaticToolBatch(snapshot, persistence.state!)[0] ?? null;
4288
+ if (!ids.length) {
4289
+ throw new Error(
4290
+ "Nothing to rescue without ids: /fold with no arguments commits every staged mark; " +
4291
+ "name the span to fold now (/fold <start-id> [end-id] -- brief)",
4292
+ );
4293
+ }
4294
+ const candidate = manualFoldCandidate(snapshot, persistence.state!, ids);
3678
4295
  if (!candidate) throw new Error("No exact stale rescue span is currently eligible");
3679
4296
  const stateBefore = persistence.state!;
3680
4297
  const persistedBefore = persistence.persisted ? clone(persistence.persisted) : null;
@@ -3721,12 +4338,199 @@ export function registerActiveContext(pi: any, options: {
3721
4338
  }
3722
4339
  };
3723
4340
 
4341
+ /**
4342
+ * /fold-editor, V2.2. The handler assembles a plain data snapshot of the working
4343
+ * window and hands it to the view; the view holds no runtime reference and no
4344
+ * mutation path. What changed in V2.2: raw entries are MARK POINTS, and a span
4345
+ * laid down between two of them stages through `stageFoldMarks` -- the SAME
4346
+ * validated path the tool's fold action runs -- with origin "user". The view
4347
+ * computes nothing but the arithmetic it is handed (spanCost) and reports one
4348
+ * intent (onStageMark); this handler owns every state transition.
4349
+ */
4350
+ const foldEditorCommandHandler = async (_args: string, ctx: any): Promise<void> => {
4351
+ if (typeof ctx.ui?.custom !== "function") {
4352
+ throw new Error("/fold-editor needs an interactive UI; use /fold-status for the text form");
4353
+ }
4354
+ const snapshot = lifecycle.latestSnapshot
4355
+ ? authoritativeSnapshotFor(ctx)
4356
+ : snapshotForEvent(ctx, ctx.sessionManager.buildSessionContext().messages);
4357
+ if (!lifecycle.latestSnapshot) lifecycle.latestSnapshot = snapshot;
4358
+ if (!persistence.state) throw new Error("The context window is empty; there is nothing to show yet");
4359
+
4360
+ // THE WOULD-BE FOLD SIZE (Shane, 2026-08-23). The footer's number while a span is
4361
+ // being laid down is the runtime's own estimate -- span bytes over the mapped
4362
+ // window at ESTIMATED_BYTES_PER_TOKEN -- so what the user sees before staging is
4363
+ // what the staged mark answers with after it.
4364
+ const spanCost = (from: number, to: number) => ({
4365
+ entries: Math.abs(to - from) + 1,
4366
+ tokens: estimatedTokens(spanBytes(snapshot, Math.min(from, to), Math.max(from, to) + 1)),
4367
+ });
4368
+
4369
+ // Marks move no bytes until the commit, so window geometry cannot change while
4370
+ // the editor holds it open; only pending state grows. Rebuilding against the SAME
4371
+ // snapshot with fresh state keeps PROPOSED rows honest without re-deriving indices.
4372
+ const buildData = () => {
4373
+ const state = persistence.state!;
4374
+ const capacity = servingCapacity(snapshot.contextWindow);
4375
+ const occupancy = capacity.usedTokens !== null && capacity.budgetTokens > 0
4376
+ ? capacity.usedTokens / capacity.budgetTokens
4377
+ : null;
4378
+ const accounting = markAccounting(snapshot, state);
4379
+ return {
4380
+ title: `pi-fold context editor · ${snapshot.sessionId.slice(0, 8)}`,
4381
+ occupancy: {
4382
+ usedTokens: capacity.usedTokens,
4383
+ budgetTokens: capacity.budgetTokens,
4384
+ commitOccupancy: thresholds.maxTarget,
4385
+ commitDue: occupancy !== null && occupancy >= thresholds.maxTarget,
4386
+ },
4387
+ blocks: buildFoldEditorData(snapshot, state, {
4388
+ foldRows: () => (state.folds ?? []).map((fold: any) => {
4389
+ const row = foldIndexRow(fold, state, snapshot);
4390
+ const entries = flattenFoldRefs(fold, state)
4391
+ .map((ref) => {
4392
+ const item = exactMapped(snapshot, ref);
4393
+ const message = item?.message as Record<string, unknown> | undefined;
4394
+ const role = String(item?.ref?.role ?? message?.role ?? "entry");
4395
+ const content = message?.content;
4396
+ let preview = "";
4397
+ if (typeof content === "string") preview = content.slice(0, 300);
4398
+ else if (Array.isArray(content)) {
4399
+ const firstText = content.find((part) => part?.type === "text");
4400
+ preview = typeof firstText?.text === "string" ? firstText.text.slice(0, 300) : "";
4401
+ }
4402
+ return { id: ref.entryId, role, preview: preview.replace(/\s+/g, " ") };
4403
+ });
4404
+ return {
4405
+ id: String(row.id),
4406
+ kind: String(row.kind ?? ""),
4407
+ brief: String(row.brief ?? ""),
4408
+ sourceCount: Number(row.sourceCount ?? 0),
4409
+ startPosition: Number(row.startPosition),
4410
+ endPosition: Number(row.endPosition),
4411
+ entries,
4412
+ };
4413
+ }),
4414
+ pendingMarkRefs: () => pendingMarks(state)
4415
+ .filter((mark) => mark.mark === "fold")
4416
+ .map((mark) => ({
4417
+ id: mark.id,
4418
+ origin: String(mark.origin),
4419
+ brief: String(mark.brief ?? ""),
4420
+ // WHAT THE STAGED MARK FREES, so a PROPOSED row states its size the
4421
+ // way the footer prices an unstaged span.
4422
+ ...(typeof markFreedBytes(snapshot, state, mark) === "number"
4423
+ ? { tokens: estimatedTokens(markFreedBytes(snapshot, state, mark)) }
4424
+ : {}),
4425
+ entryIds: markSpanRefs(state, mark).refs
4426
+ .map((ref) => ref.entryId)
4427
+ .filter((entryId): entryId is string => Boolean(entryId)),
4428
+ })),
4429
+ mappedRange: (from: number, to: number) => {
4430
+ const out: Array<{ id: string; role: string; preview: string; index?: number }> = [];
4431
+ for (let index = from; index <= to; index += 1) {
4432
+ const item = snapshot.mapped[index];
4433
+ if (!item) continue;
4434
+ const message = item.message as Record<string, unknown> | undefined;
4435
+ const role = String(item.ref?.role ?? message?.role ?? "entry");
4436
+ const content = message?.content;
4437
+ // 300 chars, same as fold entries: the view shows 48 on the row and
4438
+ // deepens to 240 on Enter, so a 48-char preview made detail a no-op.
4439
+ let preview = "";
4440
+ if (typeof content === "string") preview = content.slice(0, 300);
4441
+ else if (Array.isArray(content)) {
4442
+ const firstText = content.find((part) => part?.type === "text");
4443
+ preview = typeof firstText?.text === "string" ? firstText.text.slice(0, 300) : "";
4444
+ }
4445
+ // THE INDEX IS THE MARK POINT: raw entries carry their mapped position so
4446
+ // the view can price a span and name its two boundary ids.
4447
+ out.push({
4448
+ id: item.ref?.entryId ?? String(index),
4449
+ role,
4450
+ preview: preview.replace(/\s+/g, " "),
4451
+ ...(item.ref?.entryId ? { index } : {}),
4452
+ });
4453
+ }
4454
+ return out;
4455
+ },
4456
+ entryCount: snapshot.mapped.length,
4457
+ }),
4458
+ pending: {
4459
+ count: pendingMarks(state).length,
4460
+ agentMarks: accounting.agentMarks,
4461
+ ladderMarks: accounting.ladderMarks,
4462
+ userMarks: accounting.userMarks,
4463
+ freedTokens: accounting.freedTokens,
4464
+ },
4465
+ pinned: state.protected.map((ref) => ref.entryId),
4466
+ };
4467
+ };
4468
+
4469
+ let view: FoldEditorView | null = null;
4470
+ const runQueued = async (work: () => Promise<void>): Promise<void> => {
4471
+ const operation = ladder.actionQueue.then(work);
4472
+ ladder.actionQueue = operation.catch(() => undefined);
4473
+ await operation;
4474
+ };
4475
+ const refreshAfter = (): void => {
4476
+ if (view) view.refresh(buildData());
4477
+ };
4478
+ const editorActions = {
4479
+ spanCost,
4480
+ onStageMark: async (fromId: string, toId: string, brief?: string): Promise<void> => {
4481
+ await runQueued(async () => {
4482
+ const fresh = authoritativeSnapshotFor(ctx);
4483
+ const { mark, staged } = await stageFoldMark(
4484
+ [fromId, toId], brief && brief.trim() ? brief.trim() : undefined,
4485
+ new AbortController().signal, ctx, fresh);
4486
+ // Refuse BEFORE persisting: a refused mark leaves state untouched and
4487
+ // persisting it would be a no-op write against the same-state projection.
4488
+ if (mark.ok !== true) throw new Error(String(mark.reason ?? "the span was refused"));
4489
+ await persistManual(staged, ctx);
4490
+ updateStatus(ctx);
4491
+ });
4492
+ safeNotify(ctx, "Staged user mark(s); they apply at the next commit epoch.", "info");
4493
+ refreshAfter();
4494
+ },
4495
+ onWithdrawMark: async (markId: string): Promise<void> => {
4496
+ let removed: Array<Record<string, unknown>> = [];
4497
+ await runQueued(async () => {
4498
+ removed = await withdrawMarks([markId], ctx) as Array<Record<string, unknown>>;
4499
+ });
4500
+ safeNotify(ctx,
4501
+ `Withdrew ${removed.length} staged mark(s); no context bytes moved.`, "info");
4502
+ refreshAfter();
4503
+ },
4504
+ onTogglePin: async (entryId: string): Promise<void> => {
4505
+ const pinnedAlready = persistence.state!.protected
4506
+ .some((ref: any) => ref.entryId === entryId);
4507
+ await runQueued(async () => {
4508
+ await applyProtectionChange(authoritativeSnapshotFor(ctx), [entryId], !pinnedAlready, ctx);
4509
+ });
4510
+ safeNotify(ctx, pinnedAlready
4511
+ ? "Unpinned: the released entries rejoin the next commit's candidates."
4512
+ : "Pinned raw: held through every fold; frees nothing toward the drop.", "info");
4513
+ refreshAfter();
4514
+ },
4515
+ };
4516
+ await ctx.ui.custom((
4517
+ _tui: unknown,
4518
+ theme: any,
4519
+ keybindings: { matches(data: string, action: string): boolean } | null,
4520
+ done: () => void,
4521
+ ) => {
4522
+ view = new FoldEditorView(buildData(), done, keybindings, theme, editorActions);
4523
+ return view;
4524
+ });
4525
+ updateStatus(ctx);
4526
+ };
4527
+
3724
4528
  pi.registerTool(buildActiveContextTool({
3725
4529
  name: toolName,
3726
4530
  label: toolLabel,
3727
4531
  allowedActions: allowedToolActions,
3728
4532
  fullSurface: true,
3729
- maxBriefChars: ACTIVE_CONTEXT_POLICY.maxBriefChars,
4533
+ maxBriefChars: ACTIVE_CONTEXT_POLICY.agentBriefReserve,
3730
4534
  statusDetails: ["fold_candidates", "tree", "folds", "objects"],
3731
4535
  minPeekSliceBytes: PEEK_MIN_SLICE_BYTES,
3732
4536
  defaultPeekBytes: PEEK_DEFAULT_MAX_BYTES,
@@ -3735,8 +4539,10 @@ export function registerActiveContext(pi: any, options: {
3735
4539
  for (const command of buildActiveContextCommands({
3736
4540
  statusName: commandNames.status,
3737
4541
  foldName: commandNames.fold,
4542
+ editorName: "fold-editor",
3738
4543
  statusHandler: statusCommandHandler,
3739
4544
  foldHandler: foldCommandHandler,
4545
+ editorHandler: foldEditorCommandHandler,
3740
4546
  })) {
3741
4547
  pi.registerCommand(command.name, {
3742
4548
  description: command.description,