pi-fold 1.0.2 → 1.0.4

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.
@@ -2,25 +2,29 @@ import {
2
2
  denseOwnArrayValues,
3
3
  evidenceSha256,
4
4
  objectRefKey,
5
+ sha256Text,
5
6
  sha256Value,
6
7
  stableStringify,
7
8
  } from "./json.ts";
8
- import {
9
- } from "./lib/advisory.ts";
9
+ import type { EvidenceRef } from "./json.ts";
10
10
  import {
11
11
  bytes,
12
12
  clone,
13
13
  emptyActiveContextState,
14
14
  ownValue,
15
+ sessionEntryMessages,
15
16
  uniqueMessageDigestAnchor,
16
17
  } from "./lib/canonical.ts";
17
18
  import {
18
19
  activeContextStatus,
19
20
  automaticPreparationId,
21
+ boundedOrientation,
20
22
  boundStatusPayload,
21
23
  commitPreparedFold,
24
+ consolidationSourceText,
22
25
  descendantIds,
23
26
  encodedFoldSource,
27
+ generatedBriefs,
24
28
  foldCandidatesDetail,
25
29
  foldTreeDetail,
26
30
  peekFoldSource,
@@ -42,6 +46,8 @@ import {
42
46
  capacityAccounting,
43
47
  contextUsageRatio,
44
48
  contextWindowFor,
49
+ explicitProtectedMass,
50
+ budgetOccupancy,
45
51
  hardFenceRatio,
46
52
  latestProviderContextMeasurement,
47
53
  orderedRoots,
@@ -49,6 +55,7 @@ import {
49
55
  parseNativeCompactionDecision,
50
56
  parseProviderContextMeasurementReceipt,
51
57
  persistenceProjection,
58
+ protectedStaleMass,
52
59
  providerContextMeasurement,
53
60
  providerTokens,
54
61
  refsProtected,
@@ -74,6 +81,7 @@ import {
74
81
  makeStateDelta,
75
82
  MAX_ACTIVE_FOLD_RECORDS,
76
83
  materializeStatePersistence,
84
+ foldProvenance,
77
85
  normalizeFoldsForPersistedRecords,
78
86
  normalizeLegacyProvenance,
79
87
  protectionSha256,
@@ -93,47 +101,58 @@ import {
93
101
  DEFAULT_ACTIVE_CONTEXT_TOOL_LABEL,
94
102
  DEFAULT_ACTIVE_CONTEXT_TOOL_NAME,
95
103
  COMMIT_RECLAIM_FLOOR_SHARE,
96
- CURATION_OCCUPANCY_SHARE,
97
- CURATION_STALE_TOOL_SHARE,
98
104
  CONTEXT_RECEIPT_BLOCK_BYTES,
99
- CURATION_TARGET_OCCUPANCY_SHARE,
100
105
  DEFAULT_CONTEXT_WINDOW,
101
- DEFAULT_FOLD_SCHEDULING,
106
+ EPOCH_CONSOLIDATION_ROUNDS,
107
+ MAX_THRESHOLD_NOTICES,
108
+ THRESHOLD_NOTICE_SHARES,
109
+ resolveGuidance,
110
+ assertThresholdsServable,
111
+ resolveThresholds,
112
+ servingBudgetTokens,
102
113
  ESTIMATED_BYTES_PER_TOKEN,
103
114
  entryTypeNamespace,
104
- EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS,
105
- EPOCH_COMMIT_TARGET_WINDOW_SHARE,
115
+ MAX_BRIEF_BATCH_SPANS,
116
+ MAX_BRIEF_UPGRADE_QUEUE,
117
+ MAX_BRIEF_UPGRADES_IN_FLIGHT,
106
118
  MAX_FOLD_SPAN_CHARS,
119
+ MAX_PINNED_SHARE,
107
120
  MAX_WEDGE_ABSORB_TOKENS,
108
121
  OVERFLOW_RECOVERY_MAX_ATTEMPTS,
109
122
  PEEK_DEFAULT_MAX_BYTES,
110
123
  PEEK_MIN_SLICE_BYTES,
111
124
  PEEK_READ_ONLY_CONTEXT_ACTIONS,
112
- FOLD_SCHEDULING_MODES,
113
- READ_ONLY_CONTEXT_ACTIONS_DEFAULT,
114
- READ_ONLY_TOOLS_DEFAULT,
125
+ AUTO_FOLD_BLACKLIST_DEFAULT,
126
+ SURFACING_BRIEF_HIT,
127
+ SURFACING_CONTENT_HIT,
128
+ SURFACING_DIVERGENCE_MARGIN,
129
+ SURFACING_OUTCOME_WINDOW_ORDINALS,
130
+ SURFACING_SLATE_SIZE,
115
131
  USER_RESCUE_MAX_SOURCE_CHARS,
116
132
  } from "./lib/policy.ts";
117
133
  import type {
134
+ ActiveContextGuidance,
118
135
  ActiveContextSnapshot,
119
136
  ActiveContextState,
137
+ ActiveContextThresholds,
120
138
  ActiveContextToolAction,
139
+ BriefProvenance,
121
140
  FoldCandidate,
122
141
  FoldKind,
123
142
  FoldRecordEntry,
124
- FoldSchedulingMode,
125
143
  PreparedFold,
126
- SuggestionSource,
127
144
  } from "./lib/policy.ts";
128
145
  import {
129
146
  absorbWedgeMarks,
130
147
  } from "./lib/scheduling.ts";
131
148
  import {
132
149
  contextReceipt,
150
+ contextRiderText,
133
151
  curationSignals,
134
- curationTriggerFires,
152
+ lastCallText,
135
153
  markAwarenessText,
136
154
  receiptBlockText,
155
+ thresholdNoticeText,
137
156
  withReceipt,
138
157
  } from "./lib/curation.ts";
139
158
  import type {
@@ -144,6 +163,7 @@ import {
144
163
  addPendingMark,
145
164
  claimedRefKeys,
146
165
  commitPendingMarks,
166
+ consolidationMarks,
147
167
  ephemeralPeekMarks,
148
168
  epochCommitDue,
149
169
  estimatedTokens,
@@ -169,16 +189,21 @@ import {
169
189
  deterministicChapterCandidateBrief,
170
190
  deterministicConsolidationBrief,
171
191
  manualFoldCandidate,
192
+ peekedSourceFoldIds,
172
193
  selectAutomaticToolBatch,
173
- selectAutomaticToolForRung,
174
194
  snapFoldCandidate,
175
195
  snapToFoldBoundaries,
176
196
  splitCandidateBySize,
177
197
  } from "./lib/selection.ts";
178
198
  import type { SpanCorrection } from "./lib/selection.ts";
179
199
  import {
180
- acceptSurfacingSuggestion,
181
- surfacingOrdinal,
200
+ issueSurfacing,
201
+ noteSurfacingAction,
202
+ resolveSurfacing,
203
+ selectSurfacingSlate,
204
+ surfacingLedger,
205
+ surfacingSilenced,
206
+ surfacingSlateText,
182
207
  } from "./lib/surfacing.ts";
183
208
  import {
184
209
  compareProjections,
@@ -199,11 +224,27 @@ import { buildActiveContextCommands, buildActiveContextTool } from "./lib/tool-s
199
224
  import {
200
225
  currentTurnRefKeys,
201
226
  mapActiveContext,
202
- toolFreshIndices,
203
227
  } from "./lib/transcript.ts";
228
+ import {
229
+ findOverflowErrorEntry,
230
+ overflowEventShape,
231
+ preStripHolds,
232
+ probeRollbackSurfaces,
233
+ rollbackNoticeText,
234
+ unansweredToolCalls,
235
+ } from "./lib/rollback.ts";
236
+
237
+ /**
238
+ * How a projection's token reading was reached.
239
+ *
240
+ * "anchored": the provider's own count for the projection this one extends, plus a rate
241
+ * estimate of the bytes appended after it. "rewritten": the current projection does not
242
+ * begin with the measured one, so there is nothing to add a delta to and the whole
243
+ * projection is estimated. "unmeasured": no accepted provider count yet.
244
+ */
245
+ export type ProjectionReadingBasis = "anchored" | "rewritten" | "unmeasured";
204
246
 
205
247
  // Test seam only; the package API is registerPiFold because package.json exports block deep imports.
206
- export * from "./lib/advisory.ts";
207
248
  export * from "./lib/canonical.ts";
208
249
  export * from "./lib/curation.ts";
209
250
  export * from "./lib/folding.ts";
@@ -211,19 +252,12 @@ export * from "./lib/instrumentation.ts";
211
252
  export * from "./lib/measurement.ts";
212
253
  export * from "./lib/persistence.ts";
213
254
  export * from "./lib/policy.ts";
255
+ export * from "./lib/rollback.ts";
214
256
  export * from "./lib/scheduling.ts";
215
257
  export * from "./lib/selection.ts";
216
258
  export * from "./lib/surfacing.ts";
217
259
  export * from "./lib/transcript.ts";
218
260
 
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
261
  /**
228
262
  * The batched fold request: several {span, brief} pairs in one call, with the single
229
263
  * `ids` form still accepted because it is the shape a status action hands back.
@@ -261,113 +295,139 @@ export function batchedMarkRequests(params: Record<string, unknown>): BatchedMar
261
295
  }
262
296
 
263
297
  export function registerActiveContext(pi: any, options: {
298
+ /**
299
+ * INTERNAL brief-generator seam, not a public option: `registerPiFold` refuses the
300
+ * name and builds the generator from `summarizer`. It stays a parameter because it
301
+ * is the runtime's only brief-generator interface.
302
+ */
264
303
  summarizeContextSpan?: (request: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>;
265
- setProjectionProvider?: (provider: (ctx: any) => Array<Record<string, unknown>>) => void;
266
- setSuggestionSourceRegistrar?: (register: SuggestionSourceRegistrar) => void;
267
- toolActions?: readonly ActiveContextToolAction[];
304
+ /**
305
+ * INTERNAL deployment-identity seam, not public options: the package entry refuses all
306
+ * five by name and the shipped identity is the one in `policy.ts`. They survive here
307
+ * because the neutrality gate has to register a synthetic brand to prove none of it
308
+ * reaches the defaults, and the experiment harness registers the pi-fold identity
309
+ * explicitly so its sealed runs keep their entry types.
310
+ */
268
311
  toolName?: string;
269
312
  toolLabel?: string;
270
313
  brandNoun?: string;
271
314
  entryTypePrefix?: string;
272
- commandPrefix?: string;
273
315
  commandNames?: { status?: string; fold?: string };
274
- readOnlyTools?: ReadonlySet<string>;
275
- blockingTools?: readonly string[];
276
- foldScheduling?: FoldSchedulingMode;
277
- foldPeekResults?: boolean;
278
- providerTotalWindow?: number;
316
+ /**
317
+ * The auto-fold EXCEPTION list, empty by default: every completed tool batch folds
318
+ * unmarked, and this names the tools whose results must stay raw.
319
+ */
320
+ blacklistAutoFoldTools?: ReadonlySet<string>;
321
+ /** The serving budget itself, ALREADY NET of the deployment's output reservation. */
322
+ providerInputBudget?: number;
323
+ /**
324
+ * The thermostat, set whole or not at all. USER policy: no agent action reads it back
325
+ * as a mutable surface, and status reports the values without offering to change them.
326
+ */
327
+ thresholds?: ActiveContextThresholds;
328
+ /** Both guidance surfaces, set together, defaulting on. */
329
+ guidance?: Partial<ActiveContextGuidance>;
279
330
  }): {
280
331
  projectionCandidates: (ctx: any) => Array<Record<string, unknown>>;
281
- registerSuggestionSource: SuggestionSourceRegistrar;
282
332
  } {
283
333
  const toolName = options.toolName ?? DEFAULT_ACTIVE_CONTEXT_TOOL_NAME;
284
334
  const toolLabel = options.toolLabel ?? DEFAULT_ACTIVE_CONTEXT_TOOL_LABEL;
285
335
  const brandNoun = options.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN;
286
336
  const entryTypePrefix = options.entryTypePrefix ?? DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX;
287
- const commandPrefix = options.commandPrefix ?? "";
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(", ")}`);
337
+ const blacklistAutoFoldTools = options.blacklistAutoFoldTools ?? AUTO_FOLD_BLACKLIST_DEFAULT;
338
+ // Deleted options are REFUSED by name, never ignored. A deployment still passing one
339
+ // believes it asked for something, and silence would hand it the opposite behavior:
340
+ // `foldScheduling` chose between epoch and the deleted immediate scheduler,
341
+ // `foldPeekResults` opted peek results out of the foldable classification, and
342
+ // `toolActions` narrowed the action surface. All three are unconditional now.
343
+ for (const removed of ["foldScheduling", "foldPeekResults", "toolActions"]) {
344
+ if (Object.hasOwn(options, removed)) {
345
+ throw new Error(`${removed} is no longer an option: epoch scheduling, peek foldability ` +
346
+ "and the whole action surface are unconditional");
347
+ }
348
+ }
349
+ if (Object.hasOwn(options, "blockingTools")) {
350
+ throw new Error("blockingTools is no longer an option: a tool call never causes a rewrite of " +
351
+ "its own, and the commit epoch is the only path that mutates the projection");
352
+ }
353
+ // The per-request surfacing channel. `setProjectionProvider` was a second delivery
354
+ // path for the value this call already returns, and `setSuggestionSourceRegistrar`
355
+ // handed out registration into a carrier that no longer exists: the slate rode a
356
+ // per-request ephemeral tail, which moved the prefix on every single pass. The
357
+ // selector survives and returns at the commit boundary; the channel does not.
358
+ for (const removed of ["setProjectionProvider", "setSuggestionSourceRegistrar"]) {
359
+ if (Object.hasOwn(options, removed)) {
360
+ throw new Error(`${removed} is no longer an option: projection candidates are returned by ` +
361
+ "registration, and external suggestion sources have no carrier to render into");
362
+ }
292
363
  }
293
- const epochScheduling = foldScheduling === "epoch";
294
- if (options.foldPeekResults !== undefined && typeof options.foldPeekResults !== "boolean") {
295
- throw new Error("foldPeekResults must be a boolean");
364
+ // The renamed options are refused by their OLD names, each pointing at the new one.
365
+ // `readOnlyTools` and then `autoFoldableTools` were both ALLOW-lists, and the list runs
366
+ // the other way now, so neither may be forwarded silently: an allow-list read as a
367
+ // blacklist bars exactly the tools it meant to permit. `commandPrefix` derived two
368
+ // command names from a stem the full-name override then overrode anyway;
369
+ // `providerTotalWindow` was a gross window the runtime netted down with a GUESSED
370
+ // reservation, and the guess is what the reshape deleted.
371
+ for (const inverted of ["readOnlyTools", "autoFoldableTools"]) {
372
+ if (Object.hasOwn(options, inverted)) {
373
+ throw new Error(`${inverted} is now blacklistAutoFoldTools, and the sense is INVERTED: ` +
374
+ "every completed tool batch folds unmarked, and the list names the exceptions whose " +
375
+ "results must stay raw. An allow-list moved across verbatim would bar exactly the " +
376
+ "tools it meant to permit");
377
+ }
378
+ }
379
+ if (Object.hasOwn(options, "commandPrefix")) {
380
+ throw new Error("commandPrefix is no longer an option: give whole names with commandNames");
296
381
  }
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");
382
+ if (Object.hasOwn(options, "providerTotalWindow")) {
383
+ throw new Error("providerTotalWindow is now providerInputBudget, and it is ALREADY NET: " +
384
+ "pass the tokens the deployment may actually fill, not the total window the runtime " +
385
+ "then subtracts a guessed output reservation from");
386
+ }
387
+ if (options.providerInputBudget !== undefined &&
388
+ (!Number.isSafeInteger(options.providerInputBudget) || options.providerInputBudget <= 0)) {
389
+ throw new Error("providerInputBudget must be a positive integer");
305
390
  }
306
391
  // The deployment's own fact, and the only capacity knob: declaring it makes every
307
392
  // ratio, fence and budget truthful, and leaving it out falls back to the provider
308
393
  // 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;
313
- if (!toolName || !toolLabel || !brandNoun || !entryTypePrefix || typeof commandPrefix !== "string" ||
314
- (commandPrefix && !/^[a-z0-9-]+$/.test(commandPrefix)) ||
315
- [...readOnlyTools].some((name) => typeof name !== "string" || !name)) {
316
- throw new Error("Active-context names and read-only tools must be nonempty strings");
394
+ const providerInputBudget = options.providerInputBudget ?? null;
395
+ const readOnlyContextActions = PEEK_READ_ONLY_CONTEXT_ACTIONS;
396
+ if (!toolName || !toolLabel || !brandNoun || !entryTypePrefix ||
397
+ [...blacklistAutoFoldTools].some((name) => typeof name !== "string" || !name)) {
398
+ throw new Error("Active-context names and blacklisted auto-fold tools must be nonempty strings");
317
399
  }
318
- const commandStem = commandPrefix ? `${commandPrefix.replace(/-+$/, "")}-` : "";
319
- // Full-name override for hosts that need non-default command names (e.g. the
320
- // pi-fold package's neutral "context"); commandPrefix remains the derived form.
321
400
  const commandNames = {
322
- status: options.commandNames?.status ?? `${commandStem}${DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES.status}`,
323
- fold: options.commandNames?.fold ?? `${commandStem}${DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES.fold}`,
401
+ status: options.commandNames?.status ?? DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES.status,
402
+ fold: options.commandNames?.fold ?? DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES.fold,
324
403
  };
325
404
  if (![commandNames.status, commandNames.fold].every((name) =>
326
405
  typeof name === "string" && /^[a-z0-9][a-z0-9-]*$/.test(name)) ||
327
406
  commandNames.status === commandNames.fold) {
328
407
  throw new Error("Active-context command names must be distinct kebab-case strings");
329
408
  }
330
- const configuredBlockingTools = denseOwnArrayValues(options.blockingTools ?? ["Agent"]);
331
- if (!configuredBlockingTools || configuredBlockingTools.some((name) => typeof name !== "string" || !name) ||
332
- new Set(configuredBlockingTools).size !== configuredBlockingTools.length) {
333
- throw new Error("Blocking tools must be one dense array of unique nonempty strings");
334
- }
335
- const blockingTools = new Set(configuredBlockingTools as string[]);
409
+ // The thermostat, validated atomically before anything else reads a threshold, and
410
+ // then checked against the budget this deployment can actually serve.
411
+ const thresholds = resolveThresholds(options.thresholds);
412
+ assertThresholdsServable(thresholds, providerInputBudget ?? servingBudgetTokens(DEFAULT_CONTEXT_WINDOW));
413
+ const guidance = resolveGuidance(options.guidance);
336
414
  const stateEntryType = `${entryTypePrefix}-state`;
337
415
  const foldRecordEntryType = `${entryTypePrefix}-fold-record`;
338
416
  const milestoneProjectionType = `${entryTypePrefix}-milestone`;
339
417
  const advisoryProjectionType = `${entryTypePrefix}-advisory`;
340
418
  const surfacingProjectionType = `${entryTypePrefix}-surfacing`;
341
419
  const receiptProjectionType = `${entryTypePrefix}-receipts`;
420
+ const riderProjectionType = `${entryTypePrefix}-rider`;
421
+ const lastCallProjectionType = `${entryTypePrefix}-lastcall`;
422
+ const noticeProjectionType = `${entryTypePrefix}-notice`;
342
423
  const curationProjectionType = `${entryTypePrefix}-curation`;
343
424
  const entryNamespace = entryTypeNamespace(entryTypePrefix);
344
425
  const providerMeasurementEntryType = `${entryNamespace}-provider-context-measurement`;
345
426
  const nativeReceiptEntryType = `${entryNamespace}-native-compaction-receipt`;
346
427
  const contextEventEntryType = `${entryNamespace}-context-event`;
347
428
  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;
353
- const configuredToolActions = denseOwnArrayValues(
354
- options.toolActions ?? defaultToolActions,
355
- );
356
- if (!configuredToolActions || configuredToolActions.length < 1) {
357
- throw new Error("Active-context tool actions must be one non-empty dense array");
358
- }
359
- const allowedToolActions: ActiveContextToolAction[] = [];
360
- const allowedToolActionSet = new Set<string>();
361
- for (const value of configuredToolActions) {
362
- if (typeof value !== "string" ||
363
- !EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS.includes(value as ActiveContextToolAction) ||
364
- allowedToolActionSet.has(value)) {
365
- throw new Error(`Invalid or duplicate active-context tool action '${String(value)}'`);
366
- }
367
- allowedToolActions.push(value as ActiveContextToolAction);
368
- allowedToolActionSet.add(value);
369
- }
370
- Object.freeze(allowedToolActions);
429
+ const allowedToolActions: readonly ActiveContextToolAction[] = ACTIVE_CONTEXT_TOOL_ACTIONS;
430
+ const allowedToolActionSet = new Set<string>(allowedToolActions);
371
431
 
372
432
  type AutomaticFailureState = {
373
433
  key: string;
@@ -408,6 +468,62 @@ export function registerActiveContext(pi: any, options: {
408
468
  actionQueue: Promise.resolve<unknown>(undefined),
409
469
  };
410
470
 
471
+ /**
472
+ * THE BRIEF UPGRADE LANE.
473
+ *
474
+ * A fold brief is model-written from this campaign forward; the deterministic brief is
475
+ * what a failure leaves behind. The automatic ladder cannot wait for a generator: the
476
+ * commit path is synchronous with a measured projection and a fold deferred until a
477
+ * provider answers is a fold that does not happen. So the ladder commits deterministic,
478
+ * a generator runs between boundaries, and the brief it writes lands as an UPGRADE
479
+ * riding the next commit: the same law wedge absorption and the surfacing slate follow,
480
+ * mutations only inside a rewrite the session already paid for.
481
+ *
482
+ * Everything here is process-local. An upgrade lost to a reload is a brief that stays
483
+ * deterministic, which is exactly the failure the fallback exists for, and the state
484
+ * wire keeps one shape instead of carrying work in progress.
485
+ */
486
+ const upgrades = {
487
+ /**
488
+ * BATCHES of folds committed deterministic, each holding the exact source its briefs
489
+ * will cover. One batch is one generator call: a commit folds its spans together, so
490
+ * they are briefed together, and the spans of one batch orient each other. Sending them
491
+ * one at a time is what left the 2026-08-11 rep with 14 model briefs across 87 folds.
492
+ */
493
+ queue: [] as Array<{
494
+ members: Array<{ foldId: string; sourceSha256: string }>;
495
+ request: Record<string, unknown>;
496
+ }>,
497
+ /**
498
+ * Generator calls in flight, keyed by every fold each one briefs, bounded by a constant
499
+ * and cancel-safe on session change or shutdown. Keyed rather than counted because
500
+ * the queue has to know a fold is already being briefed.
501
+ */
502
+ running: new Map<string, { controller: AbortController; promise: Promise<void> }>(),
503
+ /** Written briefs waiting for a boundary. They never apply anywhere else. */
504
+ ready: [] as Array<{ foldId: string; sourceSha256: string; brief: string; provenance: BriefProvenance }>,
505
+ /** A generator that failed a fold does not get it back: one attempt, then deterministic stands. */
506
+ failed: new Set<string>(),
507
+ /**
508
+ * Parents whose turn has not come: a group is read at depth one, so a child still
509
+ * waiting on its own brief would be read through the sentence that brief is about to
510
+ * replace. They are retried at every boundary and never dropped, because the wait ends
511
+ * the moment the lane drains and a parent that was skipped once is a parent no
512
+ * generator ever writes.
513
+ */
514
+ deferred: new Set<string>(),
515
+ /** Failures since the last commit record carried them, so none of this is silent. */
516
+ failures: 0,
517
+ /**
518
+ * Briefs that took a second ask before they met the contract. Counted because the cure
519
+ * is the mechanism that lets the brief cap be a real limit rather than a truncation
520
+ * point, so how often it fires is how we learn whether the cap is set anywhere near
521
+ * right (Shane 2026-08-11).
522
+ */
523
+ cures: 0,
524
+ lastError: null as string | null,
525
+ };
526
+
411
527
  // Owns provider receipts, anchors, and usage; providerMeasurementQueue serializes receipt writes.
412
528
  const measurements = {
413
529
  latestRatio: null as number | null,
@@ -430,10 +546,31 @@ export function registerActiveContext(pi: any, options: {
430
546
  lastProjectedEstimate: null as number | null,
431
547
  /** Whether that estimate used a measured ratio rather than the bootstrap constant. */
432
548
  lastProjectedEstimateCalibrated: false,
549
+ /** How that reading was reached, so an audit can tell an anchor from a fallback. */
550
+ lastProjectedEstimateBasis: "unmeasured" as ProjectionReadingBasis,
551
+ /** That projection's own size at one rate, the baseline a reduction is measured from. */
552
+ lastProjectedSizeTokens: null as number | null,
553
+ /**
554
+ * The provider's own count for the last projection this process TRANSMITTED, held
555
+ * with that projection's serialized text so a later projection can be tested against
556
+ * it by construction. `head` is the serialization minus its closing bracket, which is
557
+ * what makes "the same projection with more appended after it" a byte-exact prefix
558
+ * test rather than a size comparison.
559
+ */
560
+ projectionAnchor: null as {
561
+ tokens: number;
562
+ chars: number;
563
+ head: string;
564
+ messageSha256: string;
565
+ sessionId: string;
566
+ generation: number;
567
+ } | null,
433
568
  /** Signed estimator error against recent measurements, as a share of measured tokens. */
434
569
  estimatorErrors: [] as number[],
435
570
  /** Growth in measured tokens between consecutive requests. */
436
571
  inflowSteps: [] as number[],
572
+ /** Growth steps the OUTPUT WALL reads. Separate from `inflowSteps` on purpose: see noteWallInflow. */
573
+ wallInflowSteps: [] as number[],
437
574
  providerMeasurementQueue: Promise.resolve<void>(undefined),
438
575
  providerMeasurementReceipts: new Set<string>(),
439
576
  providerMeasurementRevisionByMessageSha: new Map<string, number>(),
@@ -517,6 +654,15 @@ export function registerActiveContext(pi: any, options: {
517
654
  * runtime rewrites the projection, which is the whole cost model.
518
655
  */
519
656
  reopenBaselineShare: null as number | null,
657
+ /** Whether the output wall's crossing has already spent its one forced commit. */
658
+ wallEpisodeOpen: false,
659
+ /**
660
+ * Where the armed last-call actually LANDED: the exposure it rendered and the
661
+ * ordinal of the pass that rendered it. In-memory on purpose: after a reload the
662
+ * carrier re-renders on the first projection, this re-arms, and the commit waits
663
+ * one more round rather than firing on a prompt nobody saw.
664
+ */
665
+ lastCallDelivery: null as { exposure: number; ordinal: number } | null,
520
666
  /** Recovery attempts spent on the CURRENT inflow; reset by any accepted request. */
521
667
  recoveryAttempts: 0,
522
668
  /** A provider rejection observed but not yet recovered from. */
@@ -525,6 +671,29 @@ export function registerActiveContext(pi: any, options: {
525
671
  instrumentationQueue: Promise.resolve<void>(undefined),
526
672
  };
527
673
 
674
+ /**
675
+ * The tree-rollback lane.
676
+ *
677
+ * Armed once per session against the pi surfaces it calls, because every one of them
678
+ * is an internal the extension contract does not promise. Disarmed, the lane is OFF
679
+ * and says so on the record: an overflow still emits `context.rollback` with
680
+ * `armed: false` and the reason, the user is notified, and the session behaves
681
+ * exactly as it did before this build -- the projection-budget fence aborts an
682
+ * over-budget request rather than half-performing a recovery.
683
+ */
684
+ const rollback = {
685
+ probes: null as ReturnType<typeof probeRollbackSurfaces> | null,
686
+ armed: false,
687
+ /** pi's own overflow classifier, resolved from the host; null when unreachable. */
688
+ classifier: null as ((message: unknown, contextWindow: number) => boolean) | null,
689
+ classifierSource: null as string | null,
690
+ /** Ordinals of episodes handled, so the one-shot and the ledger agree. */
691
+ attempts: 0,
692
+ last: null as Record<string, unknown> | null,
693
+ /** An overflow seen at message_end and not yet claimed by the compaction event. */
694
+ pendingOverflow: null as { at: number; entryId: string | null } | null,
695
+ };
696
+
528
697
  // Owns advisory arming and hard-fence delivery; durable effects use persistenceQueue.
529
698
  const advisory = {
530
699
  hardFenceNoticeKey: null as string | null,
@@ -532,16 +701,6 @@ export function registerActiveContext(pi: any, options: {
532
701
  hardFenceReleasedProjectionKeys: new Set<string>(),
533
702
  };
534
703
 
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
-
545
704
  // Owns native-compaction decisions and completion retry state; nativeReceiptQueue serializes it.
546
705
  const nativeCompaction = {
547
706
  lastThresholdDecision: null as Record<string, unknown> | null,
@@ -560,8 +719,6 @@ export function registerActiveContext(pi: any, options: {
560
719
  shuttingDown: false,
561
720
  latestSnapshot: null as ActiveContextSnapshot | null,
562
721
  latestSnapshotError: null as string | null,
563
- blockingToolHarvestedThisTurn: false,
564
- blockingToolHarvestQueuedThisTurn: false,
565
722
  contextQueue: Promise.resolve<void>(undefined),
566
723
  };
567
724
 
@@ -620,6 +777,103 @@ export function registerActiveContext(pi: any, options: {
620
777
  return record;
621
778
  };
622
779
 
780
+ /**
781
+ * The generator, wrapped once so every brief it writes is on the record.
782
+ *
783
+ * Wrapping HERE rather than instrumenting the two call sites is deliberate. The inline
784
+ * path in `prepareFold` and the async upgrade lane both receive this function, so one
785
+ * wrapper covers both, no signature changes and no way to add a third caller that
786
+ * quietly escapes the ledger. It also covers a deployment's OWN summarizer, which is
787
+ * the case a call-site patch would have missed.
788
+ *
789
+ * A generator call is invisible everywhere else: it runs outside the session, so it
790
+ * lands in no provider ledger, and it runs off the turn, so it lands in no turn timing.
791
+ * Duration, queue wait and usage are therefore recorded here or nowhere.
792
+ *
793
+ * A failure is emitted and rethrown unchanged. This observes; it decides nothing, and
794
+ * the fallback to the deterministic brief stays exactly where it was.
795
+ */
796
+ const observedSummarize = options.summarizeContextSpan
797
+ ? async (request: Record<string, unknown>, summarizerCtx: unknown): Promise<Record<string, unknown>> => {
798
+ const startedAt = Date.now();
799
+ const queuedAt = typeof request.queuedAtMs === "number" ? request.queuedAtMs : null;
800
+ const spans = Array.isArray(request.spans)
801
+ ? request.spans as Array<Record<string, unknown>>
802
+ : null;
803
+ const sourceText = typeof request.sourceText === "string" ? request.sourceText : "";
804
+ // A span is a CONSOLIDATION when it is a group of folds, and an automatic fold
805
+ // otherwise. Both kinds are counted and both kinds' source is measured, because a
806
+ // call can carry a mix and the honest way to attribute its tokens across kinds is by
807
+ // the share of source each kind contributed, never by pretending the call was one
808
+ // kind (Shane 2026-08-11: track cures and tokens by fold type).
809
+ const groupSpan = (span: Record<string, unknown>): boolean =>
810
+ Number.isInteger(span.children) && (span.children as number) > 1;
811
+ const charsOf = (span: Record<string, unknown>): number =>
812
+ typeof span.sourceText === "string" ? span.sourceText.length : 0;
813
+ const kindCounts = spans
814
+ ? {
815
+ spans: spans.length,
816
+ group_spans: spans.filter(groupSpan).length,
817
+ leaf_spans: spans.filter((span) => !groupSpan(span)).length,
818
+ group_source_chars: spans.filter(groupSpan).reduce((sum, span) => sum + charsOf(span), 0),
819
+ leaf_source_chars: spans.filter((span) => !groupSpan(span))
820
+ .reduce((sum, span) => sum + charsOf(span), 0),
821
+ fold_ids: spans.map((span) => typeof span.candidateId === "string" ? span.candidateId : ""),
822
+ }
823
+ : {
824
+ spans: 1,
825
+ group_spans: groupSpan(request) ? 1 : 0,
826
+ leaf_spans: groupSpan(request) ? 0 : 1,
827
+ group_source_chars: groupSpan(request) ? sourceText.length : 0,
828
+ leaf_source_chars: groupSpan(request) ? 0 : sourceText.length,
829
+ };
830
+ const base = {
831
+ fold_id: typeof request.candidateId === "string" ? request.candidateId : "",
832
+ source_chars: spans ? spans.reduce((sum, span) => sum + charsOf(span), 0) : sourceText.length,
833
+ source_sha256: typeof request.sourceSha256 === "string" ? request.sourceSha256 : sha256Text(sourceText),
834
+ ...kindCounts,
835
+ // The second ask, not the first. How often this fires is how we learn whether the
836
+ // brief cap is anywhere near right, so it is a field rather than an inference.
837
+ cure: typeof request.cure === "string" && request.cure.length > 0,
838
+ ...(queuedAt === null ? {} : { queued_ms: Math.max(0, startedAt - queuedAt) }),
839
+ };
840
+ try {
841
+ const result = await options.summarizeContextSpan!(request, summarizerCtx);
842
+ const written = Array.isArray(result?.briefs)
843
+ ? (result.briefs as unknown[]).map((item) => typeof item === "string" ? item.trim() : "")
844
+ : [typeof result?.brief === "string" ? result.brief.trim() : ""];
845
+ emit("context.brief", {
846
+ ...base,
847
+ outcome: "ok",
848
+ duration_ms: Date.now() - startedAt,
849
+ provider: typeof result?.provider === "string" ? result.provider : "",
850
+ model: typeof result?.model === "string" ? result.model : "",
851
+ effort: typeof result?.effort === "string" ? result.effort : "",
852
+ brief_chars: written.reduce((sum, item) => sum + item.length, 0),
853
+ // Per span, so a batch where one brief came back empty is visible as that rather
854
+ // than as a slightly short total.
855
+ brief_chars_each: written.map((item) => item.length),
856
+ brief_sha256: written.length === 1 && written[0] ? sha256Text(written[0]) : "",
857
+ // Absent when the provider reported none. Never defaulted: a call whose cost we
858
+ // do not know and a call that cost nothing are different facts.
859
+ usage: result?.usage && typeof result.usage === "object" ? clone(result.usage) : null,
860
+ });
861
+ return result;
862
+ } catch (error) {
863
+ const message = error instanceof Error ? error.message : String(error);
864
+ emit("context.brief", {
865
+ ...base,
866
+ // A timeout is not a refusal and not a provider fault, and telling them apart is
867
+ // the difference between raising the bound and changing the generator.
868
+ outcome: /exceeded \d+ms/.test(message) ? "timeout" : "error",
869
+ duration_ms: Date.now() - startedAt,
870
+ error: boundReceiptText(message, 240, "brief generator"),
871
+ });
872
+ throw error;
873
+ }
874
+ }
875
+ : undefined;
876
+
623
877
  /**
624
878
  * Actions of ours that move bytes at or before a prefix position. Receipts, gate
625
879
  * notices and suggestions are appended after the whole projection, so they can only
@@ -641,6 +895,7 @@ export function registerActiveContext(pi: any, options: {
641
895
  split_from_chars: receipt.splitFromChars,
642
896
  absorbed_wedges: receipt.absorbedWedges,
643
897
  recovered: receipt.recovered,
898
+ protected_bytes: receipt.protectedBytes,
644
899
  note: receipt.note,
645
900
  });
646
901
  };
@@ -671,18 +926,18 @@ export function registerActiveContext(pi: any, options: {
671
926
  };
672
927
 
673
928
  /**
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
929
+ * The number every ratio, fence and budget is computed against. Declared, it is the
930
+ * deployment's own serving budget, already net of whatever output reservation it holds
931
+ * back; undeclared, it is the per-request max-input descriptor, which bakes in a full
932
+ * output reservation the deployment never asked for and so aborts requests inside real
933
+ * headroom. The descriptor is still read, and still reported, so the gap stays
679
934
  * auditable rather than assumed.
680
935
  */
681
936
  const budgetWindowFor = (ctx: any): number | null => {
682
937
  // Remembered so the ctx-free callers (trigger, gate, reminders, advisory) report the
683
938
  // same descriptor gap the fence does instead of re-reading a second source.
684
939
  measurements.descriptorWindow = contextWindowFor(ctx);
685
- return providerTotalWindow ?? measurements.descriptorWindow;
940
+ return providerInputBudget ?? measurements.descriptorWindow;
686
941
  };
687
942
 
688
943
  /**
@@ -698,7 +953,7 @@ export function registerActiveContext(pi: any, options: {
698
953
  const servingCapacity = (window: number | null): ReturnType<typeof capacityAccounting> =>
699
954
  capacityAccounting({
700
955
  window: window ?? lifecycle.latestSnapshot?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
701
- truthful: providerTotalWindow !== null,
956
+ truthful: providerInputBudget !== null,
702
957
  descriptorWindow: measurements.descriptorWindow,
703
958
  usedTokens: measurements.lastProviderMeasurement?.tokens ?? null,
704
959
  });
@@ -713,9 +968,11 @@ export function registerActiveContext(pi: any, options: {
713
968
  toolName,
714
969
  brandNoun,
715
970
  entryTypePrefix,
716
- readOnlyTools,
971
+ blacklistAutoFoldTools,
717
972
  readOnlyContextActions,
718
973
  contextWindow: budgetWindowFor(ctx) ?? undefined,
974
+ netBudget: providerInputBudget !== null,
975
+ thresholds,
719
976
  });
720
977
 
721
978
  const authoritativeSnapshotFor = (ctx: any): ActiveContextSnapshot => {
@@ -731,9 +988,11 @@ export function registerActiveContext(pi: any, options: {
731
988
  toolName,
732
989
  brandNoun,
733
990
  entryTypePrefix,
734
- readOnlyTools,
991
+ blacklistAutoFoldTools,
735
992
  readOnlyContextActions,
736
993
  contextWindow: budgetWindowFor(ctx) ?? undefined,
994
+ netBudget: providerInputBudget !== null,
995
+ thresholds,
737
996
  });
738
997
  };
739
998
 
@@ -777,13 +1036,15 @@ export function registerActiveContext(pi: any, options: {
777
1036
  lifecycle.generation += 1;
778
1037
  lifecycle.shuttingDown = false;
779
1038
  cancelPreparation();
1039
+ cancelBriefUpgrades();
780
1040
  lifecycle.latestSnapshot = null;
781
1041
  lifecycle.latestSnapshotError = null;
782
1042
  measurements.latestRatio = null;
783
1043
  measurements.lastProviderMeasurement = null;
1044
+ measurements.wallInflowSteps.length = 0;
1045
+ measurements.projectionAnchor = null;
1046
+ measurements.lastProjectedEstimateBasis = "unmeasured";
784
1047
  ladder.pendingManual = false;
785
- lifecycle.blockingToolHarvestedThisTurn = false;
786
- lifecycle.blockingToolHarvestQueuedThisTurn = false;
787
1048
  if (!preserveThresholdDecision) nativeCompaction.lastThresholdDecision = null;
788
1049
  ladder.lastPreparationError = null;
789
1050
  ladder.boundaryFailure = null;
@@ -898,11 +1159,45 @@ export function registerActiveContext(pi: any, options: {
898
1159
  const operation = persistence.persistenceQueue.then(async () => {
899
1160
  if (!persistence.state || !persistence.persisted) return;
900
1161
  let next = clone(persistence.state);
1162
+ // Which folds this write is TRYING to add, read before the projection can drop them.
1163
+ const persistedFoldIds = new Set(persistence.persisted.folds.map((fold) => fold.id));
1164
+ const arrivingFoldIds = next.folds.filter((fold) => !persistedFoldIds.has(fold.id)).map((fold) => fold.id);
1165
+ const foldsInMemory = next.folds.length;
901
1166
  if (ctx && lifecycle.latestSnapshot?.sessionId === next.sessionId) {
902
1167
  next = persistenceProjection(next, authoritativeSnapshotFor(ctx));
903
1168
  }
904
1169
  next.folds = normalizeFoldsForPersistedRecords(next.folds, persistence.persistedFoldRecords);
905
1170
  if (sameStateProjection(next, persistence.persisted)) {
1171
+ // A COMMIT THAT VANISHES IS NOT A NO-OP.
1172
+ //
1173
+ // `persistenceProjection` keeps only folds whose refs still resolve against the
1174
+ // snapshot, which is correct when evidence has genuinely left the branch. But when
1175
+ // a write ARRIVES with new folds and projects down to something byte-equal to what
1176
+ // is already durable, every one of those folds was dropped, and returning quietly
1177
+ // here also resets `persistence.state` BACKWARDS to the persisted revision. The
1178
+ // marks that produced them go back to pending, the same commit is attempted again
1179
+ // on the next boundary, and it vanishes again.
1180
+ //
1181
+ // Measured, sol-20260812 rep 3: a band-top commit ran at revision 143 and folded
1182
+ // 579,489 source chars into 85,463 of placeholder. Durable state never passed 134.
1183
+ // None of its 11 folds reached a record, no receipt was written, occupancy climbed
1184
+ // 237,682 -> 253,423 against a 251,520 budget unopposed, and the next request was
1185
+ // refused. Nothing was reported, because a silent return is not a failure. Rep 2
1186
+ // ran 15 commits on the same plan with no gap at all, so this is rare rather than
1187
+ // systemic, which is exactly why it has to announce itself the first time.
1188
+ //
1189
+ // Raising it routes through the automatic-failure path, which restores the state
1190
+ // this call was about to discard, suspends automatic folding and tells the user.
1191
+ // Suspending is the safe direction: continuing to compute folds that are thrown
1192
+ // away is what turned one dropped commit into a dead session.
1193
+ if (arrivingFoldIds.length) {
1194
+ throw new Error(
1195
+ `Active-context commit discarded at persistence: ${arrivingFoldIds.length} arriving fold(s) ` +
1196
+ `did not survive the persistence projection (${foldsInMemory} in memory, ` +
1197
+ `${next.folds.length} durable) at revision ${persistence.state.revision}; ` +
1198
+ `first ${arrivingFoldIds[0]}`,
1199
+ );
1200
+ }
906
1201
  persistence.state = clone(persistence.persisted);
907
1202
  return;
908
1203
  }
@@ -1240,11 +1535,10 @@ export function registerActiveContext(pi: any, options: {
1240
1535
  protection: persistence.state ? protectionSha256(persistence.state) : null,
1241
1536
  selection,
1242
1537
  policy: snapshot ? {
1243
- toolFoldRatio: snapshot.policy.toolFoldRatio,
1244
- refoldRatio: snapshot.policy.refoldRatio,
1245
- prepareRatio: snapshot.policy.prepareRatio,
1538
+ maxTarget: snapshot.thresholds.maxTarget,
1539
+ minTarget: snapshot.thresholds.minTarget,
1540
+ freshTail: snapshot.thresholds.freshTail,
1246
1541
  hardFenceRatio: hardFenceRatio(snapshot),
1247
- consolidationRatio: snapshot.policy.consolidationRatio,
1248
1542
  } : null,
1249
1543
  });
1250
1544
  };
@@ -1424,8 +1718,126 @@ export function registerActiveContext(pi: any, options: {
1424
1718
  );
1425
1719
  };
1426
1720
 
1427
- const projectedTokenEstimate = (projected: unknown[]): number =>
1428
- Math.ceil(bytes(projected) / projectionCharsPerToken());
1721
+ /**
1722
+ * Bind the provider's count to the projection it counted.
1723
+ *
1724
+ * The anchor is the number the provider reported for a request this process actually
1725
+ * TRANSMITTED, held with that projection's serialized bytes. Identity is carried three
1726
+ * ways so a stale anchor cannot pass for a fresh one: the session it belongs to, the
1727
+ * attribution generation (a model or thinking-level change is a different tokenizer, so
1728
+ * its counts describe nothing here), and the projection text itself, which the reading
1729
+ * re-checks byte for byte before adding anything to it.
1730
+ */
1731
+ const noteProviderProjectionAnchor = (measurement: ProviderContextMeasurement): void => {
1732
+ // ONE ANCHOR PER MEASURED RESPONSE. A pass that re-observes the same assistant
1733
+ // message -- the retry after a provider rejection, or any pass the host runs without
1734
+ // a new response -- would otherwise re-bind that count to a newer projection the
1735
+ // provider never counted, and the count would silently describe the wrong bytes.
1736
+ if (measurements.projectionAnchor?.messageSha256 === measurement.messageSha256) return;
1737
+ const text = instrumentation.previousText;
1738
+ // Only a TRANSMITTED projection can be anchored: an aborted pass built a projection
1739
+ // the provider never saw, so no reported count describes it.
1740
+ if (!text || text.length < 2 || !text.endsWith("]") || !persistence.state) {
1741
+ measurements.projectionAnchor = null;
1742
+ return;
1743
+ }
1744
+ const chars = bytes(text);
1745
+ // AND THE COUNT HAS TO FIT THE BYTES IT IS BOUND TO.
1746
+ //
1747
+ // An anchor is exact for the bytes it covers only if it actually describes them. A
1748
+ // pairing that implies fewer serialized chars per token than any tokenizer produces
1749
+ // is not describing this projection, and taken at face value it would put the fence
1750
+ // under permanent pressure a projection of that size cannot explain. The calibration
1751
+ // window already refuses such a ratio through the same floor; the anchor refuses it
1752
+ // outright and falls back to the estimate. Only the FLOOR applies: a rate ABOVE the
1753
+ // calibration ceiling is the signature-heavy composition this mechanism exists to
1754
+ // read, and clamping it would throw away the measurement that motivated the build.
1755
+ if (!(measurement.tokens > 0) || chars / measurement.tokens < PROJECTION_CHARS_PER_TOKEN_FLOOR) {
1756
+ measurements.projectionAnchor = null;
1757
+ return;
1758
+ }
1759
+ measurements.projectionAnchor = {
1760
+ tokens: measurement.tokens,
1761
+ chars,
1762
+ head: text.slice(0, -1),
1763
+ messageSha256: measurement.messageSha256,
1764
+ sessionId: persistence.state.sessionId,
1765
+ generation: lifecycle.generation,
1766
+ };
1767
+ };
1768
+
1769
+ /**
1770
+ * THE OCCUPANCY READING: what the provider counted, plus only what arrived after it.
1771
+ *
1772
+ * A whole-projection estimate divides every byte in the window by one rate, and one
1773
+ * rate does not fit the window's contents. Measured 2026-08-10 on the sealed rep-23
1774
+ * run, byte exact against that run's own projection records: the estimate is unbiased
1775
+ * at the pre-commit PEAKS (provider over estimate, mean 1.004 across six) and over-reads
1776
+ * the post-fold TROUGHS by 22 to 35 percent (mean ratio 0.784 across six). It is a phase
1777
+ * effect, not drift: encrypted reasoning-signature blobs on assistant messages grew from
1778
+ * 14.6 to 51.3 percent of projection bytes over that run and price at a fitted 24.8 chars
1779
+ * per token against 3.957 for ordinary content, so a projection's rate depends on what it
1780
+ * is made of. The runtime therefore believed its floor climbed 0.298 to 0.490 of budget
1781
+ * while the provider read 0.276 to 0.382: roughly 45 percent of the apparent floor rise
1782
+ * was measurement error, five of six true troughs sat at or below the 0.35 minTarget, and
1783
+ * the ladder was folding past its own floor without seeing it.
1784
+ *
1785
+ * So the rate is applied to the DELTA only. The provider's count for the projection this
1786
+ * one extends is exact for every byte it covers, whatever those bytes are made of, and
1787
+ * only the material appended since is estimated. Measured on the same run: mean absolute
1788
+ * occupancy error across all requests falls from 8.38 to 3.08 percent, and inside every
1789
+ * post-fold refill window it drops from the +9 to +27 percent band to under 4 percent from
1790
+ * the second request onward.
1791
+ *
1792
+ * Two things this deliberately does NOT do. It does not move the commit trigger: at all
1793
+ * twelve commit points of that run the delta since the last provider count was exactly
1794
+ * zero, because the commit hook runs after the response and its usage record have landed.
1795
+ * And it does not fix the one pass per cycle where the projection is REWRITTEN rather than
1796
+ * appended, right after a commit: the anchor describes a projection this one does not
1797
+ * begin with, so there is nothing to add a delta to and the whole-projection estimate
1798
+ * stands, error and all (+27 to +35 percent, measured). That case is detected by
1799
+ * construction, not by the sign of the delta: the current serialization must begin with
1800
+ * the anchored one verbatim, up to the array separator.
1801
+ *
1802
+ * The anchor is the provider's TOTAL for that request, which includes the output tokens
1803
+ * of the response the next projection appends and then prices again as bytes. That is one
1804
+ * response's worth of double count, it runs HIGH rather than low, and it keeps a single
1805
+ * definition of "the provider's number" across occupancy, the ratio and the calibration
1806
+ * instead of a second one that only this reading would use.
1807
+ */
1808
+ const projectedTokenReading = (projected: unknown[]): {
1809
+ tokens: number;
1810
+ basis: ProjectionReadingBasis;
1811
+ chars: number;
1812
+ anchorTokens: number | null;
1813
+ deltaChars: number | null;
1814
+ } => {
1815
+ const text = stableStringify(projected);
1816
+ const chars = Buffer.byteLength(text, "utf8");
1817
+ const charsPerToken = projectionCharsPerToken();
1818
+ const estimate = Math.ceil(chars / charsPerToken);
1819
+ const anchor = measurements.projectionAnchor;
1820
+ if (!anchor || anchor.generation !== lifecycle.generation ||
1821
+ anchor.sessionId !== persistence.state?.sessionId) {
1822
+ return { tokens: estimate, basis: "unmeasured", chars, anchorTokens: null, deltaChars: null };
1823
+ }
1824
+ // By construction: the anchored serialization minus its closing bracket must be a
1825
+ // byte-exact prefix, and the byte after it must be the array separator or the close.
1826
+ // A message that merely GREW in place fails this, because the anchored head ends with
1827
+ // that message's own closing brace.
1828
+ const separator = text.length > anchor.head.length ? text[anchor.head.length] : "";
1829
+ if (!text.startsWith(anchor.head) || (separator !== "," && separator !== "]")) {
1830
+ return { tokens: estimate, basis: "rewritten", chars, anchorTokens: anchor.tokens, deltaChars: null };
1831
+ }
1832
+ const deltaChars = chars - anchor.chars;
1833
+ return {
1834
+ tokens: Math.max(0, anchor.tokens + Math.ceil(deltaChars / charsPerToken)),
1835
+ basis: "anchored",
1836
+ chars,
1837
+ anchorTokens: anchor.tokens,
1838
+ deltaChars,
1839
+ };
1840
+ };
1429
1841
 
1430
1842
  /** The largest recent estimator error, as a share. Unmeasured sessions assume none. */
1431
1843
  const estimatorErrorShare = (): number => measurements.estimatorErrors.length
@@ -1437,6 +1849,38 @@ export function registerActiveContext(pi: any, options: {
1437
1849
  ? Math.max(...measurements.inflowSteps)
1438
1850
  : 0;
1439
1851
 
1852
+ /**
1853
+ * THE OUTPUT WALL'S OWN FORWARD LOOK, AND WHY IT IS NOT THE ONE ABOVE.
1854
+ *
1855
+ * `inflowSteps` is filled by `noteProjectionCalibration`, which runs on the context pass.
1856
+ * By then the message-end path has already advanced `lastProviderMeasurement` to the
1857
+ * count being calibrated, so the difference is a measurement against itself and the step
1858
+ * is zero. That window has therefore been empty in every session ever run, which means
1859
+ * the transmission fence margin and the commit depth floor, both of which read it, have
1860
+ * been running on their floor shares alone. Reviving it moves both, and both are
1861
+ * calibrated against reps recorded while it was empty, so that is its own build with its
1862
+ * own gates rather than a rider on this one.
1863
+ *
1864
+ * This window is filled where the previous count is still readable, and nothing but the
1865
+ * wall reads it. When a count reaches only the context pass the window stays empty and
1866
+ * the wall degrades to the condition it replaced, which is the safe direction.
1867
+ */
1868
+ const WALL_INFLOW_WINDOW = 8;
1869
+ const noteWallInflow = (measurement: ProviderContextMeasurement): void => {
1870
+ const previous = measurements.lastProviderMeasurement;
1871
+ if (!previous || !Number.isFinite(measurement.tokens) || !Number.isFinite(previous.tokens)) return;
1872
+ const step = measurement.tokens - previous.tokens;
1873
+ // Only GROWTH. A commit shrinks the window; that is what the wall has to survive, not
1874
+ // a size the next request can be predicted from.
1875
+ if (step <= 0) return;
1876
+ measurements.wallInflowSteps.push(step);
1877
+ if (measurements.wallInflowSteps.length > WALL_INFLOW_WINDOW) measurements.wallInflowSteps.shift();
1878
+ };
1879
+
1880
+ const expectedWallInflowTokens = (): number => measurements.wallInflowSteps.length
1881
+ ? Math.max(...measurements.wallInflowSteps)
1882
+ : 0;
1883
+
1440
1884
  /**
1441
1885
  * The safety margin.
1442
1886
  *
@@ -1478,7 +1922,18 @@ export function registerActiveContext(pi: any, options: {
1478
1922
  * transmission, which is the whole point of having a fence.
1479
1923
  */
1480
1924
  const projectionExceedsBudget = (projected: unknown[], ctx: any): {
1925
+ /** Occupancy: what the provider counted, plus what was appended after it. */
1481
1926
  tokens: number;
1927
+ /**
1928
+ * This projection's OWN size, every byte of it at one rate. Occupancy answers "how
1929
+ * full is the window"; a before-and-after comparison between two projections answers
1930
+ * "did this pass make the request smaller", and those are different questions. A
1931
+ * reduction is measured in size space on both sides, because the projection the pass
1932
+ * rebuilds is a rewrite the anchor does not describe, and subtracting an anchored
1933
+ * reading from an unanchored one reports a change that no folding caused.
1934
+ */
1935
+ sizeTokens: number;
1936
+ basis: ProjectionReadingBasis;
1482
1937
  budgetTokens: number;
1483
1938
  marginTokens: number;
1484
1939
  /** Past the wire: this request must not be transmitted at all. */
@@ -1490,12 +1945,16 @@ export function registerActiveContext(pi: any, options: {
1490
1945
  const budgetTokens = Number.isFinite(capacity.budgetTokens) && capacity.budgetTokens > 0
1491
1946
  ? capacity.budgetTokens
1492
1947
  : Number.POSITIVE_INFINITY;
1493
- const tokens = projectedTokenEstimate(projected);
1948
+ const reading = projectedTokenReading(projected);
1949
+ const tokens = reading.tokens;
1950
+ const sizeTokens = Math.ceil(reading.chars / projectionCharsPerToken());
1494
1951
  const marginTokens = Number.isFinite(budgetTokens)
1495
1952
  ? projectionMarginTokens(tokens, capacity.window)
1496
1953
  : 0;
1497
1954
  return {
1498
1955
  tokens,
1956
+ sizeTokens,
1957
+ basis: reading.basis,
1499
1958
  budgetTokens,
1500
1959
  marginTokens,
1501
1960
  over: tokens > budgetTokens,
@@ -1560,6 +2019,24 @@ export function registerActiveContext(pi: any, options: {
1560
2019
  // is the one built after this one. A provider rejection outranks our own estimate:
1561
2020
  // it is ground truth that the last request did not fit, whatever we measured.
1562
2021
  if (!measured.crowded && !rejected) return { projected, aborted: false };
2022
+ // Crowded is a MARGIN PREDICTION, not an overflow. While a last-call round is
2023
+ // genuinely OPEN, exposure rendered this pass or the agent's response still
2024
+ // outstanding, the prediction waits for it: this request still fits and the
2025
+ // round ends on the very next context pass. Once the round has elapsed the
2026
+ // margin lane acts freely, because a stale exposure parked behind the reopen
2027
+ // latch must never muzzle the one reducer that can still act (the 20k-window
2028
+ // probe climbed from crowded to the wire in exactly that state). Genuine
2029
+ // untransmissibility, an over-budget projection or a provider rejection, never
2030
+ // waits: a request whose projection exceeds the provider input budget is rejected
2031
+ // outright, so recovery must produce a window that fits. Economy does not outrank
2032
+ // the one round.
2033
+ const lastCall = persistence.state?.lastCall;
2034
+ const delivery = curation.lastCallDelivery;
2035
+ const roundOpen = Boolean(lastCall) && (!delivery || delivery.exposure !== lastCall.exposure ||
2036
+ markOrdinal(snapshot) <= delivery.ordinal);
2037
+ if (!measured.over && !rejected && roundOpen) {
2038
+ return { projected, aborted: false };
2039
+ }
1563
2040
  // Why this fired, recorded from the measurement that TRIGGERED it rather than the
1564
2041
  // one taken afterwards, which by then describes a projection that fits.
1565
2042
  const trigger = measured;
@@ -1574,7 +2051,6 @@ export function registerActiveContext(pi: any, options: {
1574
2051
  let action: Record<string, unknown> | null = null;
1575
2052
  try {
1576
2053
  action = await attemptAutomaticRung(snapshot, 1, ctx, "projection-budget", {
1577
- waiveToolCadence: true,
1578
2054
  waiverRatio: 1,
1579
2055
  });
1580
2056
  } catch (error) {
@@ -1589,8 +2065,11 @@ export function registerActiveContext(pi: any, options: {
1589
2065
  }
1590
2066
  if (reducedAtLeastOnce) {
1591
2067
  ladder.overBudgetReduction = {
1592
- estimatedTokensBefore: projectedTokenEstimate(projected),
1593
- estimatedTokensAfter: measured.tokens,
2068
+ // Size space on both sides: what this pass CHANGED, not how full the window is.
2069
+ estimatedTokensBefore: trigger.sizeTokens,
2070
+ estimatedTokensAfter: measured.sizeTokens,
2071
+ occupancyTokensAfter: measured.tokens,
2072
+ occupancyBasis: measured.basis,
1594
2073
  budgetTokens: trigger.budgetTokens,
1595
2074
  marginTokens: trigger.marginTokens,
1596
2075
  estimatorErrorShare: estimatorErrorShare(),
@@ -1603,39 +2082,87 @@ export function registerActiveContext(pi: any, options: {
1603
2082
  };
1604
2083
  }
1605
2084
  if (rejected) {
2085
+ const overflowBefore = trigger.sizeTokens;
2086
+ // RECOVERY IS A CHANGE, NOT AN OPINION.
2087
+ //
2088
+ // This read `!measured.over`: our own estimator answering the question the
2089
+ // provider had just answered differently. A rejection is ground truth that the
2090
+ // estimate was wrong, so the estimate cannot also be the evidence that the retry
2091
+ // will work. And a rejection mutates nothing durable -- the rolled-back branch
2092
+ // rebuilds the same projection -- so a pass that changed nothing hands the
2093
+ // provider a byte-identical request and calls it a rescue. Measured 2026-08-10
2094
+ // (luna-20260810 pifold rep 2): two rejections at stage 25, not one byte folded
2095
+ // either time, both recorded recovered true, and the run died anyway.
2096
+ //
2097
+ // The baseline is the request that was actually REJECTED, which is the last
2098
+ // projection this process built: it is written at the end of every pass that
2099
+ // builds one, and this lane runs before the current pass overwrites it. Measuring
2100
+ // against the projection handed to this function instead would credit only the
2101
+ // recovery loop's own folds and miss the ordinary commit that already ran earlier
2102
+ // in the same pass, which is the usual way a retried request gets smaller.
2103
+ //
2104
+ // Size space on both sides. Occupancy is anchored to the provider's count for the
2105
+ // projection it measured, and the projection this lane rebuilds is a rewrite that
2106
+ // anchor does not describe, so a rejected-minus-retried subtraction across the two
2107
+ // bases reports a change no folding caused.
2108
+ const rejectedTokens = measurements.lastProjectedSizeTokens;
2109
+ const freedTokens = typeof rejectedTokens === "number"
2110
+ ? Math.max(0, rejectedTokens - measured.sizeTokens)
2111
+ : 0;
2112
+ // Smaller than what the provider refused, AND inside the budget. Either half alone
2113
+ // is a claim this runtime has already been wrong about once.
2114
+ const recovered = freedTokens > 0 && !measured.over;
1606
2115
  curation.lastRecovery = {
1607
2116
  status: curation.pendingRejection?.status ?? null,
1608
2117
  attempts,
1609
- estimatedTokensAfter: measured.tokens,
2118
+ estimatedTokensAfter: measured.sizeTokens,
1610
2119
  budgetTokens: measured.budgetTokens,
1611
- recovered: !measured.over,
2120
+ recovered,
1612
2121
  };
1613
2122
  emit("context.recovery", {
1614
2123
  provider_status: curation.pendingRejection?.status ?? null,
1615
2124
  attempts,
1616
2125
  max_attempts: OVERFLOW_RECOVERY_MAX_ATTEMPTS,
1617
- tokens_before: projectedTokenEstimate(projected),
1618
- tokens_after: measured.tokens,
2126
+ tokens_before: overflowBefore,
2127
+ tokens_after: measured.sizeTokens,
2128
+ occupancy_tokens_after: measured.tokens,
2129
+ occupancy_basis: measured.basis,
1619
2130
  budget_tokens: measured.budgetTokens,
1620
2131
  margin_tokens: measured.marginTokens,
1621
- recovered: !measured.over,
2132
+ recovered,
2133
+ // What the verdict rests on, so a false claim cannot be read back as a true one:
2134
+ // the size of the request the provider refused, and how much smaller this one is.
2135
+ rejected_tokens: typeof rejectedTokens === "number" ? rejectedTokens : null,
2136
+ freed_tokens: freedTokens,
2137
+ // Whether the recovery loop itself folded, as distinct from the pass as a whole.
2138
+ loop_reduced: reducedAtLeastOnce,
2139
+ // The join. `context.rollback` records what left the branch; this records what
2140
+ // the retried pass folded to make the shorter window fit, and the two are one
2141
+ // episode. Null when a rejection was recorded without a tree rollback.
2142
+ rollback_seq: typeof rollback.last?.seq === "number" ? rollback.last.seq : null,
1622
2143
  });
1623
- const overflowBefore = projectedTokenEstimate(projected);
2144
+ const unchangedNote = "The provider rejected the last request and this pass made it no smaller, so the " +
2145
+ `rebuilt request is ${measured.tokens} estimated tokens against a ${measured.budgetTokens}-token ` +
2146
+ "serving budget. Our own estimate says it fits; the provider already said otherwise about a request " +
2147
+ "this size, so this is not a recovery. Fold or protect deliberately, or the next request meets the " +
2148
+ "same rejection.";
1624
2149
  deliverReceipt(contextReceipt({
1625
2150
  kind: "overflow-recovery",
1626
2151
  ordinal: markOrdinal(snapshot),
1627
2152
  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,
2153
+ freedTokens,
2154
+ occupancyBefore: typeof rejectedTokens === "number" ? rejectedTokens : overflowBefore,
2155
+ occupancyAfter: measured.sizeTokens,
2156
+ recovered,
1632
2157
  note: measured.over
1633
2158
  ? `The rebuilt request is still ${measured.tokens} tokens against a ${measured.budgetTokens}-token ` +
1634
2159
  "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.",
2160
+ : recovered
2161
+ ? "A rollback was required: the provider rejected the last request, which overfilled the serving " +
2162
+ `budget at ${rejectedTokens} estimated tokens against ${measured.budgetTokens}. This pass ` +
2163
+ `landed it at ${measured.sizeTokens} tokens. Nothing durable was written for it, and the request ` +
2164
+ "was rebuilt inside the budget rather than dropped."
2165
+ : unchangedNote,
1639
2166
  }));
1640
2167
  curation.pendingRejection = null;
1641
2168
  }
@@ -1675,7 +2202,7 @@ export function registerActiveContext(pi: any, options: {
1675
2202
  snapshot,
1676
2203
  state: capturedState,
1677
2204
  generation: capturedGeneration,
1678
- summarize: ladder.failedPreparations.has(id) ? undefined : options.summarizeContextSpan,
2205
+ summarize: ladder.failedPreparations.has(id) ? undefined : observedSummarize,
1679
2206
  onSummarizerFailure: (error) => {
1680
2207
  ladder.lastPreparationError = error instanceof Error ? error.message : String(error);
1681
2208
  ladder.failedPreparations.add(id);
@@ -1745,6 +2272,96 @@ export function registerActiveContext(pi: any, options: {
1745
2272
  return projected;
1746
2273
  };
1747
2274
 
2275
+ /**
2276
+ * The rider rides the SAME admission as the receipt block: it renders from the
2277
+ * persisted literal bytes, lands once per freeze cycle, and the freeze closes over
2278
+ * it, so it can never diverge a prefix on its own. It is never regenerated: the
2279
+ * bytes the epoch persisted are the bytes every re-render carries.
2280
+ */
2281
+ const appendRider = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
2282
+ const rider = persistence.state?.rider;
2283
+ if (!rider) return projected;
2284
+ if (!carrierAdmitted("rider")) return projected;
2285
+ projected.push({
2286
+ role: "custom",
2287
+ customType: riderProjectionType,
2288
+ content: rider.text,
2289
+ display: false,
2290
+ details: {
2291
+ source: activeContextSource(entryTypePrefix),
2292
+ ephemeral: true,
2293
+ riderEpoch: rider.epoch,
2294
+ },
2295
+ timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
2296
+ ? ownValue(snapshot.messages.at(-1), "timestamp")
2297
+ : 0,
2298
+ });
2299
+ return projected;
2300
+ };
2301
+
2302
+ /**
2303
+ * Threshold notices are APPEND-ONCE: each delivered notice is its own carrier with
2304
+ * its own key, so a notice that fires mid-freeze lands at the tail as a pure append,
2305
+ * the freeze closes over it, and it then persists in the window the way a tool
2306
+ * result does. A freeze break re-renders every retained notice from its persisted
2307
+ * literal bytes inside the rewrite that broke the freeze.
2308
+ */
2309
+ const appendNotices = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
2310
+ const notices = persistence.state?.notices;
2311
+ if (!notices?.ring.length) return projected;
2312
+ for (const notice of notices.ring) {
2313
+ if (!carrierAdmitted(`notice-${notice.share}-${notice.ordinal}`)) continue;
2314
+ projected.push({
2315
+ role: "custom",
2316
+ customType: noticeProjectionType,
2317
+ content: notice.text,
2318
+ display: false,
2319
+ details: {
2320
+ source: activeContextSource(entryTypePrefix),
2321
+ ephemeral: true,
2322
+ share: notice.share,
2323
+ noticeOrdinal: notice.ordinal,
2324
+ },
2325
+ timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
2326
+ ? ownValue(snapshot.messages.at(-1), "timestamp")
2327
+ : 0,
2328
+ });
2329
+ }
2330
+ return projected;
2331
+ };
2332
+
2333
+ /**
2334
+ * The pre-commit last-call rides the commit BOUNDARY: it is armed when the band-top
2335
+ * trigger fires, appended at the tail (a pure append) for exactly one gated round,
2336
+ * and the commit that consumes it clears the state, so the carrier's disappearance
2337
+ * rides the rewrite that commit already pays for. Rendering records the delivery,
2338
+ * which is what the one-round clock is measured from.
2339
+ */
2340
+ const appendLastCall = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
2341
+ const lastCall = persistence.state?.lastCall;
2342
+ if (!lastCall) return projected;
2343
+ if (carrierAdmitted(`lastcall-${lastCall.exposure}`)) {
2344
+ projected.push({
2345
+ role: "custom",
2346
+ customType: lastCallProjectionType,
2347
+ content: lastCall.text,
2348
+ display: false,
2349
+ details: {
2350
+ source: activeContextSource(entryTypePrefix),
2351
+ ephemeral: true,
2352
+ exposure: lastCall.exposure,
2353
+ },
2354
+ timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
2355
+ ? ownValue(snapshot.messages.at(-1), "timestamp")
2356
+ : 0,
2357
+ });
2358
+ }
2359
+ if (curation.lastCallDelivery?.exposure !== lastCall.exposure) {
2360
+ curation.lastCallDelivery = { exposure: lastCall.exposure, ordinal: markOrdinal(snapshot) };
2361
+ }
2362
+ return projected;
2363
+ };
2364
+
1748
2365
  /**
1749
2366
  * Whatever this pass ended up sending becomes the frozen surface for the next one, in
1750
2367
  * full. Nothing rides after the frozen region.
@@ -1779,7 +2396,8 @@ export function registerActiveContext(pi: any, options: {
1779
2396
  const customType = ownValue(message, "customType");
1780
2397
  return customType !== milestoneProjectionType && customType !== advisoryProjectionType &&
1781
2398
  customType !== surfacingProjectionType && customType !== receiptProjectionType &&
1782
- customType !== curationProjectionType;
2399
+ customType !== riderProjectionType && customType !== curationProjectionType &&
2400
+ customType !== lastCallProjectionType && customType !== noticeProjectionType;
1783
2401
  });
1784
2402
  // The freeze test, stated as bytes rather than as intent: if the rebuilt body still
1785
2403
  // OPENS with the frozen body verbatim, nothing structural moved and the previous
@@ -1805,6 +2423,9 @@ export function registerActiveContext(pi: any, options: {
1805
2423
  // bought nothing anyway: voluntary fold share was 0.00, and the three runs built
1806
2424
  // specifically to invite curation produced 0, 1 and 0 voluntary folds.
1807
2425
  appendReceipts(projected, snapshot);
2426
+ appendRider(projected, snapshot);
2427
+ appendNotices(projected, snapshot);
2428
+ appendLastCall(projected, snapshot);
1808
2429
  return holdFrozen(projected);
1809
2430
  };
1810
2431
  /**
@@ -1830,14 +2451,20 @@ export function registerActiveContext(pi: any, options: {
1830
2451
  const charsPerToken = projectionCharsPerToken();
1831
2452
  const causes = instrumentation.sinceHandoff.filter((event) =>
1832
2453
  PREFIX_MUTATING_KINDS.has(event.kind));
2454
+ // The reading this projection was judged by, with the pieces it was built from, so
2455
+ // the anchored series is readable from the stream instead of reconstructed.
2456
+ const reading = projectedTokenReading(projected);
1833
2457
  emit("context.projection", {
1834
2458
  change: comparison.change,
1835
2459
  previous_count: comparison.previousCount,
1836
2460
  next_count: comparison.nextCount,
1837
2461
  appended_count: comparison.appendedCount,
1838
2462
  first_divergent_index: comparison.firstDivergentIndex,
1839
- chars: bytes(projected),
1840
- estimated_tokens: projectedTokenEstimate(projected),
2463
+ chars: reading.chars,
2464
+ estimated_tokens: reading.tokens,
2465
+ estimate_basis: reading.basis,
2466
+ anchor_tokens: reading.anchorTokens,
2467
+ delta_chars: reading.deltaChars,
1841
2468
  chars_per_token: charsPerToken,
1842
2469
  });
1843
2470
  // Emission only. Which of these is a provider-side miss and which is a rewrite we
@@ -1870,15 +2497,354 @@ export function registerActiveContext(pi: any, options: {
1870
2497
  instrumentation.mutationsSinceHandoff = 0;
1871
2498
  };
1872
2499
 
2500
+ /**
2501
+ * Enqueue the folds this commit created with a deterministic brief.
2502
+ *
2503
+ * The request is built HERE, against the snapshot and the pre-commit state, because
2504
+ * this is the last moment the fold's source is still exact active evidence: after the
2505
+ * commit its span is a placeholder and the orientation either side of it is gone.
2506
+ * These are the bytes `prepareFold` already hashed into the fold's identity, so the
2507
+ * generator briefs the span that was folded and nothing else.
2508
+ *
2509
+ * A PARENT is queued too, and reads its children at depth one rather than through their
2510
+ * briefs: `consolidationSourceText` opens each child and leaves the grandchildren folded,
2511
+ * so the group is briefed from material instead of from a summary of summaries. The one
2512
+ * thing it must not race is a child's own upgrade, so a parent whose children are still
2513
+ * in this lane is deferred to the next boundary instead of reading briefs about to be
2514
+ * replaced. Unlike a leaf, a parent's source keeps: its children are folds, and folds
2515
+ * stay resolvable after the commit that nested them.
2516
+ */
2517
+ const queueBriefUpgrades = (
2518
+ snapshot: ActiveContextSnapshot,
2519
+ stateBeforeCommit: ActiveContextState,
2520
+ foldIds: readonly string[],
2521
+ ): void => {
2522
+ if (!observedSummarize || !persistence.state) return;
2523
+ const queued = (id: string): boolean =>
2524
+ upgrades.queue.some((entry) => entry.members.some((member) => member.foldId === id));
2525
+ const pending = (id: string): boolean => upgrades.running.has(id) || queued(id) ||
2526
+ upgrades.ready.some((entry) => entry.foldId === id) ||
2527
+ upgrades.deferred.has(id);
2528
+ // Gathered before any packing, because a batch is a property of the whole commit and
2529
+ // not of the fold that happens to be read first.
2530
+ const gathered: Array<{
2531
+ foldId: string;
2532
+ sourceSha256: string;
2533
+ refs: EvidenceRef[];
2534
+ span: Record<string, unknown>;
2535
+ chars: number;
2536
+ parent: boolean;
2537
+ }> = [];
2538
+ // Parents that waited are considered first and at every boundary, so the wait ends as
2539
+ // soon as the children settle rather than whenever the ladder happens to build another.
2540
+ for (const foldId of [...upgrades.deferred, ...foldIds]) {
2541
+ const full = upgrades.queue.length >= MAX_BRIEF_UPGRADE_QUEUE;
2542
+ if (upgrades.failed.has(foldId) || upgrades.running.has(foldId) || queued(foldId) ||
2543
+ upgrades.ready.some((entry) => entry.foldId === foldId)) continue;
2544
+ const fold = persistence.state.folds.find((item) => item.id === foldId);
2545
+ // A supplied brief is agent judgment and outranks the generator; a model brief is
2546
+ // already what this lane exists to produce. A fold that is no longer there is no
2547
+ // longer owed anything, so a rolled-back parent leaves the wait rather than sitting
2548
+ // in it.
2549
+ if (!fold || foldProvenance(fold, persistence.state).kind !== "deterministic") {
2550
+ upgrades.deferred.delete(foldId);
2551
+ continue;
2552
+ }
2553
+ const parent = fold.parts.some((part) => part.kind === "fold");
2554
+ // A parent waits rather than being shed: its children are folds, so its source is
2555
+ // still there whenever the lane has room, and a group that loses its turn at a busy
2556
+ // boundary would otherwise keep the index its children could not write. A leaf is the
2557
+ // other way round. Its source is exact active evidence at this commit and a
2558
+ // placeholder after it, so a full queue drops it here and the receipt says so.
2559
+ if (parent && (full || childFoldIds(fold).some(pending))) {
2560
+ upgrades.deferred.add(foldId);
2561
+ continue;
2562
+ }
2563
+ if (full) continue;
2564
+ upgrades.deferred.delete(foldId);
2565
+ const refs = parent
2566
+ ? flattenFoldRefs(fold, persistence.state)
2567
+ : fold.parts.every((part) => part.kind === "raw")
2568
+ ? fold.parts.map((part) => (part as { kind: "raw"; ref: EvidenceRef }).ref)
2569
+ : null;
2570
+ if (!refs) continue;
2571
+ try {
2572
+ // The parent reads the state that holds its children and their upgraded briefs;
2573
+ // a leaf reads the state where its own span was still raw active evidence.
2574
+ const sourceText = parent
2575
+ ? consolidationSourceText(snapshot, persistence.state, fold.parts)
2576
+ : encodedFoldSource(snapshot, stateBeforeCommit, fold.parts, fold.kind);
2577
+ const chars = bytes(sourceText);
2578
+ if (chars > snapshot.policy.maxSourceChars) continue;
2579
+ gathered.push({
2580
+ foldId,
2581
+ sourceSha256: fold.sourceSha256,
2582
+ refs,
2583
+ chars,
2584
+ parent,
2585
+ span: {
2586
+ candidateId: fold.id,
2587
+ sourceRefs: clone(refs),
2588
+ sourceText,
2589
+ sourceSha256: sha256Text(sourceText),
2590
+ // What the PAYLOAD holds, which is one entry per part: a consolidation can
2591
+ // carry absorbed raw evidence beside its folded children, and the coverage
2592
+ // rule counts the things the model is actually shown. Counting only the fold
2593
+ // children told it to write fewer clauses than the payload has subjects.
2594
+ children: parent ? fold.parts.length : 0,
2595
+ },
2596
+ });
2597
+ } catch (error) {
2598
+ // Evidence drift between the commit and this read leaves the deterministic brief
2599
+ // standing, and the next commit record reports the attempt rather than hiding it.
2600
+ upgrades.failed.add(foldId);
2601
+ upgrades.failures += 1;
2602
+ upgrades.lastError = boundReceiptText(
2603
+ error instanceof Error ? error.message : String(error), 240, "brief upgrade",
2604
+ );
2605
+ }
2606
+ }
2607
+ // Pack the commit's spans into as few calls as the source budget allows. Order is the
2608
+ // order they were gathered, which is span order within the commit, so the numbered
2609
+ // spans a batch shows the generator run the way the session did and one really is the
2610
+ // orientation for the next.
2611
+ for (let at = 0; at < gathered.length;) {
2612
+ if (upgrades.queue.length >= MAX_BRIEF_UPGRADE_QUEUE) {
2613
+ // No room for another call. A parent goes back to waiting, because its children are
2614
+ // folds and its source will still be there whenever the lane frees up. A leaf
2615
+ // cannot wait: its source is exact active evidence at THIS commit and a placeholder
2616
+ // after it, so it keeps the deterministic brief it committed with.
2617
+ for (const item of gathered.slice(at)) {
2618
+ if (item.parent) upgrades.deferred.add(item.foldId);
2619
+ }
2620
+ break;
2621
+ }
2622
+ const members: typeof gathered = [];
2623
+ let chars = 0;
2624
+ while (at < gathered.length && members.length < MAX_BRIEF_BATCH_SPANS &&
2625
+ (!members.length || chars + gathered[at].chars <= snapshot.policy.maxSourceChars)) {
2626
+ chars += gathered[at].chars;
2627
+ members.push(gathered[at]);
2628
+ at += 1;
2629
+ }
2630
+ // Orientation is the run's, not each span's: where this batch of consecutive spans
2631
+ // sits in the wider conversation. Taken from the ends rather than from the union,
2632
+ // because a commit can fold spans with protected material still raw between them and
2633
+ // the union of those refs is not one contiguous range.
2634
+ const before = boundedOrientation(snapshot, members[0].refs);
2635
+ const after = boundedOrientation(snapshot, members[members.length - 1].refs);
2636
+ upgrades.queue.push({
2637
+ members: members.map((item) => ({ foldId: item.foldId, sourceSha256: item.sourceSha256 })),
2638
+ request: {
2639
+ spans: members.map((item) => item.span),
2640
+ beforeRefs: clone(before.beforeRefs),
2641
+ beforeText: before.beforeText,
2642
+ beforeSha256: sha256Text(before.beforeText),
2643
+ afterRefs: clone(after.afterRefs),
2644
+ afterText: after.afterText,
2645
+ afterSha256: sha256Text(after.afterText),
2646
+ maxBriefChars: snapshot.policy.maxBriefChars,
2647
+ // Read by the observed wrapper and by nothing else, so a queue that backs up is
2648
+ // visible as waiting rather than as a slow generator. The summarizer contract is
2649
+ // on the RESULT, so an extra request field reaches it harmlessly.
2650
+ queuedAtMs: Date.now(),
2651
+ },
2652
+ });
2653
+ }
2654
+ };
2655
+
2656
+ /**
2657
+ * One generator call, between boundaries, cancel-safe. It writes nothing durable: a
2658
+ * finished brief waits in `ready` for a commit to carry it, and an aborted or failed
2659
+ * call leaves the fold exactly as it committed.
2660
+ *
2661
+ * Returns whether a call was started, which is what lets the drain below stop.
2662
+ */
2663
+ const startBriefUpgrade = (snapshot: ActiveContextSnapshot, ctx: any): boolean => {
2664
+ const summarize = observedSummarize;
2665
+ // In CALLS, not folds. `running` is keyed by every fold a call briefs so the queue can
2666
+ // ask whether one is already being written, so its size is a fold count and reading it
2667
+ // as the in-flight bound would stop the lane after a single batch.
2668
+ const inFlight = new Set(upgrades.running.values()).size;
2669
+ if (!summarize || lifecycle.shuttingDown || !upgrades.queue.length ||
2670
+ inFlight >= MAX_BRIEF_UPGRADES_IN_FLIGHT) return false;
2671
+ const entry = upgrades.queue.shift()!;
2672
+ const controller = new AbortController();
2673
+ const slot = { controller, promise: Promise.resolve() };
2674
+ // Keyed by EVERY fold the call briefs, so the queue's "already being briefed" check and
2675
+ // the in-flight bound both read one call as the one thing it is.
2676
+ for (const member of entry.members) upgrades.running.set(member.foldId, slot);
2677
+ const capturedSessionId = snapshot.sessionId;
2678
+ const capturedGeneration = lifecycle.generation;
2679
+ let timeout: ReturnType<typeof setTimeout> | undefined;
2680
+ slot.promise = (async () => {
2681
+ const timed = new Promise<never>((_resolve, reject) => {
2682
+ timeout = setTimeout(() => {
2683
+ controller.abort();
2684
+ reject(new Error(`Brief upgrade exceeded ${snapshot.policy.briefTimeoutMs}ms`));
2685
+ }, snapshot.policy.briefTimeoutMs);
2686
+ });
2687
+ // The same contract `prepareFold` holds a generated brief to, through the same
2688
+ // function: an upgrade with its own copy of the check would drift from the fold
2689
+ // path's, and the cure a missed criterion earns belongs to both or to neither.
2690
+ const generated = await Promise.race([
2691
+ generatedBriefs({
2692
+ summarize: (request, callCtx) => summarize({ ...request, signal: controller.signal }, callCtx),
2693
+ request: entry.request,
2694
+ spans: entry.request.spans as Array<Record<string, unknown>>,
2695
+ ctx,
2696
+ maxBriefChars: snapshot.policy.maxBriefChars,
2697
+ toolName: snapshot.toolName,
2698
+ }),
2699
+ timed,
2700
+ ]);
2701
+ if (controller.signal.aborted ||
2702
+ !sessionIdentityStillValid(ctx, capturedSessionId, capturedGeneration)) return;
2703
+ // Per span, not per call. A batch is one request and many answers, so one span that
2704
+ // could not be cured is one fold keeping its deterministic brief while its neighbours
2705
+ // take theirs; the whole call failing on it would be the coverage problem batching
2706
+ // exists to solve, wearing a different hat.
2707
+ entry.members.forEach((member, at) => {
2708
+ const written = generated.briefs[at];
2709
+ if (!written) {
2710
+ upgrades.failed.add(member.foldId);
2711
+ upgrades.failures += 1;
2712
+ upgrades.lastError = boundReceiptText(
2713
+ `span ${at + 1} of ${entry.members.length}: ${generated.complaints[0] ?? "no brief"}`,
2714
+ 240, "brief upgrade",
2715
+ );
2716
+ return;
2717
+ }
2718
+ upgrades.ready.push({
2719
+ foldId: member.foldId,
2720
+ sourceSha256: member.sourceSha256,
2721
+ brief: written.brief,
2722
+ provenance: written.provenance,
2723
+ });
2724
+ });
2725
+ upgrades.cures += generated.cured;
2726
+ })().catch((error) => {
2727
+ if (controller.signal.aborted) return;
2728
+ // LOUD, and once, for every fold the call carried. They keep their deterministic
2729
+ // briefs, the failure is counted onto the next commit record, and none of them is
2730
+ // ever sent again: a generator that fails on a span fails on it again, and a retry
2731
+ // loop would spend the session's provider calls proving that.
2732
+ for (const member of entry.members) upgrades.failed.add(member.foldId);
2733
+ upgrades.failures += 1;
2734
+ upgrades.lastError = boundReceiptText(
2735
+ error instanceof Error ? error.message : String(error), 240, "brief upgrade",
2736
+ );
2737
+ }).finally(() => {
2738
+ clearTimeout(timeout);
2739
+ for (const member of entry.members) {
2740
+ if (upgrades.running.get(member.foldId) === slot) upgrades.running.delete(member.foldId);
2741
+ }
2742
+ // A finished call starts the next one, so the drain rate is the generator's latency
2743
+ // rather than however often a ladder pass happens to look. Still between
2744
+ // boundaries: nothing here touches the projection, and the generation check is the
2745
+ // same fence `cancelBriefUpgrades` is called behind, so a session that moved on
2746
+ // cannot be restarted by a call that outlived it.
2747
+ if (!lifecycle.shuttingDown && lifecycle.generation === capturedGeneration) {
2748
+ resumeBriefUpgrades(snapshot, ctx);
2749
+ }
2750
+ });
2751
+ return true;
2752
+ };
2753
+
2754
+ /**
2755
+ * Fill the in-flight slots from the queue. The bound is a constant, not a pace: the
2756
+ * lane starts everything it is allowed to start the moment there is work, because a
2757
+ * fold whose brief is written late is a fold the session read deterministic in the
2758
+ * meantime.
2759
+ */
2760
+ const startBriefUpgrades = (snapshot: ActiveContextSnapshot, ctx: any): void => {
2761
+ while (startBriefUpgrade(snapshot, ctx)) { /* Bounded by the in-flight constant. */ }
2762
+ };
2763
+
2764
+ /**
2765
+ * Take up the waiting parents, then fill the slots.
2766
+ *
2767
+ * A deferred parent was waiting on the lane, so the lane finishing is the news, not the
2768
+ * next commit. Boundaries are where briefs APPLY; hanging the retry on them too would
2769
+ * leave a session that stops folding holding parents whose children were briefed long
2770
+ * ago, and the last group the ladder built is exactly the one a session ends beside.
2771
+ */
2772
+ const resumeBriefUpgrades = (snapshot: ActiveContextSnapshot, ctx: any): void => {
2773
+ if (upgrades.deferred.size && persistence.state) {
2774
+ queueBriefUpgrades(snapshot, persistence.state, []);
2775
+ }
2776
+ startBriefUpgrades(snapshot, ctx);
2777
+ };
2778
+
2779
+ const cancelBriefUpgrades = (): void => {
2780
+ for (const slot of upgrades.running.values()) slot.controller.abort();
2781
+ upgrades.running.clear();
2782
+ upgrades.queue = [];
2783
+ upgrades.ready = [];
2784
+ upgrades.deferred.clear();
2785
+ };
2786
+
2787
+ /**
2788
+ * Apply finished upgrades INSIDE the commit's own mutation.
2789
+ *
2790
+ * Called between `commitPendingMarks` and the byte accounting, so an upgraded brief is
2791
+ * part of what this commit is measured to have done rather than a rewrite nobody
2792
+ * charged for. Nothing else may call this: an upgrade that finishes between boundaries
2793
+ * waits, however long the wait is.
2794
+ *
2795
+ * Identity is revalidated the way `preparedFoldError` revalidates a prepared fold. A
2796
+ * fold that was expanded, reclaimed, re-briefed by the agent, or rebuilt over a
2797
+ * different span is a different object than the one the generator read, and its upgrade
2798
+ * is DROPPED rather than deferred.
2799
+ *
2800
+ * It lands in the brief OVERRIDE map, the same channel the agent's own `rebrief` uses,
2801
+ * and carries its generator beside the text. A fold record is content-addressed and
2802
+ * immutable: rewriting one in place reports a conflicting durable fold at the next
2803
+ * persist and suspends automatic management, which is precisely why the override map
2804
+ * exists.
2805
+ *
2806
+ * EVERY finished brief lands, and there is no per-boundary cap. A prefix cache is
2807
+ * invalidated from the FIRST byte that differs, so the commit's mutation point is the
2808
+ * position of the earliest fold it upgrades and every later upgrade in the same commit
2809
+ * sits inside a suffix that break already re-sent: the second and third upgrade on one
2810
+ * boundary cost nothing the first did not already cost. Holding one back does not save
2811
+ * that cost, it defers the same break to a later commit and pays it there on top of
2812
+ * whatever that commit does. What bounds the count is the work that can be outstanding,
2813
+ * MAX_BRIEF_UPGRADE_QUEUE waiting plus MAX_BRIEF_UPGRADES_IN_FLIGHT running, and the
2814
+ * boundary that clears them.
2815
+ */
2816
+ const applyBriefUpgrades = (): string[] => {
2817
+ if (!upgrades.ready.length || !persistence.state) return [];
2818
+ const applied: string[] = [];
2819
+ for (const entry of upgrades.ready) {
2820
+ const state = persistence.state;
2821
+ const fold = state.folds.find((item) => item.id === entry.foldId);
2822
+ if (!fold || fold.sourceSha256 !== entry.sourceSha256 ||
2823
+ foldProvenance(fold, state).kind !== "deterministic" ||
2824
+ state.expanded.includes(fold.id) || state.briefs?.[fold.id] !== undefined) continue;
2825
+ persistence.state = {
2826
+ ...state,
2827
+ briefs: {
2828
+ ...(state.briefs ?? {}),
2829
+ [fold.id]: { brief: entry.brief, provenance: clone(entry.provenance) },
2830
+ },
2831
+ };
2832
+ applied.push(fold.id);
2833
+ }
2834
+ upgrades.ready = [];
2835
+ return applied;
2836
+ };
2837
+
1873
2838
  const commitDeterministicCandidate = async (
1874
2839
  snapshot: ActiveContextSnapshot,
1875
2840
  candidate: FoldCandidate,
1876
2841
  brief: string,
1877
2842
  ): Promise<string> => {
2843
+ const stateBeforeCommit = persistence.state!;
1878
2844
  const preparedFold = await prepareFold({
1879
2845
  candidate,
1880
2846
  snapshot,
1881
- state: persistence.state!,
2847
+ state: stateBeforeCommit,
1882
2848
  generation: lifecycle.generation,
1883
2849
  brief,
1884
2850
  briefProvenance: "deterministic",
@@ -1886,9 +2852,10 @@ export function registerActiveContext(pi: any, options: {
1886
2852
  persistence.state = commitPreparedFold({
1887
2853
  prepared: preparedFold,
1888
2854
  snapshot,
1889
- state: persistence.state!,
2855
+ state: stateBeforeCommit,
1890
2856
  generation: lifecycle.generation,
1891
2857
  });
2858
+ queueBriefUpgrades(snapshot, stateBeforeCommit, [preparedFold.id]);
1892
2859
  return preparedFold.id;
1893
2860
  };
1894
2861
 
@@ -1913,7 +2880,7 @@ export function registerActiveContext(pi: any, options: {
1913
2880
  state: baseState,
1914
2881
  generation: generationAtStart,
1915
2882
  brief: input.brief,
1916
- summarize: options.summarizeContextSpan,
2883
+ summarize: observedSummarize,
1917
2884
  ctx: input.ctx,
1918
2885
  signal: input.signal,
1919
2886
  });
@@ -1952,36 +2919,77 @@ export function registerActiveContext(pi: any, options: {
1952
2919
  return Math.max(COMMIT_RECLAIM_FLOOR_SHARE, inflow > 0 ? inflow / window : 0);
1953
2920
  };
1954
2921
 
1955
- const atOrAboveBackstop = (snapshot: ActiveContextSnapshot, ratio: number | null): boolean =>
1956
- typeof ratio === "number" && Number.isFinite(ratio) && ratio >= snapshot.policy.refoldRatio;
2922
+ const atOrAboveBackstop = (snapshot: ActiveContextSnapshot, ratio: number | null): boolean => {
2923
+ const occupancy = budgetOccupancy(snapshot, ratio);
2924
+ return occupancy !== null && occupancy >= snapshot.policy.refoldRatio;
2925
+ };
1957
2926
 
1958
2927
  /**
1959
- * A snapshot whose FRESH tail is narrowed.
2928
+ * THE OUTPUT WALL.
2929
+ *
2930
+ * A declared serving budget has already netted out the reservation the answer needs,
2931
+ * so the runtime treats the whole budget as spendable and fences only when it is
2932
+ * spent. That fence is the input one, and the input is not what fails first. The host
2933
+ * derives the answer's ceiling per request as its DESCRIPTOR window less its own
2934
+ * estimate of the projection less a fixed safety margin, so filling the declared
2935
+ * budget to the brim leaves the answer exactly the reservation and nothing else.
1960
2936
  *
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.
2937
+ * Which is a fence that fires after the wire, the same defect the transmission fence
2938
+ * was given a forward look to fix in rep 13. The commit trigger never got one.
2939
+ * Measured 2026-08-12 (sol-20260812 rep 1): occupancy walked 218,713 -> 236,595 over
2940
+ * twelve ordinals against a 251,520 budget with no commit, and the request built after
2941
+ * the last measurement was served 16 output tokens and aborted. Every number was known
2942
+ * to the runtime; nothing was weighing the request that had not been built yet.
1967
2943
  *
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.
2944
+ * So the trigger carries what the transmission fence already carries: one worst recent
2945
+ * inflow step. With no measured inflow this is exactly the old `used > budget`
2946
+ * condition, which is the state it degrades to on a session's first turns.
2947
+ */
2948
+ const outputWallDue = (snapshot: ActiveContextSnapshot): boolean => {
2949
+ const capacity = servingCapacity(snapshot.contextWindow);
2950
+ if (capacity.usedTokens === null || !(capacity.budgetTokens > 0)) return false;
2951
+ // THE EARLY ZONE ONLY, so this is strictly additive.
2952
+ //
2953
+ // Past the budget the pre-existing overflow paths already own the emergency: the
2954
+ // commit-path exemption, the last-call gate and the fence all read `used > budget`
2955
+ // and have since before this existed. A second trigger in that territory changes the
2956
+ // cadence of sessions that were already being handled, and gate 107 measured what
2957
+ // that costs: a fixture parked at 88,000 against an 83,616 budget put four generator
2958
+ // calls in flight against a bound of two. So the wall speaks only where nothing spoke
2959
+ // before, in the band one inflow step wide below the budget.
2960
+ if (capacity.usedTokens > capacity.budgetTokens) return false;
2961
+ return capacity.usedTokens + expectedWallInflowTokens() > capacity.budgetTokens;
2962
+ };
2963
+
2964
+ /**
2965
+ * What the gated round did, measured against the arming snapshot. Pins and unpins
2966
+ * are read from the stream itself (context.protect records after the exposure); the
2967
+ * call and mark deltas are clamped at zero because contextCalls is process-local and
2968
+ * a reload inside a round must not report a negative response.
1970
2969
  */
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),
2970
+ const lastCallAttribution = (
2971
+ lastCall: NonNullable<ActiveContextState["lastCall"]>,
2972
+ agentMarksNow: number,
2973
+ ): { responded: boolean; context_calls: number; marks_added: number; protects: number; unprotects: number } => {
2974
+ const contextCalls = Math.max(0, curation.contextCalls - lastCall.contextCalls);
2975
+ const protectRecords = instrumentation.ledger.events.filter((record) =>
2976
+ record.seq > lastCall.exposure && record.kind === "context.protect");
2977
+ const protects = protectRecords.filter((record) => record.protect === true).length;
2978
+ return {
2979
+ responded: contextCalls > 0,
2980
+ context_calls: contextCalls,
2981
+ marks_added: Math.max(0, agentMarksNow - lastCall.agentMarks),
2982
+ protects,
2983
+ unprotects: protectRecords.length - protects,
1979
2984
  };
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 };
2985
+ };
2986
+
2987
+ const clearLastCall = (): void => {
2988
+ if (!persistence.state?.lastCall) return;
2989
+ const next = { ...persistence.state };
2990
+ delete next.lastCall;
2991
+ persistence.state = next;
2992
+ curation.lastCallDelivery = null;
1985
2993
  };
1986
2994
 
1987
2995
  const runCommitEpoch = async (
@@ -1993,44 +3001,85 @@ export function registerActiveContext(pi: any, options: {
1993
3001
  * over-budget reduction forces the fence so nothing survivable is held back.
1994
3002
  */
1995
3003
  waiverRatio: number | null = measurements.latestRatio,
3004
+ /**
3005
+ * A user asked for this commit. It outranks the two ECONOMIC guards -- the reclaim
3006
+ * floor and the one-mutation-per-handoff budget -- because those exist to spend the
3007
+ * session's rewrites well and the user just said where to spend one. It outranks
3008
+ * nothing else: freshness, pins, exactness and the provider fence are unchanged.
3009
+ */
3010
+ userRequested = false,
1996
3011
  ): Promise<Record<string, unknown> | null> => {
1997
3012
  const ordinal = markOrdinal(snapshot);
1998
3013
  let state = persistence.state!;
1999
3014
  let peekAdded = 0;
2000
3015
  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;
3016
+ let consolidationAdded = 0;
3017
+ let closingAdded = 0;
2007
3018
  for (const mark of ephemeralPeekMarks({ snapshot, state, ordinal })) {
2008
3019
  const addition = addPendingMark(state, mark);
2009
3020
  if (addition.added) { state = addition.state; peekAdded += 1; }
2010
3021
  }
3022
+ // THE COUNT LAW RUNS AT EVERY COMMIT EPOCH, BEFORE ANYTHING ELSE PROPOSES.
3023
+ //
3024
+ // The number of parents the eligible-root count owes is arithmetic, not a judgment
3025
+ // about pressure, so it is not the top-up's to make and not the user commit's to
3026
+ // skip: at any point the count allows k groups, k parents form. Running first is
3027
+ // what makes the rest coherent -- the groups claim their children before the top-up
3028
+ // reads the claim set, so nothing proposes a span over a fold a parent is taking.
3029
+ for (const mark of consolidationMarks({ snapshot, state, ordinal })) {
3030
+ const addition = addPendingMark(state, mark);
3031
+ if (addition.added) { state = addition.state; consolidationAdded += 1; }
3032
+ }
2011
3033
  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);
3034
+ // THE GUARD IS ADJUDICATED AT COMMIT, NEVER AT PROPOSAL.
3035
+ //
3036
+ // `guarded` is every tool result since the last terminal assistant message. When no
3037
+ // turn has ever closed, `currentTurnBoundary` is -1 and that set is the WHOLE
3038
+ // window. Handing it to `topUpMarks` as `excludeRefKeys` therefore proposed nothing
3039
+ // at all on that shape, so `guardWaiverCount` below counted zero guarded marks and
3040
+ // the waiver it exists to grant could never fire. Measured 2026-08-10
3041
+ // (luna-20260810 pifold rep 2): 274,173 tokens of unmarked stale spans, zero marks
3042
+ // proposed, zero commits, two provider rejections.
3043
+ //
3044
+ // So the top-up proposes freely and `commitPendingMarks({ guardCurrentTurn: true,
3045
+ // guardWaiver })` decides. That is the only place the guard has ever had a waiver,
3046
+ // and one adjudicator is why the starvation case is reachable at all.
3047
+ //
3048
+ // Wedge absorption keeps the exclusion (below): it grows a mark backward over a
3049
+ // gap without any waiver of its own, so the guard there has no second chance.
3050
+ // THE CLASS LAW IS UNCONDITIONAL.
3051
+ //
3052
+ // There was a fence-only snapshot here: at high occupancy it narrowed the fresh
3053
+ // tail to a quarter and extended the automatic reach over the middle, so a reduction
3054
+ // that had to make a rejected request sendable had mass to reach. That existed
3055
+ // because the runtime had exactly one answer to a provider rejection, folding
3056
+ // harder, and folding harder inside a positional region runs out of legal material.
3057
+ // The rollback lane is the answer now: an overflow rolls the leaf back past the
3058
+ // request that failed and the ordinary commit runs on the shorter window. So the
3059
+ // law holds in EVERY snapshot at every occupancy -- membership decides what folds,
3060
+ // the fresh tail never folds, pins are exempt -- and there is one set of rules to
3061
+ // reason about instead of two.
2018
3062
  // The thermostat. Firing at the trigger line and folding down to the target line is
2019
3063
  // what makes event SPACING structural rather than hoped for.
2020
3064
  const capacity = servingCapacity(snapshot.contextWindow);
2021
3065
  const usedTokens = capacity.usedTokens;
2022
3066
  const budgetTokens = capacity.budgetTokens;
2023
- // THE SAFETY EXEMPTION, NARROWED.
3067
+ // THE UNTRANSMISSIBILITY EXEMPTION, NARROWED.
2024
3068
  //
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
3069
+ // A request whose projection exceeds the provider input budget is rejected outright,
3070
+ // so recovery must produce a window that fits. The hard fence, though, is a RATIO
3071
+ // prediction. Standing near it is not the same as being unable to send, and treating
3072
+ // the two alike handed the fence path a standing waiver from both economy guards. Measured 2026-08-07 (rep 17): fence-path
2028
3073
  // window-pressure commits fired at eligible-freed shares as low as 0.018, under the
2029
3074
  // 0.02 floor the guided path honors, and re-fired inside a single ordinal. So only
2030
3075
  // two states outrank the economy: the overflow recovery lane, which runs because a
2031
3076
  // request already did not fit, and an occupancy genuinely past the serving budget,
2032
3077
  // where the next request aborts unless something moves. Everything else defers.
2033
- const overflowExempt = curation.recoveryAttempts > 0 ||
3078
+ // The output wall deliberately does NOT join this list. It decides WHEN the one
3079
+ // structural mutation of a handoff fires, not how many fire: exempting it here let a
3080
+ // window parked one inflow step under the budget rebuild its prefix on every pass,
3081
+ // which is the cost the mutation budget exists to refuse and what gate 58 caught.
3082
+ const overflowExempt = userRequested || curation.recoveryAttempts > 0 ||
2034
3083
  (usedTokens !== null && budgetTokens > 0 && usedTokens > budgetTokens);
2035
3084
  // ONE STRUCTURAL MUTATION PER HANDOFF.
2036
3085
  //
@@ -2040,8 +3089,9 @@ export function registerActiveContext(pi: any, options: {
2040
3089
  // still CROWDED (inside the fence margin, but transmissible), and committed again.
2041
3090
  // The second commit bought margin the next pass would have bought anyway and cost a
2042
3091
  // 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.
3092
+ // the request is genuinely untransmittable: a request whose projection exceeds the
3093
+ // provider input budget is rejected outright, so recovery must produce a window that
3094
+ // fits, and the fence and the recovery lane spend the budget to get one.
2045
3095
  if (instrumentation.mutationsSinceHandoff > 0 && !overflowExempt) {
2046
3096
  emit("context.commit", {
2047
3097
  trigger,
@@ -2053,11 +3103,16 @@ export function registerActiveContext(pi: any, options: {
2053
3103
  });
2054
3104
  return null;
2055
3105
  }
2056
- const hysteresisShare = usedTokens === null || snapshot.contextWindow <= 0
3106
+ // THE HYSTERESIS, AND THE WHOLE FREEING TARGET.
3107
+ //
3108
+ // How deep this event cuts is one subtraction on one denominator: what is used,
3109
+ // less where the thermostat wants to land, over the serving budget. The separate
3110
+ // 0.40 floor is gone -- it was a share of the WINDOW under a share of the BUDGET,
3111
+ // so it never once bound (the hysteresis share at these thresholds bottoms out at
3112
+ // 0.405 of window), and a floor that cannot fire is a number that only drifts.
3113
+ const freeingTarget = usedTokens === null || budgetTokens <= 0
2057
3114
  ? 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);
3115
+ : Math.max(0, (usedTokens - thresholds.minTarget * budgetTokens) / budgetTokens);
2061
3116
  if (topUp) {
2062
3117
  // Measuring top-up progress against ELIGIBLE mass is what keeps the pressure
2063
3118
  // backstop working under a peek-heavy agent: pinned and retained marks are mass
@@ -2066,41 +3121,35 @@ export function registerActiveContext(pi: any, options: {
2066
3121
  snapshot,
2067
3122
  state,
2068
3123
  ordinal,
2069
- excludeRefKeys: fenceLevel ? new Set<string>() : guarded,
2070
3124
  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,
3125
+ targetShare: freeingTarget,
2074
3126
  })) {
2075
3127
  const addition = addPendingMark(state, mark);
2076
3128
  if (addition.added) { state = addition.state; topUpAdded += 1; }
2077
3129
  }
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);
3130
+ // DEPTH, inside the zones.
3131
+ //
3132
+ // Near the top of the window a commit that frees less than one turn of inflow
3133
+ // does not reduce anything: it pays a full prefix rewrite, gives back less than
3134
+ // the next stage adds, and the window ratchets UP through commit after commit.
3135
+ // Measured 2026-08-06 (rep13): once the big stale mass was folded, six commits of
3136
+ // two to four folds each carried the window from 340k to 370k and into a provider
3137
+ // rejection, with the backstop firing the whole way. So a commit at or above the
3138
+ // backstop that has not reached one inflow step tops up against everything
3139
+ // ELIGIBLE instead of billing a rewrite for crumbs. Eligible is the operative
3140
+ // word: this reaches harder inside what the class law admits, never outside it.
3141
+ const reachedShare = markAccounting(snapshot, state).eligibleFreedBudgetShare;
3142
+ const shallow = reachedShare < Math.max(commitDepthFloorShare(snapshot), freeingTarget);
3143
+ if (shallow && atOrAboveBackstop(snapshot, waiverRatio)) {
2094
3144
  for (const mark of topUpMarks({
2095
- snapshot: commitSnapshot,
3145
+ snapshot,
2096
3146
  state,
2097
3147
  ordinal,
2098
- excludeRefKeys: fenceLevel ? new Set<string>() : guarded,
2099
3148
  eligibleOnly: true,
2100
3149
  targetShare: 1,
2101
3150
  })) {
2102
3151
  const addition = addPendingMark(state, mark);
2103
- if (addition.added) { state = addition.state; topUpAdded += 1; deepenedMarks += 1; }
3152
+ if (addition.added) { state = addition.state; topUpAdded += 1; }
2104
3153
  }
2105
3154
  }
2106
3155
  }
@@ -2111,18 +3160,44 @@ export function registerActiveContext(pi: any, options: {
2111
3160
  snapshot,
2112
3161
  state,
2113
3162
  charsPerToken: projectionCharsPerToken(),
2114
- excludeRefKeys: fenceLevel ? new Set<string>() : guarded,
3163
+ excludeRefKeys: guarded,
2115
3164
  });
2116
3165
  state = wedges.state;
2117
3166
  const accounting = markAccounting(snapshot, state);
2118
- if (!accounting.pending) return null;
3167
+ // A COMMIT PASS THAT FINDS NOTHING SAYS SO.
3168
+ //
3169
+ // This was a bare `return null`, and it is how the rep-2 starvation stayed invisible
3170
+ // for 25 stages: the last call announced 274,173 tokens of unmarked stale spans, the
3171
+ // pass that answered it proposed nothing, and the stream carried no record that a
3172
+ // commit had even been attempted. Every other deferral on this path already names
3173
+ // itself; this one is the same shape with the reason that matters, and it reports
3174
+ // the remainder beside the emptiness so the contradiction -- mass on the table, no
3175
+ // proposal -- is readable on one record instead of inferred across two.
3176
+ //
3177
+ // Stream record, not a `context.receipt`: receipts are window carriers, and a pass
3178
+ // that changed nothing must not add bytes to the projection to announce it.
3179
+ if (!accounting.pending) {
3180
+ const remainder = unmarkedRemainder(snapshot, state, projectionCharsPerToken());
3181
+ emit("context.commit", {
3182
+ trigger,
3183
+ deferred: true,
3184
+ reason: "nothing-proposable",
3185
+ applied_marks: 0,
3186
+ pending_marks: 0,
3187
+ unmarked_stale_spans: remainder.spans,
3188
+ unmarked_stale_tokens: remainder.tokens,
3189
+ window_tokens: snapshot.contextWindow,
3190
+ });
3191
+ return null;
3192
+ }
2119
3193
  // The reclaim floor. One structural mutation per model call is the budget, so a
2120
3194
  // 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
3195
+ // the marks accumulate. A request whose projection exceeds the provider input budget
3196
+ // is rejected outright, so recovery must produce a window that fits: genuine overflow
3197
+ // -- the recovery lane, or an occupancy already past the serving budget -- fires
2123
3198
  // regardless of what it frees. Standing at the fence RATIO does not.
2124
3199
  if (!overflowExempt &&
2125
- accounting.eligibleFreedWindowShare < COMMIT_RECLAIM_FLOOR_SHARE) {
3200
+ accounting.eligibleFreedBudgetShare < COMMIT_RECLAIM_FLOOR_SHARE) {
2126
3201
  persistence.state = state;
2127
3202
  emit("context.commit", {
2128
3203
  trigger,
@@ -2131,7 +3206,7 @@ export function registerActiveContext(pi: any, options: {
2131
3206
  applied_marks: 0,
2132
3207
  pending_marks: accounting.pending,
2133
3208
  eligible_marks: accounting.eligibleMarks,
2134
- eligible_freed_share: accounting.eligibleFreedWindowShare,
3209
+ eligible_freed_share: accounting.eligibleFreedBudgetShare,
2135
3210
  reclaim_floor_share: COMMIT_RECLAIM_FLOOR_SHARE,
2136
3211
  window_tokens: snapshot.contextWindow,
2137
3212
  });
@@ -2140,51 +3215,101 @@ export function registerActiveContext(pi: any, options: {
2140
3215
  const bytesBefore = bytes(projectActiveContext(snapshot, state));
2141
3216
  // The guard protects an in-flight excursion, never at the cost of the session's
2142
3217
  // 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.
3218
+ // marks are released. The guard is a protection with a WAIVER, which is why it
3219
+ // survived the fence-snapshot deletion: one protection, one owner, one waiver.
2144
3220
  const guardWaiver = guardWaiverCount({
2145
3221
  snapshot,
2146
3222
  ratio: waiverRatio,
2147
3223
  guardedMarks: pendingMarks(state).filter((mark) =>
2148
3224
  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.
3225
+ // The starvation test asks the same question the commit will, against the same
3226
+ // snapshot the commit adjudicates against: does this epoch have work that does
3227
+ // not touch the open turn? Two snapshots gave two answers, and the one that
3228
+ // decided the commit was not the one being asked. There is one snapshot now.
2158
3229
  otherApplicableMarks: pendingMarks(state).filter((mark) =>
2159
3230
  !markTouchesCurrentTurn(state, mark, guarded) &&
2160
- markEligibility(commitSnapshot, state, mark) === "eligible").length,
3231
+ markEligibility(snapshot, state, mark) === "eligible").length,
2161
3232
  });
2162
- const result = await commitPendingMarks({
2163
- snapshot: commitSnapshot,
3233
+ let result = await commitPendingMarks({
3234
+ snapshot,
2164
3235
  state,
2165
3236
  generation: lifecycle.generation,
2166
3237
  retainIneligible: true,
2167
3238
  guardCurrentTurn: true,
2168
3239
  guardWaiver,
2169
3240
  });
3241
+ // THE EPOCH CLOSES ITS OWN COUNT.
3242
+ //
3243
+ // The folds this commit just landed are eligible roots the moment they exist, and
3244
+ // the law is about the count, not about which pass noticed it. Leaving them for the
3245
+ // next epoch is what let the pile stand between commits: measured 2026-08-10 (PT-3),
3246
+ // a commit that landed 26 tool folds left 32 visible roots up until the next band
3247
+ // crossing four cycles later. So the epoch re-reads its own count and parents what
3248
+ // it just made, inside the rewrite it has already paid for. No waiver here: a parent
3249
+ // this round cannot apply is held by the ordinary guard and waits, exactly like any
3250
+ // other fold. It converges because a parent is excluded from the count it came from.
3251
+ for (let round = 0; round < EPOCH_CONSOLIDATION_ROUNDS; round += 1) {
3252
+ let closing = result.state;
3253
+ for (const mark of consolidationMarks({ snapshot, state: closing, ordinal })) {
3254
+ const addition = addPendingMark(closing, mark);
3255
+ if (addition.added) { closing = addition.state; closingAdded += 1; }
3256
+ }
3257
+ if (closing === result.state) break;
3258
+ const closed = await commitPendingMarks({
3259
+ snapshot,
3260
+ state: closing,
3261
+ generation: lifecycle.generation,
3262
+ retainIneligible: true,
3263
+ guardCurrentTurn: true,
3264
+ });
3265
+ if (!closed.applied.length) break;
3266
+ // A mark the guard RETAINED is still pending, so the closing round sees it again
3267
+ // and reports it again; only the terminal refusals accumulate, and the last round
3268
+ // is the truth about what is still held.
3269
+ result = {
3270
+ ...closed,
3271
+ applied: [...result.applied, ...closed.applied],
3272
+ refused: [...result.refused.filter((mark) => !mark.retained), ...closed.refused],
3273
+ waived: [...result.waived, ...closed.waived],
3274
+ };
3275
+ }
2170
3276
  persistence.state = result.state;
2171
- const bytesAfter = bytes(projectActiveContext(snapshot, result.state));
3277
+ // The upgrade wedge: model briefs written since the last boundary replace their
3278
+ // deterministic ones HERE, inside the mutation this commit already pays for, before
3279
+ // anything measures what the commit did.
3280
+ const upgradedFolds = applyBriefUpgrades();
3281
+ const upgradeFailures = upgrades.failures;
3282
+ const upgradeError = upgrades.lastError;
3283
+ const upgradeCures = upgrades.cures;
3284
+ upgrades.failures = 0;
3285
+ upgrades.cures = 0;
3286
+ upgrades.lastError = null;
3287
+ const bytesAfter = bytes(projectActiveContext(snapshot, persistence.state));
2172
3288
  const freedBytes = Math.max(0, bytesBefore - bytesAfter);
3289
+ // The cost of the agent's pins, measured at the moment it matters: mass this
3290
+ // commit could not touch because protect holds it.
3291
+ const pinHeld = protectedStaleMass(snapshot, result.state);
2173
3292
  const commitEvent = emit("context.commit", {
2174
3293
  trigger,
2175
3294
  deferred: false,
2176
3295
  reason: null,
2177
- eligible_freed_share: accounting.eligibleFreedWindowShare,
3296
+ eligible_freed_share: accounting.eligibleFreedBudgetShare,
2178
3297
  reclaim_floor_share: COMMIT_RECLAIM_FLOOR_SHARE,
2179
3298
  // The two first-class dials: how much ONE event reclaims, and how long since the
2180
3299
  // last one. A trigger that frees too little re-fires immediately, and that shape
2181
3300
  // is only visible with both numbers on the same record.
2182
3301
  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),
3302
+ hysteresis_target_share: freeingTarget,
3303
+ target_occupancy_share: thresholds.minTarget,
3304
+ shortfall_share: Math.max(0, freeingTarget - accounting.eligibleFreedBudgetShare),
2186
3305
  occupancy_tokens_before: usedTokens,
2187
3306
  budget_tokens: budgetTokens,
3307
+ // The forward look, on the record beside the occupancy it was added to. A commit
3308
+ // that fired because one more turn would have spent the answer's reservation reads
3309
+ // differently from one that fired at the band top, and the difference is only
3310
+ // legible with the allowance printed.
3311
+ expected_inflow_tokens: expectedWallInflowTokens(),
3312
+ output_wall: outputWallDue(snapshot),
2188
3313
  requests_since_previous: instrumentation.requests - instrumentation.lastMutationRequest,
2189
3314
  inflow_tokens_since_previous: usedTokens === null || instrumentation.lastMutationTokens === null
2190
3315
  ? null
@@ -2200,25 +3325,119 @@ export function registerActiveContext(pi: any, options: {
2200
3325
  ladder_marks: accounting.ladderMarks,
2201
3326
  peek_marks: peekAdded,
2202
3327
  topup_marks: topUpAdded,
2203
- deepened: deepened,
2204
- deepened_marks: deepenedMarks,
3328
+ consolidation_marks: consolidationAdded,
3329
+ closing_consolidation_marks: closingAdded,
2205
3330
  absorbed_wedges: wedges.absorbed.length,
3331
+ // The upgrade is a mutation of a fold whose `context.fold` record was written at
3332
+ // creation, so the stream would otherwise show a brief that silently changed. The
3333
+ // count, the ids, and the failures ride the boundary that carried them.
3334
+ brief_upgrades: upgradedFolds.length,
3335
+ brief_upgrade_ids: upgradedFolds.join(","),
3336
+ brief_upgrade_failures: upgradeFailures,
3337
+ // Briefs that needed a second ask before they met the contract. Beside the failures
3338
+ // rather than inside them: a cured brief is a brief that landed, and the two together
3339
+ // are how the brief cap gets judged against something other than intuition.
3340
+ brief_upgrade_cures: upgradeCures,
3341
+ brief_upgrade_error: upgradeError,
3342
+ // Everything still owed to a FOLD: queued, in flight, and (always zero once the
3343
+ // wedge above ran) written but uncarried. In flight is counted because the lane
3344
+ // holds several calls open and work nobody can see is work nobody bounds. Counted in
3345
+ // folds rather than calls because a call now carries a whole commit's spans, and the
3346
+ // question this answers is how many folds are still reading deterministic.
3347
+ brief_upgrades_waiting: upgrades.ready.length + upgrades.running.size +
3348
+ upgrades.queue.reduce((total, entry) => total + entry.members.length, 0),
3349
+ // The same work in the unit the lane's two constants actually bound: calls, queued
3350
+ // plus in flight. Reported beside the fold count so a backed-up lane cannot read as
3351
+ // a busy one, or the reverse.
3352
+ brief_upgrade_calls: upgrades.queue.length + new Set(upgrades.running.values()).size,
2206
3353
  freed_bytes: freedBytes,
2207
3354
  freed_tokens: estimatedTokens(freedBytes),
2208
3355
  rewrite_tokens: accounting.rewriteTokens,
2209
3356
  pinned_bytes: accounting.pinnedBytes,
2210
3357
  pinned_results: accounting.pinnedResults,
3358
+ protected_stale_bytes: pinHeld.bytes,
3359
+ protected_stale_refs: pinHeld.refs,
2211
3360
  window_tokens: snapshot.contextWindow,
2212
3361
  });
2213
3362
  instrumentation.mutationsSinceHandoff += 1;
2214
3363
  instrumentation.lastMutationRequest = instrumentation.requests;
2215
3364
  instrumentation.lastMutationTokens = usedTokens;
3365
+ // THE RESPONSE ATTRIBUTION. Whatever the gated round did, or that it did nothing,
3366
+ // is linked to the exposure it answered, and the exposure is consumed by the
3367
+ // commit whatever its trigger: a fence or user commit landing mid-round is still
3368
+ // the commit boundary the last-call announced. The carrier's disappearance rides
3369
+ // this commit's own rewrite.
3370
+ if (persistence.state.lastCall) {
3371
+ const lastCall = persistence.state.lastCall;
3372
+ const attribution = lastCallAttribution(lastCall, accounting.agentMarks);
3373
+ emit("context.response", {
3374
+ exposure_seq: lastCall.exposure,
3375
+ commit_seq: commitEvent.seq,
3376
+ trigger,
3377
+ outcome: attribution.responded ? "responded" : "silent",
3378
+ ...attribution,
3379
+ });
3380
+ clearLastCall();
3381
+ }
3382
+ // A commit that drops occupancy back under a waypoint re-arms its notice: the next
3383
+ // upward crossing is a new event and earns a new one. The estimate is the same one
3384
+ // the receipt reports: measured occupancy before, less what this commit freed.
3385
+ if (persistence.state.notices && usedTokens !== null && budgetTokens > 0) {
3386
+ const postOccupancy = Math.max(0, usedTokens - estimatedTokens(freedBytes)) / budgetTokens;
3387
+ const fired = persistence.state.notices.fired.filter((share) => postOccupancy >= share);
3388
+ if (fired.length !== persistence.state.notices.fired.length) {
3389
+ persistence.state = { ...persistence.state, notices: { ...persistence.state.notices, fired } };
3390
+ }
3391
+ }
3392
+ // The rider: at most ONE action prompt per fold epoch, composed from post-commit
3393
+ // numbers, persisted as literal bytes, delivered beside the receipt inside the
3394
+ // rewrite this commit already paid for. The epoch key is the commit event's own
3395
+ // stream sequence: strictly monotone, one per applied commit, so refold-only
3396
+ // epochs get their rider too and no two epochs can ever share a key. The next
3397
+ // epoch REPLACES the text; nothing stacks.
3398
+ const riderEpoch = commitEvent.seq;
3399
+ if (persistence.state.rider?.epoch !== riderEpoch) {
3400
+ const postAccounting = markAccounting(snapshot, persistence.state);
3401
+ const remainder = unmarkedRemainder(snapshot, persistence.state, projectionCharsPerToken());
3402
+ const pinnedMass = explicitProtectedMass(snapshot, persistence.state);
3403
+ const pinnedShare = budgetTokens > 0
3404
+ ? estimatedTokens(pinnedMass.bytes) / budgetTokens
3405
+ : 0;
3406
+ const riderText = contextRiderText({
3407
+ toolName,
3408
+ brandNoun,
3409
+ suggestion: deliverSurfacing(snapshot, "rider"),
3410
+ pendingAgentMarks: postAccounting.agentMarks,
3411
+ eligibleMarks: postAccounting.eligibleMarks,
3412
+ freedTokens: postAccounting.freedTokens,
3413
+ eligibleFreedTokens: postAccounting.eligibleFreedTokens,
3414
+ anchors: remainder.candidates.slice(0, 3).map((candidate) => candidate.id),
3415
+ pinnedShare,
3416
+ maxPinnedShare: MAX_PINNED_SHARE,
3417
+ });
3418
+ persistence.state = { ...persistence.state, rider: { epoch: riderEpoch, text: riderText } };
3419
+ emit("context.rider", {
3420
+ epoch: riderEpoch,
3421
+ chars: riderText.length,
3422
+ anchors: Math.min(3, remainder.candidates.length),
3423
+ pending_agent_marks: postAccounting.agentMarks,
3424
+ eligible_marks: postAccounting.eligibleMarks,
3425
+ pinned_share: pinnedShare,
3426
+ max_pinned_share: MAX_PINNED_SHARE,
3427
+ });
3428
+ }
2216
3429
  let foldedToolResults = 0;
2217
3430
  let foldedSpans = 0;
2218
3431
  for (const applied of result.applied) {
2219
3432
  const fold = persistence.state.folds.find((item) => item.id === applied.foldId);
2220
3433
  if (fold?.kind === "tool-result") foldedToolResults += 1;
2221
3434
  else foldedSpans += 1;
3435
+ // A reclaimed peek copy names what it copied. Derived from the transcript rather
3436
+ // than carried on the mark, so the pointer is the same whichever mechanism claimed
3437
+ // the copy: the exposure's reclaim marking, the commit's, or the doorless ladder.
3438
+ const peekedSources = fold?.kind === "tool-result"
3439
+ ? peekedSourceFoldIds(snapshot, flattenFoldRefs(fold, persistence.state))
3440
+ : null;
2222
3441
  emit("context.fold", {
2223
3442
  commit_seq: commitEvent.seq,
2224
3443
  fold_id: applied.foldId,
@@ -2227,15 +3446,20 @@ export function registerActiveContext(pi: any, options: {
2227
3446
  mark_id: applied.id,
2228
3447
  fold_kind: fold?.kind ?? applied.mark,
2229
3448
  origin: applied.origin,
3449
+ peek_of: peekedSources ? peekedSources.join(",") : null,
2230
3450
  source_chars: fold?.sourceChars ?? 0,
2231
3451
  placeholder_chars: fold?.placeholderChars ?? 0,
2232
- brief_provenance: fold ? normalizeLegacyProvenance(fold.provenance).kind : null,
3452
+ brief_provenance: fold ? foldProvenance(fold, persistence.state).kind : null,
2233
3453
  });
2234
3454
  }
3455
+ // Queued AFTER the applications above, so no fold can be committed and upgraded on
3456
+ // one boundary: the generator has not run yet, and the wait is the mechanism.
3457
+ queueBriefUpgrades(snapshot, state, result.applied.map((applied) => applied.foldId));
2235
3458
  for (const wedge of wedges.absorbed) {
2236
3459
  emit("context.absorb", {
2237
3460
  commit_seq: commitEvent.seq,
2238
3461
  into_fold_id: wedge.intoMarkId,
3462
+ from_mark_id: wedge.fromMarkId,
2239
3463
  start_id: wedge.startId,
2240
3464
  end_id: wedge.endId,
2241
3465
  entries: wedge.entries,
@@ -2254,8 +3478,10 @@ export function registerActiveContext(pi: any, options: {
2254
3478
  ladderMarks: accounting.ladderMarks,
2255
3479
  peekMarks: peekAdded,
2256
3480
  topUpMarks: topUpAdded,
2257
- deepenedTarget: deepened,
2258
- deepenedMarks,
3481
+ consolidationMarks: consolidationAdded,
3482
+ // Parents formed over the folds THIS commit landed. They were never in
3483
+ // `pendingMarks`, so an epoch's arithmetic is applied = pending + closing.
3484
+ closingMarks: closingAdded,
2259
3485
  absorbedWedges: wedges.absorbed.length,
2260
3486
  absorbed: wedges.absorbed,
2261
3487
  // What the commit actually folded, the way an agent counts it, so the receipt can
@@ -2263,7 +3489,6 @@ export function registerActiveContext(pi: any, options: {
2263
3489
  foldedSpans,
2264
3490
  foldedToolResults,
2265
3491
  occupancyTokensBefore: usedTokens,
2266
- preDeepenFreedShare: preDeepenShare,
2267
3492
  depthFloorShare: commitDepthFloorShare(snapshot),
2268
3493
  reclaimFloorShare: COMMIT_RECLAIM_FLOOR_SHARE,
2269
3494
  guardWaived: result.waived.length > 0,
@@ -2273,19 +3498,21 @@ export function registerActiveContext(pi: any, options: {
2273
3498
  deferredMarks: result.retained.length,
2274
3499
  pinnedBytes: accounting.pinnedBytes,
2275
3500
  pinnedResults: accounting.pinnedResults,
3501
+ protectedStaleBytes: pinHeld.bytes,
3502
+ protectedStaleRefs: pinHeld.refs,
2276
3503
  retainedMarks: result.retained.length,
2277
3504
  currentTurnRetained: result.retained.filter((mark) =>
2278
3505
  markTouchesCurrentTurn(state, mark, guarded)).length,
2279
3506
  eligibleMarks: accounting.eligibleMarks,
2280
3507
  estimatedRewriteTokens: accounting.rewriteTokens,
2281
3508
  estimatedFreedTokens: accounting.freedTokens,
2282
- freedWindowShare: accounting.freedWindowShare,
3509
+ freedBudgetShare: accounting.freedBudgetShare,
2283
3510
  sourceBytesSaved: freedBytes,
2284
- actualFreedWindowShare: snapshot.contextWindow > 0
2285
- ? estimatedTokens(freedBytes) / snapshot.contextWindow
3511
+ actualFreedBudgetShare: snapshot.budgetTokens > 0
3512
+ ? estimatedTokens(freedBytes) / snapshot.budgetTokens
2286
3513
  : 0,
2287
- targetWindowShare: freeingTarget,
2288
- hysteresisTargetShare: hysteresisShare,
3514
+ targetBudgetShare: freeingTarget,
3515
+ hysteresisTargetShare: freeingTarget,
2289
3516
  requestsSincePreviousCommit: instrumentation.requests - instrumentation.lastMutationRequest,
2290
3517
  instrumentation: ledgerSummary(instrumentation.ledger),
2291
3518
  };
@@ -2311,12 +3538,78 @@ export function registerActiveContext(pi: any, options: {
2311
3538
  * that would free nothing, and the hysteresis stops a plateau from reading as a
2312
3539
  * stream of events. What is gone is only the announcement in front of it.
2313
3540
  */
2314
- const curationCommitDue = (snapshot: ActiveContextSnapshot): boolean => {
3541
+ /**
3542
+ * THE trigger, with its one piece of hysteresis.
3543
+ *
3544
+ * Occupancy at or above maxTarget, and then the reopen latch: a window that is still
3545
+ * full because the last commit could not reach the target is the SAME event, not a
3546
+ * new one, so a second commit waits for at least a reclaim floor of genuinely new
3547
+ * eligible mass. Without it a parked window fires a commit per pass and every one of
3548
+ * them rebuilds the prefix for crumbs.
3549
+ */
3550
+ const commitTriggerDue = (snapshot: ActiveContextSnapshot, ratio: number | null): boolean => {
2315
3551
  if (!persistence.state) return false;
2316
- if (!curationTriggerFires(measuredCurationSignals(snapshot))) return false;
3552
+ // The output wall outranks the quiet-runtime line rather than sitting under it. A
3553
+ // window one inflow step from spending the answer's whole reservation is past
3554
+ // economising, and the band top is not the thing it is about to cross.
3555
+ //
3556
+ // ONE COMMIT PER CROSSING, NOT ONE PER PASS. The wall is a level, and a window that
3557
+ // sits on it sits there for many passes: releasing the reopen latch on each of them
3558
+ // fires a commit per pass, which is the prefix-rewrite churn the latch exists to
3559
+ // prevent and which the quiet cadence is built around. Measured on gate 52's fixture:
3560
+ // the first wall commit applied 44 marks and correctly retained all ten of the open
3561
+ // turn, and the two that followed it applied nothing, rewrote the prefix anyway, and
3562
+ // dragged the guard's starvation waiver in on a session that had just made progress.
3563
+ // So the crossing is the event. While the wall stays due the ordinary hysteresis
3564
+ // decides, which is what makes a second commit wait for genuinely new foldable mass.
3565
+ const wall = outputWallDue(snapshot);
3566
+ if (!wall) curation.wallEpisodeOpen = false;
3567
+ if (!wall && !epochCommitDue(snapshot, ratio)) return false;
3568
+ measuredCurationSignals(snapshot);
3569
+ if (wall && !curation.wallEpisodeOpen) {
3570
+ curation.wallEpisodeOpen = true;
3571
+ curation.reopenBaselineShare = null;
3572
+ return true;
3573
+ }
3574
+ // The latch is an ECONOMY rule, and the fence is not latched: a request whose
3575
+ // projection exceeds the provider input budget is rejected outright, so recovery
3576
+ // must produce a window that fits.
3577
+ // A request that does not fit gets its commit whether or not the previous one left
3578
+ // new eligible mass behind; holding it back would be spending the session's
3579
+ // transmissibility to save a prefix rewrite.
3580
+ if (typeof ratio === "number" && Number.isFinite(ratio) && ratio >= hardFenceRatio(snapshot)) {
3581
+ curation.reopenBaselineShare = null;
3582
+ return true;
3583
+ }
2317
3584
  if (curation.reopenBaselineShare !== null) {
2318
- const eligibleShare = markAccounting(snapshot, persistence.state).eligibleFreedWindowShare;
2319
- if (eligibleShare - curation.reopenBaselineShare < COMMIT_RECLAIM_FLOOR_SHARE) return false;
3585
+ const eligibleShare = markAccounting(snapshot, persistence.state).eligibleFreedBudgetShare;
3586
+ if (eligibleShare - curation.reopenBaselineShare < COMMIT_RECLAIM_FLOOR_SHARE) {
3587
+ // A HOLD THAT SAYS SO.
3588
+ //
3589
+ // This was a bare `return false`, and it is the last silent deferral on the
3590
+ // commit path: every other one names itself and reports its numbers. In
3591
+ // sol-20260812 rep 1 the trigger was due for twelve straight ordinals while
3592
+ // occupancy climbed to 94 percent of budget, and the stream carries no record of
3593
+ // what held it, so the stall can be proven but not explained. Emitted on every
3594
+ // held pass rather than once per episode: under-reporting is the defect being
3595
+ // fixed, the record is a stream record that never reaches the projection, and
3596
+ // the volume is bounded by passes above the band top.
3597
+ const capacity = servingCapacity(snapshot.contextWindow);
3598
+ emit("context.commit", {
3599
+ trigger: "band-top",
3600
+ deferred: true,
3601
+ reason: "reopen-latch",
3602
+ applied_marks: 0,
3603
+ eligible_freed_share: eligibleShare,
3604
+ reopen_baseline_share: curation.reopenBaselineShare,
3605
+ reclaim_floor_share: COMMIT_RECLAIM_FLOOR_SHARE,
3606
+ occupancy_tokens: capacity.usedTokens,
3607
+ expected_inflow_tokens: expectedWallInflowTokens(),
3608
+ budget_tokens: capacity.budgetTokens,
3609
+ window_tokens: snapshot.contextWindow,
3610
+ });
3611
+ return false;
3612
+ }
2320
3613
  curation.reopenBaselineShare = null;
2321
3614
  }
2322
3615
  return true;
@@ -2332,7 +3625,7 @@ export function registerActiveContext(pi: any, options: {
2332
3625
  if (curation.reopenBaselineShare === null) return;
2333
3626
  const capacity = servingCapacity(lifecycle.latestSnapshot?.contextWindow ?? null);
2334
3627
  if (capacity.usedTokens === null || capacity.budgetTokens <= 0) return;
2335
- if (capacity.usedTokens / capacity.budgetTokens < CURATION_OCCUPANCY_SHARE) {
3628
+ if (capacity.usedTokens / capacity.budgetTokens < thresholds.maxTarget) {
2336
3629
  curation.reopenBaselineShare = null;
2337
3630
  }
2338
3631
  };
@@ -2373,26 +3666,250 @@ export function registerActiveContext(pi: any, options: {
2373
3666
  splitFromChars: Number(action.splitFromChars ?? 0),
2374
3667
  absorbedWedges: Number(epoch?.absorbedWedges ?? 0),
2375
3668
  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,
3669
+ protectedBytes: Number(epoch?.protectedStaleBytes ?? 0),
3670
+ note: null,
2380
3671
  }));
2381
3672
  };
2382
3673
 
3674
+ /**
3675
+ * Threshold notices, evaluated on every pass the ladder runs on. One waypoint fires
3676
+ * once per upward crossing of the measured occupancy; delivery is an append-once
3677
+ * carrier plus its stream record, and re-arming belongs to the commit that drops
3678
+ * occupancy back under the line. Default on; `guidance.thresholdNotices: false`
3679
+ * registers the runtime with the waypoints silent.
3680
+ */
3681
+ const deliverThresholdNotices = (snapshot: ActiveContextSnapshot): boolean => {
3682
+ if (!guidance.thresholdNotices || !persistence.state) return false;
3683
+ const capacity = servingCapacity(snapshot.contextWindow);
3684
+ if (capacity.usedTokens === null || capacity.budgetTokens <= 0) return false;
3685
+ const occupancy = capacity.usedTokens / capacity.budgetTokens;
3686
+ const current = persistence.state.notices ?? { fired: [], ring: [] };
3687
+ let fired = current.fired;
3688
+ let ring = current.ring;
3689
+ let changed = false;
3690
+ for (const share of THRESHOLD_NOTICE_SHARES) {
3691
+ if (occupancy < share || fired.includes(share)) continue;
3692
+ const text = thresholdNoticeText({
3693
+ share,
3694
+ occupancyTokens: capacity.usedTokens,
3695
+ budgetTokens: capacity.budgetTokens,
3696
+ maxTarget: thresholds.maxTarget,
3697
+ toolName,
3698
+ brandNoun,
3699
+ });
3700
+ fired = [...fired, share];
3701
+ ring = [...ring, { share, ordinal: markOrdinal(snapshot), text }];
3702
+ if (ring.length > MAX_THRESHOLD_NOTICES) ring = ring.slice(ring.length - MAX_THRESHOLD_NOTICES);
3703
+ changed = true;
3704
+ emit("context.notice", {
3705
+ share,
3706
+ occupancy,
3707
+ occupancy_tokens: capacity.usedTokens,
3708
+ budget_tokens: capacity.budgetTokens,
3709
+ max_target: thresholds.maxTarget,
3710
+ chars: text.length,
3711
+ });
3712
+ }
3713
+ if (changed) persistence.state = { ...persistence.state, notices: { fired, ring } };
3714
+ return changed;
3715
+ };
3716
+
3717
+ /**
3718
+ * The same selection, read rather than delivered: what the next carrier WOULD say,
3719
+ * with the ledger that decides whether it says anything at all.
3720
+ */
3721
+ const statusSurfacing = (snapshot: ActiveContextSnapshot): Record<string, unknown> => {
3722
+ const ledger = persistence.state ? surfacingLedger(persistence.state) : [];
3723
+ if (!persistence.state) return { slate: [], line: null, ledger, silenced: [] };
3724
+ const selection = selectSurfacingSlate({
3725
+ state: persistence.state,
3726
+ snapshot,
3727
+ toolName,
3728
+ ordinal: markOrdinal(snapshot),
3729
+ });
3730
+ return {
3731
+ slate: selection.slate.map((suggestion) => ({
3732
+ id: suggestion.id,
3733
+ route: suggestion.route,
3734
+ contentScore: suggestion.contentScore,
3735
+ briefScore: suggestion.briefScore,
3736
+ margin: suggestion.margin,
3737
+ slot: suggestion.slot,
3738
+ })),
3739
+ line: surfacingSlateText({ slate: selection.slate, queryTerms: selection.queryTerms, brandNoun }),
3740
+ considered: selection.considered,
3741
+ divergent: selection.divergent,
3742
+ suppressed: selection.suppressed,
3743
+ intentTerms: selection.queryTerms.size,
3744
+ contentHit: SURFACING_CONTENT_HIT,
3745
+ briefHit: SURFACING_BRIEF_HIT,
3746
+ divergenceMargin: SURFACING_DIVERGENCE_MARGIN,
3747
+ slateSize: SURFACING_SLATE_SIZE,
3748
+ silenced: [...surfacingSilenced(ledger)],
3749
+ ledger,
3750
+ };
3751
+ };
3752
+
3753
+ /**
3754
+ * THE SURFACING SLATE, delivered.
3755
+ *
3756
+ * The agent cannot peek a fold it does not know to want, so one line names the fold
3757
+ * whose stored content matches the task in hand when its brief does not. Where it
3758
+ * lands is the whole economics: only carriers that ride a rewrite the runtime is
3759
+ * already paying for (the pre-commit last call and the post-commit rider) plus the
3760
+ * on-demand status surface, which the agent asked for. The per-request ephemeral
3761
+ * carrier stays dead; it cost 21.9% of every input token in rep 21 and nothing else
3762
+ * about this build changes that verdict.
3763
+ *
3764
+ * One suggestion per delivery point, and most delivery points get none. Every
3765
+ * delivery first resolves whatever offers have run out of window, so the ignore that
3766
+ * silences a fold is recorded before the next selection reads the ledger.
3767
+ */
3768
+ const deliverSurfacing = (snapshot: ActiveContextSnapshot, carrier: string): string | null => {
3769
+ if (!persistence.state) return null;
3770
+ const ordinal = markOrdinal(snapshot);
3771
+ const resolved = resolveSurfacing({ state: persistence.state, snapshot, ordinal });
3772
+ persistence.state = resolved.state;
3773
+ for (const transition of resolved.transitions) {
3774
+ emit("context.outcome", {
3775
+ fold_id: transition.id,
3776
+ from_outcome: transition.from,
3777
+ outcome: transition.to,
3778
+ outcome_ordinal: transition.ordinal,
3779
+ window_ordinals: SURFACING_OUTCOME_WINDOW_ORDINALS,
3780
+ });
3781
+ }
3782
+ const selection = selectSurfacingSlate({ state: persistence.state, snapshot, toolName, ordinal });
3783
+ const suggestion = selection.slate[0];
3784
+ if (!suggestion) return null;
3785
+ const text = surfacingSlateText({
3786
+ slate: selection.slate,
3787
+ queryTerms: selection.queryTerms,
3788
+ brandNoun,
3789
+ });
3790
+ if (!text) return null;
3791
+ persistence.state = issueSurfacing(persistence.state, suggestion.id, ordinal);
3792
+ emit("context.suggestion", {
3793
+ carrier,
3794
+ fold_id: suggestion.id,
3795
+ content_score: suggestion.contentScore,
3796
+ brief_score: suggestion.briefScore,
3797
+ margin: suggestion.margin,
3798
+ content_hit: SURFACING_CONTENT_HIT,
3799
+ brief_hit: SURFACING_BRIEF_HIT,
3800
+ divergence_margin: SURFACING_DIVERGENCE_MARGIN,
3801
+ slot: suggestion.slot,
3802
+ slate_size: SURFACING_SLATE_SIZE,
3803
+ fold_depth: suggestion.depth,
3804
+ considered: selection.considered,
3805
+ divergent: selection.divergent,
3806
+ suppressed: selection.suppressed,
3807
+ intent_terms: selection.queryTerms.size,
3808
+ chars: text.length,
3809
+ });
3810
+ return text;
3811
+ };
3812
+
3813
+ /**
3814
+ * THE PRE-COMMIT LAST-CALL GATE. When the band-top trigger fires, the commit defers
3815
+ * exactly one gated round behind one exposure of the ruled prompt; then it proceeds
3816
+ * with whatever marks exist. A request whose projection exceeds the provider input
3817
+ * budget is rejected outright, so recovery must produce a window that fits: fence
3818
+ * pressure, the recovery lane, or an occupancy already past the serving budget
3819
+ * commit NOW, and the user
3820
+ * command never reaches this gate because explicit intent is its own answer. The
3821
+ * round is measured from DELIVERY: the commit proceeds only on a context pass whose
3822
+ * ordinal is past the pass that rendered the exposure, which is exactly one agent
3823
+ * response between prompt and rewrite.
3824
+ */
3825
+ const lastCallVerdict = (
3826
+ snapshot: ActiveContextSnapshot,
3827
+ pressure: number | null,
3828
+ phase: string,
3829
+ ): "expose" | "hold" | "proceed" => {
3830
+ const state = persistence.state!;
3831
+ const capacity = servingCapacity(snapshot.contextWindow);
3832
+ const fenceLevel = typeof pressure === "number" && Number.isFinite(pressure) &&
3833
+ pressure >= hardFenceRatio(snapshot);
3834
+ // The output wall deliberately does NOT proceed here. It buys the crossing one inflow
3835
+ // step of lead, and the gated round is what that lead is for: the agent still gets its
3836
+ // one bounded turn to curate before a rewrite, which is a contract the wall has no
3837
+ // business cancelling. If the round overruns and occupancy passes the budget outright,
3838
+ // the condition below is what proceeds, on the pass that builds the next request.
3839
+ const overBudget = capacity.usedTokens !== null && capacity.budgetTokens > 0 &&
3840
+ capacity.usedTokens > capacity.budgetTokens;
3841
+ if (fenceLevel || overBudget || curation.recoveryAttempts > 0 || curation.pendingRejection) {
3842
+ return "proceed";
3843
+ }
3844
+ if (!state.lastCall) return "expose";
3845
+ if (phase !== "context") return "hold";
3846
+ const delivery = curation.lastCallDelivery;
3847
+ if (!delivery || delivery.exposure !== state.lastCall.exposure) return "hold";
3848
+ return markOrdinal(snapshot) > delivery.ordinal ? "proceed" : "hold";
3849
+ };
3850
+
3851
+ const exposeLastCall = (snapshot: ActiveContextSnapshot): void => {
3852
+ if (!persistence.state) return;
3853
+ // THE PEEK RECLAIM IS MARKED HERE, NOT ONLY AT THE COMMIT.
3854
+ //
3855
+ // A peek copy is reclaimed at the next commit by contract, and a contract the agent
3856
+ // cannot see until after it executes is not one it can veto. The exposure is the one
3857
+ // bounded round before the rewrite, so minting the reclaim marks now puts the pending
3858
+ // disposal in front of the agent while a pin still changes the outcome: a pinned mark
3859
+ // waits instead of applying, and lifting the pin hands the copy to a later commit.
3860
+ const exposureOrdinal = markOrdinal(snapshot);
3861
+ let peekReclaims = 0;
3862
+ for (const mark of ephemeralPeekMarks({ snapshot, state: persistence.state, ordinal: exposureOrdinal })) {
3863
+ const addition = addPendingMark(persistence.state, mark);
3864
+ if (addition.added) { persistence.state = addition.state; peekReclaims += 1; }
3865
+ }
3866
+ const signals = measuredCurationSignals(snapshot);
3867
+ const remainder = unmarkedRemainder(snapshot, persistence.state, projectionCharsPerToken());
3868
+ const accounting = markAccounting(snapshot, persistence.state);
3869
+ const text = lastCallText({
3870
+ signals,
3871
+ unmarked: { spans: remainder.spans, tokens: remainder.tokens },
3872
+ pendingMarks: accounting.pending,
3873
+ peekReclaims,
3874
+ suggestion: deliverSurfacing(snapshot, "lastcall"),
3875
+ toolName,
3876
+ brandNoun,
3877
+ });
3878
+ const record = emit("context.lastcall", {
3879
+ occupancy: signals.occupancy,
3880
+ max_target: thresholds.maxTarget,
3881
+ occupancy_tokens: signals.occupancyTokens,
3882
+ budget_tokens: signals.budgetTokens,
3883
+ unmarked_stale_spans: remainder.spans,
3884
+ unmarked_stale_tokens: remainder.tokens,
3885
+ pending_marks: accounting.pending,
3886
+ pending_agent_marks: accounting.agentMarks,
3887
+ peek_marks: peekReclaims,
3888
+ chars: text.length,
3889
+ });
3890
+ persistence.state = {
3891
+ ...persistence.state,
3892
+ lastCall: {
3893
+ exposure: record.seq,
3894
+ ordinal: markOrdinal(snapshot),
3895
+ contextCalls: curation.contextCalls,
3896
+ agentMarks: accounting.agentMarks,
3897
+ text,
3898
+ },
3899
+ };
3900
+ };
3901
+
2383
3902
  const applyAutomaticRung = async (
2384
3903
  snapshot: ActiveContextSnapshot,
2385
3904
  ratio: number,
2386
3905
  rungOptions: {
2387
- waiveToolCadence?: boolean;
2388
3906
  toolOnly?: boolean;
2389
3907
  waiverRatio?: number;
2390
- announcing?: boolean;
2391
3908
  } = {},
3909
+ phase = "context",
2392
3910
  ): Promise<Record<string, unknown> | null> => {
2393
3911
  if (!persistence.state || ladder.automaticFailure || ladder.preparing) return null;
2394
3912
  const rungSelectionOptions = {
2395
- waiveToolCadence: rungOptions.waiveToolCadence,
2396
3913
  toolOnly: rungOptions.toolOnly,
2397
3914
  summarizerAvailable: Boolean(options.summarizeContextSpan),
2398
3915
  failedPreparationIds: ladder.failedPreparations,
@@ -2433,59 +3950,98 @@ export function registerActiveContext(pi: any, options: {
2433
3950
  };
2434
3951
  let epoch: Record<string, unknown> | null = null;
2435
3952
  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) {
3953
+ // QUIET RUNTIME, AND THE ONE TRIGGER.
3954
+ //
3955
+ // A commit is an epoch transition: it rewrites the projection, every rewrite risks
3956
+ // the whole prefix cache, so the cadence has to be the fewest commits that still
3957
+ // keep the window inside its budget. Occupancy reaching maxTarget is the whole
3958
+ // automatic condition. Below it the runtime is quiet and marks accumulate free;
3959
+ // at it, one commit folds down toward minTarget, and the gap between the two lines
3960
+ // is what makes the spacing of events structural rather than hoped for.
3961
+ //
3962
+ // Two other arms used to reach this line and both are deleted. The eligible-share
3963
+ // arm fired 164 commits from ordinal 17 in rep 15 on marks that had not earned a
3964
+ // rewrite, and it could fire below maxTarget, which is the quiet law it sat under.
3965
+ // The stale-mass AND-condition guarded an ANNOUNCEMENT that no longer exists: a
3966
+ // commit is never announced, so a commit with nothing eligible now applies nothing
3967
+ // and reports it rather than being suppressed in advance.
3968
+ //
3969
+ // The trigger decides early and says nothing. Nothing warns that a commit is
3970
+ // coming, because a warning has to arrive before the event it warns about and
3971
+ // therefore has to break a prefix nothing else was breaking.
3972
+ // Evaluated on EVERY pass the ladder runs on, which is where the pressure backstop
3973
+ // always sat. Crossing the band top is a property of occupancy, not of which
3974
+ // lifecycle hook happened to notice it, and a trigger that waits for the next
3975
+ // projection pass is a window that keeps climbing while it waits.
3976
+ const noticesChanged = deliverThresholdNotices(snapshot);
3977
+ let lastCallChanged = false;
3978
+ const commitDue = commitTriggerDue(snapshot, ratio);
3979
+ if (commitDue) {
3980
+ const verdict = lastCallVerdict(snapshot, rungOptions.waiverRatio ?? ratio, phase);
3981
+ if (verdict === "expose") {
3982
+ exposeLastCall(snapshot);
3983
+ lastCallChanged = true;
3984
+ } else if (verdict === "proceed") {
2461
3985
  epoch = await runCommitEpoch(
2462
3986
  snapshot,
2463
- backstopDue ? "window-pressure" : "curation-trigger",
3987
+ "band-top",
2464
3988
  true,
2465
3989
  rungOptions.waiverRatio ?? ratio,
2466
3990
  );
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)) {
3991
+ // Latch the eligible share this commit left behind, so the next crossing waits
3992
+ // for genuinely new foldable mass rather than for the same window to still be
3993
+ // full. The band top is a LINE, and a window parked on it is one event.
3994
+ if (persistence.state) {
2470
3995
  curation.reopenBaselineShare =
2471
- markAccounting(snapshot, persistence.state).eligibleFreedWindowShare;
3996
+ markAccounting(snapshot, persistence.state).eligibleFreedBudgetShare;
2472
3997
  }
2473
3998
  }
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
- }
3999
+ // "hold": the exposure is out and the round is still the agent's. Marks keep
4000
+ // accumulating below; nothing commits and nothing re-exposes.
4001
+ } else if (persistence.state.lastCall) {
4002
+ // The crossing died without its commit (a user rescue or expiry dropped
4003
+ // occupancy back under the trigger), so the exposure lapses: attributed,
4004
+ // cleared, and the next crossing is a fresh event with a fresh exposure.
4005
+ const capacity = servingCapacity(snapshot.contextWindow);
4006
+ if (capacity.usedTokens !== null && capacity.budgetTokens > 0 &&
4007
+ capacity.usedTokens / capacity.budgetTokens < thresholds.maxTarget) {
4008
+ const lastCall = persistence.state.lastCall;
4009
+ const attribution = lastCallAttribution(
4010
+ lastCall,
4011
+ markAccounting(snapshot, persistence.state).agentMarks,
4012
+ );
4013
+ emit("context.response", {
4014
+ exposure_seq: lastCall.exposure,
4015
+ commit_seq: null,
4016
+ trigger: null,
4017
+ outcome: "lapsed",
4018
+ ...attribution,
4019
+ });
4020
+ clearLastCall();
4021
+ lastCallChanged = true;
4022
+ }
4023
+ }
4024
+ // An inline rung is free only INSIDE a rewrite the epoch already paid for. Below
4025
+ // the threshold no commit ran, and an epoch that applied NOTHING -- every mark
4026
+ // held back by an open turn or by ineligibility -- paid for no rewrite either,
4027
+ // so folding inline there is a fresh single-fold rewrite of its own. Measured
4028
+ // 2026-08-06 (rep 10): the current-turn guard retained all nine marks every turn
4029
+ // while the inline rung folded one batch per turn anyway, 52 single-fold
4030
+ // rewrites that left the prefix cache share at zero. Below a paid rewrite the
4031
+ // ladder MARKS, so the decisions batch into the first commit that can apply them.
4032
+ inlineRungs = Boolean(epoch) && Number(epoch?.appliedMarks ?? 0) > 0;
4033
+ if (!inlineRungs) {
4034
+ const marked = markLadderSelection();
4035
+ if (marked) {
4036
+ if (epoch) ladder.lastAutomaticAction = { ...marked, epoch };
4037
+ return ladder.lastAutomaticAction;
4038
+ }
4039
+ // A carrier change is durable state even when no mark and no epoch moved: the
4040
+ // exposure and the notices must survive a reload, so the pass returns an action
4041
+ // for the transaction to persist. Never a receipt: a carrier reports itself.
4042
+ if (!epoch && (lastCallChanged || noticesChanged)) {
4043
+ ladder.lastAutomaticAction = { kind: "carrier", foldIds: [], sourceIds: [], sourceBytesSaved: 0 };
4044
+ return ladder.lastAutomaticAction;
2489
4045
  }
2490
4046
  }
2491
4047
  // Evidence a pending mark already covers is not the inline rung's to fold: a mark
@@ -2501,16 +4057,14 @@ export function registerActiveContext(pi: any, options: {
2501
4057
  // quiet cadence started landing a commit on the same context pass, which is what
2502
4058
  // turns the inline rung on in the first place.
2503
4059
  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)
4060
+ ? selectAutomaticRung(snapshot, persistence.state, ratio, {
4061
+ ...rungSelectionOptions,
4062
+ claimed: new Set([
4063
+ ...claimedRefKeys(persistence.state),
4064
+ ...currentTurnRefKeys(snapshot),
4065
+ ]),
4066
+ claimedFoldIds: markedFoldIds(persistence.state),
4067
+ })
2514
4068
  : null;
2515
4069
  // Above the commit threshold we are already inside the epoch's rewrite turn, so
2516
4070
  // every rung applies inline: the extra rung costs at most one more invalidation
@@ -2631,10 +4185,8 @@ export function registerActiveContext(pi: any, options: {
2631
4185
  ctx: any,
2632
4186
  phase: string,
2633
4187
  rungOptions: {
2634
- waiveToolCadence?: boolean;
2635
4188
  toolOnly?: boolean;
2636
4189
  waiverRatio?: number;
2637
- announcing?: boolean;
2638
4190
  } = {},
2639
4191
  ): Promise<Record<string, unknown> | null> => {
2640
4192
  if (!persistence.state || !measurements.lastProviderMeasurement ||
@@ -2653,7 +4205,7 @@ export function registerActiveContext(pi: any, options: {
2653
4205
  const transientAtEntry = captureTransient();
2654
4206
  let action: Record<string, unknown> | null = null;
2655
4207
  try {
2656
- action = await applyAutomaticRung(snapshot, ratio, rungOptions);
4208
+ action = await applyAutomaticRung(snapshot, ratio, rungOptions, phase);
2657
4209
  if (action) await persist(ctx);
2658
4210
  if (action) ladder.boundaryFailure = null;
2659
4211
  return action;
@@ -2681,14 +4233,21 @@ export function registerActiveContext(pi: any, options: {
2681
4233
  ctx: any,
2682
4234
  phase: string,
2683
4235
  rungOptions: {
2684
- waiveToolCadence?: boolean;
2685
4236
  toolOnly?: boolean;
2686
4237
  waiverRatio?: number;
2687
- announcing?: boolean;
2688
4238
  } = {},
2689
4239
  ): Promise<Record<string, unknown> | null> => {
2690
- const operation = ladder.actionQueue.then(() =>
2691
- runAutomaticRungTransaction(snapshot, ratio, ctx, phase, rungOptions));
4240
+ const operation = ladder.actionQueue.then(async () => {
4241
+ const action = await runAutomaticRungTransaction(snapshot, ratio, ctx, phase, rungOptions);
4242
+ // Where the generator is started from a pass: after the transaction, so the calls
4243
+ // sit BETWEEN boundaries. Folds queued by the commit this pass just made are
4244
+ // briefed now and land on the next commit, which is the whole shape of the lane.
4245
+ // Afterwards each finishing call starts the next, so this is the lane's ignition
4246
+ // rather than its pace. Parents waiting on their children are taken up here too:
4247
+ // a pass is the other place we know the lane may have room.
4248
+ resumeBriefUpgrades(snapshot, ctx);
4249
+ return action;
4250
+ });
2692
4251
  ladder.actionQueue = operation.catch(() => undefined);
2693
4252
  return operation;
2694
4253
  };
@@ -2700,40 +4259,6 @@ export function registerActiveContext(pi: any, options: {
2700
4259
  return [];
2701
4260
  }
2702
4261
  };
2703
- options.setProjectionProvider?.(projectionCandidates);
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
-
2737
4262
  // A same-session start/tree reload is a projection-generation mutation.
2738
4263
  // Queue it behind every context authority → preparation → commit →
2739
4264
  // projection transaction, then serialize the actual load with the action
@@ -2784,6 +4309,7 @@ export function registerActiveContext(pi: any, options: {
2784
4309
  pi.on("session_start", async (_event: unknown, ctx: any) => {
2785
4310
  await safeLifecycleLoad(ctx, "session-start");
2786
4311
  recordResolvedCapacity(ctx);
4312
+ await armRollbackLane(ctx);
2787
4313
  });
2788
4314
  pi.on("session_tree", async (_event: unknown, ctx: any) => { await safeLifecycleLoad(ctx, "session-tree"); });
2789
4315
  pi.on("session_compact", async (event: Record<string, unknown>, ctx: any) => {
@@ -2831,9 +4357,15 @@ export function registerActiveContext(pi: any, options: {
2831
4357
  const attributionChanged = async (_event: unknown, ctx: any): Promise<void> => {
2832
4358
  lifecycle.generation += 1;
2833
4359
  cancelPreparation();
4360
+ cancelBriefUpgrades();
2834
4361
  if (persistence.state?.prepared) persistence.state = clearPrepared(persistence.state);
2835
4362
  measurements.latestRatio = null;
2836
4363
  measurements.lastProviderMeasurement = null;
4364
+ measurements.wallInflowSteps.length = 0;
4365
+ // A different model or thinking level is a different tokenizer, so counts taken
4366
+ // under the old one describe nothing here. The generation the anchor carries would
4367
+ // already refuse it; dropping it releases the projection text it held as well.
4368
+ measurements.projectionAnchor = null;
2837
4369
  nativeCompaction.lastThresholdDecision = null;
2838
4370
  updateStatus(ctx);
2839
4371
  };
@@ -2904,6 +4436,10 @@ export function registerActiveContext(pi: any, options: {
2904
4436
  // Calibrate BEFORE the new measurement becomes the previous one: the inflow
2905
4437
  // step is the difference between them.
2906
4438
  noteProjectionCalibration(observedMeasurement);
4439
+ // Anchor before this pass builds anything: `previousText` is still the
4440
+ // projection the provider just counted, and the projection built below is the
4441
+ // one that either extends it or does not.
4442
+ noteProviderProjectionAnchor(observedMeasurement);
2907
4443
  measurements.lastProviderMeasurement = observedMeasurement;
2908
4444
  startPreparation(snapshot, measurements.latestRatio, ctx);
2909
4445
  if (!ladder.automaticFailure && measurements.latestRatio >= hardFenceRatio(snapshot) && ladder.preparing) {
@@ -2913,9 +4449,9 @@ export function registerActiveContext(pi: any, options: {
2913
4449
  return { messages: event.messages };
2914
4450
  }
2915
4451
  }
2916
- if (selectAutomaticToolForRung(snapshot, persistence.state, measurements.latestRatio)) mutationAttempted = true;
4452
+ if (selectAutomaticToolBatch(snapshot, persistence.state)[0]) mutationAttempted = true;
2917
4453
  const action = await attemptAutomaticRung(
2918
- snapshot, measurements.latestRatio, ctx, "context", { announcing: true },
4454
+ snapshot, measurements.latestRatio, ctx, "context",
2919
4455
  );
2920
4456
  if (action) {
2921
4457
  mutationAttempted = true;
@@ -2951,8 +4487,11 @@ export function registerActiveContext(pi: any, options: {
2951
4487
  // already-large session out of calibrating at all: its first projection aborts
2952
4488
  // under the uncalibrated constant, so nothing is ever sent, so nothing ever
2953
4489
  // measures the ratio, so it aborts forever.
2954
- measurements.lastProjectedChars = bytes(projected);
2955
- measurements.lastProjectedEstimate = projectedTokenEstimate(projected);
4490
+ const reading = projectedTokenReading(projected);
4491
+ measurements.lastProjectedChars = reading.chars;
4492
+ measurements.lastProjectedEstimate = reading.tokens;
4493
+ measurements.lastProjectedEstimateBasis = reading.basis;
4494
+ measurements.lastProjectedSizeTokens = Math.ceil(reading.chars / projectionCharsPerToken());
2956
4495
  measurements.lastProjectedEstimateCalibrated = measurements.projectionCalibrations.length > 0;
2957
4496
  // An aborted request still returns the PROJECTION. Handing back the raw branch
2958
4497
  // instead makes the aborted turn the largest message list the session ever
@@ -3015,6 +4554,7 @@ export function registerActiveContext(pi: any, options: {
3015
4554
  // inflow that is now behind us, and the cap resets for the next one.
3016
4555
  curation.recoveryAttempts = 0;
3017
4556
  curation.pendingRejection = null;
4557
+ noteWallInflow(measurement);
3018
4558
  measurements.lastProviderMeasurement = measurement;
3019
4559
  measurements.latestRatio = measuredRatio;
3020
4560
  let snapshot: ActiveContextSnapshot;
@@ -3045,6 +4585,7 @@ export function registerActiveContext(pi: any, options: {
3045
4585
  beginMutationPass();
3046
4586
  try {
3047
4587
  const message = ownValue(event, "message");
4588
+ noteOverflowAtMessageEnd(message, ctx);
3048
4589
  instrumentation.requests += 1;
3049
4590
  const observation = observeCacheUsage(instrumentation.ledger, {
3050
4591
  usage: ownValue(message, "usage"),
@@ -3089,58 +4630,17 @@ export function registerActiveContext(pi: any, options: {
3089
4630
  try { updateStatus(ctx); } catch { /* The provider loop must keep running. */ }
3090
4631
  }
3091
4632
  });
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.
4633
+ /*
4634
+ * There was an `after_provider_response` handler here that treated `status >= 400` as
4635
+ * the rejection signal. It could never fire on the case it was written for. Every
4636
+ * provider adapter calls `onResponse` exactly once and only AFTER the request promise
4637
+ * resolves, so a 4xx rejects the promise and control never reaches that call at all
4638
+ * (verified across all seven adapters in pi-ai 0.83.0). `pendingRejection` was
4639
+ * therefore never set in production and the recovery branch of the projection budget
4640
+ * never ran. It is set by the rollback lane now, from an event that does fire.
3098
4641
  */
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
- });
3105
- pi.on("tool_call", (event: Record<string, unknown>, ctx: any) => {
3106
- const calledTool = ownValue(event, "toolName");
3107
- if (lifecycle.shuttingDown || lifecycle.blockingToolHarvestedThisTurn || lifecycle.blockingToolHarvestQueuedThisTurn ||
3108
- typeof calledTool !== "string" || !blockingTools.has(calledTool)) return;
3109
- lifecycle.blockingToolHarvestQueuedThisTurn = true;
3110
- beginMutationPass();
3111
- const operation = ladder.actionQueue.then(async () => {
3112
- try {
3113
- if (!persistence.state || measurements.latestRatio === null || !measurements.lastProviderMeasurement || ladder.automaticFailure ||
3114
- !durableProviderMeasurementMatches(measurements.lastProviderMeasurement)) return null;
3115
- cancelPreparation();
3116
- let snapshot: ActiveContextSnapshot;
3117
- try { snapshot = authoritativeSnapshotFor(ctx); }
3118
- catch { return null; }
3119
- const candidate = selectAutomaticToolForRung(snapshot, persistence.state, measurements.latestRatio, true);
3120
- if (!candidate) {
3121
- lifecycle.blockingToolHarvestedThisTurn = true;
3122
- return null;
3123
- }
3124
- const action = await runAutomaticRungTransaction(
3125
- snapshot,
3126
- measurements.latestRatio,
3127
- ctx,
3128
- "tool-call",
3129
- { waiveToolCadence: true, toolOnly: true },
3130
- );
3131
- if (action) lifecycle.blockingToolHarvestedThisTurn = true;
3132
- return action;
3133
- } finally {
3134
- lifecycle.blockingToolHarvestQueuedThisTurn = false;
3135
- try { updateStatus(ctx); } catch { /* A blocking tool call must never wait on presentation. */ }
3136
- }
3137
- });
3138
- ladder.actionQueue = operation.catch(() => undefined);
3139
- });
3140
4642
  pi.on("turn_end", async (_event: unknown, ctx: any) => {
3141
4643
  beginMutationPass();
3142
- lifecycle.blockingToolHarvestedThisTurn = false;
3143
- lifecycle.blockingToolHarvestQueuedThisTurn = false;
3144
4644
  if (lifecycle.shuttingDown || !persistence.state || !persistence.persisted) return;
3145
4645
  const stateAtEntry = clone(persistence.state);
3146
4646
  const persistedAtEntry = persistence.persisted;
@@ -3170,8 +4670,287 @@ export function registerActiveContext(pi: any, options: {
3170
4670
  return ownValue(preparation, "userWantsSummary") === true ? { cancel: true } : undefined;
3171
4671
  });
3172
4672
 
4673
+ /**
4674
+ * pi's own overflow classifier, borrowed rather than re-implemented.
4675
+ *
4676
+ * `isContextOverflow` owns the provider-message patterns AND the silent cases (usage
4677
+ * past the window with a clean stop, the truncating `length` stop). Re-stating that
4678
+ * pattern set here would drift on the first provider that reworded its 400. The
4679
+ * bare specifier is tried first, for a deployment where pi-ai is hoisted; the second
4680
+ * form reaches pi's own nested copy, which is where a default install puts it.
4681
+ */
4682
+ const resolveOverflowClassifier = async (): Promise<void> => {
4683
+ if (rollback.classifier || rollback.classifierSource === "unresolved") return;
4684
+ const host = "@earendil-works/pi-coding-agent";
4685
+ const marker = `/${host}/`;
4686
+ const specifiers = ["@earendil-works/pi-ai/compat"];
4687
+ try {
4688
+ const entry = import.meta.resolve(host);
4689
+ const at = entry.lastIndexOf(marker);
4690
+ if (at >= 0) {
4691
+ specifiers.push(`${entry.slice(0, at + marker.length)}node_modules/@earendil-works/pi-ai/dist/compat.js`);
4692
+ }
4693
+ } catch { /* An unresolvable host is the same answer as an unresolvable classifier. */ }
4694
+ for (const specifier of specifiers) {
4695
+ try {
4696
+ const module = await import(specifier) as Record<string, unknown>;
4697
+ if (typeof module.isContextOverflow === "function") {
4698
+ rollback.classifier = module.isContextOverflow as (message: unknown, contextWindow: number) => boolean;
4699
+ rollback.classifierSource = specifier;
4700
+ return;
4701
+ }
4702
+ } catch { /* Try the next form; the last failure is the answer. */ }
4703
+ }
4704
+ rollback.classifierSource = "unresolved";
4705
+ };
4706
+
4707
+ const armRollbackLane = async (ctx: any): Promise<void> => {
4708
+ const probes = probeRollbackSurfaces(ctx?.sessionManager);
4709
+ rollback.probes = probes;
4710
+ rollback.armed = probes.armed;
4711
+ await resolveOverflowClassifier();
4712
+ if (!probes.armed) {
4713
+ safeNotify(
4714
+ ctx,
4715
+ `${brandNoun} overflow rollback is DISARMED for this session (${probes.failures.join("; ")}). ` +
4716
+ "An input-overflow rejection will abort the request instead of rolling the session back.",
4717
+ "warning",
4718
+ );
4719
+ }
4720
+ };
4721
+
4722
+ /**
4723
+ * The disarmed answer, and the only one: say so on the record and leave the session
4724
+ * behaving exactly as it did before the lane existed.
4725
+ */
4726
+ const refuseRollback = (reason: string, trigger: string, ctx: any): void => {
4727
+ emit("context.rollback", {
4728
+ trigger,
4729
+ armed: false,
4730
+ disarm_reason: reason,
4731
+ error_entry_id: null,
4732
+ old_leaf_id: null,
4733
+ new_leaf_id: null,
4734
+ entries_abandoned: 0,
4735
+ occupancy_tokens_before: measurements.lastProviderMeasurement?.tokens ?? null,
4736
+ tokens_rolled_back: 0,
4737
+ replayed: false,
4738
+ replay_skip_reason: "lane-disarmed",
4739
+ notice_chars: 0,
4740
+ attempt_ordinal: rollback.attempts,
4741
+ probes_passed: rollback.probes ? rollback.probes.failures.length === 0 : false,
4742
+ });
4743
+ safeNotify(
4744
+ ctx,
4745
+ `${brandNoun} could not roll back after a provider input-overflow rejection: ${reason}. ` +
4746
+ "The request was not retried.",
4747
+ "error",
4748
+ );
4749
+ };
4750
+
4751
+ /**
4752
+ * The fallback trigger, and the honest limit of it.
4753
+ *
4754
+ * `session_before_compact` never fires for a deployment that turned auto-compaction
4755
+ * off, so the primary trigger is deaf there. This is the second detector: pi's own
4756
+ * classifier over the assistant message at `message_end`, which is where the error
4757
+ * message first becomes visible. It records the episode and, if no compaction event
4758
+ * claims it, reports it.
4759
+ *
4760
+ * It does NOT roll back, and the reason is mechanical rather than cautious. The
4761
+ * pre-strip this lane depends on happens INSIDE `_checkCompaction`, which is exactly
4762
+ * the code an auto-compaction-disabled deployment never reaches, so on this path
4763
+ * agent state still carries the oversized window plus the error message. A bare
4764
+ * `branch()` there would leave the tree and agent state disagreeing, which is the
4765
+ * failure mode the whole design exists to avoid. Held, and reported as held.
4766
+ */
4767
+ const noteOverflowAtMessageEnd = (message: unknown, ctx: any): void => {
4768
+ if (!rollback.classifier || !message || typeof message !== "object") return;
4769
+ const window = budgetWindowFor(ctx) ?? DEFAULT_CONTEXT_WINDOW;
4770
+ let overflow = false;
4771
+ try { overflow = rollback.classifier(message, window) === true; }
4772
+ catch { return; }
4773
+ if (!overflow) return;
4774
+ rollback.pendingOverflow = { at: currentOrdinal(), entryId: null };
4775
+ };
4776
+
4777
+ /**
4778
+ * An overflow that no compaction event claimed: the auto-compaction-disabled shape.
4779
+ * Loud, terminal, and never silent.
4780
+ */
4781
+ const reportUnclaimedOverflow = (ctx: any): void => {
4782
+ if (!rollback.pendingOverflow) return;
4783
+ rollback.pendingOverflow = null;
4784
+ rollback.attempts += 1;
4785
+ refuseRollback(
4786
+ "the overflow arrived without a compaction event, so pi never stripped the failed message from agent " +
4787
+ "state and a tree rollback would leave the tree and agent state disagreeing (auto-compaction is off)",
4788
+ "message_end",
4789
+ ctx,
4790
+ );
4791
+ };
4792
+
4793
+ /**
4794
+ * ROLLBACK, COMMIT, REPLAY.
4795
+ *
4796
+ * pi has already stripped the trailing error message from agent state by the time
4797
+ * this fires, so agent state IS the rolled-back window and all that remains is to
4798
+ * move the session leaf to match. The label goes on FIRST, because a label appends at
4799
+ * the current leaf and advances it, so applied after the branch it would mark the
4800
+ * surviving path instead of the abandoned one.
4801
+ *
4802
+ * The commit deliberately does NOT happen here. The retried request runs its own
4803
+ * `context` pass, and that pass folds and commits before the payload is built. A
4804
+ * commit here would adjudicate a snapshot taken outside a projection pass and then be
4805
+ * evaluated again by the retry, which is two mutations for one episode.
4806
+ */
4807
+ const rebuiltMessageCount = (manager: Record<string, any>): number | null => {
4808
+ try {
4809
+ const rebuilt = manager.buildSessionContext?.();
4810
+ return Array.isArray(rebuilt?.messages) ? rebuilt.messages.length : null;
4811
+ } catch { return null; }
4812
+ };
4813
+
4814
+ const recoverFromOverflow = (event: Record<string, unknown>, ctx: any): void => {
4815
+ rollback.attempts += 1;
4816
+ rollback.pendingOverflow = null;
4817
+ if (!rollback.armed) {
4818
+ refuseRollback(
4819
+ rollback.probes?.failures.join("; ") ?? "the rollback probes never ran",
4820
+ "session_before_compact",
4821
+ ctx,
4822
+ );
4823
+ return;
4824
+ }
4825
+ const shape = overflowEventShape(event);
4826
+ if (!shape.ok) {
4827
+ refuseRollback(`the overflow event is missing ${shape.missing.join(", ")}`, "session_before_compact", ctx);
4828
+ return;
4829
+ }
4830
+ const branchEntries = ownValue(event, "branchEntries");
4831
+ // Copied, not aliased. `getBranch()` hands back the live array and the branch call
4832
+ // below truncates it in place, so a handler holding the original reference watches
4833
+ // the entries it is still reasoning about disappear underneath it.
4834
+ const entries = Array.isArray(branchEntries) ? [...branchEntries] : [];
4835
+ const errorEntry = findOverflowErrorEntry(entries);
4836
+ if (!errorEntry) {
4837
+ refuseRollback("the rejected assistant entry is not the branch tail", "session_before_compact", ctx);
4838
+ return;
4839
+ }
4840
+ const manager = ctx.sessionManager as Record<string, any>;
4841
+ const oldLeafId = manager.getLeafId();
4842
+ const sessionMessagesBefore = rebuiltMessageCount(manager);
4843
+ const abandoned = entries.slice(errorEntry.index);
4844
+ const rolledBackBytes = bytes(abandoned.flatMap((entry: any) => sessionEntryMessages(entry) ?? []));
4845
+ // Lineage first. The abandoned path stays in the tree, and it says why it was left.
4846
+ try {
4847
+ manager.appendLabelChange(errorEntry.id, `${entryTypePrefix} overflow rollback`);
4848
+ } catch (error) {
4849
+ refuseRollback(`the lineage label failed (${String(error)})`, "session_before_compact", ctx);
4850
+ return;
4851
+ }
4852
+ try {
4853
+ if (errorEntry.parentId === null) manager.resetLeaf();
4854
+ else manager.branch(errorEntry.parentId);
4855
+ } catch (error) {
4856
+ refuseRollback(`the branch failed (${String(error)})`, "session_before_compact", ctx);
4857
+ return;
4858
+ }
4859
+ // The invariant, measured rather than assumed: the rolled-back window has to be
4860
+ // exactly the failed entry shorter, or the retry sends something nobody built.
4861
+ const errorEntryMessages = (sessionEntryMessages(entries[errorEntry.index]) ?? []).length;
4862
+ const sessionMessagesAfter = rebuiltMessageCount(manager);
4863
+ const holds = sessionMessagesBefore === null || sessionMessagesAfter === null ||
4864
+ preStripHolds({ sessionMessagesBefore, sessionMessagesAfter, errorEntryMessages });
4865
+ const unanswered = unansweredToolCalls(entries.slice(0, errorEntry.index));
4866
+ let replaySkipReason: string | null = null;
4867
+ if (!holds) {
4868
+ replaySkipReason = `the rolled-back window came back ${sessionMessagesAfter} messages against the ` +
4869
+ `${(sessionMessagesBefore ?? 0) - errorEntryMessages} the rollback should have left`;
4870
+ } else if (unanswered.length) {
4871
+ replaySkipReason = `the rolled-back tail leaves ${unanswered.length} tool call(s) unanswered, and a user ` +
4872
+ "turn after an unsatisfied tool call is a malformed transcript for every provider";
4873
+ }
4874
+ const occupancyBefore = measurements.lastProviderMeasurement?.tokens ?? null;
4875
+ const notice = rollbackNoticeText({
4876
+ brandNoun,
4877
+ toolName,
4878
+ tokensRolledBack: estimatedTokens(rolledBackBytes),
4879
+ entriesAbandoned: abandoned.length,
4880
+ replayed: replaySkipReason === null,
4881
+ replaySkipReason,
4882
+ });
4883
+ // Exactly ONE steered message. Steering drains one at a time at the top of the
4884
+ // retried loop, so a second would be silently held to a turn that never comes.
4885
+ let replayed = false;
4886
+ if (replaySkipReason === null) {
4887
+ if (typeof pi.sendMessage !== "function") {
4888
+ replaySkipReason = "this pi build exposes no message verb, so the retry cannot be steered";
4889
+ } else {
4890
+ try {
4891
+ pi.sendMessage(
4892
+ { customType: `${entryTypePrefix}-overflow-recovery`, content: notice },
4893
+ { deliverAs: "steer" },
4894
+ );
4895
+ replayed = true;
4896
+ } catch (error) {
4897
+ replaySkipReason = `the recovery notice could not be queued (${String(error)})`;
4898
+ }
4899
+ }
4900
+ }
4901
+ if (!replayed) safeNotify(ctx, notice, "warning");
4902
+ const record = emit("context.rollback", {
4903
+ trigger: "session_before_compact",
4904
+ armed: true,
4905
+ disarm_reason: null,
4906
+ error_entry_id: errorEntry.id,
4907
+ old_leaf_id: typeof oldLeafId === "string" ? oldLeafId : null,
4908
+ new_leaf_id: errorEntry.parentId,
4909
+ entries_abandoned: abandoned.length,
4910
+ occupancy_tokens_before: occupancyBefore,
4911
+ tokens_rolled_back: estimatedTokens(rolledBackBytes),
4912
+ replayed,
4913
+ replay_skip_reason: replaySkipReason,
4914
+ notice_chars: notice.length,
4915
+ attempt_ordinal: rollback.attempts,
4916
+ probes_passed: true,
4917
+ });
4918
+ rollback.last = {
4919
+ seq: record.seq,
4920
+ errorEntryId: errorEntry.id,
4921
+ newLeafId: errorEntry.parentId,
4922
+ entriesAbandoned: abandoned.length,
4923
+ tokensRolledBack: estimatedTokens(rolledBackBytes),
4924
+ replayed,
4925
+ replaySkipReason,
4926
+ noticeChars: notice.length,
4927
+ attemptOrdinal: rollback.attempts,
4928
+ };
4929
+ // The tree moved, so the mapping is rebuilt from it. THEN the fold is armed for the
4930
+ // retried pass, in that order: a reload clears the recovery flags, and arming before
4931
+ // it would hand the retry a cleared one. The guards are waived in that pass because
4932
+ // a request that already did not fit has nothing left to economize.
4933
+ load(ctx, true);
4934
+ curation.pendingRejection = { status: 400, ordinal: currentOrdinal() };
4935
+ };
4936
+
3173
4937
  pi.on("session_before_compact", (event: Record<string, unknown>, ctx: any) => {
3174
4938
  const reason = ownValue(event, "reason");
4939
+ // The overflow reason is a provider rejection pi is prepared to retry, and it is
4940
+ // the rollback lane's trigger. Threshold and manual compaction are unchanged.
4941
+ if (reason === "overflow" && ownValue(event, "willRetry") === true) {
4942
+ nativeCompaction.lastThresholdDecision = {
4943
+ handled: true,
4944
+ retry: true,
4945
+ reason: `provider input overflow; ${contextBrand(brandNoun)} rolled the session tree back instead`,
4946
+ compactionReason: reason,
4947
+ nativeCompactionCompleted: false,
4948
+ };
4949
+ try { recoverFromOverflow(event, ctx); }
4950
+ catch (error) { suspendAutomatic(error, "overflow-rollback", ctx); }
4951
+ try { updateStatus(ctx); } catch { /* Recovery must survive presentation failure. */ }
4952
+ return { cancel: true };
4953
+ }
3175
4954
  if (reason === "manual") {
3176
4955
  nativeCompaction.lastThresholdDecision = {
3177
4956
  handled: false,
@@ -3192,6 +4971,7 @@ export function registerActiveContext(pi: any, options: {
3192
4971
  return { cancel: true };
3193
4972
  });
3194
4973
  pi.on("agent_settled", async (_event: unknown, ctx: any) => {
4974
+ reportUnclaimedOverflow(ctx);
3195
4975
  if (ladder.pendingManual && persistence.persisted && ladder.boundaryFailure === null) {
3196
4976
  cancelPreparation();
3197
4977
  persistence.state = clone(persistence.persisted);
@@ -3300,31 +5080,34 @@ export function registerActiveContext(pi: any, options: {
3300
5080
  pendingMarks: accounting.pending,
3301
5081
  eligibleMarks: accounting.eligibleMarks,
3302
5082
  retainedMarks: accounting.retainedMarks,
3303
- eligibleMarkedShare: accounting.eligibleFreedWindowShare,
3304
- markedShare: accounting.freedWindowShare,
5083
+ eligibleMarkedShare: accounting.eligibleFreedBudgetShare,
5084
+ markedShare: accounting.freedBudgetShare,
3305
5085
  available: true,
3306
5086
  automatic: {
3307
5087
  pressureRatio: measurements.latestRatio,
3308
- surfacing: {
3309
- rendered: false,
3310
- sources: surfacing.sources.map((source) => source.id),
3311
- log: clone(persistence.state.surfacing ?? []),
3312
- },
3313
- warningRatio: snapshot.policy.warningRatio,
3314
- toolFoldRatio: snapshot.policy.toolFoldRatio,
5088
+ // Status is a READ of the slate, never a delivery of one. `status` is a
5089
+ // read-only context action and the tool-batch safety scan is built on
5090
+ // read-only actions not writing durable state, so asking must not issue a
5091
+ // suggestion, spend the precision budget, or move a suppression counter. An
5092
+ // agent that asks gets the answer; only the push carriers are metered.
5093
+ surfacing: statusSurfacing(snapshot),
3315
5094
  refoldRatio: snapshot.policy.refoldRatio,
3316
5095
  chapterPrepareRatio: snapshot.policy.prepareRatio,
3317
5096
  hardFenceRatio: hardFenceRatio(snapshot),
3318
- responseReserve: Math.min(
3319
- ACTIVE_CONTEXT_POLICY.responseReserve,
3320
- Math.floor(snapshot.contextWindow * 0.1),
3321
- ),
5097
+ // The declared policy, reported. No action reads it back as a setting.
5098
+ thresholds: { ...snapshot.thresholds },
5099
+ zones: {
5100
+ freshBoundary: snapshot.freshBoundary,
5101
+ budgetTokens: snapshot.budgetTokens,
5102
+ },
5103
+ // What THIS runtime withheld, which is nothing once the deployment declared an
5104
+ // already-net budget. One number, from the same accounting the fence divides by.
5105
+ responseReserve: currentCapacity(ctx).outputReservation,
3322
5106
  windowSource: snapshot.windowSource,
3323
5107
  capacity: {
3324
5108
  ...currentCapacity(ctx),
3325
5109
  bytesPerToken: projectionCharsPerToken(),
3326
5110
  },
3327
- consolidationRatio: snapshot.policy.consolidationRatio,
3328
5111
  providerMeasurement: measurements.lastProviderMeasurement ? {
3329
5112
  tokens: measurements.lastProviderMeasurement.tokens,
3330
5113
  contextWindow: measurements.lastProviderMeasurement.contextWindow,
@@ -3346,8 +5129,7 @@ export function registerActiveContext(pi: any, options: {
3346
5129
  lastAutomaticAction: ladder.lastAutomaticAction,
3347
5130
  overBudgetReduction: ladder.overBudgetReduction ? clone(ladder.overBudgetReduction) : null,
3348
5131
  curation: {
3349
- occupancyThreshold: CURATION_OCCUPANCY_SHARE,
3350
- staleToolThreshold: CURATION_STALE_TOOL_SHARE,
5132
+ occupancyThreshold: thresholds.maxTarget,
3351
5133
  signals: curation.lastSignals ? { ...curation.lastSignals } : null,
3352
5134
  contextCalls: curation.contextCalls,
3353
5135
  receipts: curation.receipts.map((receipt) => ({ ...receipt })),
@@ -3357,28 +5139,34 @@ export function registerActiveContext(pi: any, options: {
3357
5139
  maxAttempts: OVERFLOW_RECOVERY_MAX_ATTEMPTS,
3358
5140
  pendingRejection: curation.pendingRejection ? { ...curation.pendingRejection } : null,
3359
5141
  last: curation.lastRecovery ? clone(curation.lastRecovery) : null,
5142
+ rollback: {
5143
+ armed: rollback.armed,
5144
+ probeFailures: rollback.probes ? [...rollback.probes.failures] : null,
5145
+ classifier: rollback.classifierSource,
5146
+ attempts: rollback.attempts,
5147
+ last: rollback.last ? clone(rollback.last) : null,
5148
+ },
3360
5149
  },
3361
5150
  foldSpanCap: MAX_FOLD_SPAN_CHARS,
3362
5151
  projectionBudgetTokens: currentCapacity(ctx).budgetTokens,
3363
5152
  projectionCharsPerToken: projectionCharsPerToken(),
3364
5153
  projectionEstimatorErrorShare: estimatorErrorShare(),
3365
5154
  projectionExpectedInflowTokens: expectedInflowTokens(),
3366
- projectionMarginTokens: measurements.lastProjectedChars === null
5155
+ // What the fence actually weighed, not a second arithmetic over the same bytes:
5156
+ // once occupancy is anchored, re-deriving it here from chars alone would report
5157
+ // a number no decision was made on.
5158
+ projectionMarginTokens: measurements.lastProjectedEstimate === null
3367
5159
  ? 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()),
5160
+ : projectionMarginTokens(measurements.lastProjectedEstimate, currentCapacity(ctx).window),
5161
+ projectionEstimatedTokens: measurements.lastProjectedEstimate,
5162
+ projectionEstimateBasis: measurements.lastProjectedEstimateBasis,
5163
+ projectionAnchorTokens: measurements.projectionAnchor?.tokens ?? null,
3375
5164
  automaticSuspended: ladder.automaticFailure !== null,
3376
5165
  automaticFailure: ladder.automaticFailure ? clone(ladder.automaticFailure) : null,
3377
5166
  lastCompactionDecision: nativeCompaction.lastThresholdDecision,
3378
5167
  scheduling: schedulingStatus({
3379
5168
  snapshot,
3380
5169
  state: persistence.state,
3381
- mode: foldScheduling,
3382
5170
  ratio: measurements.latestRatio,
3383
5171
  }),
3384
5172
  nativeSummaries: "disabled",
@@ -3390,12 +5178,10 @@ export function registerActiveContext(pi: any, options: {
3390
5178
  // THE stream, as the durable entries carry it. One shape, one convention.
3391
5179
  events: clone(instrumentation.ledger.events),
3392
5180
  },
3393
- foldPeekResults,
3394
5181
  peek: {
3395
5182
  defaultMaxBytes: PEEK_DEFAULT_MAX_BYTES,
3396
- lifetime: "append-only",
5183
+ lifetime: "append-only; the copy is reclaimed at the next commit unless pinned",
3397
5184
  },
3398
- freeHarvest: blockingTools.size === 0 ? "disabled" : "enabled",
3399
5185
  pressureSource: "last-successful-provider-response-only",
3400
5186
  postOverflowCallback: "blocked-while-stock-native-compaction-is-disabled",
3401
5187
  sameOperationRetry: false,
@@ -3414,12 +5200,12 @@ export function registerActiveContext(pi: any, options: {
3414
5200
  ...(detail === "tree" ? { tree: foldTreeDetail(snapshot, persistence.state).slice(statusOffset) } : {}),
3415
5201
  }, typeof detail === "string" ? detail : null, statusOffset));
3416
5202
  }
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.
5203
+ // Peeking or expanding a surfaced fold is the ACTED label, joined to the offer that
5204
+ // named it. It stays ephemeral: the ledger rides the next persisted state, never an
5205
+ // entry of its own, and the used/ignored grades land when the window closes.
3420
5206
  const noteSurfacingAccept = (id: string): void => {
3421
5207
  if (!persistence.state) return;
3422
- persistence.state = acceptSurfacingSuggestion(persistence.state, id, surfacingOrdinal(snapshot));
5208
+ persistence.state = noteSurfacingAction(persistence.state, id, markOrdinal(snapshot));
3423
5209
  };
3424
5210
  if (action === "peek") {
3425
5211
  const id = String(params.id ?? "").trim();
@@ -3592,7 +5378,7 @@ export function registerActiveContext(pi: any, options: {
3592
5378
  // An expand with marks pending opens the commit epoch: the restore and the
3593
5379
  // batch of folds then cost one rewrite between them instead of two.
3594
5380
  let epochApplied: unknown[] = [];
3595
- if (epochScheduling && action === "expand" && pendingMarks(persistence.state).length) {
5381
+ if (action === "expand" && pendingMarks(persistence.state).length) {
3596
5382
  const result = await commitPendingMarks({
3597
5383
  snapshot,
3598
5384
  state: persistence.state,
@@ -3637,12 +5423,39 @@ export function registerActiveContext(pi: any, options: {
3637
5423
  }
3638
5424
  if (action === "protect" || action === "unprotect") {
3639
5425
  const ids = stringIds(params.ids);
3640
- await persistManual(protectEvidence(snapshot, persistence.state, ids, action === "protect"), action, ctx);
5426
+ const refsBefore = persistence.state.protected.length;
5427
+ const nextProtection = protectEvidence(snapshot, persistence.state, ids, action === "protect");
5428
+ if (action === "protect") {
5429
+ // The pin ceiling: protect is a promise to hold bytes raw through every fold,
5430
+ // and a promise with no mass bound can pin the window solid. The refusal is
5431
+ // corrective, not denial: it names the cap and the release valve.
5432
+ const capacity = servingCapacity(snapshot.contextWindow);
5433
+ const prospectiveShare = capacity.budgetTokens > 0
5434
+ ? estimatedTokens(explicitProtectedMass(snapshot, nextProtection).bytes) / capacity.budgetTokens
5435
+ : 0;
5436
+ if (prospectiveShare > MAX_PINNED_SHARE) {
5437
+ throw new Error(
5438
+ `protect refused: these pins would hold ${Math.round(prospectiveShare * 100)}% of the ` +
5439
+ `working window raw, past the ${Math.round(MAX_PINNED_SHARE * 100)}% pinned-share cap. ` +
5440
+ 'Release earlier pins with {"action":"unprotect","ids":["<entry-id>"]} first; ' +
5441
+ "folding keeps entries exactly recoverable without pinning them.");
5442
+ }
5443
+ }
5444
+ await persistManual(nextProtection, action, ctx);
5445
+ const refsAfter = persistence.state.protected.length;
5446
+ emit("context.protect", {
5447
+ protect: action === "protect",
5448
+ ids: ids.join(","),
5449
+ id_count: ids.length,
5450
+ protected_refs_before: refsBefore,
5451
+ protected_refs_after: refsAfter,
5452
+ });
3641
5453
  updateStatus(ctx);
3642
5454
  return toolPayload({
3643
5455
  version: 1,
3644
5456
  action,
3645
5457
  ids,
5458
+ protectedRefs: refsAfter,
3646
5459
  activation: "durable immediately; projected on the next model call in this same turn",
3647
5460
  });
3648
5461
  }
@@ -3714,172 +5527,135 @@ export function registerActiveContext(pi: any, options: {
3714
5527
  }
3715
5528
  }
3716
5529
  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({
5530
+ // Resolve every brief now, while the source is in hand, so the commit epoch
5531
+ // itself stays deterministic and free of provider calls. A span that is not
5532
+ // eligible YET cannot go through preparation at all, so it takes the
5533
+ // deterministic brief: the mark is a decision, and refusing to record it
5534
+ // because the span is momentarily fresh is exactly the drop being fixed.
5535
+ const marks: Array<Record<string, unknown>> = [];
5536
+ let staged = persistence.state;
5537
+ for (const item of resolved) {
5538
+ const { candidate } = item;
5539
+ // ACCEPT AND HOLD. A span that is still fresh or protected is not a refusal:
5540
+ // the mark is a standing decision, it is recorded now, and it folds at the
5541
+ // first commit after the span ages out. Refusal is reserved for a mark that
5542
+ // cannot be constructed at all.
5543
+ // A REPEATED DECISION IS INERT. The fold id is derived from the span, so a
5544
+ // span the ladder already prepared carries that exact id in the forest
5545
+ // already, and preparing it a second time throws "Prepared fold already
5546
+ // exists" out of the tool call. Under the old cadence commits were frequent
5547
+ // enough to drain prepared folds before a second decision could land on one;
5548
+ // the quiet runtime commits far less often, so the collision became reachable
5549
+ // in ordinary use -- the ladder marks a span, the agent then folds the same
5550
+ // span, and the agent's call fails. Marking is a standing decision about a
5551
+ // span, and deciding the same thing twice has to be a no-op.
5552
+ const alreadyPrepared = staged.folds.some((fold) =>
5553
+ fold.id === foldIdFor(candidate.kind, candidate.parts));
5554
+ const deferred = refsProtected(candidate.sourceRefs, staged, snapshot) ||
5555
+ (candidate.kind === "tool-result" &&
5556
+ toolRefsProtected(candidate.sourceRefs, staged, snapshot));
5557
+ const briefed = !deferred && !alreadyPrepared
5558
+ ? await prepareFold({
3758
5559
  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
- });
5560
+ snapshot,
5561
+ state: staged,
5562
+ generation: lifecycle.generation,
5563
+ brief: item.brief,
5564
+ summarize: observedSummarize,
5565
+ ctx,
5566
+ signal,
5567
+ })
5568
+ : null;
5569
+ const mark = foldMarkFor({
5570
+ candidate,
5571
+ brief: briefed?.fold.brief ?? item.brief ?? ladderBrief(snapshot, staged, candidate),
5572
+ briefProvenance: briefed?.fold.provenance ??
5573
+ (item.brief ? { kind: "supplied" } : { kind: "deterministic" }),
5574
+ origin: "agent",
5575
+ ordinal: markOrdinal(snapshot),
5576
+ });
5577
+ const addition = addPendingMark(staged, mark);
5578
+ if (!addition.added) {
5579
+ marks.push({ id: mark.id, kind: mark.kind, ok: false, deferred: false, reason: addition.reason });
5580
+ continue;
3785
5581
  }
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) => ({
5582
+ staged = addition.state;
5583
+ marks.push({
3793
5584
  id: mark.id,
3794
5585
  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
5586
+ ok: true,
5587
+ deferred,
5588
+ ...(deferred
3809
5589
  ? {
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 } : {}),
5590
+ scheduled: "the span is still in the fresh window; this mark is held and folds at the " +
5591
+ "first commit after it ages out",
3816
5592
  }
3817
5593
  : {}),
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 },
5594
+ brief: mark.brief,
5595
+ provenance: normalizeLegacyProvenance(mark.briefProvenance),
3860
5596
  });
3861
5597
  }
3862
- await persistManual(persistence.state, "fold", ctx);
5598
+ await persistManual(staged, action, ctx);
3863
5599
  updateStatus(ctx);
3864
- const single = applied.length === 1 ? applied[0] : null;
5600
+ const accounting = markAccounting(snapshot, persistence.state);
5601
+ // MECHANISM 3. The projection is byte-frozen between fold events, so this reply
5602
+ // is the only cache-free channel left: it carries the whole picture, not just
5603
+ // an acknowledgement of the spans this call named.
5604
+ const held = pendingMarks(persistence.state).map((mark) => ({
5605
+ id: mark.id,
5606
+ kind: mark.kind,
5607
+ tokens: estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)),
5608
+ }));
5609
+ const remainder = unmarkedRemainder(snapshot, persistence.state, projectionCharsPerToken());
5610
+ // The single-span shape is the head of the batched one: one call carrying one
5611
+ // span answers exactly as it always did, and a batch adds fields rather than
5612
+ // replacing them.
5613
+ const only = marks.length === 1 && marks[0].ok === true ? marks[0] : null;
3865
5614
  return toolPayload({
3866
5615
  version: 1,
3867
5616
  action,
3868
- scheduling: "immediate",
3869
- ...(single
5617
+ scheduling: "epoch",
5618
+ ok: marks.some((mark) => mark.ok === true),
5619
+ deferredMarks: marks.filter((mark) => mark.deferred === true).length,
5620
+ ...(only
3870
5621
  ? {
3871
- id: single.id,
3872
- kind: single.kind,
3873
- brief: single.brief,
3874
- provenance: single.provenance,
3875
- expand: single.expand,
5622
+ id: only.id,
5623
+ kind: only.kind,
5624
+ brief: only.brief,
5625
+ provenance: only.provenance,
5626
+ deferred: only.deferred,
5627
+ ...(only.scheduled ? { scheduled: only.scheduled } : {}),
3876
5628
  }
3877
5629
  : {}),
3878
- folds: applied,
5630
+ marks,
5631
+ // Never silently reinterpreted: a span the runtime moved says so, here.
3879
5632
  corrections,
3880
5633
  argumentsSha256: executionArgumentsSha256,
3881
5634
  durableRevision: persistence.state.revision,
3882
- activation: "durable immediately; projected on the next model call in this same turn",
5635
+ pendingMarks: accounting.pending,
5636
+ eligibleMarks: accounting.eligibleMarks,
5637
+ retainedMarks: accounting.retainedMarks,
5638
+ estimatedFreedWindowShare: accounting.freedBudgetShare,
5639
+ estimatedEligibleWindowShare: accounting.eligibleFreedBudgetShare,
5640
+ estimatedRewriteTokens: accounting.rewriteTokens,
5641
+ held,
5642
+ heldNote: "held until it ages out or the next fold event",
5643
+ unmarkedSpans: remainder.spans,
5644
+ unmarkedTokens: remainder.tokens,
5645
+ unmarkedShare: remainder.share,
5646
+ unmarkedCandidates: remainder.candidates,
5647
+ // The acknowledgement rides the tool result, so it PERSISTS in context the way
5648
+ // every tool result does. Default on;
5649
+ // `guidance.actionResponses: false` returns the accounting without the prose.
5650
+ ...(guidance.actionResponses ? {
5651
+ awareness: markAwarenessText({ held, remainder, toolName, brandNoun }),
5652
+ activation: "accepted as pending marks; no context bytes moved, and nothing else in your " +
5653
+ "context changed either. They apply together at the next commit epoch, which the runtime " +
5654
+ "opens at the fold event. " +
5655
+ "A mark over a still-fresh span is held, not refused: it is scheduled, and it folds at the " +
5656
+ "first commit after that span ages out of the fresh window. " +
5657
+ "Mark several spans in one call: this whole picture comes back with each one.",
5658
+ } : {}),
3883
5659
  });
3884
5660
  }
3885
5661
  throw new Error(`Unknown ${toolName} action '${action}'`);
@@ -3899,7 +5675,7 @@ export function registerActiveContext(pi: any, options: {
3899
5675
  };
3900
5676
 
3901
5677
  const toolHandler = async (
3902
- _toolCallId: string,
5678
+ toolCallId: string,
3903
5679
  params: Record<string, unknown>,
3904
5680
  signal: AbortSignal,
3905
5681
  _onUpdate: unknown,
@@ -3915,6 +5691,9 @@ export function registerActiveContext(pi: any, options: {
3915
5691
  action,
3916
5692
  ok,
3917
5693
  error,
5694
+ // The join key against the worker's own tool-call log: one logical row per
5695
+ // model-emitted invocation, matched by id and never by order or clock.
5696
+ tool_call_id: toolCallId,
3918
5697
  marks_requested: requestedMarkCount(params),
3919
5698
  corrections_applied: corrections.length,
3920
5699
  arguments_sha256: sha256Value(params ?? {}),
@@ -3972,11 +5751,39 @@ export function registerActiveContext(pi: any, options: {
3972
5751
  if (!lifecycle.latestSnapshot) lifecycle.latestSnapshot = snapshot;
3973
5752
  const divider = args.indexOf(" -- ");
3974
5753
  const selector = (divider >= 0 ? args.slice(0, divider) : args).trim();
5754
+ // THE USER COMMIT. One word, and the only commit verb any caller has: the model
5755
+ // tool still has no commit action, because a verb the runtime is entitled to
5756
+ // overrule is surface without function. A user is not overruled.
5757
+ //
5758
+ // Below maxTarget it applies the eligible marks in hand and stops -- no automatic
5759
+ // top-up, because the user asked to bank the curation they made, not to have the
5760
+ // runtime pick more. At or above maxTarget it is the ordinary event: the planner
5761
+ // may top up toward minTarget. Neither path touches freshness, pins, exactness or
5762
+ // the provider fence.
5763
+ if (selector === "commit") {
5764
+ const capacity = servingCapacity(snapshot.contextWindow);
5765
+ const occupancy = capacity.usedTokens !== null && capacity.budgetTokens > 0
5766
+ ? capacity.usedTokens / capacity.budgetTokens
5767
+ : null;
5768
+ const topUp = occupancy !== null && occupancy >= thresholds.maxTarget;
5769
+ const committed = await runCommitEpoch(snapshot, "user-command", topUp, measurements.latestRatio, true);
5770
+ try { await persist(ctx); } catch { /* The commit already reported its own outcome. */ }
5771
+ updateStatus(ctx);
5772
+ safeNotify(
5773
+ ctx,
5774
+ committed
5775
+ ? `Committed ${ownValue(committed, "applied_marks") ?? 0} mark(s)${topUp ? " with automatic top-up" : ""}. ` +
5776
+ "Exact source remains expandable."
5777
+ : "Nothing eligible to commit: no pending mark is outside the fresh tail.",
5778
+ "info",
5779
+ );
5780
+ return;
5781
+ }
3975
5782
  const supplied = (divider >= 0 ? args.slice(divider + 4) : "").trim() || undefined;
3976
5783
  const ids = selector ? selector.replace(/\.\./g, " ").split(/[\s,]+/).filter(Boolean) : [];
3977
5784
  const candidate = ids.length
3978
5785
  ? manualFoldCandidate(snapshot, persistence.state!, ids)
3979
- : selectAutomaticChapter(snapshot, persistence.state!) ?? selectAutomaticToolBatch(snapshot, persistence.state!, 1)[0] ?? null;
5786
+ : selectAutomaticChapter(snapshot, persistence.state!) ?? selectAutomaticToolBatch(snapshot, persistence.state!)[0] ?? null;
3980
5787
  if (!candidate) throw new Error("No exact stale rescue span is currently eligible");
3981
5788
  const stateBefore = persistence.state!;
3982
5789
  const persistedBefore = persistence.persisted ? clone(persistence.persisted) : null;
@@ -4027,7 +5834,7 @@ export function registerActiveContext(pi: any, options: {
4027
5834
  name: toolName,
4028
5835
  label: toolLabel,
4029
5836
  allowedActions: allowedToolActions,
4030
- fullSurface: allowedToolActions.length === defaultToolActions.length,
5837
+ fullSurface: true,
4031
5838
  maxBriefChars: ACTIVE_CONTEXT_POLICY.maxBriefChars,
4032
5839
  statusDetails: ["fold_candidates", "tree", "folds", "objects"],
4033
5840
  minPeekSliceBytes: PEEK_MIN_SLICE_BYTES,
@@ -4049,6 +5856,7 @@ export function registerActiveContext(pi: any, options: {
4049
5856
  lifecycle.generation += 1;
4050
5857
  lifecycle.shuttingDown = true;
4051
5858
  cancelPreparation();
5859
+ cancelBriefUpgrades();
4052
5860
  persistence.state = null;
4053
5861
  persistence.persisted = null;
4054
5862
  lifecycle.latestSnapshot = null;
@@ -4056,5 +5864,5 @@ export function registerActiveContext(pi: any, options: {
4056
5864
  try { ctx.ui?.setStatus?.(entryTypePrefix, undefined); } catch { /* Shutdown cannot be blocked by UI. */ }
4057
5865
  });
4058
5866
 
4059
- return { projectionCandidates, registerSuggestionSource };
5867
+ return { projectionCandidates };
4060
5868
  }