pi-fold 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,11 @@
1
1
  import {
2
2
  denseOwnArrayValues,
3
+ evidenceSha256,
3
4
  objectRefKey,
4
5
  sha256Value,
5
6
  stableStringify,
6
7
  } from "./json.ts";
7
8
  import {
8
- advisorySchedule,
9
- advisoryState,
10
- clearArmedAdvisory,
11
- liveAdvisoryText,
12
- milestoneText,
13
- updateAdvisoryMilestone,
14
9
  } from "./lib/advisory.ts";
15
10
  import {
16
11
  bytes,
@@ -22,9 +17,13 @@ import {
22
17
  import {
23
18
  activeContextStatus,
24
19
  automaticPreparationId,
20
+ boundStatusPayload,
25
21
  commitPreparedFold,
22
+ descendantIds,
26
23
  encodedFoldSource,
27
24
  foldCandidatesDetail,
25
+ foldTreeDetail,
26
+ peekFoldSource,
28
27
  prepareFold,
29
28
  preparedFoldError,
30
29
  projectActiveContext,
@@ -37,8 +36,10 @@ import {
37
36
  withExpandLease,
38
37
  } from "./lib/folding.ts";
39
38
  import {
39
+ admissionVerdict,
40
40
  boundReceiptText,
41
41
  boundedInteger,
42
+ capacityAccounting,
42
43
  contextUsageRatio,
43
44
  contextWindowFor,
44
45
  hardFenceRatio,
@@ -49,7 +50,10 @@ import {
49
50
  parseProviderContextMeasurementReceipt,
50
51
  persistenceProjection,
51
52
  providerContextMeasurement,
53
+ providerTokens,
54
+ refsProtected,
52
55
  stringIds,
56
+ toolRefsProtected,
53
57
  toolPayload,
54
58
  } from "./lib/measurement.ts";
55
59
  import type {
@@ -59,7 +63,12 @@ import type {
59
63
  ProviderMeasurementAnchor,
60
64
  } from "./lib/measurement.ts";
61
65
  import {
66
+ childFoldIds,
62
67
  clearPrepared,
68
+ deriveFoldParents,
69
+ flattenFoldRefs,
70
+ foldBrief,
71
+ foldIdFor,
63
72
  makeFoldRecordEntry,
64
73
  makeStateCheckpoint,
65
74
  makeStateDelta,
@@ -76,7 +85,6 @@ import type { MaterializedStatePersistence } from "./lib/persistence.ts";
76
85
  import {
77
86
  activeContextSource,
78
87
  ACTIVE_CONTEXT_POLICY,
79
- ACTIVE_CONTEXT_STATUS_KEY,
80
88
  ACTIVE_CONTEXT_TOOL_ACTIONS,
81
89
  contextBrand,
82
90
  DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN,
@@ -84,8 +92,25 @@ import {
84
92
  DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX,
85
93
  DEFAULT_ACTIVE_CONTEXT_TOOL_LABEL,
86
94
  DEFAULT_ACTIVE_CONTEXT_TOOL_NAME,
95
+ COMMIT_RECLAIM_FLOOR_SHARE,
96
+ CURATION_OCCUPANCY_SHARE,
97
+ CURATION_STALE_TOOL_SHARE,
98
+ CONTEXT_RECEIPT_BLOCK_BYTES,
99
+ CURATION_TARGET_OCCUPANCY_SHARE,
87
100
  DEFAULT_CONTEXT_WINDOW,
101
+ DEFAULT_FOLD_SCHEDULING,
102
+ ESTIMATED_BYTES_PER_TOKEN,
88
103
  entryTypeNamespace,
104
+ EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS,
105
+ EPOCH_COMMIT_TARGET_WINDOW_SHARE,
106
+ MAX_FOLD_SPAN_CHARS,
107
+ MAX_WEDGE_ABSORB_TOKENS,
108
+ OVERFLOW_RECOVERY_MAX_ATTEMPTS,
109
+ PEEK_DEFAULT_MAX_BYTES,
110
+ PEEK_MIN_SLICE_BYTES,
111
+ PEEK_READ_ONLY_CONTEXT_ACTIONS,
112
+ FOLD_SCHEDULING_MODES,
113
+ READ_ONLY_CONTEXT_ACTIONS_DEFAULT,
89
114
  READ_ONLY_TOOLS_DEFAULT,
90
115
  USER_RESCUE_MAX_SOURCE_CHARS,
91
116
  } from "./lib/policy.ts";
@@ -93,36 +118,152 @@ import type {
93
118
  ActiveContextSnapshot,
94
119
  ActiveContextState,
95
120
  ActiveContextToolAction,
96
- AdvisoryMilestone,
97
121
  FoldCandidate,
98
122
  FoldKind,
99
123
  FoldRecordEntry,
124
+ FoldSchedulingMode,
100
125
  PreparedFold,
126
+ SuggestionSource,
101
127
  } from "./lib/policy.ts";
128
+ import {
129
+ absorbWedgeMarks,
130
+ } from "./lib/scheduling.ts";
131
+ import {
132
+ contextReceipt,
133
+ curationSignals,
134
+ curationTriggerFires,
135
+ markAwarenessText,
136
+ receiptBlockText,
137
+ withReceipt,
138
+ } from "./lib/curation.ts";
139
+ import type {
140
+ ContextReceipt,
141
+ CurationSignals,
142
+ } from "./lib/curation.ts";
143
+ import {
144
+ addPendingMark,
145
+ claimedRefKeys,
146
+ commitPendingMarks,
147
+ ephemeralPeekMarks,
148
+ epochCommitDue,
149
+ estimatedTokens,
150
+ foldMarkFor,
151
+ ladderSelectionMark,
152
+ ladderBrief,
153
+ markAccounting,
154
+ markEligibility,
155
+ markFreedBytes,
156
+ unmarkedRemainder,
157
+ guardWaiverCount,
158
+ markedFoldIds,
159
+ markOrdinal,
160
+ pendingMarks,
161
+ markTouchesCurrentTurn,
162
+ schedulingStatus,
163
+ topUpMarks,
164
+ withPendingMarks,
165
+ } from "./lib/scheduling.ts";
102
166
  import {
103
167
  automaticToolBrief,
168
+ candidateSpanChars,
104
169
  deterministicChapterCandidateBrief,
105
170
  deterministicConsolidationBrief,
106
171
  manualFoldCandidate,
107
172
  selectAutomaticToolBatch,
108
173
  selectAutomaticToolForRung,
174
+ snapFoldCandidate,
175
+ snapToFoldBoundaries,
176
+ splitCandidateBySize,
109
177
  } from "./lib/selection.ts";
178
+ import type { SpanCorrection } from "./lib/selection.ts";
179
+ import {
180
+ acceptSurfacingSuggestion,
181
+ surfacingOrdinal,
182
+ } from "./lib/surfacing.ts";
183
+ import {
184
+ compareProjections,
185
+ emptyLedger,
186
+ ledgerSummary,
187
+ messageDigests,
188
+ observeCacheUsage,
189
+ prefixDivergence,
190
+ recordContextEvent,
191
+ recordProjection,
192
+ } from "./lib/instrumentation.ts";
193
+ import type {
194
+ ContextEvent,
195
+ ContextEventKind,
196
+ ProjectionChange,
197
+ } from "./lib/instrumentation.ts";
110
198
  import { buildActiveContextCommands, buildActiveContextTool } from "./lib/tool-surface.ts";
111
- import { mapActiveContext } from "./lib/transcript.ts";
199
+ import {
200
+ currentTurnRefKeys,
201
+ mapActiveContext,
202
+ toolFreshIndices,
203
+ } from "./lib/transcript.ts";
112
204
 
113
205
  // Test seam only; the package API is registerPiFold because package.json exports block deep imports.
114
206
  export * from "./lib/advisory.ts";
115
207
  export * from "./lib/canonical.ts";
208
+ export * from "./lib/curation.ts";
116
209
  export * from "./lib/folding.ts";
210
+ export * from "./lib/instrumentation.ts";
117
211
  export * from "./lib/measurement.ts";
118
212
  export * from "./lib/persistence.ts";
119
213
  export * from "./lib/policy.ts";
214
+ export * from "./lib/scheduling.ts";
120
215
  export * from "./lib/selection.ts";
216
+ export * from "./lib/surfacing.ts";
121
217
  export * from "./lib/transcript.ts";
122
218
 
219
+ /** Handle a suggestion source keeps: withdraw itself, and report that its item was acted on. */
220
+ export interface SuggestionSourceHandle {
221
+ unregister: () => void;
222
+ accepted: (id: string) => void;
223
+ }
224
+
225
+ export type SuggestionSourceRegistrar = (source: SuggestionSource) => SuggestionSourceHandle;
226
+
227
+ /**
228
+ * The batched fold request: several {span, brief} pairs in one call, with the single
229
+ * `ids` form still accepted because it is the shape a status action hands back.
230
+ */
231
+ export interface BatchedMarkRequest {
232
+ ids: string[];
233
+ brief?: string;
234
+ }
235
+
236
+ export function batchedMarkRequests(params: Record<string, unknown>): BatchedMarkRequest[] {
237
+ const batched = params.marks;
238
+ if (batched === undefined) {
239
+ return [{
240
+ ids: stringIds(params.ids),
241
+ ...(typeof params.brief === "string" && params.brief.trim() ? { brief: params.brief } : {}),
242
+ }];
243
+ }
244
+ const values = denseOwnArrayValues(batched);
245
+ if (!values || !values.length || values.length > 64) {
246
+ throw new Error("marks must be a dense array of 1-64 {ids, brief} objects");
247
+ }
248
+ return values.map((value, index) => {
249
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
250
+ throw new Error(`marks[${index}] must be an object with ids and an optional brief`);
251
+ }
252
+ const brief = ownValue(value, "brief");
253
+ if (brief !== undefined && (typeof brief !== "string" || !brief.trim())) {
254
+ throw new Error(`marks[${index}].brief must be a nonempty string`);
255
+ }
256
+ return {
257
+ ids: stringIds(ownValue(value, "ids")),
258
+ ...(typeof brief === "string" && brief.trim() ? { brief } : {}),
259
+ };
260
+ });
261
+ }
262
+
123
263
  export function registerActiveContext(pi: any, options: {
124
264
  summarizeContextSpan?: (request: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>;
125
265
  setProjectionProvider?: (provider: (ctx: any) => Array<Record<string, unknown>>) => void;
266
+ setSuggestionSourceRegistrar?: (register: SuggestionSourceRegistrar) => void;
126
267
  toolActions?: readonly ActiveContextToolAction[];
127
268
  toolName?: string;
128
269
  toolLabel?: string;
@@ -132,13 +273,43 @@ export function registerActiveContext(pi: any, options: {
132
273
  commandNames?: { status?: string; fold?: string };
133
274
  readOnlyTools?: ReadonlySet<string>;
134
275
  blockingTools?: readonly string[];
135
- }): { projectionCandidates: (ctx: any) => Array<Record<string, unknown>> } {
276
+ foldScheduling?: FoldSchedulingMode;
277
+ foldPeekResults?: boolean;
278
+ providerTotalWindow?: number;
279
+ }): {
280
+ projectionCandidates: (ctx: any) => Array<Record<string, unknown>>;
281
+ registerSuggestionSource: SuggestionSourceRegistrar;
282
+ } {
136
283
  const toolName = options.toolName ?? DEFAULT_ACTIVE_CONTEXT_TOOL_NAME;
137
284
  const toolLabel = options.toolLabel ?? DEFAULT_ACTIVE_CONTEXT_TOOL_LABEL;
138
285
  const brandNoun = options.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN;
139
286
  const entryTypePrefix = options.entryTypePrefix ?? DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX;
140
287
  const commandPrefix = options.commandPrefix ?? "";
141
288
  const readOnlyTools = options.readOnlyTools ?? READ_ONLY_TOOLS_DEFAULT;
289
+ const foldScheduling = options.foldScheduling ?? DEFAULT_FOLD_SCHEDULING;
290
+ if (!FOLD_SCHEDULING_MODES.includes(foldScheduling)) {
291
+ throw new Error(`foldScheduling must be one of ${FOLD_SCHEDULING_MODES.join(", ")}`);
292
+ }
293
+ const epochScheduling = foldScheduling === "epoch";
294
+ if (options.foldPeekResults !== undefined && typeof options.foldPeekResults !== "boolean") {
295
+ throw new Error("foldPeekResults must be a boolean");
296
+ }
297
+ // Peek mutates nothing, so its own tool result is a foldable read batch. Epoch mode
298
+ // adopts that classification with its scheduling; immediate mode keeps the narrower
299
+ // one unless the deployment opts in, because a peek copies a fold's stored source
300
+ // back into the window and a window that cannot reclaim it grows without bound.
301
+ const foldPeekResults = options.foldPeekResults ?? epochScheduling;
302
+ if (options.providerTotalWindow !== undefined &&
303
+ (!Number.isSafeInteger(options.providerTotalWindow) || options.providerTotalWindow <= 0)) {
304
+ throw new Error("providerTotalWindow must be a positive integer");
305
+ }
306
+ // The deployment's own fact, and the only capacity knob: declaring it makes every
307
+ // ratio, fence and budget truthful, and leaving it out falls back to the provider
308
+ // descriptor and SAYS "descriptor" in the capacity accounting.
309
+ const providerTotalWindow = options.providerTotalWindow ?? null;
310
+ const readOnlyContextActions = foldPeekResults
311
+ ? PEEK_READ_ONLY_CONTEXT_ACTIONS
312
+ : READ_ONLY_CONTEXT_ACTIONS_DEFAULT;
142
313
  if (!toolName || !toolLabel || !brandNoun || !entryTypePrefix || typeof commandPrefix !== "string" ||
143
314
  (commandPrefix && !/^[a-z0-9-]+$/.test(commandPrefix)) ||
144
315
  [...readOnlyTools].some((name) => typeof name !== "string" || !name)) {
@@ -166,12 +337,21 @@ export function registerActiveContext(pi: any, options: {
166
337
  const foldRecordEntryType = `${entryTypePrefix}-fold-record`;
167
338
  const milestoneProjectionType = `${entryTypePrefix}-milestone`;
168
339
  const advisoryProjectionType = `${entryTypePrefix}-advisory`;
340
+ const surfacingProjectionType = `${entryTypePrefix}-surfacing`;
341
+ const receiptProjectionType = `${entryTypePrefix}-receipts`;
342
+ const curationProjectionType = `${entryTypePrefix}-curation`;
169
343
  const entryNamespace = entryTypeNamespace(entryTypePrefix);
170
344
  const providerMeasurementEntryType = `${entryNamespace}-provider-context-measurement`;
171
345
  const nativeReceiptEntryType = `${entryNamespace}-native-compaction-receipt`;
346
+ const contextEventEntryType = `${entryNamespace}-context-event`;
172
347
  const nativeDecisionEntryType = `${entryNamespace}-native-compaction-decision`;
348
+ // The commit verb only exists where marks exist; immediate mode keeps its exact
349
+ // seven-action surface and description.
350
+ const defaultToolActions = epochScheduling
351
+ ? EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS
352
+ : ACTIVE_CONTEXT_TOOL_ACTIONS;
173
353
  const configuredToolActions = denseOwnArrayValues(
174
- options.toolActions ?? ACTIVE_CONTEXT_TOOL_ACTIONS,
354
+ options.toolActions ?? defaultToolActions,
175
355
  );
176
356
  if (!configuredToolActions || configuredToolActions.length < 1) {
177
357
  throw new Error("Active-context tool actions must be one non-empty dense array");
@@ -180,7 +360,7 @@ export function registerActiveContext(pi: any, options: {
180
360
  const allowedToolActionSet = new Set<string>();
181
361
  for (const value of configuredToolActions) {
182
362
  if (typeof value !== "string" ||
183
- !ACTIVE_CONTEXT_TOOL_ACTIONS.includes(value as ActiveContextToolAction) ||
363
+ !EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS.includes(value as ActiveContextToolAction) ||
184
364
  allowedToolActionSet.has(value)) {
185
365
  throw new Error(`Invalid or duplicate active-context tool action '${String(value)}'`);
186
366
  }
@@ -221,6 +401,8 @@ export function registerActiveContext(pi: any, options: {
221
401
  lastSelectionSourceIds: [] as string[],
222
402
  pendingContextNote: null as string | null,
223
403
  lastAutomaticAction: null as Record<string, unknown> | null,
404
+ /** What the last over-budget projection cost and whether the reduction saved it. */
405
+ overBudgetReduction: null as Record<string, unknown> | null,
224
406
  automaticFailure: null as AutomaticFailureState | null,
225
407
  failedPreparations: new Set<string>(),
226
408
  actionQueue: Promise.resolve<unknown>(undefined),
@@ -230,6 +412,28 @@ export function registerActiveContext(pi: any, options: {
230
412
  const measurements = {
231
413
  latestRatio: null as number | null,
232
414
  lastProviderMeasurement: null as ProviderContextMeasurement | null,
415
+ /** What the host/model descriptor last claimed, kept so the gap stays auditable. */
416
+ descriptorWindow: null as number | null,
417
+ /** Serialized size of the projection this process last handed the host. */
418
+ lastProjectedChars: null as number | null,
419
+ /**
420
+ * Ground truth for the transmission fence: recent pairings of a projection's size
421
+ * with the token count the provider reported for it. A fixed bytes-per-token
422
+ * constant is a guess about a tokenizer, and it is wrong by different amounts in
423
+ * different sessions -- measured 2026-08-06, 4.7 chars/token in rep11 and 7.0 in
424
+ * rep12 for the SAME workload. It also DRIFTS within one session: rep13 moved
425
+ * between 5.34 and 6.01 over its last twenty requests. Only the recent window
426
+ * counts, and the fence reads it pessimistically.
427
+ */
428
+ projectionCalibrations: [] as Array<{ chars: number; tokens: number }>,
429
+ /** What the fence estimated for the projection it last handed the host. */
430
+ lastProjectedEstimate: null as number | null,
431
+ /** Whether that estimate used a measured ratio rather than the bootstrap constant. */
432
+ lastProjectedEstimateCalibrated: false,
433
+ /** Signed estimator error against recent measurements, as a share of measured tokens. */
434
+ estimatorErrors: [] as number[],
435
+ /** Growth in measured tokens between consecutive requests. */
436
+ inflowSteps: [] as number[],
233
437
  providerMeasurementQueue: Promise.resolve<void>(undefined),
234
438
  providerMeasurementReceipts: new Set<string>(),
235
439
  providerMeasurementRevisionByMessageSha: new Map<string, number>(),
@@ -237,16 +441,107 @@ export function registerActiveContext(pi: any, options: {
237
441
  providerMeasurementAnchorByMessageSha: new Map<string, ProviderMeasurementAnchor>(),
238
442
  };
239
443
 
444
+ // Owns the projection/cache ledger. Nothing here is durable: it is telemetry about
445
+ // this process's own projections, rebuilt from scratch on every load.
446
+ const instrumentation = {
447
+ ledger: emptyLedger(),
448
+ previousDigests: null as string[] | null,
449
+ /** The previous TRANSMITTED projection, for the byte-level prefix comparison. */
450
+ previousText: null as string | null,
451
+ lastChange: "append" as ProjectionChange,
452
+ /** Share of the PREVIOUS prompt the last projection kept, for miss attribution. */
453
+ lastPreservedShare: null as number | null,
454
+ /** Events emitted since the last handoff, which is the attribution candidate set. */
455
+ sinceHandoff: [] as Array<{ seq: number; kind: string }>,
456
+ /** Structural mutations emitted since the last handoff. The per-generation budget. */
457
+ mutationsSinceHandoff: 0,
458
+ /** Provider responses this session, so event SPACING is readable from the stream. */
459
+ requests: 0,
460
+ lastMutationRequest: 0,
461
+ lastMutationTokens: null as number | null,
462
+ };
463
+ /**
464
+ * MECHANISM 1. The frozen surface.
465
+ *
466
+ * Between fold events the projection is byte-frozen: the only change a context action
467
+ * may cause is the append of its own tool result at the tail. Rep 17 measured every
468
+ * single context attempt landing as a projection REWRITE -- status 0.60-0.90 prefix
469
+ * identity, peek 0.96, the held gate down to 0.013 -- because the surface answered
470
+ * each one by re-rendering blocks the agent had already paid to cache. Provider prefix
471
+ * caches are positional, so each of those refreshes was a full or partial cache kill.
472
+ *
473
+ * `body` is the last rebuilt fold projection and `projection` is that body plus the
474
+ * carriers appended to it. While the rebuilt body still starts with `body` verbatim,
475
+ * nothing structural moved, so the previous projection is reused WHOLE and only the
476
+ * newly arrived raw messages are appended after it. A carrier whose key is already in
477
+ * the window is never re-rendered; a fold event, a reveal or an overflow recovery
478
+ * diverges the body and takes the sanctioned rewrite.
479
+ */
480
+ const freeze = {
481
+ body: null as unknown[] | null,
482
+ bodyText: null as string | null,
483
+ projection: null as unknown[] | null,
484
+ keys: new Set<string>(),
485
+ /** True only while assembling a pass that reused the frozen projection. */
486
+ active: false,
487
+ };
488
+ /**
489
+ * A carrier may be built exactly once per freeze window. This is checked BEFORE the
490
+ * builder runs, because building a milestone spends its delivery budget and building
491
+ * a reminder advances the reminder cursor: a carrier that cannot land must not pay.
492
+ */
493
+ const carrierAdmitted = (key: string): boolean => {
494
+ if (freeze.active && freeze.keys.has(key)) return false;
495
+ freeze.keys.add(key);
496
+ return true;
497
+ };
498
+
499
+ // Owns the guided-curation gate, the receipt ring, the context-event stream, and the
500
+ // overflow recovery lane. Nothing here is durable session STATE: it describes what
501
+ // this process did to the live window, and the durable copy is the appended
502
+ // instrumentation entry, which is what an external adjudicator reads.
503
+ const curation = {
504
+ receipts: [] as ContextReceipt[],
505
+ /** Every context-management tool call this session. */
506
+ contextCalls: 0,
507
+ /** The last measurement the commit trigger took, reported by status. */
508
+ lastSignals: null as CurationSignals | null,
509
+ /**
510
+ * COMMIT REOPEN HYSTERESIS. The eligible foldable share measured just after the last
511
+ * triggered commit, or null when the trigger is free to fire. Measured 2026-08-07
512
+ * (rep 17): occupancy plateaued at 0.85-0.92 of the truthful budget for the final
513
+ * third of the run, and the trigger fired ten times over a window whose foldable mass
514
+ * had not changed. A plateau is not an event. The trigger re-arms only once occupancy
515
+ * falls back under it and crosses again, or once a reclaim floor of eligible mass is
516
+ * NEW. This outlived the announcement it was built beside: it bounds how often the
517
+ * runtime rewrites the projection, which is the whole cost model.
518
+ */
519
+ reopenBaselineShare: null as number | null,
520
+ /** Recovery attempts spent on the CURRENT inflow; reset by any accepted request. */
521
+ recoveryAttempts: 0,
522
+ /** A provider rejection observed but not yet recovered from. */
523
+ pendingRejection: null as { status: number; ordinal: number } | null,
524
+ lastRecovery: null as Record<string, unknown> | null,
525
+ instrumentationQueue: Promise.resolve<void>(undefined),
526
+ };
527
+
240
528
  // Owns advisory arming and hard-fence delivery; durable effects use persistenceQueue.
241
529
  const advisory = {
242
- historicalGuidanceEntries: 0,
243
- armedMilestone: null as AdvisoryMilestone | null,
244
- advisoryScheduleKey: null as string | null,
245
530
  hardFenceNoticeKey: null as string | null,
246
531
  hardFenceReleaseSessionId: null as string | null,
247
532
  hardFenceReleasedProjectionKeys: new Set<string>(),
248
533
  };
249
534
 
535
+ // Registered suggestion sources. RETRIEVAL IS UNWIRED FROM THE PROJECTION as of this
536
+ // build: the slate that rendered these was a per-pass tail carrier, and a tail carrier
537
+ // is displaced by every append, so it diverged the prefix on every single pass. The
538
+ // selector in lib/surfacing.ts is kept and still gated, because proactive retrieval is
539
+ // the right idea in the wrong place: when it returns it must ride a commit like every
540
+ // other carrier. Until then nothing renders a suggestion, so nothing selects one.
541
+ const surfacing = {
542
+ sources: [] as SuggestionSource[],
543
+ };
544
+
250
545
  // Owns native-compaction decisions and completion retry state; nativeReceiptQueue serializes it.
251
546
  const nativeCompaction = {
252
547
  lastThresholdDecision: null as Record<string, unknown> | null,
@@ -294,6 +589,62 @@ export function registerActiveContext(pi: any, options: {
294
589
  receipt.tokens === measurement.tokens && receipt.contextWindow === measurement.contextWindow);
295
590
  };
296
591
 
592
+ const currentOrdinal = (): number =>
593
+ lifecycle.latestSnapshot ? markOrdinal(lifecycle.latestSnapshot) : 0;
594
+
595
+ /**
596
+ * Emit one record into THE context event stream, in the ledger AND as a durable
597
+ * session entry. An in-memory ledger is invisible to an analyst reading session
598
+ * artifacts, and telemetry may never block or fail the action it describes.
599
+ */
600
+ /**
601
+ * Open a pass's mutation budget. ONE structural mutation per pass is the budget: the
602
+ * rep-15 defect was a second commit inside the SAME pass, after the first had already
603
+ * rebuilt the projection, so the counter is armed wherever a pass begins.
604
+ */
605
+ const beginMutationPass = (): void => { instrumentation.mutationsSinceHandoff = 0; };
606
+
607
+ const emit = (kind: ContextEventKind, payload: Record<string, unknown> = {}): ContextEvent => {
608
+ const record = recordContextEvent(instrumentation.ledger, kind, {
609
+ session_id: persistence.state?.sessionId ?? "",
610
+ ordinal: currentOrdinal(),
611
+ revision: persistence.state?.revision ?? 0,
612
+ at: Date.now(),
613
+ }, payload);
614
+ instrumentation.sinceHandoff.push({ seq: record.seq, kind });
615
+ const operation = curation.instrumentationQueue.then(async () => {
616
+ try { await pi.appendEntry(contextEventEntryType, record); }
617
+ catch { /* Telemetry is never a lifecycle boundary. */ }
618
+ });
619
+ curation.instrumentationQueue = operation.then(() => undefined, () => undefined);
620
+ return record;
621
+ };
622
+
623
+ /**
624
+ * Actions of ours that move bytes at or before a prefix position. Receipts, gate
625
+ * notices and suggestions are appended after the whole projection, so they can only
626
+ * move the tail and are never a prefix cause.
627
+ */
628
+ const PREFIX_MUTATING_KINDS: ReadonlySet<string> = new Set([
629
+ "context.commit", "context.fold", "context.absorb", "context.split", "context.recovery",
630
+ ]);
631
+
632
+ const deliverReceipt = (receipt: ContextReceipt): void => {
633
+ curation.receipts = withReceipt(curation.receipts, receipt);
634
+ emit("context.receipt", {
635
+ receipt_kind: receipt.kind,
636
+ trigger: receipt.trigger,
637
+ folds_committed: receipt.foldsCommitted,
638
+ folds_created: receipt.foldsCreated,
639
+ freed_tokens: receipt.freedTokens,
640
+ split_folds: receipt.splitFolds,
641
+ split_from_chars: receipt.splitFromChars,
642
+ absorbed_wedges: receipt.absorbedWedges,
643
+ recovered: receipt.recovered,
644
+ note: receipt.note,
645
+ });
646
+ };
647
+
297
648
  const safeNotify = (ctx: any, message: string, level: "info" | "warning" | "error"): void => {
298
649
  try { ctx.ui?.notify?.(message, level); } catch { /* Presentation cannot block Pi lifecycle progress. */ }
299
650
  };
@@ -319,6 +670,42 @@ export function registerActiveContext(pi: any, options: {
319
670
  } catch { /* Status presentation is request-ephemeral and never a lifecycle boundary. */ }
320
671
  };
321
672
 
673
+ /**
674
+ * The window every ratio, fence and budget is computed against. With truthful
675
+ * capacity on this is the provider's TOTAL admission window, not the per-request
676
+ * max-input descriptor: the descriptor bakes in a full output reservation the
677
+ * deployment never asked for, and treating it as the ceiling aborts requests inside
678
+ * real headroom. The descriptor is still read, and still reported, so the gap stays
679
+ * auditable rather than assumed.
680
+ */
681
+ const budgetWindowFor = (ctx: any): number | null => {
682
+ // Remembered so the ctx-free callers (trigger, gate, reminders, advisory) report the
683
+ // same descriptor gap the fence does instead of re-reading a second source.
684
+ measurements.descriptorWindow = contextWindowFor(ctx);
685
+ return providerTotalWindow ?? measurements.descriptorWindow;
686
+ };
687
+
688
+ /**
689
+ * THE serving budget. One resolution, one formula, one value.
690
+ *
691
+ * Every consumer -- the curation trigger, the last-call gate, the sparse reminders,
692
+ * the transmission fence, the projection estimator and every instrumentation record --
693
+ * reads this. Measured 2026-08-06 (rep 15): the trigger and gate ran a whole run
694
+ * against a 272,000-token per-request DESCRIPTOR (budget 255,616) while the fence used
695
+ * the truthful 383,616, because the same arithmetic lived in three places. A second
696
+ * copy of this formula is the defect; there is now only one.
697
+ */
698
+ const servingCapacity = (window: number | null): ReturnType<typeof capacityAccounting> =>
699
+ capacityAccounting({
700
+ window: window ?? lifecycle.latestSnapshot?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
701
+ truthful: providerTotalWindow !== null,
702
+ descriptorWindow: measurements.descriptorWindow,
703
+ usedTokens: measurements.lastProviderMeasurement?.tokens ?? null,
704
+ });
705
+
706
+ const currentCapacity = (ctx: any): ReturnType<typeof capacityAccounting> =>
707
+ servingCapacity(budgetWindowFor(ctx));
708
+
322
709
  const snapshotForEvent = (ctx: any, messages: unknown[]): ActiveContextSnapshot => mapActiveContext({
323
710
  sessionId: ctx.sessionManager.getSessionId(),
324
711
  eventMessages: messages,
@@ -327,7 +714,8 @@ export function registerActiveContext(pi: any, options: {
327
714
  brandNoun,
328
715
  entryTypePrefix,
329
716
  readOnlyTools,
330
- contextWindow: contextWindowFor(ctx) ?? undefined,
717
+ readOnlyContextActions,
718
+ contextWindow: budgetWindowFor(ctx) ?? undefined,
331
719
  });
332
720
 
333
721
  const authoritativeSnapshotFor = (ctx: any): ActiveContextSnapshot => {
@@ -344,7 +732,8 @@ export function registerActiveContext(pi: any, options: {
344
732
  brandNoun,
345
733
  entryTypePrefix,
346
734
  readOnlyTools,
347
- contextWindow: contextWindowFor(ctx) ?? undefined,
735
+ readOnlyContextActions,
736
+ contextWindow: budgetWindowFor(ctx) ?? undefined,
348
737
  });
349
738
  };
350
739
 
@@ -402,9 +791,23 @@ export function registerActiveContext(pi: any, options: {
402
791
  ladder.lastSelectionKind = null;
403
792
  ladder.lastSelectionSourceIds = [];
404
793
  ladder.pendingContextNote = null;
405
- advisory.historicalGuidanceEntries = 0;
406
- advisory.armedMilestone = null;
407
- advisory.advisoryScheduleKey = null;
794
+ instrumentation.ledger = emptyLedger();
795
+ instrumentation.previousDigests = null;
796
+ instrumentation.previousText = null;
797
+ instrumentation.requests = 0;
798
+ instrumentation.lastMutationRequest = 0;
799
+ instrumentation.lastMutationTokens = null;
800
+ instrumentation.lastChange = "append";
801
+ instrumentation.lastPreservedShare = null;
802
+ instrumentation.sinceHandoff = [];
803
+ instrumentation.mutationsSinceHandoff = 0;
804
+ curation.receipts = [];
805
+ curation.contextCalls = 0;
806
+ curation.lastSignals = null;
807
+ curation.reopenBaselineShare = null;
808
+ curation.recoveryAttempts = 0;
809
+ curation.pendingRejection = null;
810
+ curation.lastRecovery = null;
408
811
  ladder.lastAutomaticAction = null;
409
812
  ladder.automaticFailure = null;
410
813
  advisory.hardFenceNoticeKey = null;
@@ -436,13 +839,6 @@ export function registerActiveContext(pi: any, options: {
436
839
  }
437
840
  for (const entry of branchEntries) {
438
841
  if (entry?.type !== "custom") continue;
439
- if (typeof entry.customType === "string" && [
440
- `${entryTypePrefix}-guidance-`,
441
- `${ACTIVE_CONTEXT_STATUS_KEY}-guidance-`,
442
- ].some((prefix) => entry.customType.startsWith(prefix))) {
443
- advisory.historicalGuidanceEntries += 1;
444
- continue;
445
- }
446
842
  if (entry.customType !== providerMeasurementEntryType) continue;
447
843
  try {
448
844
  const receipt = parseProviderContextMeasurementReceipt(entry.data, sessionId);
@@ -475,14 +871,13 @@ export function registerActiveContext(pi: any, options: {
475
871
  }
476
872
  const durableRestored = restored ?? emptyActiveContextState(sessionId);
477
873
  persistence.state = durableRestored.prepared ? clearPrepared(durableRestored) : clone(durableRestored);
478
- advisory.armedMilestone = advisoryState(persistence.state).armed?.milestone ?? null;
479
874
  persistence.persistedWireVersion = restoredPersistence?.wireVersion ?? 0;
480
875
  persistence.persistedFoldRecords = restoredPersistence?.records ?? new Map<string, FoldRecordEntry>();
481
876
  persistence.persistedStateSha256 = restoredPersistence?.stateSha256 ?? semanticStateSha256(durableRestored);
482
877
  const restoredMessages = ctx.sessionManager.buildSessionContext?.()?.messages;
483
878
  measurements.lastProviderMeasurement = latestProviderContextMeasurement(
484
879
  Array.isArray(restoredMessages) ? restoredMessages : [],
485
- contextWindowFor(ctx),
880
+ budgetWindowFor(ctx),
486
881
  ctx.model,
487
882
  );
488
883
  measurements.latestRatio = contextUsageRatio(measurements.lastProviderMeasurement);
@@ -763,14 +1158,10 @@ export function registerActiveContext(pi: any, options: {
763
1158
  }
764
1159
  };
765
1160
 
766
- const markManual = (
767
- next: ActiveContextState,
768
- action: Exclude<ActiveContextToolAction, "status">,
769
- ): void => {
1161
+ const markManual = (next: ActiveContextState): void => {
770
1162
  cancelPreparation();
771
1163
  persistence.state = clearPrepared(next);
772
1164
  ladder.pendingManual = true;
773
- if (action === "fold") advisory.armedMilestone = null;
774
1165
  ladder.boundaryFailure = null;
775
1166
  };
776
1167
 
@@ -782,7 +1173,7 @@ export function registerActiveContext(pi: any, options: {
782
1173
  const stateAtEntry = persistence.state ? clone(persistence.state) : null;
783
1174
  const persistedAtEntry = persistence.persisted;
784
1175
  const transientAtEntry = captureTransient();
785
- markManual(next, action);
1176
+ markManual(next);
786
1177
  try {
787
1178
  await persist(ctx);
788
1179
  ladder.pendingManual = false;
@@ -803,7 +1194,6 @@ export function registerActiveContext(pi: any, options: {
803
1194
  pendingManual: ladder.pendingManual,
804
1195
  preparing: ladder.preparing,
805
1196
  pendingContextNote: ladder.pendingContextNote,
806
- armedMilestone: advisory.armedMilestone,
807
1197
  lastAutomaticAction: ladder.lastAutomaticAction,
808
1198
  automaticFailure: ladder.automaticFailure ? clone(ladder.automaticFailure) : null,
809
1199
  boundaryFailure: ladder.boundaryFailure,
@@ -812,7 +1202,6 @@ export function registerActiveContext(pi: any, options: {
812
1202
  ladder.pendingManual = saved.pendingManual;
813
1203
  ladder.preparing = saved.preparing?.controller.signal.aborted ? null : saved.preparing;
814
1204
  ladder.pendingContextNote = saved.pendingContextNote;
815
- advisory.armedMilestone = saved.armedMilestone;
816
1205
  ladder.lastAutomaticAction = saved.lastAutomaticAction;
817
1206
  ladder.automaticFailure = saved.automaticFailure;
818
1207
  ladder.boundaryFailure = saved.boundaryFailure;
@@ -953,6 +1342,308 @@ export function registerActiveContext(pi: any, options: {
953
1342
  return true;
954
1343
  };
955
1344
 
1345
+ /**
1346
+ * Bytes per token, measured rather than assumed.
1347
+ *
1348
+ * `ESTIMATED_BYTES_PER_TOKEN` is a constant over RAW text, and the fence weighs a
1349
+ * SERIALIZED projection: roles, ids, custom types and JSON escaping all count toward
1350
+ * the bytes and none of them reach the provider's tokenizer the same way. Measured
1351
+ * 2026-08-06 on one workload: rep11 ran at 4.7 serialized chars per measured token
1352
+ * and rep12 at 7.0. Against the fixed 4 that is a 76% over-estimate in rep12, which
1353
+ * is exactly how a session whose real window was 49% full (187,805 tokens of a
1354
+ * 400,000 window, ratio 0.47) had its request judged over a 383,616-token budget,
1355
+ * reduced, judged over again, and finally ABORTED at stage 37 of 64.
1356
+ *
1357
+ * So the ratio is calibrated per session against ground truth: the size of a
1358
+ * projection this process handed the host, paired with the token count the provider
1359
+ * reported for it. Tiny early requests are ignored -- a 466-char first projection
1360
+ * measured against a system prompt gives a meaningless 0.46 -- and the result is
1361
+ * bounded, so a pathological pairing can neither disable the fence nor trip it.
1362
+ */
1363
+ const PROJECTION_CALIBRATION_MIN_CHARS = 20_000;
1364
+ const PROJECTION_CALIBRATION_MIN_TOKENS = 5_000;
1365
+ const PROJECTION_CHARS_PER_TOKEN_FLOOR = 2;
1366
+ const PROJECTION_CHARS_PER_TOKEN_CEILING = 12;
1367
+ /** How many recent pairings the ratio, the error window and the inflow window keep. */
1368
+ const PROJECTION_CALIBRATION_WINDOW = 6;
1369
+ const PROJECTION_ERROR_WINDOW = 8;
1370
+ /** Margin floor, as a share of the window, before any error or inflow is measured. */
1371
+ const PROJECTION_MARGIN_FLOOR_SHARE = 0.05;
1372
+
1373
+ const noteProjectionCalibration = (measurement: ProviderContextMeasurement): void => {
1374
+ const previous = measurements.lastProviderMeasurement;
1375
+ if (Number.isFinite(measurement.tokens) && previous && Number.isFinite(previous.tokens)) {
1376
+ const step = measurement.tokens - previous.tokens;
1377
+ // Only GROWTH is an inflow step. A commit shrinks the window; that is the thing
1378
+ // the margin has to survive, not a size the next request can be predicted from.
1379
+ if (step > 0) {
1380
+ measurements.inflowSteps.push(step);
1381
+ if (measurements.inflowSteps.length > PROJECTION_ERROR_WINDOW) measurements.inflowSteps.shift();
1382
+ }
1383
+ }
1384
+ const chars = measurements.lastProjectedChars;
1385
+ if (chars === null || chars < PROJECTION_CALIBRATION_MIN_CHARS) return;
1386
+ if (!Number.isFinite(measurement.tokens) || measurement.tokens < PROJECTION_CALIBRATION_MIN_TOKENS) return;
1387
+ // The signed error of the estimate we made for the projection this measurement
1388
+ // describes. This is the only honest measure of how wrong the fence can be.
1389
+ // Only a CALIBRATED estimate's error belongs in the window. The bootstrap constant
1390
+ // is wrong by design -- 76% high in rep12 -- and letting that one reading set the
1391
+ // margin would keep every later request permanently inside it.
1392
+ if (measurements.lastProjectedEstimate !== null && measurements.lastProjectedEstimateCalibrated &&
1393
+ measurement.tokens > 0) {
1394
+ measurements.estimatorErrors.push(
1395
+ (measurements.lastProjectedEstimate - measurement.tokens) / measurement.tokens,
1396
+ );
1397
+ if (measurements.estimatorErrors.length > PROJECTION_ERROR_WINDOW) measurements.estimatorErrors.shift();
1398
+ }
1399
+ measurements.projectionCalibrations.push({ chars, tokens: measurement.tokens });
1400
+ if (measurements.projectionCalibrations.length > PROJECTION_CALIBRATION_WINDOW) {
1401
+ measurements.projectionCalibrations.shift();
1402
+ }
1403
+ };
1404
+
1405
+ /**
1406
+ * Recent, and pessimistic.
1407
+ *
1408
+ * A single latest pairing tracks drift but carries its noise; an average lags it.
1409
+ * Near the top of the window neither is acceptable, because the error that matters is
1410
+ * one-sided: a ratio that is too HIGH under-counts tokens and transmits a request the
1411
+ * provider will reject. So the fence takes the smallest ratio in the recent window,
1412
+ * which converges instantly in the dangerous direction and within a window in the
1413
+ * cheap one. Measured 2026-08-06 (rep13): the ratio moved 5.34 to 6.01 and back
1414
+ * across twenty requests while the window sat above 90% full.
1415
+ */
1416
+ const projectionCharsPerToken = (): number => {
1417
+ const usable = measurements.projectionCalibrations.filter((entry) =>
1418
+ entry.tokens > 0 && Number.isFinite(entry.chars / entry.tokens) && entry.chars / entry.tokens > 0);
1419
+ if (!usable.length) return ESTIMATED_BYTES_PER_TOKEN;
1420
+ const measured = Math.min(...usable.map((entry) => entry.chars / entry.tokens));
1421
+ return Math.min(
1422
+ PROJECTION_CHARS_PER_TOKEN_CEILING,
1423
+ Math.max(PROJECTION_CHARS_PER_TOKEN_FLOOR, measured),
1424
+ );
1425
+ };
1426
+
1427
+ const projectedTokenEstimate = (projected: unknown[]): number =>
1428
+ Math.ceil(bytes(projected) / projectionCharsPerToken());
1429
+
1430
+ /** The largest recent estimator error, as a share. Unmeasured sessions assume none. */
1431
+ const estimatorErrorShare = (): number => measurements.estimatorErrors.length
1432
+ ? Math.max(...measurements.estimatorErrors.map((error) => Math.abs(error)))
1433
+ : 0;
1434
+
1435
+ /** The largest recent growth step, which is what one more turn can add. */
1436
+ const expectedInflowTokens = (): number => measurements.inflowSteps.length
1437
+ ? Math.max(...measurements.inflowSteps)
1438
+ : 0;
1439
+
1440
+ /**
1441
+ * The safety margin.
1442
+ *
1443
+ * A fence that fires AT the budget is a fence that fires after the wire: the request
1444
+ * that kills the session is the one built after the last measurement, and it is never
1445
+ * the one being weighed. Measured 2026-08-06 (rep13): the last measurement read
1446
+ * 370,320 tokens against a 383,616 budget -- 13,296 of headroom against a median
1447
+ * inflow of 13,865 and a maximum of 27,815 -- while the estimate for that request was
1448
+ * 366,934, comfortably "under budget". The next request crossed the real limit and
1449
+ * the provider rejected it. Every number in that sentence was known to the runtime.
1450
+ *
1451
+ * So the reduction threshold carries what the runtime knows it does not know: the
1452
+ * worst recent estimator error applied to this estimate, plus one worst recent inflow
1453
+ * step, never less than a floor share of the window.
1454
+ */
1455
+ const projectionMarginTokens = (estimate: number, windowTokens: number): number => Math.ceil(Math.max(
1456
+ PROJECTION_MARGIN_FLOOR_SHARE * windowTokens,
1457
+ estimatorErrorShare() * estimate + expectedInflowTokens(),
1458
+ ));
1459
+
1460
+ /**
1461
+ * The TRANSMISSION fence.
1462
+ *
1463
+ * `abortUnsafeHardContext` gates on `measurements.latestRatio`, which describes the
1464
+ * request the provider ALREADY answered. It is a lagging indicator: between that
1465
+ * response and the next request an excursion can add a hundred thousand tokens of
1466
+ * tool results, and nothing in the fence looks at the projection actually about to
1467
+ * be sent. Measured 2026-08-06 (rep 11): the last measurement read 359,625 tokens of
1468
+ * a 400,000 window, ratio 0.937 against a 0.959 hard fence, so no abort fired -- and
1469
+ * the projection that went out was 1,831,936 chars, about 458k estimated tokens, 1.2x
1470
+ * the whole window. The provider rejected it twice and the run died. rep4 aborted
1471
+ * correctly only because its 272k DESCRIPTOR window put the stale ratio over the
1472
+ * fence by luck of arithmetic, not because anything measured the request.
1473
+ *
1474
+ * So the projection itself is measured here, against the truthful serving budget the
1475
+ * capacity accounting already computes. Over budget, an emergency reduction runs at
1476
+ * fence pressure -- where every guarded mark is waived -- and the rebuilt projection
1477
+ * is measured again. If it still does not fit, the request is aborted BEFORE
1478
+ * transmission, which is the whole point of having a fence.
1479
+ */
1480
+ const projectionExceedsBudget = (projected: unknown[], ctx: any): {
1481
+ tokens: number;
1482
+ budgetTokens: number;
1483
+ marginTokens: number;
1484
+ /** Past the wire: this request must not be transmitted at all. */
1485
+ over: boolean;
1486
+ /** Inside the margin: still sendable, but the next one may not be. Reduce NOW. */
1487
+ crowded: boolean;
1488
+ } => {
1489
+ const capacity = currentCapacity(ctx);
1490
+ const budgetTokens = Number.isFinite(capacity.budgetTokens) && capacity.budgetTokens > 0
1491
+ ? capacity.budgetTokens
1492
+ : Number.POSITIVE_INFINITY;
1493
+ const tokens = projectedTokenEstimate(projected);
1494
+ const marginTokens = Number.isFinite(budgetTokens)
1495
+ ? projectionMarginTokens(tokens, capacity.window)
1496
+ : 0;
1497
+ return {
1498
+ tokens,
1499
+ budgetTokens,
1500
+ marginTokens,
1501
+ over: tokens > budgetTokens,
1502
+ crowded: tokens + marginTokens > budgetTokens,
1503
+ };
1504
+ };
1505
+
1506
+ const abortOverBudgetProjection = (
1507
+ tokens: number,
1508
+ budgetTokens: number,
1509
+ ctx: any,
1510
+ recoveryAttempts = 0,
1511
+ ): void => {
1512
+ const key = sha256Value({
1513
+ sessionId: persistence.state?.sessionId ?? null,
1514
+ revision: persistence.state?.revision ?? null,
1515
+ tokens,
1516
+ budgetTokens,
1517
+ phase: "projection-budget-fence",
1518
+ });
1519
+ ladder.pendingContextNote =
1520
+ `The ${brandNoun} projection estimates ${tokens} tokens against a ${budgetTokens}-token serving budget` +
1521
+ (recoveryAttempts
1522
+ ? `, after ${recoveryAttempts} recovery attempt(s) that folded everything foldable. The remaining ` +
1523
+ "inflow does not fit at any folding depth, which is an impossibility rather than a recoverable state."
1524
+ : ".") +
1525
+ " The provider request was aborted before transmission; run /compact or make an explicit bounded context fold.";
1526
+ if (advisory.hardFenceNoticeKey !== key) {
1527
+ advisory.hardFenceNoticeKey = key;
1528
+ safeNotify(
1529
+ ctx,
1530
+ "Provider request aborted: the projection exceeds the serving budget. " +
1531
+ "Run /compact or make an explicit bounded context fold.",
1532
+ "error",
1533
+ );
1534
+ }
1535
+ if (typeof ctx.abort !== "function") {
1536
+ throw new Error(`Pi projection-budget abort capability is unavailable at ${tokens} estimated tokens`);
1537
+ }
1538
+ ctx.abort();
1539
+ };
1540
+
1541
+ /**
1542
+ * Enforce the budget, and RECOVER rather than die.
1543
+ *
1544
+ * A provider context-overflow rejection mutates nothing durable: the assistant
1545
+ * message never lands and this runtime rebuilds the projection from the branch on
1546
+ * every request. So the terminal path is not an abort, it is a rollback: do not
1547
+ * append the failed exchange, fold at fence pressure until the request fits, rebuild,
1548
+ * and let Pi resend. Recovery is capped, and the cap is what makes it safe -- an
1549
+ * inflow that still will not fit after maximal folding is a genuine impossibility,
1550
+ * and that one fails LOUDLY exactly as before.
1551
+ */
1552
+ const enforceProjectionBudget = async (
1553
+ snapshot: ActiveContextSnapshot,
1554
+ projected: unknown[],
1555
+ ctx: any,
1556
+ ): Promise<{ projected: unknown[]; aborted: boolean }> => {
1557
+ let measured = projectionExceedsBudget(projected, ctx);
1558
+ const rejected = curation.pendingRejection !== null;
1559
+ // CROWDED, not over, is the trigger for reducing: the request that kills a session
1560
+ // is the one built after this one. A provider rejection outranks our own estimate:
1561
+ // it is ground truth that the last request did not fit, whatever we measured.
1562
+ if (!measured.crowded && !rejected) return { projected, aborted: false };
1563
+ // Why this fired, recorded from the measurement that TRIGGERED it rather than the
1564
+ // one taken afterwards, which by then describes a projection that fits.
1565
+ const trigger = measured;
1566
+ let reduced = projected;
1567
+ let attempts = 0;
1568
+ let reducedAtLeastOnce = false;
1569
+ // At the fence the only useful action is the fold that keeps the request
1570
+ // transmissible, so every reduction runs with every guarded mark waived.
1571
+ while (attempts < OVERFLOW_RECOVERY_MAX_ATTEMPTS && (measured.crowded || measured.over || rejected)) {
1572
+ attempts += 1;
1573
+ curation.recoveryAttempts += 1;
1574
+ let action: Record<string, unknown> | null = null;
1575
+ try {
1576
+ action = await attemptAutomaticRung(snapshot, 1, ctx, "projection-budget", {
1577
+ waiveToolCadence: true,
1578
+ waiverRatio: 1,
1579
+ });
1580
+ } catch (error) {
1581
+ suspendAutomatic(error, "projection-budget", ctx);
1582
+ break;
1583
+ }
1584
+ if (!action || !persistence.state) break;
1585
+ reducedAtLeastOnce = true;
1586
+ reduced = projectWithAdvisory(snapshot);
1587
+ measured = projectionExceedsBudget(reduced, ctx);
1588
+ if (!measured.over && !measured.crowded) break;
1589
+ }
1590
+ if (reducedAtLeastOnce) {
1591
+ ladder.overBudgetReduction = {
1592
+ estimatedTokensBefore: projectedTokenEstimate(projected),
1593
+ estimatedTokensAfter: measured.tokens,
1594
+ budgetTokens: trigger.budgetTokens,
1595
+ marginTokens: trigger.marginTokens,
1596
+ estimatorErrorShare: estimatorErrorShare(),
1597
+ expectedInflowTokens: expectedInflowTokens(),
1598
+ crowded: trigger.crowded,
1599
+ overBeforeReduction: trigger.over,
1600
+ transmitted: !measured.over,
1601
+ recoveryAttempts: attempts,
1602
+ providerRejected: rejected,
1603
+ };
1604
+ }
1605
+ if (rejected) {
1606
+ curation.lastRecovery = {
1607
+ status: curation.pendingRejection?.status ?? null,
1608
+ attempts,
1609
+ estimatedTokensAfter: measured.tokens,
1610
+ budgetTokens: measured.budgetTokens,
1611
+ recovered: !measured.over,
1612
+ };
1613
+ emit("context.recovery", {
1614
+ provider_status: curation.pendingRejection?.status ?? null,
1615
+ attempts,
1616
+ max_attempts: OVERFLOW_RECOVERY_MAX_ATTEMPTS,
1617
+ tokens_before: projectedTokenEstimate(projected),
1618
+ tokens_after: measured.tokens,
1619
+ budget_tokens: measured.budgetTokens,
1620
+ margin_tokens: measured.marginTokens,
1621
+ recovered: !measured.over,
1622
+ });
1623
+ const overflowBefore = projectedTokenEstimate(projected);
1624
+ deliverReceipt(contextReceipt({
1625
+ kind: "overflow-recovery",
1626
+ ordinal: markOrdinal(snapshot),
1627
+ trigger: `provider-rejection:${curation.pendingRejection?.status ?? "unknown"}`,
1628
+ freedTokens: Math.max(0, overflowBefore - measured.tokens),
1629
+ occupancyBefore: overflowBefore,
1630
+ occupancyAfter: measured.tokens,
1631
+ recovered: true,
1632
+ note: measured.over
1633
+ ? `The rebuilt request is still ${measured.tokens} tokens against a ${measured.budgetTokens}-token ` +
1634
+ "serving budget, so the run stops here rather than sending a request the provider will reject again."
1635
+ : "A rollback was required: the provider rejected the last request, which overfilled the serving " +
1636
+ `budget at ${overflowBefore} estimated tokens against ${measured.budgetTokens}. ` +
1637
+ `${attempts} reduction(s) landed it at ${measured.tokens} tokens. Nothing durable was written ` +
1638
+ "for it, and the request was rebuilt inside the budget rather than dropped.",
1639
+ }));
1640
+ curation.pendingRejection = null;
1641
+ }
1642
+ if (!measured.over) return { projected: reduced, aborted: false };
1643
+ abortOverBudgetProjection(measured.tokens, measured.budgetTokens, ctx, attempts);
1644
+ return { projected: reduced, aborted: true };
1645
+ };
1646
+
956
1647
  const startPreparation = (snapshot: ActiveContextSnapshot, ratio: number | null, ctx: any): void => {
957
1648
  if (lifecycle.shuttingDown || !persistence.state || ladder.automaticFailure || ratio === null || persistence.state.prepared || ladder.preparing ||
958
1649
  ratio < snapshot.policy.warmRatio ||
@@ -968,6 +1659,8 @@ export function registerActiveContext(pi: any, options: {
968
1659
  ? selection.candidate.sourceRefs.slice(0, 8).map((ref) => ref.entryId)
969
1660
  : [];
970
1661
  if (selection?.kind !== "chapter-prepare") return;
1662
+ // Already bite-sized: the chapter selector never proposes a span past the cap, so a
1663
+ // model brief is never spent on a fold nobody can read back cheaply.
971
1664
  const candidate = selection.candidate;
972
1665
  const id = automaticPreparationId(candidate, persistence.state);
973
1666
  const controller = new AbortController();
@@ -1018,52 +1711,165 @@ export function registerActiveContext(pi: any, options: {
1018
1711
  if (ownsSlot && sessionIdentityStillValid(ctx, snapshot.sessionId, capturedGeneration)) updateStatus(ctx);
1019
1712
  });
1020
1713
  };
1021
- const projectWithAdvisory = (snapshot: ActiveContextSnapshot): unknown[] => {
1022
- const projected = projectActiveContext(snapshot, persistence.state!).filter((message) => {
1023
- const customType = ownValue(message, "customType");
1024
- return customType !== milestoneProjectionType && customType !== advisoryProjectionType;
1025
- });
1026
- const armed = advisoryState(persistence.state!).armed;
1027
- if (!armed || armed.milestone !== advisory.armedMilestone || measurements.latestRatio === null ||
1028
- measurements.latestRatio < 0.85 * armed.threshold) return projected;
1029
- const status = activeContextStatus(snapshot, persistence.state!, 0, 1, snapshot.policy.maxFoldSourceRefs);
1030
- const eligible = ownValue(status, "eligibleChapter");
1031
- const startId = ownValue(eligible, "startId");
1032
- const endId = ownValue(eligible, "endId");
1033
- const chapterEndpoints = typeof startId === "string" && typeof endId === "string"
1034
- ? [startId, endId]
1035
- : [];
1036
- const toolEndpoints = selectAutomaticToolBatch(snapshot, persistence.state!, 1)
1037
- .flatMap((candidate) => candidate.sourceRefs.at(-1)?.entryId ?? [])
1038
- .slice(0, 3);
1039
- const remediationCount = advisoryState(persistence.state!).delivered[armed.milestone] ?? 0;
1714
+ // Appended at the very TAIL, after the stable prefix, so a suggestion never
1715
+ // invalidates a cached prefix and never becomes durable transcript.
1716
+ /**
1717
+ * The receipt block: what the runtime did to this window, as status rather than as
1718
+ * advice. It is the only carrier this runtime builds.
1719
+ *
1720
+ * It is free because it is RETROSPECTIVE. A receipt exists only after a commit has
1721
+ * already rewritten the projection, so it rides a cache break the runtime had to pay
1722
+ * for regardless, and the freeze then closes over it so it is never paid for again.
1723
+ * It is hard-bounded and its ring evicts the oldest entry, because a report about
1724
+ * bloat that becomes bloat has argued against itself.
1725
+ */
1726
+ const appendReceipts = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
1727
+ const content = receiptBlockText({ receipts: curation.receipts, toolName, brandNoun });
1728
+ if (!content) return projected;
1729
+ if (!carrierAdmitted("receipts")) return projected;
1040
1730
  projected.push({
1041
1731
  role: "custom",
1042
- customType: milestoneProjectionType,
1043
- content: milestoneText(armed.milestone, persistence.state!.sessionId, armed.threshold, toolName, brandNoun),
1732
+ customType: receiptProjectionType,
1733
+ content,
1044
1734
  display: false,
1045
- details: { source: activeContextSource(entryTypePrefix), ephemeral: true, milestone: armed.milestone },
1046
- timestamp: 0,
1047
- });
1048
- projected.push({
1049
- role: "custom",
1050
- customType: advisoryProjectionType,
1051
- content: liveAdvisoryText({
1052
- milestone: armed.milestone,
1053
- ratio: measurements.latestRatio,
1054
- toolEndpoints,
1055
- chapterEndpoints,
1056
- remediationCount,
1057
- brandNoun,
1058
- }),
1059
- display: false,
1060
- details: { source: activeContextSource(entryTypePrefix), ephemeral: true, milestone: armed.milestone },
1735
+ details: {
1736
+ source: activeContextSource(entryTypePrefix),
1737
+ ephemeral: true,
1738
+ receipts: curation.receipts.map((receipt) => ({ ...receipt })),
1739
+ maxBytes: CONTEXT_RECEIPT_BLOCK_BYTES,
1740
+ },
1061
1741
  timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
1062
1742
  ? ownValue(snapshot.messages.at(-1), "timestamp")
1063
1743
  : 0,
1064
1744
  });
1065
1745
  return projected;
1066
1746
  };
1747
+
1748
+ /**
1749
+ * Whatever this pass ended up sending becomes the frozen surface for the next one, in
1750
+ * full. Nothing rides after the frozen region.
1751
+ *
1752
+ * The earlier build kept a live suggestion slate outside the freeze, reasoning that
1753
+ * advice goes stale and that a re-rendered block "only diverges the prefix when its
1754
+ * own content changes". That reasoning is wrong, and rep 21 measured the cost. A
1755
+ * positional cache compares bytes from the start: this pass sends [frozen][slate] and
1756
+ * the next sends [frozen][new messages][slate], so at the offset where the slate sat
1757
+ * the next pass has new messages instead. The slate diverges the prefix EVERY pass
1758
+ * whatever it says, because appending anything displaces it. There is no stable
1759
+ * position at a moving tail.
1760
+ *
1761
+ * Measured on rep 21: a 1503-character slate against a 1.5M-character prompt, 99.86%
1762
+ * of the projection preserved by our own accounting, and 16 full cache rebuilds
1763
+ * costing 3.71M tokens -- 21.9% of every input token in the run. Prefix caching is a
1764
+ * step function, not a gradient: mutation cost has no relation to mutation size.
1765
+ *
1766
+ * So the rule this build enforces: the runtime adds to the window only at a moment it
1767
+ * is already rewriting the window, which means at a commit. A carrier landed there is
1768
+ * free, because the break is already paid for, and it stays landed forever because
1769
+ * the freeze closes over it.
1770
+ */
1771
+ const holdFrozen = (projected: unknown[]): unknown[] => {
1772
+ freeze.projection = [...projected];
1773
+ freeze.active = false;
1774
+ return projected;
1775
+ };
1776
+
1777
+ const projectWithAdvisory = (snapshot: ActiveContextSnapshot): unknown[] => {
1778
+ const body = projectActiveContext(snapshot, persistence.state!).filter((message) => {
1779
+ const customType = ownValue(message, "customType");
1780
+ return customType !== milestoneProjectionType && customType !== advisoryProjectionType &&
1781
+ customType !== surfacingProjectionType && customType !== receiptProjectionType &&
1782
+ customType !== curationProjectionType;
1783
+ });
1784
+ // The freeze test, stated as bytes rather than as intent: if the rebuilt body still
1785
+ // OPENS with the frozen body verbatim, nothing structural moved and the previous
1786
+ // projection is reused whole, with only what arrived since appended after it.
1787
+ const held = freeze.body?.length ?? 0;
1788
+ freeze.active = freeze.projection !== null && body.length >= held &&
1789
+ stableStringify(body.slice(0, held)) === freeze.bodyText;
1790
+ const projected = freeze.active
1791
+ ? [...freeze.projection!, ...body.slice(held)]
1792
+ : [...body];
1793
+ if (!freeze.active) freeze.keys.clear();
1794
+ freeze.body = body;
1795
+ freeze.bodyText = stableStringify(body);
1796
+ // The one carrier left, and the only one that can ever be free: a statement of what
1797
+ // the runtime just did. It is built only when a commit produced a receipt, it lands
1798
+ // inside the freeze, and it is never rendered twice.
1799
+ //
1800
+ // Everything that used to stand here was ANTICIPATORY -- pressure milestones, the
1801
+ // live advisory, curation reminders, the last-call notice. Anticipatory guidance has
1802
+ // to arrive BEFORE the event it warns about, so it can never ride a break the
1803
+ // runtime was already paying for; it has to create one. Anticipatory guidance and an
1804
+ // append-only projection are mutually exclusive, and eleven runs say the warning
1805
+ // bought nothing anyway: voluntary fold share was 0.00, and the three runs built
1806
+ // specifically to invite curation produced 0, 1 and 0 voluntary folds.
1807
+ appendReceipts(projected, snapshot);
1808
+ return holdFrozen(projected);
1809
+ };
1810
+ /**
1811
+ * Classify the projection we are about to send against the one before it. A rewrite
1812
+ * is ours; a pure append that the provider still re-reads is not, and conflating the
1813
+ * two is what made a scheduling lever look responsible for 12 misses it never caused.
1814
+ */
1815
+ const noteProjection = (projected: unknown[]): void => {
1816
+ const digests = messageDigests(projected);
1817
+ const comparison = compareProjections(instrumentation.previousDigests, digests);
1818
+ recordProjection(instrumentation.ledger, comparison, digests);
1819
+ instrumentation.previousDigests = digests;
1820
+ instrumentation.lastChange = comparison.change;
1821
+ const text = stableStringify(projected);
1822
+ const divergence = prefixDivergence(instrumentation.previousText, text);
1823
+ const previousChars = instrumentation.previousText?.length ?? 0;
1824
+ // How much of the PREVIOUS prompt this projection still opens with. identicalShare
1825
+ // measures the new projection instead, which grows every pass and would call a
1826
+ // total rewrite "preserved" as soon as enough fresh content landed after it.
1827
+ instrumentation.lastPreservedShare = previousChars > 0
1828
+ ? divergence.identicalChars / previousChars
1829
+ : null;
1830
+ const charsPerToken = projectionCharsPerToken();
1831
+ const causes = instrumentation.sinceHandoff.filter((event) =>
1832
+ PREFIX_MUTATING_KINDS.has(event.kind));
1833
+ emit("context.projection", {
1834
+ change: comparison.change,
1835
+ previous_count: comparison.previousCount,
1836
+ next_count: comparison.nextCount,
1837
+ appended_count: comparison.appendedCount,
1838
+ first_divergent_index: comparison.firstDivergentIndex,
1839
+ chars: bytes(projected),
1840
+ estimated_tokens: projectedTokenEstimate(projected),
1841
+ chars_per_token: charsPerToken,
1842
+ });
1843
+ // Emission only. Which of these is a provider-side miss and which is a rewrite we
1844
+ // caused is a join against provider-reported cacheRead, and that join is the
1845
+ // analyst's, not this runtime's.
1846
+ emit("context.prefix", {
1847
+ change: comparison.change,
1848
+ divergent_char: divergence.index,
1849
+ divergent_tokens: divergence.index === null ? null : Math.ceil(divergence.index / charsPerToken),
1850
+ identical_chars: divergence.identicalChars,
1851
+ identical_share: divergence.identicalShare,
1852
+ chars: text.length,
1853
+ previous_chars: instrumentation.previousText?.length ?? 0,
1854
+ estimated_tokens: Math.ceil(text.length / charsPerToken),
1855
+ cause: divergence.index === null
1856
+ ? "pure-append"
1857
+ : (causes.length ? causes.map((event) => event.kind).join(",") : "unattributed"),
1858
+ cause_event_seqs: causes.map((event) => event.seq).join(","),
1859
+ events_since_handoff: instrumentation.sinceHandoff.length,
1860
+ // The request class, which falls straight out of the attribution and makes a run
1861
+ // directly comparable to a production cache table. Not new measurement.
1862
+ request_class: divergence.index === null
1863
+ ? "steady-state"
1864
+ : (causes.some((event) => event.kind === "context.recovery")
1865
+ ? "after-rollback"
1866
+ : (causes.length ? "after-fold" : "after-message")),
1867
+ });
1868
+ instrumentation.previousText = text;
1869
+ instrumentation.sinceHandoff = [];
1870
+ instrumentation.mutationsSinceHandoff = 0;
1871
+ };
1872
+
1067
1873
  const commitDeterministicCandidate = async (
1068
1874
  snapshot: ActiveContextSnapshot,
1069
1875
  candidate: FoldCandidate,
@@ -1127,22 +1933,603 @@ export function registerActiveContext(pi: any, options: {
1127
1933
  return { preparedFold, nextState };
1128
1934
  };
1129
1935
 
1936
+ /**
1937
+ * The one mutation of an epoch: every pending mark, plus the automatic additions
1938
+ * a commit is allowed to make, applied through the same machinery as an immediate
1939
+ * fold. Free additions come first (peek reads the agent already discarded), then
1940
+ * the quota top-up that guarantees the commit is worth its rewrite.
1941
+ */
1942
+ /**
1943
+ * One turn of inflow, as a share of the window: the least a commit at high occupancy
1944
+ * has to free to be worth its rewrite. Measured inflow is used when the session has
1945
+ * any -- rep13 ran at a median 13,865 and a maximum 27,815 tokens per request against
1946
+ * a 400,000 window -- and the floor share stands in until then.
1947
+ */
1948
+ const commitDepthFloorShare = (snapshot: ActiveContextSnapshot): number => {
1949
+ const window = snapshot.contextWindow;
1950
+ if (!Number.isFinite(window) || window <= 0) return COMMIT_RECLAIM_FLOOR_SHARE;
1951
+ const inflow = expectedInflowTokens();
1952
+ return Math.max(COMMIT_RECLAIM_FLOOR_SHARE, inflow > 0 ? inflow / window : 0);
1953
+ };
1954
+
1955
+ const atOrAboveBackstop = (snapshot: ActiveContextSnapshot, ratio: number | null): boolean =>
1956
+ typeof ratio === "number" && Number.isFinite(ratio) && ratio >= snapshot.policy.refoldRatio;
1957
+
1958
+ /**
1959
+ * A snapshot whose FRESH tail is narrowed.
1960
+ *
1961
+ * Raising the top-up target does nothing once the eligible mass is exhausted, and at
1962
+ * high occupancy that is exactly the state: measured 2026-08-06 (rep13), the big
1963
+ * stale mass was already folded and what remained was the fresh tail and the newest
1964
+ * payloads, so six commits of two to four folds carried the window from 340k to 370k
1965
+ * and into a provider rejection. Depth therefore means reaching into the fresh tail,
1966
+ * not asking harder for evidence that is not there.
1967
+ *
1968
+ * The newest complete turn stays protected and so does everything the current-turn
1969
+ * guard covers: this narrows freshness, it does not abolish it.
1970
+ */
1971
+ const DEEPENED_FRESH_TURNS = 1;
1972
+ const DEEPENED_FRESH_BYTES_SHARE = 0.25;
1973
+
1974
+ const deepenedFreshnessSnapshot = (snapshot: ActiveContextSnapshot): ActiveContextSnapshot => {
1975
+ const policy = {
1976
+ ...snapshot.policy,
1977
+ freshTurns: Math.min(snapshot.policy.freshTurns, DEEPENED_FRESH_TURNS),
1978
+ freshBytes: Math.floor(snapshot.policy.freshBytes * DEEPENED_FRESH_BYTES_SHARE),
1979
+ };
1980
+ const toolProtectedIndices = toolFreshIndices(snapshot.messages, snapshot.completeTurns, policy);
1981
+ // Unmapped messages are protected in every snapshot: nothing can fold what the
1982
+ // durable branch cannot name.
1983
+ for (const item of snapshot.mapped) if (!item.ref) toolProtectedIndices.add(item.index);
1984
+ return { ...snapshot, toolProtectedIndices };
1985
+ };
1986
+
1987
+ const runCommitEpoch = async (
1988
+ snapshot: ActiveContextSnapshot,
1989
+ trigger: string,
1990
+ topUp: boolean,
1991
+ /**
1992
+ * Pressure the waiver is measured against. Defaults to the measured ratio; the
1993
+ * over-budget reduction forces the fence so nothing survivable is held back.
1994
+ */
1995
+ waiverRatio: number | null = measurements.latestRatio,
1996
+ ): Promise<Record<string, unknown> | null> => {
1997
+ const ordinal = markOrdinal(snapshot);
1998
+ let state = persistence.state!;
1999
+ let peekAdded = 0;
2000
+ let topUpAdded = 0;
2001
+ let deepened = false;
2002
+ let deepenedMarks = 0;
2003
+ let preDeepenShare: number | null = null;
2004
+ // The snapshot the commit adjudicates against: the deepened one narrows freshness
2005
+ // so the marks the deep top-up just made are not refused as fresh.
2006
+ let commitSnapshot = snapshot;
2007
+ for (const mark of ephemeralPeekMarks({ snapshot, state, ordinal })) {
2008
+ const addition = addPendingMark(state, mark);
2009
+ if (addition.added) { state = addition.state; peekAdded += 1; }
2010
+ }
2011
+ const guarded = currentTurnRefKeys(snapshot);
2012
+ // At the fence everything unpinned is fair game, INCLUDING the top-up's reach: a
2013
+ // session whose only foldable evidence is the open excursion has nothing else to
2014
+ // propose, and excluding it there leaves the commit with nothing to apply at the
2015
+ // exact moment reduction is the only thing keeping the request sendable.
2016
+ const fenceLevel = typeof waiverRatio === "number" && Number.isFinite(waiverRatio) &&
2017
+ waiverRatio >= hardFenceRatio(snapshot);
2018
+ // The thermostat. Firing at the trigger line and folding down to the target line is
2019
+ // what makes event SPACING structural rather than hoped for.
2020
+ const capacity = servingCapacity(snapshot.contextWindow);
2021
+ const usedTokens = capacity.usedTokens;
2022
+ const budgetTokens = capacity.budgetTokens;
2023
+ // THE SAFETY EXEMPTION, NARROWED.
2024
+ //
2025
+ // The hard fence is a RATIO prediction. Standing near it is not the same as being
2026
+ // unable to send, and treating the two alike handed the fence path a standing
2027
+ // waiver from both economy guards. Measured 2026-08-07 (rep 17): fence-path
2028
+ // window-pressure commits fired at eligible-freed shares as low as 0.018, under the
2029
+ // 0.02 floor the guided path honors, and re-fired inside a single ordinal. So only
2030
+ // two states outrank the economy: the overflow recovery lane, which runs because a
2031
+ // request already did not fit, and an occupancy genuinely past the serving budget,
2032
+ // where the next request aborts unless something moves. Everything else defers.
2033
+ const overflowExempt = curation.recoveryAttempts > 0 ||
2034
+ (usedTokens !== null && budgetTokens > 0 && usedTokens > budgetTokens);
2035
+ // ONE STRUCTURAL MUTATION PER HANDOFF.
2036
+ //
2037
+ // Measured 2026-08-06 (rep 15): two context.commit records 50ms apart inside one
2038
+ // ordinal, revisions 14 and 15 -- two REAL mutations, not a duplicated record. The
2039
+ // pass had committed at the announced trigger, rebuilt the projection, found it
2040
+ // still CROWDED (inside the fence margin, but transmissible), and committed again.
2041
+ // The second commit bought margin the next pass would have bought anyway and cost a
2042
+ // whole second prefix rewrite. So a second commit in the same handoff defers unless
2043
+ // the request is genuinely untransmittable: the fence and the recovery lane are
2044
+ // safety, and safety outranks the budget.
2045
+ if (instrumentation.mutationsSinceHandoff > 0 && !overflowExempt) {
2046
+ emit("context.commit", {
2047
+ trigger,
2048
+ deferred: true,
2049
+ reason: "mutation-budget-spent",
2050
+ applied_marks: 0,
2051
+ mutations_since_handoff: instrumentation.mutationsSinceHandoff,
2052
+ pending_marks: pendingMarks(state).length,
2053
+ });
2054
+ return null;
2055
+ }
2056
+ const hysteresisShare = usedTokens === null || snapshot.contextWindow <= 0
2057
+ ? 0
2058
+ : Math.max(0, (usedTokens - CURATION_TARGET_OCCUPANCY_SHARE * budgetTokens) /
2059
+ snapshot.contextWindow);
2060
+ const freeingTarget = Math.max(EPOCH_COMMIT_TARGET_WINDOW_SHARE, hysteresisShare);
2061
+ if (topUp) {
2062
+ // Measuring top-up progress against ELIGIBLE mass is what keeps the pressure
2063
+ // backstop working under a peek-heavy agent: pinned and retained marks are mass
2064
+ // no commit can move, and counting them as progress stops the top-up dead.
2065
+ for (const mark of topUpMarks({
2066
+ snapshot,
2067
+ state,
2068
+ ordinal,
2069
+ excludeRefKeys: fenceLevel ? new Set<string>() : guarded,
2070
+ eligibleOnly: true,
2071
+ // The fence tops up until there is nothing left to propose: a reduction that
2072
+ // stops at the ordinary target can leave the request still untransmittable.
2073
+ targetShare: fenceLevel ? 1 : freeingTarget,
2074
+ })) {
2075
+ const addition = addPendingMark(state, mark);
2076
+ if (addition.added) { state = addition.state; topUpAdded += 1; }
2077
+ }
2078
+ // High-occupancy depth. Near the top of the window a commit that frees less than
2079
+ // one turn of inflow does not reduce anything: it pays a full prefix rewrite,
2080
+ // gives back less than the next stage adds, and the window ratchets UP through
2081
+ // commit after commit. Measured 2026-08-06 (rep13): once the big stale mass was
2082
+ // folded, six commits of two to four folds each carried the window from 340k to
2083
+ // 370k and into a provider rejection, with the backstop firing the whole way.
2084
+ // So a commit at or above the backstop that has not reached one inflow step
2085
+ // reaches further, into everything eligible, instead of billing a rewrite for
2086
+ // crumbs.
2087
+ preDeepenShare = markAccounting(snapshot, state).eligibleFreedWindowShare;
2088
+ // Deepen whenever the ordinary reach fell short of the thermostat's target too:
2089
+ // an event that does not reach the lower line is an event that re-fires soon.
2090
+ const shallow = preDeepenShare < Math.max(commitDepthFloorShare(snapshot), freeingTarget);
2091
+ if (fenceLevel || (shallow && atOrAboveBackstop(snapshot, waiverRatio))) {
2092
+ deepened = true;
2093
+ commitSnapshot = deepenedFreshnessSnapshot(snapshot);
2094
+ for (const mark of topUpMarks({
2095
+ snapshot: commitSnapshot,
2096
+ state,
2097
+ ordinal,
2098
+ excludeRefKeys: fenceLevel ? new Set<string>() : guarded,
2099
+ eligibleOnly: true,
2100
+ targetShare: 1,
2101
+ })) {
2102
+ const addition = addPendingMark(state, mark);
2103
+ if (addition.added) { state = addition.state; topUpAdded += 1; deepenedMarks += 1; }
2104
+ }
2105
+ }
2106
+ }
2107
+ // Boundary slivers ride along in the mutation this commit already pays for; they
2108
+ // are never a mutation of their own, and a gap above the tiny token threshold is
2109
+ // deliberate curation the absorber must not touch.
2110
+ const wedges = absorbWedgeMarks({
2111
+ snapshot,
2112
+ state,
2113
+ charsPerToken: projectionCharsPerToken(),
2114
+ excludeRefKeys: fenceLevel ? new Set<string>() : guarded,
2115
+ });
2116
+ state = wedges.state;
2117
+ const accounting = markAccounting(snapshot, state);
2118
+ if (!accounting.pending) return null;
2119
+ // The reclaim floor. One structural mutation per model call is the budget, so a
2120
+ // commit that would free crumbs spends the whole budget on nothing: it defers and
2121
+ // the marks accumulate. Safety outranks economics, so genuine overflow -- the
2122
+ // recovery lane, or an occupancy already past the serving budget -- fires
2123
+ // regardless of what it frees. Standing at the fence RATIO does not.
2124
+ if (!overflowExempt &&
2125
+ accounting.eligibleFreedWindowShare < COMMIT_RECLAIM_FLOOR_SHARE) {
2126
+ persistence.state = state;
2127
+ emit("context.commit", {
2128
+ trigger,
2129
+ deferred: true,
2130
+ reason: "below-reclaim-floor",
2131
+ applied_marks: 0,
2132
+ pending_marks: accounting.pending,
2133
+ eligible_marks: accounting.eligibleMarks,
2134
+ eligible_freed_share: accounting.eligibleFreedWindowShare,
2135
+ reclaim_floor_share: COMMIT_RECLAIM_FLOOR_SHARE,
2136
+ window_tokens: snapshot.contextWindow,
2137
+ });
2138
+ return null;
2139
+ }
2140
+ const bytesBefore = bytes(projectActiveContext(snapshot, state));
2141
+ // The guard protects an in-flight excursion, never at the cost of the session's
2142
+ // ability to send a request at all: above the pressure backstop the oldest guarded
2143
+ // marks are released, and at the fence all of them are.
2144
+ const guardWaiver = guardWaiverCount({
2145
+ snapshot,
2146
+ ratio: waiverRatio,
2147
+ guardedMarks: pendingMarks(state).filter((mark) =>
2148
+ markTouchesCurrentTurn(state, mark, guarded)).length,
2149
+ // Against commitSnapshot, NOT snapshot. The starvation test decides whether the
2150
+ // guard may be waived, so it has to ask the same question the commit will: does
2151
+ // this epoch have work that does not touch the open turn? A DEEPENED epoch tops
2152
+ // up marks that the un-deepened freshness policy still calls protected, so
2153
+ // weighing them under the shallow snapshot reported zero applicable marks while
2154
+ // the commit went on to apply two. The waiver then fired on a false starvation
2155
+ // reading and released eight open-turn marks, six of which folded evidence the
2156
+ // turn was still using. Two snapshots, two answers, and the one that decides the
2157
+ // commit was not the one being asked.
2158
+ otherApplicableMarks: pendingMarks(state).filter((mark) =>
2159
+ !markTouchesCurrentTurn(state, mark, guarded) &&
2160
+ markEligibility(commitSnapshot, state, mark) === "eligible").length,
2161
+ });
2162
+ const result = await commitPendingMarks({
2163
+ snapshot: commitSnapshot,
2164
+ state,
2165
+ generation: lifecycle.generation,
2166
+ retainIneligible: true,
2167
+ guardCurrentTurn: true,
2168
+ guardWaiver,
2169
+ });
2170
+ persistence.state = result.state;
2171
+ const bytesAfter = bytes(projectActiveContext(snapshot, result.state));
2172
+ const freedBytes = Math.max(0, bytesBefore - bytesAfter);
2173
+ const commitEvent = emit("context.commit", {
2174
+ trigger,
2175
+ deferred: false,
2176
+ reason: null,
2177
+ eligible_freed_share: accounting.eligibleFreedWindowShare,
2178
+ reclaim_floor_share: COMMIT_RECLAIM_FLOOR_SHARE,
2179
+ // The two first-class dials: how much ONE event reclaims, and how long since the
2180
+ // last one. A trigger that frees too little re-fires immediately, and that shape
2181
+ // is only visible with both numbers on the same record.
2182
+ target_freed_share: freeingTarget,
2183
+ hysteresis_target_share: hysteresisShare,
2184
+ target_occupancy_share: CURATION_TARGET_OCCUPANCY_SHARE,
2185
+ shortfall_share: Math.max(0, freeingTarget - accounting.eligibleFreedWindowShare),
2186
+ occupancy_tokens_before: usedTokens,
2187
+ budget_tokens: budgetTokens,
2188
+ requests_since_previous: instrumentation.requests - instrumentation.lastMutationRequest,
2189
+ inflow_tokens_since_previous: usedTokens === null || instrumentation.lastMutationTokens === null
2190
+ ? null
2191
+ : usedTokens - instrumentation.lastMutationTokens,
2192
+ applied_marks: result.applied.length,
2193
+ // Refusal is TERMINAL only. A mark whose span was still fresh is deferred, not
2194
+ // refused: it is held and folds at the first commit after the span ages out.
2195
+ refused_marks: result.refused.filter((mark) => !mark.retained).length,
2196
+ deferred_marks: result.retained.length,
2197
+ waived_marks: result.waived.length,
2198
+ pending_marks: accounting.pending,
2199
+ agent_marks: accounting.agentMarks,
2200
+ ladder_marks: accounting.ladderMarks,
2201
+ peek_marks: peekAdded,
2202
+ topup_marks: topUpAdded,
2203
+ deepened: deepened,
2204
+ deepened_marks: deepenedMarks,
2205
+ absorbed_wedges: wedges.absorbed.length,
2206
+ freed_bytes: freedBytes,
2207
+ freed_tokens: estimatedTokens(freedBytes),
2208
+ rewrite_tokens: accounting.rewriteTokens,
2209
+ pinned_bytes: accounting.pinnedBytes,
2210
+ pinned_results: accounting.pinnedResults,
2211
+ window_tokens: snapshot.contextWindow,
2212
+ });
2213
+ instrumentation.mutationsSinceHandoff += 1;
2214
+ instrumentation.lastMutationRequest = instrumentation.requests;
2215
+ instrumentation.lastMutationTokens = usedTokens;
2216
+ let foldedToolResults = 0;
2217
+ let foldedSpans = 0;
2218
+ for (const applied of result.applied) {
2219
+ const fold = persistence.state.folds.find((item) => item.id === applied.foldId);
2220
+ if (fold?.kind === "tool-result") foldedToolResults += 1;
2221
+ else foldedSpans += 1;
2222
+ emit("context.fold", {
2223
+ commit_seq: commitEvent.seq,
2224
+ fold_id: applied.foldId,
2225
+ // The MARK this fold came from. The stream is now the only account of what a
2226
+ // commit applied, so it has to name both ends of that mapping.
2227
+ mark_id: applied.id,
2228
+ fold_kind: fold?.kind ?? applied.mark,
2229
+ origin: applied.origin,
2230
+ source_chars: fold?.sourceChars ?? 0,
2231
+ placeholder_chars: fold?.placeholderChars ?? 0,
2232
+ brief_provenance: fold ? normalizeLegacyProvenance(fold.provenance).kind : null,
2233
+ });
2234
+ }
2235
+ for (const wedge of wedges.absorbed) {
2236
+ emit("context.absorb", {
2237
+ commit_seq: commitEvent.seq,
2238
+ into_fold_id: wedge.intoMarkId,
2239
+ start_id: wedge.startId,
2240
+ end_id: wedge.endId,
2241
+ entries: wedge.entries,
2242
+ tokens: wedge.tokens,
2243
+ threshold_tokens: MAX_WEDGE_ABSORB_TOKENS,
2244
+ });
2245
+ }
2246
+ // A bound-out top-up and a silently dropped agent mark are both invisible in an
2247
+ // applied/refused list alone, so the epoch reports its own composition.
2248
+ return {
2249
+ trigger,
2250
+ applied: result.applied,
2251
+ refused: result.refused,
2252
+ pendingMarks: accounting.pending,
2253
+ agentMarks: accounting.agentMarks,
2254
+ ladderMarks: accounting.ladderMarks,
2255
+ peekMarks: peekAdded,
2256
+ topUpMarks: topUpAdded,
2257
+ deepenedTarget: deepened,
2258
+ deepenedMarks,
2259
+ absorbedWedges: wedges.absorbed.length,
2260
+ absorbed: wedges.absorbed,
2261
+ // What the commit actually folded, the way an agent counts it, so the receipt can
2262
+ // report impact instead of an opaque mark total.
2263
+ foldedSpans,
2264
+ foldedToolResults,
2265
+ occupancyTokensBefore: usedTokens,
2266
+ preDeepenFreedShare: preDeepenShare,
2267
+ depthFloorShare: commitDepthFloorShare(snapshot),
2268
+ reclaimFloorShare: COMMIT_RECLAIM_FLOOR_SHARE,
2269
+ guardWaived: result.waived.length > 0,
2270
+ waivedMarks: result.waived.length,
2271
+ appliedMarks: result.applied.length,
2272
+ refusedMarks: result.refused.filter((mark) => !mark.retained).length,
2273
+ deferredMarks: result.retained.length,
2274
+ pinnedBytes: accounting.pinnedBytes,
2275
+ pinnedResults: accounting.pinnedResults,
2276
+ retainedMarks: result.retained.length,
2277
+ currentTurnRetained: result.retained.filter((mark) =>
2278
+ markTouchesCurrentTurn(state, mark, guarded)).length,
2279
+ eligibleMarks: accounting.eligibleMarks,
2280
+ estimatedRewriteTokens: accounting.rewriteTokens,
2281
+ estimatedFreedTokens: accounting.freedTokens,
2282
+ freedWindowShare: accounting.freedWindowShare,
2283
+ sourceBytesSaved: freedBytes,
2284
+ actualFreedWindowShare: snapshot.contextWindow > 0
2285
+ ? estimatedTokens(freedBytes) / snapshot.contextWindow
2286
+ : 0,
2287
+ targetWindowShare: freeingTarget,
2288
+ hysteresisTargetShare: hysteresisShare,
2289
+ requestsSincePreviousCommit: instrumentation.requests - instrumentation.lastMutationRequest,
2290
+ instrumentation: ledgerSummary(instrumentation.ledger),
2291
+ };
2292
+ };
2293
+
2294
+ /** The two curation signals, measured against the ONE serving budget. */
2295
+ const measuredCurationSignals = (snapshot: ActiveContextSnapshot): CurationSignals => {
2296
+ const capacity = servingCapacity(snapshot.contextWindow);
2297
+ curation.lastSignals = curationSignals({
2298
+ snapshot,
2299
+ state: persistence.state!,
2300
+ usedTokens: capacity.usedTokens,
2301
+ budgetTokens: capacity.budgetTokens,
2302
+ window: capacity.window,
2303
+ charsPerToken: projectionCharsPerToken(),
2304
+ eligibleFolds: markAccounting(snapshot, persistence.state!).eligibleMarks,
2305
+ });
2306
+ return curation.lastSignals;
2307
+ };
2308
+
2309
+ /**
2310
+ * The silent early commit. The two signals decide, the reclaim floor stops a commit
2311
+ * that would free nothing, and the hysteresis stops a plateau from reading as a
2312
+ * stream of events. What is gone is only the announcement in front of it.
2313
+ */
2314
+ const curationCommitDue = (snapshot: ActiveContextSnapshot): boolean => {
2315
+ if (!persistence.state) return false;
2316
+ if (!curationTriggerFires(measuredCurationSignals(snapshot))) return false;
2317
+ if (curation.reopenBaselineShare !== null) {
2318
+ const eligibleShare = markAccounting(snapshot, persistence.state).eligibleFreedWindowShare;
2319
+ if (eligibleShare - curation.reopenBaselineShare < COMMIT_RECLAIM_FLOOR_SHARE) return false;
2320
+ curation.reopenBaselineShare = null;
2321
+ }
2322
+ return true;
2323
+ };
2324
+
2325
+ /**
2326
+ * Condition (a) of the reopen hysteresis, evaluated on EVERY context pass. A window
2327
+ * that fell back under the trigger has ended its cycle, and the next crossing is a
2328
+ * fresh event. This cannot live inside the trigger: the quiet passes that matter are
2329
+ * exactly the ones too far below it to evaluate it.
2330
+ */
2331
+ const clearCommitLatchBelowTrigger = (): void => {
2332
+ if (curation.reopenBaselineShare === null) return;
2333
+ const capacity = servingCapacity(lifecycle.latestSnapshot?.contextWindow ?? null);
2334
+ if (capacity.usedTokens === null || capacity.budgetTokens <= 0) return;
2335
+ if (capacity.usedTokens / capacity.budgetTokens < CURATION_OCCUPANCY_SHARE) {
2336
+ curation.reopenBaselineShare = null;
2337
+ }
2338
+ };
2339
+
2340
+ /**
2341
+ * One receipt per automatic context action, in the window and in the durable stream.
2342
+ * Informatory, never exhortative: it says what happened, what it freed, and which
2343
+ * verbs correct it. A mark that changed nothing is not an event and gets no receipt.
2344
+ */
2345
+ const noteAutomaticReceipt = (
2346
+ snapshot: ActiveContextSnapshot,
2347
+ action: Record<string, unknown>,
2348
+ epoch: Record<string, unknown> | null,
2349
+ ): void => {
2350
+ const kind = String(action.kind ?? "context-action");
2351
+ if (kind === "mark") return;
2352
+ const savedBytes = Number(action.sourceBytesSaved ?? epoch?.sourceBytesSaved ?? 0);
2353
+ const freedTokens = Number.isFinite(savedBytes) && savedBytes > 0
2354
+ ? Math.ceil(savedBytes / projectionCharsPerToken())
2355
+ : 0;
2356
+ const foldIds = Array.isArray(action.foldIds) ? action.foldIds.length : 0;
2357
+ const occupancyBefore = epoch && typeof epoch.occupancyTokensBefore === "number"
2358
+ ? epoch.occupancyTokensBefore
2359
+ : measurements.lastProviderMeasurement?.tokens ?? null;
2360
+ deliverReceipt(contextReceipt({
2361
+ kind,
2362
+ ordinal: markOrdinal(snapshot),
2363
+ trigger: epoch ? String(epoch.trigger ?? "") || null : null,
2364
+ foldsCommitted: epoch ? Number(epoch.appliedMarks ?? 0) : 0,
2365
+ foldsCreated: foldIds || (epoch ? Number(epoch.appliedMarks ?? 0) : 0),
2366
+ freedTokens,
2367
+ // The impact, in the same unit the budget is stated in.
2368
+ occupancyBefore,
2369
+ occupancyAfter: occupancyBefore === null ? null : Math.max(0, occupancyBefore - freedTokens),
2370
+ spansFolded: epoch ? Number(epoch.foldedSpans ?? 0) : foldIds,
2371
+ toolResultsFolded: epoch ? Number(epoch.foldedToolResults ?? 0) : 0,
2372
+ splitFolds: Number(action.splitFolds ?? 0),
2373
+ splitFromChars: Number(action.splitFromChars ?? 0),
2374
+ absorbedWedges: Number(epoch?.absorbedWedges ?? 0),
2375
+ recovered: curation.recoveryAttempts > 0,
2376
+ note: epoch && Number(epoch.deepenedMarks ?? 0) > 0
2377
+ ? `The commit reached into the fresh tail for ${epoch.deepenedMarks} further span(s), because a ` +
2378
+ "shallower commit would have freed less than one turn of inflow."
2379
+ : null,
2380
+ }));
2381
+ };
2382
+
1130
2383
  const applyAutomaticRung = async (
1131
2384
  snapshot: ActiveContextSnapshot,
1132
2385
  ratio: number,
1133
- rungOptions: { waiveToolCadence?: boolean; toolOnly?: boolean } = {},
2386
+ rungOptions: {
2387
+ waiveToolCadence?: boolean;
2388
+ toolOnly?: boolean;
2389
+ waiverRatio?: number;
2390
+ announcing?: boolean;
2391
+ } = {},
1134
2392
  ): Promise<Record<string, unknown> | null> => {
1135
2393
  if (!persistence.state || ladder.automaticFailure || ladder.preparing) return null;
1136
- const selection = selectAutomaticRung(snapshot, persistence.state, ratio, {
2394
+ const rungSelectionOptions = {
1137
2395
  waiveToolCadence: rungOptions.waiveToolCadence,
1138
2396
  toolOnly: rungOptions.toolOnly,
1139
2397
  summarizerAvailable: Boolean(options.summarizeContextSpan),
1140
2398
  failedPreparationIds: ladder.failedPreparations,
1141
- });
1142
- if (!selection) return null;
2399
+ };
2400
+ // The ladder decides but does not move bytes. Marks already pending are decisions
2401
+ // already made: excluding the evidence they cover makes every eligible turn choose
2402
+ // NEW stale content, so marks accumulate with stale growth instead of re-proposing
2403
+ // one stale batch.
2404
+ const markLadderSelection = (): Record<string, unknown> | null => {
2405
+ if (!persistence.state) return null;
2406
+ const decision = selectAutomaticRung(snapshot, persistence.state, ratio, {
2407
+ ...rungSelectionOptions,
2408
+ claimed: claimedRefKeys(persistence.state),
2409
+ claimedFoldIds: markedFoldIds(persistence.state),
2410
+ });
2411
+ const mark = decision
2412
+ ? ladderSelectionMark({
2413
+ snapshot, state: persistence.state, selection: decision, ordinal: markOrdinal(snapshot),
2414
+ })
2415
+ : null;
2416
+ if (!mark) return null;
2417
+ const addition = addPendingMark(persistence.state, mark);
2418
+ if (!addition.added) return null;
2419
+ persistence.state = addition.state;
2420
+ ladder.pendingContextNote =
2421
+ `Pending ${mark.mark} mark ${mark.id} recorded; no context bytes moved. ` +
2422
+ "It applies at the next commit epoch.";
2423
+ ladder.lastAutomaticAction = {
2424
+ kind: "mark",
2425
+ foldIds: [mark.id],
2426
+ sourceIds: [],
2427
+ markKind: mark.mark,
2428
+ markOrigin: mark.origin,
2429
+ pendingMarks: pendingMarks(persistence.state).length,
2430
+ sourceBytesSaved: 0,
2431
+ };
2432
+ return ladder.lastAutomaticAction;
2433
+ };
2434
+ let epoch: Record<string, unknown> | null = null;
2435
+ let inlineRungs = true;
2436
+ if (epochScheduling) {
2437
+ // QUIET RUNTIME. A commit is an epoch transition: it rewrites the projection, and
2438
+ // every rewrite risks the whole prefix cache, so the cadence has to be the fewest
2439
+ // commits that still keep the window inside its budget.
2440
+ //
2441
+ // The economic eligible-share trigger is DELETED: it fired 164 commits from
2442
+ // ordinal 17 in rep 15, each a fresh cache rebuild bought with marks that had not
2443
+ // yet earned one. What remains is the two-signal curation trigger, which fired 10
2444
+ // to 17 times across reps 17 and 20, over the pressure backstop.
2445
+ //
2446
+ // The trigger stays; only its ANNOUNCEMENT is gone. Nothing warns that a commit
2447
+ // is coming and nothing waits for the agent to react, because a warning has to
2448
+ // arrive before the event it warns about and therefore has to break a prefix
2449
+ // nothing else was breaking. Deleting the trigger with the announcement was the
2450
+ // wrong cut: it left the raw backstop alone in front of the window, and gate 58
2451
+ // caught the projection overrunning its budget before anything reclaimed -- the
2452
+ // rep 13 death. The runtime still decides early. It just no longer says so.
2453
+ const backstopDue = epochCommitDue(snapshot, ratio);
2454
+ // The trigger is evaluated on CONTEXT passes only. A commit is an epoch
2455
+ // transition, and it belongs on the pass that builds the next projection rather
2456
+ // than in the middle of a tool batch: firing it mid-batch makes a MARK rewrite
2457
+ // the window, which is precisely the thing marking is supposed never to do.
2458
+ const curationDue = !backstopDue && rungOptions.announcing === true &&
2459
+ curationCommitDue(snapshot);
2460
+ if (backstopDue || curationDue) {
2461
+ epoch = await runCommitEpoch(
2462
+ snapshot,
2463
+ backstopDue ? "window-pressure" : "curation-trigger",
2464
+ true,
2465
+ rungOptions.waiverRatio ?? ratio,
2466
+ );
2467
+ // Latch the eligible share this commit left behind, so the next one waits for
2468
+ // genuinely new foldable mass rather than for the same window to still be full.
2469
+ if (persistence.state && (curationDue || curation.reopenBaselineShare !== null)) {
2470
+ curation.reopenBaselineShare =
2471
+ markAccounting(snapshot, persistence.state).eligibleFreedWindowShare;
2472
+ }
2473
+ }
2474
+ // An inline rung is free only INSIDE a rewrite the epoch already paid for. Below
2475
+ // the threshold no commit ran, and an epoch that applied NOTHING -- every mark
2476
+ // held back by an open turn or by ineligibility -- paid for no rewrite either,
2477
+ // so folding inline there is a fresh single-fold rewrite of its own. Measured
2478
+ // 2026-08-06 (rep 10): the current-turn guard retained all nine marks every turn
2479
+ // while the inline rung folded one batch per turn anyway, 52 single-fold
2480
+ // rewrites that left the prefix cache share at zero. Below a paid rewrite the
2481
+ // ladder MARKS, so the decisions batch into the first commit that can apply them.
2482
+ inlineRungs = Boolean(epoch) && Number(epoch?.appliedMarks ?? 0) > 0;
2483
+ if (!inlineRungs) {
2484
+ const marked = markLadderSelection();
2485
+ if (marked) {
2486
+ if (epoch) ladder.lastAutomaticAction = { ...marked, epoch };
2487
+ return ladder.lastAutomaticAction;
2488
+ }
2489
+ }
2490
+ }
2491
+ // Evidence a pending mark already covers is not the inline rung's to fold: a mark
2492
+ // the commit RETAINED (the open turn's own reads) would otherwise be folded here
2493
+ // by the very pass that just protected it.
2494
+ //
2495
+ // The open turn's keys are excluded DIRECTLY, not just via the marks that claim
2496
+ // them. Claiming is not protection: once a commit applies every pending mark the
2497
+ // claim set is empty, and the inline rung then folds the open turn's oldest read
2498
+ // with nothing left to stop it. That is the rep-8 shape, and byte freshness does
2499
+ // not reach it -- the first read of a long excursion sits outside the fresh tail
2500
+ // while the turn that gathered it is still running. It became reachable when the
2501
+ // quiet cadence started landing a commit on the same context pass, which is what
2502
+ // turns the inline rung on in the first place.
2503
+ const selection = inlineRungs
2504
+ ? selectAutomaticRung(snapshot, persistence.state, ratio, epochScheduling
2505
+ ? {
2506
+ ...rungSelectionOptions,
2507
+ claimed: new Set([
2508
+ ...claimedRefKeys(persistence.state),
2509
+ ...currentTurnRefKeys(snapshot),
2510
+ ]),
2511
+ claimedFoldIds: markedFoldIds(persistence.state),
2512
+ }
2513
+ : rungSelectionOptions)
2514
+ : null;
2515
+ // Above the commit threshold we are already inside the epoch's rewrite turn, so
2516
+ // every rung applies inline: the extra rung costs at most one more invalidation
2517
+ // in a turn that has already paid one. Restricting this to prepared chapters made
2518
+ // the refold and consolidation rungs unreachable in epoch mode, which drove a
2519
+ // session whose reducible bytes sit in expanded folds into the hard fence.
2520
+ const applicable = selection;
2521
+ if (!applicable || applicable.kind === "chapter-prepare") {
2522
+ if (!epoch || !persistence.state) return null;
2523
+ ladder.pendingContextNote =
2524
+ `A commit epoch applied ${(epoch.applied as unknown[]).length} pending mark(s) in one rewrite; ` +
2525
+ "exact evidence remains expandable.";
2526
+ ladder.lastAutomaticAction = { kind: "epoch-commit", foldIds: [], sourceIds: [], epoch };
2527
+ noteAutomaticReceipt(snapshot, ladder.lastAutomaticAction, epoch);
2528
+ return ladder.lastAutomaticAction;
2529
+ }
1143
2530
  const projectedBytesBefore = bytes(projectActiveContext(snapshot, persistence.state));
1144
2531
  let action: Record<string, unknown> | null = null;
1145
- if (selection.kind === "prepared-chapter" && persistence.state.prepared) {
2532
+ if (applicable.kind === "prepared-chapter" && persistence.state.prepared) {
1146
2533
  const error = preparedFoldError({
1147
2534
  prepared: persistence.state.prepared,
1148
2535
  snapshot,
@@ -1164,9 +2551,9 @@ export function registerActiveContext(pi: any, options: {
1164
2551
  action = { kind: "chapter-fold", foldIds: [id], sourceIds };
1165
2552
  ladder.pendingContextNote = `A coherent stale chapter was folded under ${id}; exact evidence remains expandable.`;
1166
2553
  }
1167
- } else if (selection.kind === "tool") {
2554
+ } else if (applicable.kind === "tool") {
1168
2555
  cancelPreparation();
1169
- const tool = selection.candidate;
2556
+ const tool = applicable.candidate;
1170
2557
  const id = await commitDeterministicCandidate(snapshot, tool, automaticToolBrief(snapshot, tool));
1171
2558
  action = {
1172
2559
  kind: "tool-fold",
@@ -1174,14 +2561,14 @@ export function registerActiveContext(pi: any, options: {
1174
2561
  sourceIds: tool.sourceRefs.map((ref) => ref.entryId),
1175
2562
  };
1176
2563
  ladder.pendingContextNote = `${tool.sourceRefs.length} stale completed read-only tool result(s) were folded.`;
1177
- } else if (selection.kind === "refold") {
2564
+ } else if (applicable.kind === "refold") {
1178
2565
  cancelPreparation();
1179
- persistence.state = setFoldProjectionState(persistence.state, selection.foldId, "folded");
1180
- action = { kind: "refold", foldIds: [selection.foldId] };
1181
- ladder.pendingContextNote = `Stale expanded fold ${selection.foldId} returned to its identical placeholder.`;
1182
- } else if (selection.kind === "consolidation") {
2566
+ persistence.state = setFoldProjectionState(persistence.state, applicable.foldId, "folded");
2567
+ action = { kind: "refold", foldIds: [applicable.foldId] };
2568
+ ladder.pendingContextNote = `Stale expanded fold ${applicable.foldId} returned to its identical placeholder.`;
2569
+ } else if (applicable.kind === "consolidation") {
1183
2570
  cancelPreparation();
1184
- const consolidation = selection.candidate;
2571
+ const consolidation = applicable.candidate;
1185
2572
  const id = await commitDeterministicCandidate(
1186
2573
  snapshot,
1187
2574
  consolidation,
@@ -1194,29 +2581,48 @@ export function registerActiveContext(pi: any, options: {
1194
2581
  };
1195
2582
  ladder.pendingContextNote =
1196
2583
  `Stale folded chapters were consolidated under ${id}; every child remains expandable.`;
1197
- } else if (selection.kind === "chapter") {
1198
- const chapter = selection.candidate;
1199
- const id = await commitDeterministicCandidate(
1200
- snapshot,
1201
- chapter,
1202
- deterministicChapterCandidateBrief(snapshot, chapter),
1203
- );
2584
+ } else if (applicable.kind === "chapter") {
2585
+ const chapter = applicable.candidate;
2586
+ // Bite-sized by construction: an oversized coherent chapter becomes sequential
2587
+ // bounded folds, each with its own brief, split only at closed unit boundaries.
2588
+ const spanChars = candidateSpanChars(snapshot, persistence.state, chapter);
2589
+ const parts = splitCandidateBySize(snapshot, persistence.state, chapter);
2590
+ const foldIds: string[] = [];
2591
+ for (const part of parts) {
2592
+ foldIds.push(await commitDeterministicCandidate(
2593
+ snapshot,
2594
+ part,
2595
+ deterministicChapterCandidateBrief(snapshot, part),
2596
+ ));
2597
+ }
1204
2598
  action = {
1205
2599
  kind: "chapter-fold",
1206
- foldIds: [id],
2600
+ foldIds,
1207
2601
  sourceIds: chapter.sourceRefs.map((ref) => ref.entryId),
2602
+ ...(parts.length > 1 ? { splitFolds: parts.length, splitFromChars: spanChars } : {}),
1208
2603
  };
1209
- ladder.pendingContextNote =
1210
- `A coherent stale chapter was folded under ${id}; exact evidence remains expandable.`;
2604
+ if (parts.length > 1) {
2605
+ emit("context.split", {
2606
+ source: "ladder",
2607
+ span_chars: spanChars,
2608
+ parts: parts.length,
2609
+ cap_chars: MAX_FOLD_SPAN_CHARS,
2610
+ fold_ids: foldIds.join(","),
2611
+ });
2612
+ }
2613
+ ladder.pendingContextNote = parts.length > 1
2614
+ ? `A ${spanChars}-char stale chapter was folded as ${parts.length} bite-sized folds ` +
2615
+ `(${foldIds.join(", ")}); exact evidence remains expandable.`
2616
+ : `A coherent stale chapter was folded under ${foldIds[0]}; exact evidence remains expandable.`;
1211
2617
  }
1212
- if (!action || !persistence.state) return null;
2618
+ if ((!action && !epoch) || !persistence.state) return null;
1213
2619
  const projectedBytesAfter = bytes(projectActiveContext(snapshot, persistence.state));
1214
- persistence.state = clearArmedAdvisory(persistence.state);
1215
- advisory.armedMilestone = null;
1216
2620
  ladder.lastAutomaticAction = {
1217
- ...action,
2621
+ ...(action ?? { kind: "epoch-commit", foldIds: [], sourceIds: [] }),
2622
+ ...(epoch ? { epoch } : {}),
1218
2623
  sourceBytesSaved: Math.max(0, projectedBytesBefore - projectedBytesAfter),
1219
2624
  };
2625
+ noteAutomaticReceipt(snapshot, ladder.lastAutomaticAction, epoch);
1220
2626
  return ladder.lastAutomaticAction;
1221
2627
  };
1222
2628
  const runAutomaticRungTransaction = async (
@@ -1224,7 +2630,12 @@ export function registerActiveContext(pi: any, options: {
1224
2630
  ratio: number,
1225
2631
  ctx: any,
1226
2632
  phase: string,
1227
- rungOptions: { waiveToolCadence?: boolean; toolOnly?: boolean } = {},
2633
+ rungOptions: {
2634
+ waiveToolCadence?: boolean;
2635
+ toolOnly?: boolean;
2636
+ waiverRatio?: number;
2637
+ announcing?: boolean;
2638
+ } = {},
1228
2639
  ): Promise<Record<string, unknown> | null> => {
1229
2640
  if (!persistence.state || !measurements.lastProviderMeasurement ||
1230
2641
  !durableProviderMeasurementMatches(measurements.lastProviderMeasurement)) return null;
@@ -1269,9 +2680,15 @@ export function registerActiveContext(pi: any, options: {
1269
2680
  ratio: number,
1270
2681
  ctx: any,
1271
2682
  phase: string,
2683
+ rungOptions: {
2684
+ waiveToolCadence?: boolean;
2685
+ toolOnly?: boolean;
2686
+ waiverRatio?: number;
2687
+ announcing?: boolean;
2688
+ } = {},
1272
2689
  ): Promise<Record<string, unknown> | null> => {
1273
2690
  const operation = ladder.actionQueue.then(() =>
1274
- runAutomaticRungTransaction(snapshot, ratio, ctx, phase));
2691
+ runAutomaticRungTransaction(snapshot, ratio, ctx, phase, rungOptions));
1275
2692
  ladder.actionQueue = operation.catch(() => undefined);
1276
2693
  return operation;
1277
2694
  };
@@ -1285,6 +2702,38 @@ export function registerActiveContext(pi: any, options: {
1285
2702
  };
1286
2703
  options.setProjectionProvider?.(projectionCandidates);
1287
2704
 
2705
+ /**
2706
+ * The suggestion-source hook. The built-in fold-brief source registers through this
2707
+ * same call, so an external memory system's items reach the identical carrier,
2708
+ * budget, ranking, and accept/reject log; pi-fold owns the channel, not the content.
2709
+ */
2710
+ const registerSuggestionSource: SuggestionSourceRegistrar = (source: SuggestionSource) => {
2711
+ if (!source || typeof source !== "object" || typeof source.id !== "string" || !source.id ||
2712
+ typeof source.candidates !== "function") {
2713
+ throw new Error("A suggestion source needs a nonempty id and a candidates function");
2714
+ }
2715
+ if (surfacing.sources.some((registered) => registered.id === source.id)) {
2716
+ throw new Error(`Suggestion source '${source.id}' is already registered`);
2717
+ }
2718
+ surfacing.sources.push(source);
2719
+ return {
2720
+ unregister: () => {
2721
+ surfacing.sources = surfacing.sources.filter((registered) => registered !== source);
2722
+ },
2723
+ accepted: (id: string) => {
2724
+ if (!persistence.state || typeof id !== "string" || !id) return;
2725
+ // In-memory only: the next persisted state carries it. Reporting an outcome
2726
+ // must never write a durable entry of its own.
2727
+ persistence.state = acceptSurfacingSuggestion(
2728
+ persistence.state,
2729
+ id,
2730
+ lifecycle.latestSnapshot ? surfacingOrdinal(lifecycle.latestSnapshot) : 0,
2731
+ );
2732
+ },
2733
+ };
2734
+ };
2735
+ options.setSuggestionSourceRegistrar?.(registerSuggestionSource);
2736
+
1288
2737
  // A same-session start/tree reload is a projection-generation mutation.
1289
2738
  // Queue it behind every context authority → preparation → commit →
1290
2739
  // projection transaction, then serialize the actual load with the action
@@ -1316,7 +2765,26 @@ export function registerActiveContext(pi: any, options: {
1316
2765
  }
1317
2766
  };
1318
2767
 
1319
- pi.on("session_start", async (_event: unknown, ctx: any) => { await safeLifecycleLoad(ctx, "session-start"); });
2768
+ /**
2769
+ * State the resolved serving budget once, at startup, before anything reads it. A
2770
+ * budget nobody can see in the stream is how rep 15 spent a whole run on a 272,000
2771
+ * descriptor without a single record saying so.
2772
+ */
2773
+ const recordResolvedCapacity = (ctx: any): void => {
2774
+ const capacity = currentCapacity(ctx);
2775
+ emit("context.capacity", {
2776
+ mode: capacity.mode,
2777
+ window_tokens: capacity.window,
2778
+ budget_tokens: capacity.budgetTokens,
2779
+ output_reservation: capacity.outputReservation,
2780
+ descriptor_window: capacity.descriptorWindow,
2781
+ });
2782
+ };
2783
+
2784
+ pi.on("session_start", async (_event: unknown, ctx: any) => {
2785
+ await safeLifecycleLoad(ctx, "session-start");
2786
+ recordResolvedCapacity(ctx);
2787
+ });
1320
2788
  pi.on("session_tree", async (_event: unknown, ctx: any) => { await safeLifecycleLoad(ctx, "session-tree"); });
1321
2789
  pi.on("session_compact", async (event: Record<string, unknown>, ctx: any) => {
1322
2790
  const reason = boundReceiptText(ownValue(event, "reason"), 64, "unknown");
@@ -1366,43 +2834,12 @@ export function registerActiveContext(pi: any, options: {
1366
2834
  if (persistence.state?.prepared) persistence.state = clearPrepared(persistence.state);
1367
2835
  measurements.latestRatio = null;
1368
2836
  measurements.lastProviderMeasurement = null;
1369
- advisory.armedMilestone = persistence.state ? advisoryState(persistence.state).armed?.milestone ?? null : null;
1370
- advisory.advisoryScheduleKey = "pending-reseed";
1371
2837
  nativeCompaction.lastThresholdDecision = null;
1372
2838
  updateStatus(ctx);
1373
2839
  };
1374
2840
  pi.on("model_select", attributionChanged);
1375
2841
  pi.on("thinking_level_select", attributionChanged);
1376
2842
 
1377
- const armMilestoneForMeasurement = (
1378
- snapshot: ActiveContextSnapshot,
1379
- measurement: ProviderContextMeasurement,
1380
- ): boolean => {
1381
- if (!persistence.state) return false;
1382
- const ratio = contextUsageRatio(measurement);
1383
- if (ratio === null) return false;
1384
- const schedule = advisorySchedule(snapshot);
1385
- const scheduleKey = sha256Value({
1386
- schedule: schedule.key,
1387
- provider: measurement.provider,
1388
- model: measurement.model,
1389
- contextWindow: measurement.contextWindow,
1390
- });
1391
- const scheduleChanged = advisory.advisoryScheduleKey !== null && advisory.advisoryScheduleKey !== scheduleKey;
1392
- advisory.advisoryScheduleKey = scheduleKey;
1393
- const advisoryBefore = stableStringify(advisoryState(persistence.state));
1394
- const advisoryUpdate = updateAdvisoryMilestone(persistence.state, ratio, schedule, scheduleChanged, scheduleKey);
1395
- persistence.state = advisoryUpdate.state;
1396
- const armed = advisoryState(persistence.state).armed;
1397
- if (armed && ratio < 0.85 * armed.threshold) {
1398
- persistence.state = clearArmedAdvisory(persistence.state);
1399
- advisory.armedMilestone = null;
1400
- } else {
1401
- advisory.armedMilestone = armed?.milestone ?? null;
1402
- }
1403
- return advisoryBefore !== stableStringify(advisoryState(persistence.state));
1404
- };
1405
-
1406
2843
  const accountAnchoredMeasurement = (measurement: ProviderContextMeasurement): boolean => {
1407
2844
  if (!persistence.state) return false;
1408
2845
  if (!measurements.lastProviderMeasurement) {
@@ -1423,6 +2860,8 @@ export function registerActiveContext(pi: any, options: {
1423
2860
 
1424
2861
  const handleContext = async (event: { messages: unknown[] }, ctx: any) => {
1425
2862
  if (lifecycle.shuttingDown) return { messages: event.messages };
2863
+ beginMutationPass();
2864
+ clearCommitLatchBelowTrigger();
1426
2865
  if (!persistence.state) persistence.state = emptyActiveContextState(ctx.sessionManager.getSessionId());
1427
2866
  lifecycle.latestSnapshot = null;
1428
2867
  lifecycle.latestSnapshotError = null;
@@ -1443,13 +2882,12 @@ export function registerActiveContext(pi: any, options: {
1443
2882
  }
1444
2883
  let observedMeasurement = latestProviderContextMeasurement(
1445
2884
  snapshot.messages,
1446
- contextWindowFor(ctx) ?? DEFAULT_CONTEXT_WINDOW,
2885
+ budgetWindowFor(ctx) ?? DEFAULT_CONTEXT_WINDOW,
1447
2886
  ctx.model,
1448
2887
  );
1449
2888
  if (observedMeasurement && providerMeasurementBranchIndex(ctx, observedMeasurement) < 0) {
1450
2889
  observedMeasurement = null;
1451
2890
  }
1452
- let advisoryChanged = false;
1453
2891
  let measurementStateChanged = false;
1454
2892
  if (observedMeasurement) {
1455
2893
  const boundRevision = measurements.providerMeasurementRevisionByMessageSha.get(
@@ -1463,8 +2901,10 @@ export function registerActiveContext(pi: any, options: {
1463
2901
  measurements.latestRatio = contextUsageRatio(observedMeasurement);
1464
2902
  if (durableProviderMeasurementMatches(observedMeasurement) && measurements.latestRatio !== null) {
1465
2903
  measurementStateChanged = accountAnchoredMeasurement(observedMeasurement);
2904
+ // Calibrate BEFORE the new measurement becomes the previous one: the inflow
2905
+ // step is the difference between them.
2906
+ noteProjectionCalibration(observedMeasurement);
1466
2907
  measurements.lastProviderMeasurement = observedMeasurement;
1467
- advisoryChanged = armMilestoneForMeasurement(snapshot, observedMeasurement);
1468
2908
  startPreparation(snapshot, measurements.latestRatio, ctx);
1469
2909
  if (!ladder.automaticFailure && measurements.latestRatio >= hardFenceRatio(snapshot) && ladder.preparing) {
1470
2910
  mutationAttempted = true;
@@ -1474,18 +2914,19 @@ export function registerActiveContext(pi: any, options: {
1474
2914
  }
1475
2915
  }
1476
2916
  if (selectAutomaticToolForRung(snapshot, persistence.state, measurements.latestRatio)) mutationAttempted = true;
1477
- const action = await attemptAutomaticRung(snapshot, measurements.latestRatio, ctx, "context");
2917
+ const action = await attemptAutomaticRung(
2918
+ snapshot, measurements.latestRatio, ctx, "context", { announcing: true },
2919
+ );
1478
2920
  if (action) {
1479
2921
  mutationAttempted = true;
1480
2922
  persistedSucceeded = true;
1481
- advisoryChanged = false;
1482
2923
  }
1483
2924
  } else measurements.lastProviderMeasurement = observedMeasurement;
1484
2925
  } else {
1485
2926
  measurements.latestRatio = contextUsageRatio(measurements.lastProviderMeasurement);
1486
2927
  }
1487
- if ((advisoryChanged || measurementStateChanged) && persistence.state && persistence.persisted &&
1488
- !sameStateProjection(persistence.state, persistence.persisted)) {
2928
+ if (measurementStateChanged && persistence.state &&
2929
+ persistence.persisted && !sameStateProjection(persistence.state, persistence.persisted)) {
1489
2930
  mutationAttempted = true;
1490
2931
  await persistThroughActionQueue(ctx);
1491
2932
  persistedSucceeded = true;
@@ -1501,6 +2942,29 @@ export function registerActiveContext(pi: any, options: {
1501
2942
  updateStatus(ctx);
1502
2943
  return { messages: event.messages };
1503
2944
  }
2945
+ // A projection larger than the serving budget is never transmitted, whatever the
2946
+ // last measured ratio says about the request before it.
2947
+ const budgeted = await enforceProjectionBudget(snapshot, projected, ctx);
2948
+ projected = budgeted.projected;
2949
+ // Every projection this process BUILDS is a size reference for the calibration,
2950
+ // including one the fence aborted. Recording only sent projections locks an
2951
+ // already-large session out of calibrating at all: its first projection aborts
2952
+ // under the uncalibrated constant, so nothing is ever sent, so nothing ever
2953
+ // measures the ratio, so it aborts forever.
2954
+ measurements.lastProjectedChars = bytes(projected);
2955
+ measurements.lastProjectedEstimate = projectedTokenEstimate(projected);
2956
+ measurements.lastProjectedEstimateCalibrated = measurements.projectionCalibrations.length > 0;
2957
+ // An aborted request still returns the PROJECTION. Handing back the raw branch
2958
+ // instead makes the aborted turn the largest message list the session ever
2959
+ // produced -- the corpus, folds and all -- which is the exact inverse of what a
2960
+ // fence is for, and it is what an abort looked like in rep12: 3,952,934 chars
2961
+ // against a 1,322,385-char projection of the same session. It is not noted as a
2962
+ // cache observation, because it was never sent.
2963
+ if (budgeted.aborted) {
2964
+ updateStatus(ctx);
2965
+ return { messages: projected };
2966
+ }
2967
+ noteProjection(projected);
1504
2968
  updateStatus(ctx);
1505
2969
  return { messages: projected };
1506
2970
  } catch (error) {
@@ -1547,6 +3011,10 @@ export function registerActiveContext(pi: any, options: {
1547
3011
  !durableProviderMeasurementMatches(measurement) ||
1548
3012
  measuredRatio === null) return;
1549
3013
  const measurementStateChanged = accountAnchoredMeasurement(measurement);
3014
+ // The provider accepted a request: whatever the recovery lane spent belongs to an
3015
+ // inflow that is now behind us, and the cap resets for the next one.
3016
+ curation.recoveryAttempts = 0;
3017
+ curation.pendingRejection = null;
1550
3018
  measurements.lastProviderMeasurement = measurement;
1551
3019
  measurements.latestRatio = measuredRatio;
1552
3020
  let snapshot: ActiveContextSnapshot;
@@ -1555,7 +3023,6 @@ export function registerActiveContext(pi: any, options: {
1555
3023
  updateStatus(ctx);
1556
3024
  return;
1557
3025
  }
1558
- const advisoryChanged = armMilestoneForMeasurement(snapshot, measurement);
1559
3026
  startPreparation(snapshot, measurements.latestRatio, ctx);
1560
3027
  if (measurements.latestRatio >= hardFenceRatio(measurement, ctx) && ladder.preparing) await ladder.preparing.promise;
1561
3028
  if (!sessionIdentityStillValid(ctx, capturedSessionId, capturedGeneration) ||
@@ -1566,7 +3033,7 @@ export function registerActiveContext(pi: any, options: {
1566
3033
  ctx,
1567
3034
  "message-end",
1568
3035
  );
1569
- if (!action && (advisoryChanged || measurementStateChanged) && persistence.state && persistence.persisted &&
3036
+ if (!action && measurementStateChanged && persistence.state && persistence.persisted &&
1570
3037
  !sameStateProjection(persistence.state, persistence.persisted)) {
1571
3038
  await persistThroughActionQueue(ctx);
1572
3039
  }
@@ -1575,11 +3042,31 @@ export function registerActiveContext(pi: any, options: {
1575
3042
 
1576
3043
  pi.on("message_end", async (event: Record<string, unknown>, ctx: any) => {
1577
3044
  if (lifecycle.shuttingDown) return;
3045
+ beginMutationPass();
1578
3046
  try {
1579
3047
  const message = ownValue(event, "message");
3048
+ instrumentation.requests += 1;
3049
+ const observation = observeCacheUsage(instrumentation.ledger, {
3050
+ usage: ownValue(message, "usage"),
3051
+ change: instrumentation.lastChange,
3052
+ preservedShare: instrumentation.lastPreservedShare,
3053
+ });
3054
+ if (observation) {
3055
+ // The join key: this is where the stream meets provider-side telemetry.
3056
+ emit("context.usage", {
3057
+ provider: ownValue(message, "provider") ?? null,
3058
+ model: ownValue(message, "model") ?? null,
3059
+ input_tokens: observation.inputTokens,
3060
+ cache_read_tokens: observation.cacheReadTokens,
3061
+ total_tokens: providerTokens(message),
3062
+ projection_change: observation.change,
3063
+ provider_side_miss: observation.providerSideMiss,
3064
+ message_sha256: evidenceSha256(message),
3065
+ });
3066
+ }
1580
3067
  const measurement = providerContextMeasurement(
1581
3068
  message,
1582
- contextWindowFor(ctx) ?? DEFAULT_CONTEXT_WINDOW,
3069
+ budgetWindowFor(ctx) ?? DEFAULT_CONTEXT_WINDOW,
1583
3070
  ctx.model,
1584
3071
  );
1585
3072
  if (!measurement || !persistence.state) return;
@@ -1602,11 +3089,25 @@ export function registerActiveContext(pi: any, options: {
1602
3089
  try { updateStatus(ctx); } catch { /* The provider loop must keep running. */ }
1603
3090
  }
1604
3091
  });
3092
+ /**
3093
+ * A provider rejection is ground truth that the last request did not fit, and it
3094
+ * mutates nothing durable: no assistant message lands, and the projection is rebuilt
3095
+ * from the branch on the next request. So it is recorded, not fatal -- the next
3096
+ * context pass folds at fence pressure and rebuilds the request rather than dying.
3097
+ * A 429 is a rate limit, not an overflow, and is deliberately not recovered from.
3098
+ */
3099
+ pi.on("after_provider_response", (event: Record<string, unknown>) => {
3100
+ const status = ownValue(event, "status");
3101
+ if (typeof status !== "number" || status < 400 || status === 429) return;
3102
+ if (curation.recoveryAttempts >= OVERFLOW_RECOVERY_MAX_ATTEMPTS) return;
3103
+ curation.pendingRejection = { status, ordinal: currentOrdinal() };
3104
+ });
1605
3105
  pi.on("tool_call", (event: Record<string, unknown>, ctx: any) => {
1606
3106
  const calledTool = ownValue(event, "toolName");
1607
3107
  if (lifecycle.shuttingDown || lifecycle.blockingToolHarvestedThisTurn || lifecycle.blockingToolHarvestQueuedThisTurn ||
1608
3108
  typeof calledTool !== "string" || !blockingTools.has(calledTool)) return;
1609
3109
  lifecycle.blockingToolHarvestQueuedThisTurn = true;
3110
+ beginMutationPass();
1610
3111
  const operation = ladder.actionQueue.then(async () => {
1611
3112
  try {
1612
3113
  if (!persistence.state || measurements.latestRatio === null || !measurements.lastProviderMeasurement || ladder.automaticFailure ||
@@ -1637,6 +3138,7 @@ export function registerActiveContext(pi: any, options: {
1637
3138
  ladder.actionQueue = operation.catch(() => undefined);
1638
3139
  });
1639
3140
  pi.on("turn_end", async (_event: unknown, ctx: any) => {
3141
+ beginMutationPass();
1640
3142
  lifecycle.blockingToolHarvestedThisTurn = false;
1641
3143
  lifecycle.blockingToolHarvestQueuedThisTurn = false;
1642
3144
  if (lifecycle.shuttingDown || !persistence.state || !persistence.persisted) return;
@@ -1706,6 +3208,51 @@ export function registerActiveContext(pi: any, options: {
1706
3208
  updateStatus(ctx);
1707
3209
  });
1708
3210
 
3211
+ /**
3212
+ * Admission control. A refusal that names no next action is denial, not governance,
3213
+ * so every refusal carries at least one CONSTRUCTIBLE alternative: a bounded slice
3214
+ * sized to the headroom we actually have, a child fold that is smaller by
3215
+ * construction, and the commit that frees the room. The exact stored size of the
3216
+ * target is known, so this is arithmetic, not a guess.
3217
+ */
3218
+ const admit = (input: {
3219
+ action: "peek" | "expand";
3220
+ ctx: any;
3221
+ foldId: string;
3222
+ requestedBytes: number;
3223
+ children: string[];
3224
+ }): void => {
3225
+ const capacity = currentCapacity(input.ctx);
3226
+ const verdict = admissionVerdict({
3227
+ requestedBytes: input.requestedBytes,
3228
+ capacity,
3229
+ bytesPerToken: ESTIMATED_BYTES_PER_TOKEN,
3230
+ });
3231
+ if (verdict.admitted) return;
3232
+ const sliceBytes = Math.max(
3233
+ PEEK_MIN_SLICE_BYTES,
3234
+ Math.floor(verdict.affordableBytes * 0.9),
3235
+ );
3236
+ const alternatives: Array<Record<string, unknown>> = [];
3237
+ if (verdict.affordableBytes >= PEEK_MIN_SLICE_BYTES) {
3238
+ alternatives.push({ action: "peek", id: input.foldId, offset: 0, bytes: sliceBytes });
3239
+ }
3240
+ for (const child of input.children.slice(0, 3)) {
3241
+ alternatives.push({ action: "peek", id: child });
3242
+ }
3243
+ if (!alternatives.length) {
3244
+ // The floor: folding is always available, and it is the action that creates
3245
+ // the room this read needs.
3246
+ alternatives.push({ action: "status", detail: "fold_candidates" });
3247
+ }
3248
+ throw new Error(
3249
+ `${input.action} of ${input.foldId} needs ~${verdict.requestedTokens} tokens and only ` +
3250
+ `${verdict.headroomTokens} remain of the ${verdict.budgetTokens}-token budget; it was refused ` +
3251
+ "before it could cross the fence. Free room or read less: " +
3252
+ JSON.stringify(alternatives),
3253
+ );
3254
+ };
3255
+
1709
3256
  const executeAction = async (
1710
3257
  params: Record<string, unknown>,
1711
3258
  signal: AbortSignal | undefined,
@@ -1729,28 +3276,40 @@ export function registerActiveContext(pi: any, options: {
1729
3276
  const snapshot = authoritativeSnapshotFor(ctx);
1730
3277
  if (action === "status") {
1731
3278
  const detail = ownValue(params, "detail");
1732
- if (detail !== undefined && detail !== "fold_candidates") {
1733
- throw new Error("status detail must be 'fold_candidates'");
3279
+ const details = ["fold_candidates", "tree", "folds", "objects"];
3280
+ if (detail !== undefined && !details.includes(String(detail))) {
3281
+ throw new Error(`status detail must be one of ${details.map((name) => `'${name}'`).join(", ")}`);
1734
3282
  }
1735
- const schedule = advisorySchedule(snapshot);
1736
- return toolPayload({
3283
+ const statusOffset = boundedInteger(params.offset, 0, 0, 1_000_000, "offset");
3284
+ const statusLimit = boundedInteger(params.limit, 40, 1, 100, "limit");
3285
+ // Paging is explicit and agent-driven: the full tree is reachable, it just
3286
+ // stops riding along on every request that wanted a count.
3287
+ const paged = detail === "folds" || detail === "objects";
3288
+ const accounting = markAccounting(snapshot, persistence.state);
3289
+ return toolPayload(boundStatusPayload({
1737
3290
  ...activeContextStatus(
1738
3291
  snapshot,
1739
3292
  persistence.state,
1740
- boundedInteger(params.offset, 0, 0, 1_000_000, "offset"),
1741
- boundedInteger(params.limit, 40, 1, 100, "limit"),
3293
+ statusOffset,
3294
+ statusLimit,
1742
3295
  snapshot.policy.maxFoldSourceRefs,
3296
+ { diet: !paged },
1743
3297
  ),
3298
+ headroomTokens: currentCapacity(ctx).headroomTokens,
3299
+ budgetTokens: currentCapacity(ctx).budgetTokens,
3300
+ pendingMarks: accounting.pending,
3301
+ eligibleMarks: accounting.eligibleMarks,
3302
+ retainedMarks: accounting.retainedMarks,
3303
+ eligibleMarkedShare: accounting.eligibleFreedWindowShare,
3304
+ markedShare: accounting.freedWindowShare,
1744
3305
  available: true,
1745
3306
  automatic: {
1746
3307
  pressureRatio: measurements.latestRatio,
1747
- milestones: Object.fromEntries(schedule.rungs.map((rung) => [
1748
- rung.milestone,
1749
- { threshold: rung.threshold, budget: rung.budget },
1750
- ])),
1751
- armedMilestone: advisory.armedMilestone,
1752
- advisory: advisoryState(persistence.state),
1753
- historicalGuidanceEntries: advisory.historicalGuidanceEntries,
3308
+ surfacing: {
3309
+ rendered: false,
3310
+ sources: surfacing.sources.map((source) => source.id),
3311
+ log: clone(persistence.state.surfacing ?? []),
3312
+ },
1754
3313
  warningRatio: snapshot.policy.warningRatio,
1755
3314
  toolFoldRatio: snapshot.policy.toolFoldRatio,
1756
3315
  refoldRatio: snapshot.policy.refoldRatio,
@@ -1761,6 +3320,10 @@ export function registerActiveContext(pi: any, options: {
1761
3320
  Math.floor(snapshot.contextWindow * 0.1),
1762
3321
  ),
1763
3322
  windowSource: snapshot.windowSource,
3323
+ capacity: {
3324
+ ...currentCapacity(ctx),
3325
+ bytesPerToken: projectionCharsPerToken(),
3326
+ },
1764
3327
  consolidationRatio: snapshot.policy.consolidationRatio,
1765
3328
  providerMeasurement: measurements.lastProviderMeasurement ? {
1766
3329
  tokens: measurements.lastProviderMeasurement.tokens,
@@ -1781,10 +3344,57 @@ export function registerActiveContext(pi: any, options: {
1781
3344
  lastSelectionKind: ladder.lastSelectionKind,
1782
3345
  lastSelectionSourceIds: ladder.lastSelectionSourceIds,
1783
3346
  lastAutomaticAction: ladder.lastAutomaticAction,
3347
+ overBudgetReduction: ladder.overBudgetReduction ? clone(ladder.overBudgetReduction) : null,
3348
+ curation: {
3349
+ occupancyThreshold: CURATION_OCCUPANCY_SHARE,
3350
+ staleToolThreshold: CURATION_STALE_TOOL_SHARE,
3351
+ signals: curation.lastSignals ? { ...curation.lastSignals } : null,
3352
+ contextCalls: curation.contextCalls,
3353
+ receipts: curation.receipts.map((receipt) => ({ ...receipt })),
3354
+ },
3355
+ recovery: {
3356
+ attempts: curation.recoveryAttempts,
3357
+ maxAttempts: OVERFLOW_RECOVERY_MAX_ATTEMPTS,
3358
+ pendingRejection: curation.pendingRejection ? { ...curation.pendingRejection } : null,
3359
+ last: curation.lastRecovery ? clone(curation.lastRecovery) : null,
3360
+ },
3361
+ foldSpanCap: MAX_FOLD_SPAN_CHARS,
3362
+ projectionBudgetTokens: currentCapacity(ctx).budgetTokens,
3363
+ projectionCharsPerToken: projectionCharsPerToken(),
3364
+ projectionEstimatorErrorShare: estimatorErrorShare(),
3365
+ projectionExpectedInflowTokens: expectedInflowTokens(),
3366
+ projectionMarginTokens: measurements.lastProjectedChars === null
3367
+ ? null
3368
+ : projectionMarginTokens(
3369
+ Math.ceil(measurements.lastProjectedChars / projectionCharsPerToken()),
3370
+ currentCapacity(ctx).window,
3371
+ ),
3372
+ projectionEstimatedTokens: measurements.lastProjectedChars === null
3373
+ ? null
3374
+ : Math.ceil(measurements.lastProjectedChars / projectionCharsPerToken()),
1784
3375
  automaticSuspended: ladder.automaticFailure !== null,
1785
3376
  automaticFailure: ladder.automaticFailure ? clone(ladder.automaticFailure) : null,
1786
3377
  lastCompactionDecision: nativeCompaction.lastThresholdDecision,
3378
+ scheduling: schedulingStatus({
3379
+ snapshot,
3380
+ state: persistence.state,
3381
+ mode: foldScheduling,
3382
+ ratio: measurements.latestRatio,
3383
+ }),
1787
3384
  nativeSummaries: "disabled",
3385
+ instrumentation: {
3386
+ enabled: true,
3387
+ ...ledgerSummary(instrumentation.ledger),
3388
+ projectionRecords: clone(instrumentation.ledger.records),
3389
+ cacheObservations: clone(instrumentation.ledger.observations),
3390
+ // THE stream, as the durable entries carry it. One shape, one convention.
3391
+ events: clone(instrumentation.ledger.events),
3392
+ },
3393
+ foldPeekResults,
3394
+ peek: {
3395
+ defaultMaxBytes: PEEK_DEFAULT_MAX_BYTES,
3396
+ lifetime: "append-only",
3397
+ },
1788
3398
  freeHarvest: blockingTools.size === 0 ? "disabled" : "enabled",
1789
3399
  pressureSource: "last-successful-provider-response-only",
1790
3400
  postOverflowCallback: "blocked-while-stock-native-compaction-is-disabled",
@@ -1801,12 +3411,208 @@ export function registerActiveContext(pi: any, options: {
1801
3411
  failedPreparationIds: ladder.failedPreparations,
1802
3412
  }),
1803
3413
  } : {}),
3414
+ ...(detail === "tree" ? { tree: foldTreeDetail(snapshot, persistence.state).slice(statusOffset) } : {}),
3415
+ }, typeof detail === "string" ? detail : null, statusOffset));
3416
+ }
3417
+ // Peeking or expanding a suggested fold is the accept signal, recorded for the
3418
+ // retrieval build even though nothing suggests anything today. It stays ephemeral:
3419
+ // the mark rides the next persisted state, never an entry of its own.
3420
+ const noteSurfacingAccept = (id: string): void => {
3421
+ if (!persistence.state) return;
3422
+ persistence.state = acceptSurfacingSuggestion(persistence.state, id, surfacingOrdinal(snapshot));
3423
+ };
3424
+ if (action === "peek") {
3425
+ const id = String(params.id ?? "").trim();
3426
+ if (!id) throw new Error("peek requires id");
3427
+ const offset = boundedInteger(params.offset, 0, 0, 1_000_000_000, "offset");
3428
+ // The DEFAULT is the bounded index view. Widening is an explicit argument, so
3429
+ // the default path cannot overfill a window: measured 2026-08-06, 14 raw peek
3430
+ // results held 1.9M characters, 82 percent of everything still unfolded.
3431
+ const sliceBytes = boundedInteger(
3432
+ params.bytes,
3433
+ PEEK_DEFAULT_MAX_BYTES,
3434
+ PEEK_MIN_SLICE_BYTES,
3435
+ snapshot.policy.maxChapterChars,
3436
+ "bytes",
3437
+ );
3438
+ const target = persistence.state.folds.find((item) => item.id === id);
3439
+ if (!target) throw new Error(`Unknown active-context fold ${id}`);
3440
+ admit({
3441
+ action: "peek",
3442
+ ctx,
3443
+ foldId: id,
3444
+ requestedBytes: Math.max(0, Math.min(sliceBytes, target.sourceChars - offset)),
3445
+ children: childFoldIds(target),
3446
+ });
3447
+ const payload = toolPayload(peekFoldSource({
3448
+ foldId: id,
3449
+ state: persistence.state,
3450
+ entries: ctx.sessionManager.getEntries?.() ?? ctx.sessionManager.getBranch(),
3451
+ sessionId: ctx.sessionManager.getSessionId(),
3452
+ maximumBytes: sliceBytes,
3453
+ offset,
3454
+ toolName,
3455
+ }));
3456
+ noteSurfacingAccept(id);
3457
+ return payload;
3458
+ }
3459
+ if (action === "rebrief") {
3460
+ const id = String(params.id ?? "").trim();
3461
+ if (!id) throw new Error("rebrief requires id");
3462
+ const brief = typeof params.brief === "string" ? params.brief.trim() : "";
3463
+ if (!brief) throw new Error("rebrief requires a nonempty brief");
3464
+ if (brief.length > snapshot.policy.maxBriefChars) {
3465
+ throw new Error(`rebrief brief must be at most ${snapshot.policy.maxBriefChars} characters`);
3466
+ }
3467
+ const fold = requireActiveFold(snapshot, persistence.state, id);
3468
+ const previous = foldBrief(fold, persistence.state);
3469
+ const briefs = { ...(persistence.state.briefs ?? {}), [id]: brief };
3470
+ await persistManual({ ...persistence.state, revision: persistence.state.revision + 1, briefs }, action, ctx);
3471
+ updateStatus(ctx);
3472
+ return toolPayload({
3473
+ version: 1,
3474
+ action,
3475
+ id,
3476
+ brief,
3477
+ previousBrief: previous,
3478
+ durableRevision: persistence.state.revision,
3479
+ activation: "the fold's placeholder, index rows and suggestions now carry your brief; " +
3480
+ "the exact source and its fold record are untouched.",
3481
+ });
3482
+ }
3483
+ if (action === "reboundary") {
3484
+ // One verb, both directions. `ids` names the span you want to BE one fold: every
3485
+ // fold overlapping it dissolves and the span is re-cut as one, which merges N
3486
+ // adjacent folds when the span covers them and splits one fold when the span sits
3487
+ // inside it. `id` alone is the plain dissolve, which returns a span to raw.
3488
+ if (params.ids !== undefined) {
3489
+ const requestedIds = stringIds(params.ids);
3490
+ const snapped = snapToFoldBoundaries(snapshot, persistence.state, requestedIds);
3491
+ const dissolvedIds = snapped.covered.map((item) => item.id);
3492
+ const briefs = { ...(persistence.state.briefs ?? {}) };
3493
+ for (const dissolvedId of dissolvedIds) delete briefs[dissolvedId];
3494
+ const leases = { ...persistence.state.leases };
3495
+ for (const dissolvedId of dissolvedIds) delete leases[dissolvedId];
3496
+ const dissolvedSet = new Set(dissolvedIds);
3497
+ const flattened: ActiveContextState = {
3498
+ ...persistence.state,
3499
+ revision: persistence.state.revision + 1,
3500
+ folds: deriveFoldParents(persistence.state.folds.filter((item) =>
3501
+ !dissolvedSet.has(item.id) && !dissolvedIds.some((rootId) =>
3502
+ descendantIds(persistence.state!, rootId).has(item.id)))),
3503
+ expanded: persistence.state.expanded.filter((expandedId) => !dissolvedSet.has(expandedId)),
3504
+ leases,
3505
+ ...(Object.keys(briefs).length ? { briefs } : {}),
3506
+ };
3507
+ if (!Object.keys(briefs).length) delete flattened.briefs;
3508
+ persistence.state = flattened;
3509
+ const supplied = typeof params.brief === "string" && params.brief.trim() ? params.brief : undefined;
3510
+ const recut = manualFoldCandidate(snapshot, persistence.state, snapped.ids, { allowProtected: true });
3511
+ const created: Array<Record<string, unknown>> = [];
3512
+ const correctedBriefs: Record<string, string> = {};
3513
+ for (const part of splitCandidateBySize(snapshot, persistence.state, recut)) {
3514
+ // A re-cut that lands back on a span this session already folded meets its own
3515
+ // immutable record. The record wins -- rewriting it would report a conflicting
3516
+ // durable fold and suspend automatic management -- and the supplied brief
3517
+ // becomes a correction beside it, which is what `rebrief` writes anyway.
3518
+ const durable = persistence.persistedFoldRecords.get(foldIdFor(part.kind, part.parts));
3519
+ const { preparedFold, nextState } = await prepareAndCommitExplicit({
3520
+ snapshot, candidate: part, brief: durable ? durable.fold.brief : supplied, ctx, signal,
3521
+ });
3522
+ persistence.state = nextState;
3523
+ if (durable && supplied) correctedBriefs[preparedFold.id] = supplied;
3524
+ created.push({
3525
+ id: preparedFold.id,
3526
+ kind: preparedFold.fold.kind,
3527
+ brief: supplied ?? preparedFold.fold.brief,
3528
+ });
3529
+ }
3530
+ if (Object.keys(correctedBriefs).length) {
3531
+ persistence.state = {
3532
+ ...persistence.state,
3533
+ briefs: { ...(persistence.state.briefs ?? {}), ...correctedBriefs },
3534
+ };
3535
+ }
3536
+ await persistManual(persistence.state, action, ctx);
3537
+ updateStatus(ctx);
3538
+ return toolPayload({
3539
+ version: 1,
3540
+ action,
3541
+ mode: dissolvedIds.length > 1 ? "merge" : "recut",
3542
+ dissolved: dissolvedIds,
3543
+ created,
3544
+ corrections: snapped.corrections,
3545
+ startId: snapped.ids[0],
3546
+ endId: snapped.ids[1],
3547
+ durableRevision: persistence.state.revision,
3548
+ activation: "durable immediately; the named span is now exactly one fold per bite-size cap, " +
3549
+ "and every dissolved fold's evidence is inside it or raw again.",
3550
+ });
3551
+ }
3552
+ const id = String(params.id ?? "").trim();
3553
+ if (!id) throw new Error("reboundary requires id, or ids naming the span to re-cut");
3554
+ const fold = requireActiveFold(snapshot, persistence.state, id);
3555
+ if (fold.parentId) {
3556
+ throw new Error(
3557
+ `Fold ${id} is nested under ${fold.parentId}; dissolve ${fold.parentId} first so ${id} is a root`,
3558
+ );
3559
+ }
3560
+ const refs = flattenFoldRefs(fold, persistence.state);
3561
+ const children = childFoldIds(fold);
3562
+ const briefs = { ...(persistence.state.briefs ?? {}) };
3563
+ delete briefs[id];
3564
+ const next: ActiveContextState = {
3565
+ ...persistence.state,
3566
+ revision: persistence.state.revision + 1,
3567
+ folds: deriveFoldParents(persistence.state.folds.filter((item) => item.id !== id)),
3568
+ expanded: persistence.state.expanded.filter((expandedId) => expandedId !== id),
3569
+ ...(Object.keys(briefs).length ? { briefs } : {}),
3570
+ };
3571
+ if (!Object.keys(briefs).length) delete next.briefs;
3572
+ const leases = { ...next.leases };
3573
+ delete leases[id];
3574
+ await persistManual({ ...next, leases }, action, ctx);
3575
+ updateStatus(ctx);
3576
+ return toolPayload({
3577
+ version: 1,
3578
+ action,
3579
+ id,
3580
+ dissolved: true,
3581
+ releasedChildren: children,
3582
+ startId: refs[0]?.entryId ?? null,
3583
+ endId: refs.at(-1)?.entryId ?? null,
3584
+ durableRevision: persistence.state.revision,
3585
+ activation: `the span ${id} held is raw again and its boundary is yours to re-cut; ` +
3586
+ "fold the endpoints you meant, or two sub-spans to split it. No evidence moved.",
1804
3587
  });
1805
3588
  }
1806
3589
  if (action === "expand" || action === "refold") {
1807
3590
  const id = String(params.id ?? "").trim();
1808
3591
  if (!id) throw new Error(`${action} requires id`);
1809
- requireActiveFold(snapshot, persistence.state, id);
3592
+ // An expand with marks pending opens the commit epoch: the restore and the
3593
+ // batch of folds then cost one rewrite between them instead of two.
3594
+ let epochApplied: unknown[] = [];
3595
+ if (epochScheduling && action === "expand" && pendingMarks(persistence.state).length) {
3596
+ const result = await commitPendingMarks({
3597
+ snapshot,
3598
+ state: persistence.state,
3599
+ generation: lifecycle.generation,
3600
+ retainIneligible: true,
3601
+ });
3602
+ persistence.state = result.state;
3603
+ epochApplied = result.applied;
3604
+ }
3605
+ const expanding = requireActiveFold(snapshot, persistence.state, id);
3606
+ if (action === "expand") {
3607
+ admit({
3608
+ action: "expand",
3609
+ ctx,
3610
+ foldId: id,
3611
+ requestedBytes: Math.max(0, expanding.sourceChars - expanding.placeholderChars),
3612
+ children: childFoldIds(expanding),
3613
+ });
3614
+ noteSurfacingAccept(id);
3615
+ }
1810
3616
  let next = setFoldProjectionState(persistence.state, id, action === "expand" ? "expanded" : "folded");
1811
3617
  if (action === "expand") next = withExpandLease(next, id);
1812
3618
  else {
@@ -1825,6 +3631,7 @@ export function registerActiveContext(pi: any, options: {
1825
3631
  action,
1826
3632
  id,
1827
3633
  state: action === "expand" ? "expanded" : "folded",
3634
+ ...(epochApplied.length ? { committedMarks: epochApplied } : {}),
1828
3635
  activation: "durable immediately; projected on the next model call in this same turn",
1829
3636
  });
1830
3637
  }
@@ -1839,35 +3646,258 @@ export function registerActiveContext(pi: any, options: {
1839
3646
  activation: "durable immediately; projected on the next model call in this same turn",
1840
3647
  });
1841
3648
  }
1842
- if (action === "fold") {
3649
+ if (action === "unmark") {
1843
3650
  const ids = stringIds(params.ids);
1844
- const candidate = manualFoldCandidate(snapshot, persistence.state, ids);
1845
- const supplied = typeof params.brief === "string" && params.brief.trim() ? params.brief : undefined;
1846
- const { preparedFold, nextState } = await prepareAndCommitExplicit({
1847
- snapshot,
1848
- candidate,
1849
- brief: supplied,
1850
- ctx,
1851
- signal,
3651
+ const requested = new Set(ids);
3652
+ const before = pendingMarks(persistence.state);
3653
+ const kept = before.filter((mark) => !requested.has(mark.id));
3654
+ const removed = before.filter((mark) => requested.has(mark.id));
3655
+ const unknown = ids.filter((id) => !before.some((mark) => mark.id === id));
3656
+ if (unknown.length) throw new Error(`No pending mark named ${unknown.join(", ")}`);
3657
+ await persistManual(withPendingMarks(persistence.state, kept), action, ctx);
3658
+ updateStatus(ctx);
3659
+ const accounting = markAccounting(snapshot, persistence.state);
3660
+ return toolPayload({
3661
+ version: 1,
3662
+ action,
3663
+ unmarked: removed.map((mark) => ({ mark: mark.mark, id: mark.id, origin: mark.origin })),
3664
+ pendingMarks: accounting.pending,
3665
+ eligibleMarks: accounting.eligibleMarks,
3666
+ retainedMarks: accounting.retainedMarks,
3667
+ durableRevision: persistence.state.revision,
3668
+ activation: "the withdrawn marks will never be applied; no context bytes moved",
1852
3669
  });
1853
- advisory.armedMilestone = null;
1854
- await persistManual(clearArmedAdvisory(nextState), "fold", ctx);
3670
+ }
3671
+ if (action === "fold") {
3672
+ // One call, several {span, brief} pairs. Marking is free and committing is not,
3673
+ // so the shape that should be cheapest to express is the BATCH: an agent that
3674
+ // must spend one call per span curates less than one that spends one call.
3675
+ const requested = batchedMarkRequests(params);
3676
+ const resolved: Array<{
3677
+ candidate: FoldCandidate;
3678
+ brief?: string;
3679
+ corrections: SpanCorrection[];
3680
+ splitFrom: number;
3681
+ }> = [];
3682
+ for (const request of requested) {
3683
+ const snapped = snapFoldCandidate(snapshot, persistence.state, request.ids, { allowProtected: true });
3684
+ const spanChars = candidateSpanChars(snapshot, persistence.state, snapped.candidate);
3685
+ const parts = splitCandidateBySize(snapshot, persistence.state, snapped.candidate);
3686
+ for (const [index, part] of parts.entries()) {
3687
+ resolved.push({
3688
+ candidate: part,
3689
+ brief: request.brief,
3690
+ corrections: index === 0
3691
+ ? [
3692
+ ...snapped.corrections,
3693
+ ...(parts.length > 1
3694
+ ? [{
3695
+ from: request.ids,
3696
+ to: [],
3697
+ reason: `span was ${spanChars} chars, over the ${MAX_FOLD_SPAN_CHARS}-char bite-size cap; ` +
3698
+ `split into ${parts.length} sequential folds, each with its own brief`,
3699
+ }]
3700
+ : []),
3701
+ ]
3702
+ : [],
3703
+ splitFrom: parts.length > 1 && index === 0 ? spanChars : 0,
3704
+ });
3705
+ }
3706
+ if (parts.length > 1) {
3707
+ emit("context.split", {
3708
+ source: "agent",
3709
+ span_chars: spanChars,
3710
+ parts: parts.length,
3711
+ cap_chars: MAX_FOLD_SPAN_CHARS,
3712
+ fold_ids: "",
3713
+ });
3714
+ }
3715
+ }
3716
+ const corrections = resolved.flatMap((item) => item.corrections);
3717
+ if (epochScheduling) {
3718
+ // Resolve every brief now, while the source is in hand, so the commit epoch
3719
+ // itself stays deterministic and free of provider calls. A span that is not
3720
+ // eligible YET cannot go through preparation at all, so it takes the
3721
+ // deterministic brief: the mark is a decision, and refusing to record it
3722
+ // because the span is momentarily fresh is exactly the drop being fixed.
3723
+ const marks: Array<Record<string, unknown>> = [];
3724
+ let staged = persistence.state;
3725
+ for (const item of resolved) {
3726
+ const { candidate } = item;
3727
+ // ACCEPT AND HOLD. A span that is still fresh or protected is not a refusal:
3728
+ // the mark is a standing decision, it is recorded now, and it folds at the
3729
+ // first commit after the span ages out. Refusal is reserved for a mark that
3730
+ // cannot be constructed at all.
3731
+ // A REPEATED DECISION IS INERT. The fold id is derived from the span, so a
3732
+ // span the ladder already prepared carries that exact id in the forest
3733
+ // already, and preparing it a second time throws "Prepared fold already
3734
+ // exists" out of the tool call. Under the old cadence commits were frequent
3735
+ // enough to drain prepared folds before a second decision could land on one;
3736
+ // the quiet runtime commits far less often, so the collision became reachable
3737
+ // in ordinary use -- the ladder marks a span, the agent then folds the same
3738
+ // span, and the agent's call fails. Marking is a standing decision about a
3739
+ // span, and deciding the same thing twice has to be a no-op.
3740
+ const alreadyPrepared = staged.folds.some((fold) =>
3741
+ fold.id === foldIdFor(candidate.kind, candidate.parts));
3742
+ const deferred = refsProtected(candidate.sourceRefs, staged, snapshot) ||
3743
+ (candidate.kind === "tool-result" &&
3744
+ toolRefsProtected(candidate.sourceRefs, staged, snapshot));
3745
+ const briefed = !deferred && !alreadyPrepared
3746
+ ? await prepareFold({
3747
+ candidate,
3748
+ snapshot,
3749
+ state: staged,
3750
+ generation: lifecycle.generation,
3751
+ brief: item.brief,
3752
+ summarize: options.summarizeContextSpan,
3753
+ ctx,
3754
+ signal,
3755
+ })
3756
+ : null;
3757
+ const mark = foldMarkFor({
3758
+ candidate,
3759
+ brief: briefed?.fold.brief ?? item.brief ?? ladderBrief(snapshot, staged, candidate),
3760
+ briefProvenance: briefed?.fold.provenance ??
3761
+ (item.brief ? { kind: "supplied" } : { kind: "deterministic" }),
3762
+ origin: "agent",
3763
+ ordinal: markOrdinal(snapshot),
3764
+ });
3765
+ const addition = addPendingMark(staged, mark);
3766
+ if (!addition.added) {
3767
+ marks.push({ id: mark.id, kind: mark.kind, ok: false, deferred: false, reason: addition.reason });
3768
+ continue;
3769
+ }
3770
+ staged = addition.state;
3771
+ marks.push({
3772
+ id: mark.id,
3773
+ kind: mark.kind,
3774
+ ok: true,
3775
+ deferred,
3776
+ ...(deferred
3777
+ ? {
3778
+ scheduled: "the span is still in the fresh window; this mark is held and folds at the " +
3779
+ "first commit after it ages out",
3780
+ }
3781
+ : {}),
3782
+ brief: mark.brief,
3783
+ provenance: normalizeLegacyProvenance(mark.briefProvenance),
3784
+ });
3785
+ }
3786
+ await persistManual(staged, action, ctx);
3787
+ updateStatus(ctx);
3788
+ const accounting = markAccounting(snapshot, persistence.state);
3789
+ // MECHANISM 3. The projection is byte-frozen between fold events, so this reply
3790
+ // is the only cache-free channel left: it carries the whole picture, not just
3791
+ // an acknowledgement of the spans this call named.
3792
+ const held = pendingMarks(persistence.state).map((mark) => ({
3793
+ id: mark.id,
3794
+ kind: mark.kind,
3795
+ tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
3796
+ }));
3797
+ const remainder = unmarkedRemainder(snapshot, persistence.state, projectionCharsPerToken());
3798
+ // The single-span shape is the head of the batched one: one call carrying one
3799
+ // span answers exactly as it always did, and a batch adds fields rather than
3800
+ // replacing them.
3801
+ const only = marks.length === 1 && marks[0].ok === true ? marks[0] : null;
3802
+ return toolPayload({
3803
+ version: 1,
3804
+ action,
3805
+ scheduling: "epoch",
3806
+ ok: marks.some((mark) => mark.ok === true),
3807
+ deferredMarks: marks.filter((mark) => mark.deferred === true).length,
3808
+ ...(only
3809
+ ? {
3810
+ id: only.id,
3811
+ kind: only.kind,
3812
+ brief: only.brief,
3813
+ provenance: only.provenance,
3814
+ deferred: only.deferred,
3815
+ ...(only.scheduled ? { scheduled: only.scheduled } : {}),
3816
+ }
3817
+ : {}),
3818
+ marks,
3819
+ // Never silently reinterpreted: a span the runtime moved says so, here.
3820
+ corrections,
3821
+ argumentsSha256: executionArgumentsSha256,
3822
+ durableRevision: persistence.state.revision,
3823
+ pendingMarks: accounting.pending,
3824
+ eligibleMarks: accounting.eligibleMarks,
3825
+ retainedMarks: accounting.retainedMarks,
3826
+ estimatedFreedWindowShare: accounting.freedWindowShare,
3827
+ estimatedEligibleWindowShare: accounting.eligibleFreedWindowShare,
3828
+ estimatedRewriteTokens: accounting.rewriteTokens,
3829
+ held,
3830
+ heldNote: "held until it ages out or the next fold event",
3831
+ unmarkedSpans: remainder.spans,
3832
+ unmarkedTokens: remainder.tokens,
3833
+ unmarkedShare: remainder.share,
3834
+ unmarkedCandidates: remainder.candidates,
3835
+ awareness: markAwarenessText({ held, remainder, toolName, brandNoun }),
3836
+ activation: "accepted as pending marks; no context bytes moved, and nothing else in your " +
3837
+ "context changed either. They apply together at the next commit epoch, which the runtime " +
3838
+ "opens at the fold event. " +
3839
+ "A mark over a still-fresh span is held, not refused: it is scheduled, and it folds at the " +
3840
+ "first commit after that span ages out of the fresh window. " +
3841
+ "Mark several spans in one call: this whole picture comes back with each one.",
3842
+ });
3843
+ }
3844
+ const applied: Array<Record<string, unknown>> = [];
3845
+ for (const item of resolved) {
3846
+ const { preparedFold, nextState } = await prepareAndCommitExplicit({
3847
+ snapshot,
3848
+ candidate: item.candidate,
3849
+ brief: item.brief,
3850
+ ctx,
3851
+ signal,
3852
+ });
3853
+ persistence.state = nextState;
3854
+ applied.push({
3855
+ id: preparedFold.id,
3856
+ kind: preparedFold.fold.kind,
3857
+ brief: preparedFold.fold.brief,
3858
+ provenance: normalizeLegacyProvenance(preparedFold.fold.provenance),
3859
+ expand: { action: "expand", id: preparedFold.id },
3860
+ });
3861
+ }
3862
+ await persistManual(persistence.state, "fold", ctx);
1855
3863
  updateStatus(ctx);
3864
+ const single = applied.length === 1 ? applied[0] : null;
1856
3865
  return toolPayload({
1857
3866
  version: 1,
1858
3867
  action,
1859
- id: preparedFold.id,
1860
- kind: preparedFold.fold.kind,
1861
- brief: preparedFold.fold.brief,
1862
- provenance: normalizeLegacyProvenance(preparedFold.fold.provenance),
3868
+ scheduling: "immediate",
3869
+ ...(single
3870
+ ? {
3871
+ id: single.id,
3872
+ kind: single.kind,
3873
+ brief: single.brief,
3874
+ provenance: single.provenance,
3875
+ expand: single.expand,
3876
+ }
3877
+ : {}),
3878
+ folds: applied,
3879
+ corrections,
1863
3880
  argumentsSha256: executionArgumentsSha256,
1864
3881
  durableRevision: persistence.state.revision,
1865
3882
  activation: "durable immediately; projected on the next model call in this same turn",
1866
- expand: { action: "expand", id: preparedFold.id },
1867
3883
  });
1868
3884
  }
1869
3885
  throw new Error(`Unknown ${toolName} action '${action}'`);
1870
3886
  };
3887
+ /**
3888
+ * Every context-management call, accepted AND refused, in the durable stream.
3889
+ *
3890
+ * A rejected call is the more informative of the two: an agent whose spans keep being
3891
+ * refused looks identical, from fold records alone, to an agent that never tried to
3892
+ * curate at all. The exact validation text is recorded verbatim, because "it failed"
3893
+ * is not a finding and "ids must contain 1-64 values" is.
3894
+ */
3895
+ const requestedMarkCount = (params: Record<string, unknown>): number => {
3896
+ const batched = denseOwnArrayValues(params?.marks);
3897
+ if (batched) return batched.length;
3898
+ return params?.ids === undefined && params?.id === undefined ? 0 : 1;
3899
+ };
3900
+
1871
3901
  const toolHandler = async (
1872
3902
  _toolCallId: string,
1873
3903
  params: Record<string, unknown>,
@@ -1875,7 +3905,46 @@ export function registerActiveContext(pi: any, options: {
1875
3905
  _onUpdate: unknown,
1876
3906
  ctx: any,
1877
3907
  ): Promise<unknown> => {
1878
- const operation = ladder.actionQueue.then(() => executeAction(params, signal, ctx));
3908
+ const operation = ladder.actionQueue.then(async () => {
3909
+ const action = String(params?.action ?? "");
3910
+ // The gate's engagement signal, counted BEFORE the call can fail: an agent that
3911
+ // reached for a context verb engaged, whether or not the span was valid.
3912
+ curation.contextCalls += 1;
3913
+ const attempt = (ok: boolean, error: string | null, corrections: Array<Record<string, unknown>>) => {
3914
+ const record = emit("context.attempt", {
3915
+ action,
3916
+ ok,
3917
+ error,
3918
+ marks_requested: requestedMarkCount(params),
3919
+ corrections_applied: corrections.length,
3920
+ arguments_sha256: sha256Value(params ?? {}),
3921
+ });
3922
+ // One record per correction, referencing its attempt: nothing nests, and a
3923
+ // reader counting corrections never has to unpack an array to do it.
3924
+ for (const correction of corrections) {
3925
+ const from = denseOwnArrayValues(ownValue(correction, "from")) ?? [];
3926
+ const to = denseOwnArrayValues(ownValue(correction, "to")) ?? [];
3927
+ emit("context.correction", {
3928
+ attempt_seq: record.seq,
3929
+ action,
3930
+ requested_start_id: from[0] ?? null,
3931
+ requested_end_id: from[1] ?? null,
3932
+ corrected_start_id: to[0] ?? null,
3933
+ corrected_end_id: to[1] ?? null,
3934
+ reason: ownValue(correction, "reason") ?? null,
3935
+ });
3936
+ }
3937
+ };
3938
+ try {
3939
+ const result = await executeAction(params, signal, ctx);
3940
+ const corrections = denseOwnArrayValues(ownValue(ownValue(result, "details"), "corrections"));
3941
+ attempt(true, null, (corrections ?? []) as Array<Record<string, unknown>>);
3942
+ return result;
3943
+ } catch (error) {
3944
+ attempt(false, error instanceof Error ? error.message : String(error), []);
3945
+ throw error;
3946
+ }
3947
+ });
1879
3948
  ladder.actionQueue = operation.catch(() => undefined);
1880
3949
  return operation;
1881
3950
  };
@@ -1922,8 +3991,7 @@ export function registerActiveContext(pi: any, options: {
1922
3991
  if (!sessionIdentityStillValid(ctx, stateBefore.sessionId, generationBefore)) {
1923
3992
  throw new Error("Active-context session changed before rescue persistence");
1924
3993
  }
1925
- persistence.state = clearArmedAdvisory(nextState);
1926
- advisory.armedMilestone = null;
3994
+ persistence.state = nextState;
1927
3995
  try { await persist(ctx); }
1928
3996
  catch (error) {
1929
3997
  if (sessionIdentityStillValid(ctx, stateBefore.sessionId, generationBefore)) {
@@ -1934,7 +4002,6 @@ export function registerActiveContext(pi: any, options: {
1934
4002
  }
1935
4003
  ladder.pendingManual = false;
1936
4004
  ladder.automaticFailure = null;
1937
- advisory.armedMilestone = null;
1938
4005
  ladder.pendingContextNote =
1939
4006
  `User rescue folded stale context under ${preparedFold.id}; exact source remains expandable.`;
1940
4007
  ladder.lastAutomaticAction = {
@@ -1960,8 +4027,11 @@ export function registerActiveContext(pi: any, options: {
1960
4027
  name: toolName,
1961
4028
  label: toolLabel,
1962
4029
  allowedActions: allowedToolActions,
1963
- fullSurface: allowedToolActions.length === ACTIVE_CONTEXT_TOOL_ACTIONS.length,
4030
+ fullSurface: allowedToolActions.length === defaultToolActions.length,
1964
4031
  maxBriefChars: ACTIVE_CONTEXT_POLICY.maxBriefChars,
4032
+ statusDetails: ["fold_candidates", "tree", "folds", "objects"],
4033
+ minPeekSliceBytes: PEEK_MIN_SLICE_BYTES,
4034
+ defaultPeekBytes: PEEK_DEFAULT_MAX_BYTES,
1965
4035
  handler: toolHandler,
1966
4036
  }));
1967
4037
  for (const command of buildActiveContextCommands({
@@ -1982,9 +4052,9 @@ export function registerActiveContext(pi: any, options: {
1982
4052
  persistence.state = null;
1983
4053
  persistence.persisted = null;
1984
4054
  lifecycle.latestSnapshot = null;
1985
- advisory.armedMilestone = null;
4055
+ curation.receipts = [];
1986
4056
  try { ctx.ui?.setStatus?.(entryTypePrefix, undefined); } catch { /* Shutdown cannot be blocked by UI. */ }
1987
4057
  });
1988
4058
 
1989
- return { projectionCandidates };
4059
+ return { projectionCandidates, registerSuggestionSource };
1990
4060
  }