pi-fold 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,5 @@
1
1
  import type { EvidenceRef } from "../json.ts";
2
2
 
3
- /**
4
- * THE deployment identity. One package, one brand, no constructor options.
5
- *
6
- * These were five public knobs through 1.0.2, on the theory that the runtime was a
7
- * generic framework a host would brand. It is not: it is a branded package with one
8
- * deployment, and the knobs bought nothing but a namespace a consumer could strand its
9
- * own durable state under. They are hardwired here and REFUSED by name at the package
10
- * entry. An internal seam survives on `registerActiveContext`, because the neutrality
11
- * gate has to register a synthetic brand to prove none of it leaks into these defaults,
12
- * and the experiment harness registers this same identity explicitly so the sealed runs
13
- * keep their entry types.
14
- */
15
3
  export const DEFAULT_ACTIVE_CONTEXT_TOOL_NAME = "pi_fold_context";
16
4
  export const DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX = "pi-fold-active-context";
17
5
  export const DEFAULT_ACTIVE_CONTEXT_TOOL_LABEL = "pi-fold Active Context";
@@ -44,24 +32,7 @@ export function contextBrand(brandNoun: string): string {
44
32
  const DEFAULT_ENTRY_NAMESPACE = entryTypeNamespace(DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX);
45
33
  export const ACTIVE_CONTEXT_STATE_ENTRY = `${DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX}-state`;
46
34
  export const ACTIVE_CONTEXT_FOLD_RECORD_ENTRY = `${DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX}-fold-record`;
47
- export const NATIVE_COMPACTION_RECEIPT_ENTRY = `${DEFAULT_ENTRY_NAMESPACE}-native-compaction-receipt`;
48
- export const NATIVE_COMPACTION_DECISION_ENTRY = `${DEFAULT_ENTRY_NAMESPACE}-native-compaction-decision`;
49
35
  export const PROVIDER_CONTEXT_MEASUREMENT_ENTRY = `${DEFAULT_ENTRY_NAMESPACE}-provider-context-measurement`;
50
- export const ACTIVE_CONTEXT_STATUS_KEY = DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX;
51
- /**
52
- * The action surface, whole. There was a second, narrower list while immediate
53
- * scheduling existed; epoch is the only scheduler now, so marks always exist and
54
- * `unmark` is an ordinary verb: a mark is a standing decision rather than a one-shot
55
- * attempt, so withdrawing one needs a verb of its own. The correction verbs are here
56
- * for the same reason: curation the agent cannot fix afterwards is curation it will
57
- * not risk making.
58
- *
59
- * There is deliberately NO agent-callable commit verb. Marking is the agent's job and
60
- * folding is the runtime's, and a verb the runtime is entitled to overrule is surface
61
- * without function: measured 2026-08-07 (rep 17), the agent called commit twice and the
62
- * runtime correctly held it both times. The internal commit paths -- the epoch commit,
63
- * the gated commit, the fence and the recovery lane -- are unchanged and unexposed.
64
- */
65
36
  export const ACTIVE_CONTEXT_TOOL_ACTIONS = Object.freeze([
66
37
  "status", "peek", "fold", "expand", "refold", "protect", "unprotect",
67
38
  "rebrief", "reboundary", "unmark",
@@ -71,177 +42,39 @@ export const USER_RESCUE_MAX_SOURCE_CHARS = 512_000;
71
42
  export const DEFAULT_CONTEXT_WINDOW = 272_000;
72
43
  export const EXPAND_LEASE_GENERATIONS = 8;
73
44
  export const MAX_EXPAND_LEASES = 64;
74
- export const MAX_ADVISORY_DELIVERIES_PER_MILESTONE = 16;
75
-
76
- // Surfacing SELECTOR structure. These stay INTERNAL constants rather than public
77
- // options: they are the knobs the surfacing experiment is meant to settle, and an
78
- // option surface fixed before the acted/used/ignored data exists would freeze a guess.
79
- // The per-request ephemeral carrier that once rendered the slate is deleted, and its
80
- // enable flag with it; the slate rides the commit-boundary carriers and status only.
81
-
82
- /** BM25's canonical parameters, and memex's fold lane runs on exactly these. */
83
- export const SURFACING_BM25_K1 = 1.5;
84
- export const SURFACING_BM25_B = 0.75;
85
- /**
86
- * The divergence trigger, as three numbers on one 0..1 scale.
87
- *
88
- * The scale is the share of the query's own saturation ceiling a document reaches, so
89
- * 1.0 would be every query term saturated in one document and a strong real match lands
90
- * in the third of the range these numbers sit in. CONTENT_HIT is the cry-wolf guard:
91
- * below it the fold does not match the task at all and nothing else matters. BRIEF_HIT
92
- * is the visibility line: at or above it the placeholder already says what the fold
93
- * holds, so the agent can see it and a suggestion repeats the window back to itself.
94
- * The MARGIN keeps the pair from meeting in the middle, where both readings are weak
95
- * and neither says anything.
96
- */
97
- export const SURFACING_CONTENT_HIT = 0.25;
98
- export const SURFACING_BRIEF_HIT = 0.15;
99
- export const SURFACING_DIVERGENCE_MARGIN = 0.10;
100
- /** Precision budget, not a rate limit: at most one suggestion per delivery point. */
101
- export const SURFACING_SLATE_SIZE = 1;
102
- /** One clock. It is the outcome window AND the cooldown: no re-offer before an answer. */
103
- export const SURFACING_OUTCOME_WINDOW_ORDINALS = 12;
104
- /** Offered this many times and never taken, and the fold leaves the candidate set. */
105
- export const SURFACING_IGNORE_LIMIT = 2;
106
- /** Distinct content-only terms downstream that grade an acted suggestion as used. */
107
- export const SURFACING_PROVENANCE_TERMS = 3;
108
- export const SURFACING_INTENT_CHARS = 1_200;
109
- export const SURFACING_INTENT_ARGUMENT_CHARS = 120;
110
- export const SURFACING_INTENT_RECENCY_SHARE = 0.5;
111
- export const SURFACING_INTENT_ARGUMENT_KEYS: readonly string[] = Object.freeze([
112
- "path", "file_path", "query", "pattern", "command", "brief", "id",
113
- ]);
114
- export const SURFACING_MAX_CONTENT_CHARS = 20_000;
115
- export const SURFACING_MAX_LEDGER_RECORDS = 256;
116
- export const SURFACING_HOOK_CHARS = 160;
117
-
118
- // Conservative LOWER bound on UTF-8 bytes per provider token, used only to cap
119
- // the protected byte tail as a share of a small window; never to estimate usage.
120
- export const BYTES_PER_TOKEN_FLOOR = 2;
121
-
122
- // Two-phase fold scheduling, and the only scheduler. A provider prefix cache is
123
- // positional: any mid-window edit invalidates every byte after it, so the cost of
124
- // folding is dominated by how OFTEN the projection changes, not by how much it saves.
125
- // A fold decision is therefore a free MARK, and a later COMMIT applies every pending
126
- // mark in one rewrite. The alternative shipped as an option through 1.0.2 and was
127
- // deleted once measured: applying each fold where it was made cost 54 prefix rewrites
128
- // and a 0.193 pooled cache share against epoch's 0.919 on the same task.
129
-
130
- // Epoch knobs stay INTERNAL constants for the same reason the surfacing knobs do:
131
- // they are what the round-2 cost measurement exists to settle, and an option surface
132
- // fixed before that data would freeze a guess.
133
- /**
134
- * The reclaim floor, and the single constant that decides whether a commit is worth
135
- * firing at all.
136
- *
137
- * Measured on memex (19,082 calls, 254 sessions): a token-weighted pooled cache share
138
- * of 89.9% across two provider wires, with steady-state requests at 91.6% and any
139
- * request following a mutation at 15.7%. A fold costs exactly what one user message
140
- * costs. Their mutations are 5.3% of requests; rep 14's were about 34%, and that
141
- * FREQUENCY difference is the entire gap between their 90% and our 64% -- the
142
- * per-mutation penalty is the same on both.
143
- *
144
- * So a commit that would free less than this share of the truthful budget does not
145
- * fire: it defers and accumulates until it is worth a full prefix. A request whose
146
- * projection exceeds the provider input budget is rejected outright, so recovery must
147
- * produce a window that fits; the hard fence and overflow recovery fire regardless.
148
- */
45
+
149
46
  export const COMMIT_RECLAIM_FLOOR_SHARE = 0.02;
150
47
 
151
- /**
152
- * The pin ceiling (Shane 2026-08-09): protect may hold at most this share of the
153
- * truthful serving budget raw. Protect was a per-entry promise with no mass bound,
154
- * so a protect-happy agent could pin the window solid and leave every commit
155
- * nothing to reclaim; past the cap, protect refuses with the cap named and
156
- * unprotect is the release valve. A constant, not a knob.
157
- */
158
48
  export const MAX_PINNED_SHARE = 0.25;
159
49
 
160
- // ---------------------------------------------------------------------------
161
- // THE THERMOSTAT. One declared object, one denominator, four numbers.
162
- //
163
- // Occupancy used to be a cluster: a trigger share of the budget, a post-commit target
164
- // share of the budget, a freeing floor stated as a share of the WINDOW, a fresh tail
165
- // stated in turns and bytes with a small-window byte cap, and a consolidation count.
166
- // Three of those divided by a different denominator than the other two, so the same
167
- // English word meant two numbers five to ten points apart at large windows. They are
168
- // one decision, so they are one object, and every one of them is now a proportion of
169
- // `capacityAccounting.budgetTokens` -- the truthful serving budget, the same value the
170
- // fence, the estimator and the trigger already read.
171
- //
172
- // There was a fifth, `staleTail`, that declared how far back automation could reach as a
173
- // byte prefix of the window. It is deleted (Shane 2026-08-10): automatic foldability is a
174
- // CLASS a span belongs to, not a position it occupies, so there is no width to declare.
175
- //
176
- // User-set, never agent-set (Sol 2026-08-09): an agent that can widen freshTail or move
177
- // maxTarget to the fence neutralizes the governor without ever calling a verb it is not
178
- // entitled to. Agent authority stays where it already is -- mark, unmark, protect,
179
- // unprotect -- and status may REPORT these values.
180
- // ---------------------------------------------------------------------------
181
-
182
- /** The four numbers that decide when a commit fires, how deep it cuts, and what stays raw. */
183
50
  export interface ActiveContextThresholds {
184
- /** Occupancy of the serving budget that fires an automatic commit. */
185
51
  maxTarget: number;
186
- /** Occupancy an automatic commit folds down toward. The hysteresis gap is maxTarget - minTarget. */
187
52
  minTarget: number;
188
- /** Newest share of the serving budget where nothing folds, marked or not. */
189
53
  freshTail: number;
190
- /** Unpinned member folds at or above which placeholders become span material. */
191
54
  consolidateAfter: number;
192
55
  }
193
56
 
194
- /**
195
- * The proven values, and the reason each one is the number it is.
196
- *
197
- * maxTarget 0.80: Shane's RULING after rep 15. Below this line the runtime is quiet --
198
- * nothing folds automatically at all -- so the line is where the fold event should
199
- * happen, and the remaining fifth of the budget is the runway the commit itself spends.
200
- *
201
- * minTarget 0.35: the thermostat's lower line, and the reason event spacing is
202
- * structural rather than hoped for. Rep 13's crumb ratchet fired, freed a little and
203
- * re-fired, so the window climbed a staircase of mutations; folding down to a lower
204
- * line means the next event cannot arrive until inflow has refilled a 45-point gap.
205
- * Rep 13's largest measured inflow step was 27,815 tokens against a 400,000 window,
206
- * about 7 points, so six worst-case steps land between events.
207
- *
208
- * freshTail 0.02: today's protected tail, re-denominated. The proven tail was 24,000
209
- * serialized bytes; at the estimator's 4 bytes per token that is 6,000 tokens, and
210
- * against the reference serving budget of 383,616 tokens (a 400,000-token window minus
211
- * the 16,384-token response reserve) that is 1.564%, rounded UP to 0.02 so the
212
- * re-denomination never narrows protection at the reference deployment.
213
- *
214
- * consolidateAfter 10: the proven count (the retired width rung counted the same roots
215
- * against the same line). Placeholder briefs are about 300 tokens, so ten siblings cost
216
- * under 1% of budget, and ten keeps an overnight session within two hops of any
217
- * verbatim byte. A count, not a proportion: table-of-contents readability does not
218
- * scale with window size, and a proportion would add a second denominator to the one
219
- * thing this object exists to make singular.
220
- */
221
57
  export const DEFAULT_THRESHOLDS: Readonly<ActiveContextThresholds> = Object.freeze({
222
58
  maxTarget: 0.80,
223
- minTarget: 0.35,
59
+ // 0.20, down from 0.35 (Shane 2026-08-14). The cut depth is what sets the epoch
60
+ // cadence: every commit rewrites the prefix from the earliest folded byte, so the
61
+ // cost of an epoch is nearly flat in how deep it cuts, while the relief it buys is
62
+ // the whole distance to the trigger. sol-20260814-traps rep 1 ran six commits, each
63
+ // freeing to 0.35 and refilling in two to three stages; at 0.20 the same run shape
64
+ // owes roughly a quarter fewer epochs, which is a quarter fewer full-prefix
65
+ // rewrites, the dominant self-inflicted cache break. The floor is a target the
66
+ // class law outranks, not a guarantee: a session pinned to the 0.25 ceiling
67
+ // beside the 0.02 fresh tail legally holds 0.27 and lands above this number,
68
+ // and the epoch receipt reports targetBudgetShare beside actualFreedBudgetShare
69
+ // so the shortfall is a readable fact per epoch rather than a silent miss. The
70
+ // validation laws below bind exactly as before.
71
+ minTarget: 0.20,
224
72
  freshTail: 0.02,
225
73
  consolidateAfter: 10,
226
74
  });
227
75
 
228
- /**
229
- * The smallest serving budget this package supports, in tokens.
230
- *
231
- * Derived from the package's own two largest bounded replies: one status payload
232
- * (CONTEXT_STATUS_RESPONSE_BYTES, 24,000) plus one default peek
233
- * (PEEK_DEFAULT_MAX_BYTES, 16,000) is 40,000 bytes, which at the estimator's 4 bytes
234
- * per token is 10,000 tokens. A budget that cannot hold the largest pair of answers
235
- * this package can emit back to back cannot serve the surface it ships, so registration
236
- * refuses it by name rather than folding its way into an unservable session.
237
- */
238
76
  export const MINIMUM_SUPPORTED_BUDGET_TOKENS = 10_000;
239
77
 
240
- /**
241
- * The narrowest fresh tail that still protects anything, in tokens: one minimum tool
242
- * result (`minToolChars` 2,000 bytes) at the estimator's 4 bytes per token. A tail
243
- * thinner than the smallest thing automation will ever fold protects nothing.
244
- */
245
78
  export const MINIMUM_FRESH_TAIL_TOKENS = 500;
246
79
 
247
80
  export class ThresholdPolicyError extends Error {
@@ -260,14 +93,6 @@ function thresholdProportion(value: unknown, field: string): number {
260
93
  return value;
261
94
  }
262
95
 
263
- /**
264
- * The whole object, validated atomically, or an error naming the invariant that failed.
265
- *
266
- * Atomic because these are four quarters of one decision: validating them one at a time
267
- * is how a trigger moved from 0.50 to 0.80 while its target stayed at 0.35 and the
268
- * docstring kept arguing a 15-point gap that had become 45. Never clamped: a policy that
269
- * cannot be served is a registration error, not a value to quietly rewrite (Sol).
270
- */
271
96
  export function resolveThresholds(value: unknown): ActiveContextThresholds {
272
97
  if (value === undefined) return { ...DEFAULT_THRESHOLDS };
273
98
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -292,25 +117,19 @@ export function resolveThresholds(value: unknown): ActiveContextThresholds {
292
117
  if (typeof consolidateAfter !== "number" || !Number.isInteger(consolidateAfter) || consolidateAfter < 1) {
293
118
  throw new ThresholdPolicyError("consolidateAfter", "thresholds.consolidateAfter must be a positive integer count");
294
119
  }
295
- // L < M: the thermostat needs a gap to fold down into.
296
120
  if (!(minTarget < maxTarget)) {
297
121
  throw new ThresholdPolicyError("minTarget<maxTarget",
298
122
  "thresholds.minTarget must sit below thresholds.maxTarget, or a commit has no depth to reach");
299
123
  }
300
- // F < M: a fresh tail wider than the trigger is a window that triggers on protection.
301
124
  if (!(freshTail < maxTarget)) {
302
125
  throw new ThresholdPolicyError("freshTail<maxTarget",
303
126
  "thresholds.freshTail must sit below thresholds.maxTarget, or the trigger fires on protected mass alone");
304
127
  }
305
- // G >= F: one refill of the protected tail must not re-arm the trigger by itself.
306
128
  if (!(maxTarget - minTarget >= freshTail)) {
307
129
  throw new ThresholdPolicyError("gap>=freshTail",
308
130
  "the hysteresis gap (maxTarget - minTarget) must be at least thresholds.freshTail, " +
309
131
  "or refilling the protected tail alone re-fires the trigger");
310
132
  }
311
- // P + F < M, in TOKENS at the smallest supported budget, so rounding cannot slip past:
312
- // the pin ceiling plus the structurally fresh tail is the floor a commit can never get
313
- // under, and a trigger at or below that floor announces commits with nothing to reclaim.
314
133
  const pinned = Math.ceil(MAX_PINNED_SHARE * MINIMUM_SUPPORTED_BUDGET_TOKENS);
315
134
  const fresh = Math.ceil(freshTail * MINIMUM_SUPPORTED_BUDGET_TOKENS);
316
135
  if (!(pinned + fresh < Math.floor(maxTarget * MINIMUM_SUPPORTED_BUDGET_TOKENS))) {
@@ -321,11 +140,6 @@ export function resolveThresholds(value: unknown): ActiveContextThresholds {
321
140
  return { maxTarget, minTarget, freshTail, consolidateAfter };
322
141
  }
323
142
 
324
- /**
325
- * The tiny-window refusal, evaluated once at registration against the declared budget.
326
- * Reject, never clamp: a deployment whose window cannot carry the policy it asked for
327
- * gets told so with both numbers rather than a silently different governor.
328
- */
329
143
  export function assertThresholdsServable(thresholds: ActiveContextThresholds, budgetTokens: number): void {
330
144
  if (!Number.isFinite(budgetTokens) || budgetTokens < MINIMUM_SUPPORTED_BUDGET_TOKENS) {
331
145
  throw new ThresholdPolicyError("minimumBudget",
@@ -340,20 +154,6 @@ export function assertThresholdsServable(thresholds: ActiveContextThresholds, bu
340
154
  }
341
155
  }
342
156
 
343
- /**
344
- * THE serving budget, from one window, by one formula.
345
- *
346
- * The output reservation is what makes the budget differ from the window, so it is
347
- * subtracted exactly once and everything downstream -- the fence, the estimator, the
348
- * trigger, and every threshold proportion -- divides by the same number.
349
- */
350
- /**
351
- * A zone width in serialized BYTES, from a share of the serving budget.
352
- *
353
- * The thresholds are proportions of one denominator and a transcript position is a byte
354
- * count, so exactly one conversion stands between them: the estimator's bytes per token.
355
- * It is stated here, once, so every share of the budget is cut by one ruler.
356
- */
357
157
  export function zoneBytes(share: number, budgetTokens: number): number {
358
158
  if (!Number.isFinite(share) || share <= 0 || !Number.isFinite(budgetTokens) || budgetTokens <= 0) return 0;
359
159
  return Math.floor(share * budgetTokens * ESTIMATED_BYTES_PER_TOKEN);
@@ -365,117 +165,34 @@ export function servingBudgetTokens(window: number): number {
365
165
  }
366
166
 
367
167
  export const MAX_PENDING_MARKS = 256;
368
- /** Enough batches to actually reach the floor on a wide window before the loop exits. */
369
- export const EPOCH_MAX_TOPUP_MARKS = 64;
370
- /**
371
- * How many times one commit re-reads its own eligible-root count.
372
- *
373
- * A termination bound, not a cadence: a parent is excluded from the count that produced
374
- * it, so each round divides the standing roots by `consolidateAfter` and the sequence
375
- * closes in two rounds at any window a session can carry.
376
- */
377
- export const EPOCH_CONSOLIDATION_ROUNDS = 4;
378
- /** Estimate only; provider token accounting always comes from a measured response. */
379
168
  export const ESTIMATED_BYTES_PER_TOKEN = 4;
380
- /** Rendered navigation/topology overhead assumed around a brief when estimating a placeholder. */
381
169
  export const ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES = 240;
382
170
 
383
- // ---------------------------------------------------------------------------
384
- // The reliability spine. Every behavior below shipped as an off-by-default lever
385
- // through iterations 2 and 3 and was sealed ON by rep 14 (64/64), so it is now
386
- // simply how pi-fold works: the flags and their conditional twins are gone, and
387
- // git history is the lineage. What stays configurable is only what is genuinely a
388
- // deployment fact (`providerTotalWindow`). The experiment conditions that used to sit
389
- // beside it (`foldScheduling`, `foldPeekResults`, `guidedCuration`) are gone too: epoch
390
- // scheduling, peek foldability and guided curation are simply how pi-fold works, and
391
- // the harness keeps reading the keys only so sealed runs still validate.
392
- //
393
- // Sealed unconditional: admission control, retained pending marks, the eligible-share
394
- // commit trigger, stage-identified briefs, the current-turn commit guard, the
395
- // pinned-mass backstop, the status index diet, delivery-counted advisories, and
396
- // projection instrumentation.
397
- //
398
- // NOT sealed, deleted: ephemeral peek and its per-call `ephemeral` override. It rewrote
399
- // a consumed peek result in place on the theory the edit was tail-local. It is not: the
400
- // rewrite waits for a later assistant message to exist, by which point the window has
401
- // grown over it and the edit lands mid-prefix. Rep 22 measured two of them costing 100k
402
- // fresh tokens. A peek is append-only; the tool-fold rung reclaims the duplicate bytes
403
- // at the next commit, which is the one moment a rewrite is already being paid for.
404
- // ---------------------------------------------------------------------------
405
-
406
- /**
407
- * The provider serving window is the TOTAL admission budget minus whatever output
408
- * reservation the deployment actually asked for; the per-request max-input descriptor
409
- * assumes a full output reservation and understates the real ceiling. Measured
410
- * 2026-08-06: a run aborted at ~297k projected tokens against a 272k descriptor while
411
- * the same provider had just accepted 339,689 tokens. Declaring the total window is
412
- * the deployment's own fact, so it is the option; there is no separate boolean, and
413
- * an undeclared window falls back to the descriptor and SAYS so in the accounting.
414
- */
415
-
416
- /** Narrowing is what makes a refusal governance instead of denial. */
417
171
  export const PEEK_MIN_SLICE_BYTES = 1_024;
418
172
 
419
- /** How many folds the dieted status payload ranks by what they would reclaim. */
420
- export const STATUS_DIET_SUGGESTIONS = 5;
421
-
422
- export const MAX_PROJECTION_HASH_RECORDS = 64;
423
-
424
- // ---------------------------------------------------------------------------
425
- // Guided curation: the two-signal curation trigger, its bounded last-call gate,
426
- // and the reactive receipt block. It shipped as the one iteration-4 experiment
427
- // condition and is unconditional now, so only the constants below remain.
428
- // ---------------------------------------------------------------------------
429
-
430
- /**
431
- * The three occupancy waypoints, as shares of the truthful serving budget.
432
- *
433
- * Each one lands exactly once per upward crossing as an append-once notice that then
434
- * persists in the window the way a tool result does: appended at the tail, closed over
435
- * by the freeze, never re-rendered mid-cycle, so it can never move a prefix byte. A
436
- * commit that drops occupancy back under a waypoint re-arms it; the next crossing is a
437
- * new event and gets a new notice.
438
- */
439
- export const THRESHOLD_NOTICE_SHARES: readonly number[] = Object.freeze([0.25, 0.50, 0.75]);
440
- /** Delivered notices kept rendered in the window; the oldest leaves the carrier, never the stream. */
441
- export const MAX_THRESHOLD_NOTICES = 9;
442
- /**
443
- * The two guidance surfaces, as ONE public option, both switches defaulting on.
444
- * Shane's ruling: notices and action responses are optional, and the default is yes.
445
- */
173
+ export const STATUS_DIET_INDEX_ROWS = 5;
174
+
446
175
  export interface ActiveContextGuidance {
447
- /** Append-once occupancy waypoints at 25/50/75 percent of the serving budget. */
448
- thresholdNotices: boolean;
449
- /** Persistent acknowledgements for the agent's own context actions. */
450
176
  actionResponses: boolean;
451
177
  }
452
178
 
453
179
  export const DEFAULT_GUIDANCE: ActiveContextGuidance = Object.freeze({
454
- thresholdNotices: true,
455
180
  actionResponses: true,
456
181
  });
457
182
 
458
183
  const GUIDANCE_KEYS: readonly (keyof ActiveContextGuidance)[] = Object.freeze([
459
- "thresholdNotices", "actionResponses",
184
+ "actionResponses",
460
185
  ]);
461
186
 
462
- /**
463
- * Validated whole, like the thresholds: an unknown key is a typo, not a silent no-op.
464
- *
465
- * Booleans and nothing else. The dosage families that used to parameterize guidance
466
- * (profiles, reminder shares, milestone budgets) were deleted for measuring nothing
467
- * across eleven runs, and shares or per-notice keys here would rebuild them by another
468
- * name. An operator either wants the runtime to speak at the boundary or does not.
469
- */
470
187
  export function resolveGuidance(value: unknown): ActiveContextGuidance {
471
188
  if (value === undefined) return DEFAULT_GUIDANCE;
472
189
  if (!value || typeof value !== "object" || Array.isArray(value)) {
473
- throw new Error("guidance must be an object of { thresholdNotices, actionResponses } booleans");
190
+ throw new Error("guidance must be an object of { actionResponses } booleans");
474
191
  }
475
192
  for (const key of Object.keys(value)) {
476
193
  if (!GUIDANCE_KEYS.includes(key as keyof ActiveContextGuidance)) {
477
194
  throw new Error(`guidance has no ${key} setting: the only keys are ` +
478
- `${GUIDANCE_KEYS.join(", ")}, and both are booleans`);
195
+ `${GUIDANCE_KEYS.join(", ")}, and they are booleans`);
479
196
  }
480
197
  }
481
198
  const resolved = { ...DEFAULT_GUIDANCE } as ActiveContextGuidance;
@@ -487,159 +204,23 @@ export function resolveGuidance(value: unknown): ActiveContextGuidance {
487
204
  }
488
205
  return Object.freeze(resolved);
489
206
  }
490
- /** The last-call is LITERAL persisted bytes; the bound is on the stored text itself. */
491
- export const MAX_LAST_CALL_TEXT_BYTES = 2_048;
492
- /** One notice is one waypoint line; a waypoint that becomes a paragraph is bloat. */
493
- export const MAX_THRESHOLD_NOTICE_TEXT_BYTES = 512;
494
- /**
495
- * The surfacing line's own bound, applied to the line and not to the carrier that
496
- * carries it, so a carrier's total overhead is its own bound plus this one and stays a
497
- * number a gate can pin. The 21.9% ephemeral-slate tax is what this is measured
498
- * against: one bounded line landing inside a rewrite the commit already paid for.
499
- */
500
- export const MAX_SURFACING_LINE_BYTES = 384;
501
-
502
- /** How many automatic-action receipts stay in the window; the oldest ages out. */
207
+
503
208
  export const MAX_CONTEXT_RECEIPTS = 3;
504
- /** Hard byte cap on the rendered receipt block, so a receipt can never itself bloat. */
505
209
  export const CONTEXT_RECEIPT_BLOCK_BYTES = 900;
506
- /** Durable instrumentation records kept in the in-memory ledger for status. */
507
- export const MAX_CONTEXT_ATTEMPT_RECORDS = 128;
508
-
509
- // ---------------------------------------------------------------------------
510
- // The mark response.
511
- //
512
- // Measured 2026-08-07 (rep 17): every context action was followed by a projection
513
- // REWRITE, never an append, because the surface answered a mark by refreshing blocks
514
- // the agent had already paid to cache. A tail-appended tool result is the one place
515
- // left that can inform the agent for free, so the awareness moved THERE and the
516
- // projection went byte-frozen between fold events. The result is bounded on the same
517
- // principle as the receipt block: a report about bloat that becomes bloat has argued
518
- // against itself.
519
- // ---------------------------------------------------------------------------
520
-
521
- /** Hard byte cap on the rendered awareness block a mark call answers with. */
210
+
522
211
  export const CONTEXT_MARK_RESPONSE_BYTES = 900;
523
- /**
524
- * Hard byte cap on one serialized status payload, every detail variant included.
525
- * Measured 2026-08-07 (rep 19): six paged status results accumulated to ~254k tokens
526
- * and the seventh answered with 526KB, 6.2x the remaining headroom, aborting the
527
- * request. Paging already existed; nothing FORCED it. Above the cap the listing is
528
- * truncated at a unit boundary and a continuation names the next offset, so the
529
- * biggest possible status answer is about 6k tokens and the rest is another page.
530
- */
531
212
  export const CONTEXT_STATUS_RESPONSE_BYTES = 24_000;
532
- /** How many unmarked candidates the awareness block names; the remainder is an aggregate. */
533
213
  export const MAX_UNMARKED_CANDIDATES = 3;
534
214
 
535
- // ---------------------------------------------------------------------------
536
- // Bite-sized folds and bounded peeks. These two constants are one decision read
537
- // from two ends, so they are stated together and kept equal.
538
- //
539
- // Measured 2026-08-06 (rep 6): a single 60,432-byte chapter fold hid the needed
540
- // fact in its tail, and every peek of it was either truncated short of the answer
541
- // or too expensive to widen. A fold is only navigable if reading one back is cheap,
542
- // so a fold's source is capped and an oversized span is SPLIT into sequential
543
- // bounded folds, each with its own brief. The peek bound is the same number, so a
544
- // default peek of one bite-sized fold returns it whole -- about 4,000 tokens, a
545
- // read any session can afford -- and only a legacy oversized fold is ever truncated.
546
- // ---------------------------------------------------------------------------
547
-
548
- /** A fold whose exact source exceeds this is split into sequential bounded folds. */
549
215
  export const MAX_FOLD_SPAN_CHARS = 16_000;
550
- /**
551
- * The other bound: a SLIVER of stale raw content hugging a fold boundary is absorbed
552
- * into its later neighbour inside a commit the epoch already paid for.
553
- *
554
- * The threshold is deliberately tiny, measured in the session's own calibrated TOKENS,
555
- * and it is not pressure-scaled. Non-sequential curation is a first-class state: an
556
- * agent may hold folds at 10:20, 40:55 and 60:70 with raw spans between them ON
557
- * PURPOSE, and a mechanism that swallowed those gaps would quietly turn pi-fold into
558
- * system-controlled compaction with the curation element deleted. 256 tokens is about
559
- * one short prompt or one one-line result -- nobody deliberately keeps a sliver that
560
- * small as standing context -- and it sits an order of magnitude under the minimum
561
- * chapter size the ladder itself will fold. Anything larger stays raw permanently,
562
- * however ragged the projection looks; the ladder's ordinary fold eligibility is the
563
- * only thing that ever touches it, and absorption never becomes a second folding path.
564
- *
565
- * Later, not earlier, because extending the LATER fold backward mutates at a shallower
566
- * prefix position and preserves more of the cache. There is no content-affinity logic,
567
- * every absorption is receipted, and a wrong grouping is one `reboundary` away.
568
- */
569
216
  export const MAX_WEDGE_ABSORB_TOKENS = 256;
570
- /**
571
- * How many generator calls the upgrade lane may hold open at once.
572
- *
573
- * The lane has to write briefs at the rate the ladder makes folds, or the folds that
574
- * carry a model brief are a shrinking fraction of the folds a long session holds. One
575
- * call in flight, started once per ladder pass, is not that rate: the live pressure run
576
- * of 2026-08-10 measured completions at 6.9s, 9.4s and 14.3s while passes came about
577
- * half a minute apart, so the lane spent most of its time idle and produced exactly one
578
- * brief per commit boundary while four folds sat waiting. Two in flight, with a finished
579
- * call starting the next one itself, makes production a function of generator latency
580
- * instead of pass cadence. Two rather than more because provider calls are real money
581
- * and real concurrency: the ladder makes about one leaf fold per boundary, and a lane
582
- * that outruns the ladder by a wider margin only buys idle capacity.
583
- */
584
- export const MAX_BRIEF_UPGRADES_IN_FLIGHT = 2;
585
- /**
586
- * How many folds may be waiting on a generator at once. The queue holds each fold's
587
- * exact source, so it is a memory bound as much as a work bound, and a fold dropped for
588
- * a full queue simply keeps the deterministic brief it committed with. With the queue
589
- * capped here and calls capped above, the work a session can have outstanding is a
590
- * constant, and so is the number of briefs any one boundary can be asked to carry.
591
- */
592
- export const MAX_BRIEF_UPGRADE_QUEUE = 4;
593
- /**
594
- * How many spans one generator call may brief at once.
595
- *
596
- * A commit folds its spans together, so they are briefed together: one call, one answer,
597
- * one brief per span. Sending them one at a time is what left the 2026-08-11 rep with 14
598
- * model briefs across 87 committed folds, because the ladder makes about a dozen folds per
599
- * boundary and the lane could hold four.
600
- *
601
- * Ten because that is the ladder's own group width (`consolidateAfter`), so a batch is
602
- * never asked for more briefs than a consolidation would produce, and the answer stays
603
- * bounded at ten brief caps. The batch's SOURCE is bounded by `maxSourceChars`, the same
604
- * budget a single span was always held to, so the queue's memory bound does not move: it
605
- * is still MAX_BRIEF_UPGRADE_QUEUE requests of at most one source budget each.
606
- */
607
- export const MAX_BRIEF_BATCH_SPANS = 10;
608
- /** What a peek returns without an explicit widening argument. */
609
217
  export const PEEK_DEFAULT_MAX_BYTES = 16_000;
610
- /** Head share of a truncated peek; the remainder is the tail, where conclusions live. */
611
218
  export const PEEK_HEAD_SHARE = 0.6;
612
219
 
613
- // ---------------------------------------------------------------------------
614
- // Overflow rollback and recovery.
615
- // ---------------------------------------------------------------------------
616
-
617
- /**
618
- * How many times one inflow may be recovered before the runtime gives up.
619
- *
620
- * A provider context-overflow rejection mutates nothing durable -- the assistant
621
- * message never lands and the projection is rebuilt per request -- so recovery is a
622
- * reduction plus a rebuild, not a branch rewind. Two attempts is the cap: the first
623
- * folds at fence pressure with the guard fully waived, the second reaches into the
624
- * fresh tail, and a third would be asking the same question of the same evidence.
625
- * Past it the run fails LOUDLY, because an inflow that cannot fit after maximal
626
- * folding is a genuine impossibility rather than a recoverable state.
627
- */
628
- export const OVERFLOW_RECOVERY_MAX_ATTEMPTS = 2;
629
-
630
- /**
631
- * Active-context tool actions that read without mutating, so their results may fold.
632
- *
633
- * A peek copies a fold's own stored source back into the window, so leaving it raw is a
634
- * pure duplicate of evidence the runtime already holds and the tool-fold rung reclaims
635
- * it. There was a narrower set while immediate scheduling existed, reached by opting
636
- * out; epoch is the only scheduler now and this classification is unconditional.
637
- */
638
220
  export const PEEK_READ_ONLY_CONTEXT_ACTIONS: ReadonlySet<string> = new Set(["status", "peek"]);
639
221
 
640
222
  export type MarkOrigin = "agent" | "ladder";
641
223
 
642
- /** A fold decided but not yet applied: no projection byte has moved for it. */
643
224
  export interface PendingFoldMark {
644
225
  mark: "fold";
645
226
  id: string;
@@ -648,11 +229,9 @@ export interface PendingFoldMark {
648
229
  brief: string;
649
230
  briefProvenance: BriefProvenance;
650
231
  origin: MarkOrigin;
651
- /** Transcript ordinal at mark time; orders the commit. Never a wall clock. */
652
232
  ordinal: number;
653
233
  }
654
234
 
655
- /** An expanded fold decided to return to its placeholder at the next commit. */
656
235
  export interface PendingRefoldMark {
657
236
  mark: "refold";
658
237
  id: string;
@@ -665,7 +244,6 @@ export type PendingMark = PendingFoldMark | PendingRefoldMark;
665
244
  export const ACTIVE_CONTEXT_POLICY = Object.freeze({
666
245
  refoldRatio: 0.85,
667
246
  prepareRatio: 0.90,
668
- warmRatio: 0.55,
669
247
  responseReserve: 16_384,
670
248
  minToolChars: 2_000,
671
249
  minChapterChars: 4_000,
@@ -673,25 +251,7 @@ export const ACTIVE_CONTEXT_POLICY = Object.freeze({
673
251
  maxChapterTurns: 4,
674
252
  maxSourceChars: 200_000,
675
253
  maxFoldSourceRefs: 256,
676
- /**
677
- * 2,000 (Shane 2026-08-11), raised from 1,200.
678
- *
679
- * The cap is a limit the generator is told and can be cured against, not a truncation
680
- * point, so it should sit where a good brief stops rather than where a cheap one does.
681
- * Measured against what a stop-the-world compaction hands forward: this harness's own
682
- * native-arm summaries ran 8,000 to 20,000 characters for a whole session, and a folded
683
- * window carries about ten briefs, so a per-fold budget of roughly a tenth of a handoff
684
- * is 800 to 2,000. The ceiling of that range is the right end to sit at, because ten
685
- * briefs at the cap is 20,000 characters against a serving budget measured in hundreds
686
- * of thousands, and because the cost of a brief that is too short is a fold the agent
687
- * cannot decide whether to expand.
688
- *
689
- * It binds rarely by design: the run before this one averaged well under 1,200 on the
690
- * calls that were not being truncated by a token ceiling. `context.brief` records every
691
- * cure so the next run says how often it binds at all.
692
- */
693
254
  maxBriefChars: 2_000,
694
- briefTimeoutMs: 120_000,
695
255
  orientationMessages: 2,
696
256
  maxOrientationChars: 12_000,
697
257
  });
@@ -709,10 +269,6 @@ export type BriefProvenance =
709
269
  launchContractDigest?: string;
710
270
  };
711
271
 
712
- /**
713
- * A brief written after its fold committed: the agent's bare correction, or a model
714
- * brief carrying the generator that wrote it.
715
- */
716
272
  export type BriefOverride = string | { brief: string; provenance: BriefProvenance };
717
273
 
718
274
  export interface ActiveFold {
@@ -744,57 +300,6 @@ export interface PreparedFold {
744
300
  fold: ActiveFold;
745
301
  }
746
302
 
747
- /**
748
- * The graded success labels. `shown` is the open offer; the other three are terminal.
749
- * `acted` is a context verb on the surfaced fold inside the window, `used` is that plus
750
- * the retrieved content showing up in what the agent went on to say or call, and
751
- * `ignored` is a closed window with neither.
752
- */
753
- export type SurfacingOutcome = "shown" | "acted" | "used" | "ignored";
754
-
755
- /**
756
- * The durable SUPPRESSION ledger: one record per fold, not one per showing. The
757
- * per-suggestion detail a bandit trains on is the event stream, which is durable and
758
- * unbounded; this is only what the next selection pass must not forget.
759
- */
760
- export interface SurfacingRecord {
761
- id: string;
762
- surfaced: number;
763
- taken: number;
764
- /** Transcript ordinal of the last state change; the cooldown and the window read it. */
765
- ordinal: number;
766
- outcome: SurfacingOutcome;
767
- }
768
-
769
- /** One suppression-state change, returned so the caller emits it rather than inferring it. */
770
- export interface SurfacingTransition {
771
- id: string;
772
- from: SurfacingOutcome;
773
- to: SurfacingOutcome;
774
- ordinal: number;
775
- }
776
-
777
- /** One collapsed fold, scored on both channels. */
778
- export interface SurfacingCandidate {
779
- id: string;
780
- /** What the placeholder shows: the brief channel. */
781
- brief: string;
782
- /** What the fold stores, descendants included: the content channel. */
783
- content: string;
784
- route: string;
785
- /** Transcript position of the fold's last entry; higher is more recent. Never a clock. */
786
- position: number;
787
- depth: number;
788
- }
789
-
790
- export interface SurfacingSuggestion extends SurfacingCandidate {
791
- contentScore: number;
792
- briefScore: number;
793
- margin: number;
794
- /** Slate position, from 0. One suggestion per delivery point, so today it is always 0. */
795
- slot: number;
796
- }
797
-
798
303
  export interface ActiveContextState {
799
304
  version: 1;
800
305
  sessionId: string;
@@ -804,17 +309,8 @@ export interface ActiveContextState {
804
309
  protected: EvidenceRef[];
805
310
  tokensSinceToolFold: number;
806
311
  leases: Record<string, number>;
807
- surfacing?: SurfacingRecord[];
808
- /** Epoch scheduling only; omitted when empty so immediate-mode state digests never move. */
809
312
  pendingMarks?: PendingMark[];
810
- /** Retired with peek reclamation; carried only so pre-cut state still parses. */
811
313
  pinnedPeeks?: never;
812
- /**
813
- * Corrected and upgraded fold briefs, by fold id. A fold RECORD is content-addressed and
814
- * immutable -- rewriting its brief in place would report a conflicting durable fold
815
- * and suspend automatic management -- so a re-brief is durable state beside the
816
- * record rather than a mutation of it. Omitted when empty.
817
- */
818
314
  briefs?: Record<string, BriefOverride>;
819
315
  prepared?: PreparedFold;
820
316
  advisory?: {
@@ -822,26 +318,7 @@ export interface ActiveContextState {
822
318
  delivered: Record<string, number>;
823
319
  armed?: { milestone: AdvisoryMilestone; threshold: number; scheduleKey: string };
824
320
  };
825
- /**
826
- * The one action-prompt carrier: computed at a fold commit, persisted as LITERAL
827
- * bytes so every re-render of the same epoch is byte-identical, replaced only by
828
- * the next epoch's rider. Omitted when absent so pre-rider state digests never move.
829
- */
830
321
  rider?: { epoch: number; text: string };
831
- /**
832
- * The armed pre-commit last-call: one exposure per band-top crossing, persisted as
833
- * LITERAL bytes like the rider. `exposure` is the context.lastcall event's stream
834
- * seq; `contextCalls` and `agentMarks` are the arming snapshot the response
835
- * attribution is measured against. Cleared by the commit that consumes it. Omitted
836
- * when absent so pre-last-call state digests never move.
837
- */
838
- lastCall?: { exposure: number; ordinal: number; contextCalls: number; agentMarks: number; text: string };
839
- /**
840
- * Threshold notices: `fired` is the waypoint shares spent this occupancy cycle
841
- * (re-armed by a commit that drops back under them), `ring` the delivered notices
842
- * still rendered in the window, literal bytes, oldest evicted. Omitted when absent.
843
- */
844
- notices?: { fired: number[]; ring: Array<{ share: number; ordinal: number; text: string }> };
845
322
  }
846
323
 
847
324
  export interface FoldRecordRef {
@@ -868,7 +345,6 @@ export interface ActiveContextCheckpointV2 {
868
345
  prepared: PreparedFold | null;
869
346
  tokensSinceToolFold?: number;
870
347
  leases?: Record<string, number>;
871
- surfacing?: SurfacingRecord[];
872
348
  pendingMarks?: PendingMark[];
873
349
  briefs?: Record<string, BriefOverride>;
874
350
  advisory?: NonNullable<ActiveContextState["advisory"]>;
@@ -892,9 +368,16 @@ export interface ActiveContextDeltaV2 {
892
368
  prepared: PreparedFold | null;
893
369
  tokensSinceToolFold?: number;
894
370
  leases?: Record<string, number>;
895
- surfacing?: SurfacingRecord[];
371
+ // `pendingMarks` and `briefs` on a delta are the WHOLE value: what every delta written
372
+ // before 2026-08-13 carries, and what sealed sessions replay. New deltas carry the change
373
+ // instead. The marks carry an ORDER as well as a membership, so the change is the marks
374
+ // that are new or rewritten plus the whole key order, which states the removals too.
896
375
  pendingMarks?: PendingMark[];
376
+ addPendingMarks?: PendingMark[];
377
+ pendingMarkOrder?: string[];
897
378
  briefs?: Record<string, BriefOverride>;
379
+ addBriefs?: Record<string, BriefOverride>;
380
+ removeBriefIds?: string[];
898
381
  advisory?: NonNullable<ActiveContextState["advisory"]>;
899
382
  rider?: NonNullable<ActiveContextState["rider"]>;
900
383
  lastCall?: NonNullable<ActiveContextState["lastCall"]>;
@@ -928,9 +411,7 @@ export interface ActiveContextSnapshot {
928
411
  branchObjects: BranchObject[];
929
412
  completeTurns: CompleteTurn[];
930
413
  freshBoundary: number;
931
- /** The four thermostat numbers in force for this session. */
932
414
  thresholds: ActiveContextThresholds;
933
- /** The serving budget the thresholds are proportions of. One denominator. */
934
415
  budgetTokens: number;
935
416
  protectedIndices: Set<number>;
936
417
  toolProtectedIndices: Set<number>;
@@ -939,7 +420,6 @@ export interface ActiveContextSnapshot {
939
420
  brandNoun: string;
940
421
  entryTypePrefix: string;
941
422
  blacklistAutoFoldTools: ReadonlySet<string>;
942
- /** Active-context tool actions treated as read-only when classifying tool batches. */
943
423
  readOnlyContextActions: ReadonlySet<string>;
944
424
  contextWindow: number;
945
425
  windowSource: "reported" | "fallback";
@@ -951,15 +431,6 @@ export interface FoldCandidate {
951
431
  sourceRefs: EvidenceRef[];
952
432
  }
953
433
 
954
- /**
955
- * The auto-fold exception list, EMPTY by default: every completed tool batch is foldable
956
- * by the ladder without an agent mark, and this names the tools whose results must stay
957
- * raw. The list ran the other way until the 2026-08-10 surface, as an allow-list seeded
958
- * with pi's four built-in readers, which meant a deployment's own tools were unfoldable
959
- * until someone remembered to name them and the ladder starved on exactly the results
960
- * that filled the window. Foldability was never the protection: pins, the fresh tail and
961
- * the commit guard are, and they apply to a blacklisted tool and an ordinary one alike.
962
- */
963
434
  export const AUTO_FOLD_BLACKLIST_DEFAULT: ReadonlySet<string> = new Set();
964
435
 
965
436
  export type AdvisoryMilestone = "orientation" | "notice" | "tools" | "chapters" | "urgent";