pi-fold 1.0.4 → 3.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,24 +1,12 @@
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";
18
6
  export const DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN = "pi-fold";
19
7
  export const DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES = Object.freeze({
20
- status: "pi-fold-context",
21
- fold: "fold-context",
8
+ status: "fold-status",
9
+ fold: "fold",
22
10
  });
23
11
 
24
12
  export function entryTypeNamespace(entryTypePrefix: string): string {
@@ -44,206 +32,153 @@ 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
36
  /**
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.
37
+ * How many unbriefed pending folds stand before the runtime says so.
58
38
  *
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.
39
+ * A CONSTANT, not a threshold the deployment can turn (Shane's KISS rule): it is a
40
+ * batching size, and the only thing a knob here would buy is a deployment that never
41
+ * speaks. Three is the smallest batch that reads as a batch rather than as a running
42
+ * commentary on every cut.
64
43
  */
65
- export const ACTIVE_CONTEXT_TOOL_ACTIONS = Object.freeze([
66
- "status", "peek", "fold", "expand", "refold", "protect", "unprotect",
67
- "rebrief", "reboundary", "unmark",
68
- ] as const);
69
- export type ActiveContextToolAction = typeof ACTIVE_CONTEXT_TOOL_ACTIONS[number];
70
- export const USER_RESCUE_MAX_SOURCE_CHARS = 512_000;
71
- export const DEFAULT_CONTEXT_WINDOW = 272_000;
72
- export const EXPAND_LEASE_GENERATIONS = 8;
73
- export const MAX_EXPAND_LEASES = 64;
74
- export const MAX_ADVISORY_DELIVERIES_PER_MILESTONE = 16;
44
+ export const UNBRIEFED_FOLDS_BEFORE_NOTICE = 3;
75
45
 
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
46
  /**
86
- * The divergence trigger, as three numbers on one 0..1 scale.
47
+ * How many spans the frontier cuts on one projection pass.
87
48
  *
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.
49
+ * A CONSTANT, and a cost bound rather than a policy. Each cut rescans the window for the
50
+ * next stalest span, so an unbounded loop is quadratic in the number of spans a window
51
+ * holds: on a 300-turn fixture it took a gate from 2.5 seconds to 152. A frontier does not
52
+ * need to catch up in one pass, because a pass costs nothing to repeat and the material it
53
+ * has not reached yet is still raw and still in front of the agent, which is where the
54
+ * design wants it. Eight is comfortably ahead of any real arrival rate.
96
55
  */
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.
56
+ export const MAX_FRONTIER_CUTS_PER_PASS = 8;
57
+
58
+ export const ACTIVE_CONTEXT_TOOL_ACTIONS = Object.freeze([
59
+ "status", "peek", "brief", "expand", "refold", "pin", "unpin",
60
+ "reboundary", "unmark",
61
+ ] as const);
133
62
  /**
134
- * The reclaim floor, and the single constant that decides whether a commit is worth
135
- * firing at all.
63
+ * FOLD IS DELETED AS AN AGENT VERB (Shane, 2026-08-23), and `brief` is what replaced it.
136
64
  *
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.
65
+ * The agent used to choose spans from nothing. Measured against real compaction
66
+ * boundaries on 2026-08-23, that produced a projection byte-identical to the same arm
67
+ * with no agent at all: six marks, 9,450 characters of briefs, same visible count, same
68
+ * fold count, same aborts. It marks what the ladder would have taken anyway, because at
69
+ * the moment it is asked the only information available is staleness and the runtime
70
+ * already has that.
143
71
  *
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.
72
+ * So the runtime cuts at the frontier and the agent annotates what it cut, while the
73
+ * material is still in front of it. `brief` attaches or replaces the brief on a PENDING
74
+ * fold and refuses on a standing one, which is the rebrief deletion above read from the
75
+ * other side: a pending mark is not in the window, so writing its brief moves no bytes
76
+ * and costs no cache, and a standing fold's placeholder is in the window, so writing that
77
+ * one costs the whole prefix from there on. The name `fold` stays spent.
78
+ */
79
+ /**
80
+ * REBRIEF IS DELETED (Shane, 2026-08-21). Every rebrief rewrites the projection at its
81
+ * fold's placeholder, which breaks the prefix cache from that point on: sol-20260820
82
+ * rep 7's first rebrief hit a front-of-projection fold, identical_share collapsed to
83
+ * 0.0007, and nine ~185K-token requests ran cacheRead 0 for $8.36 in one burst. The
84
+ * end block read 10 of 10 in every pifold rep with or without rebriefs, because
85
+ * lossless peek already carries recall, so the action was paying a full cache break
86
+ * for something the runtime did not need. The name stays spent.
148
87
  */
88
+ export const RETIRED_TOOL_ACTIONS: Readonly<Record<string, string>> = Object.freeze({
89
+ rebrief: "rebrief is deleted: rewriting a standing fold's brief rewrites the projection " +
90
+ "at that fold's placeholder and breaks the prefix cache from there on, for recall that " +
91
+ "peek already carries losslessly. Write the brief you want when you mark the span, or " +
92
+ "use reboundary to re-cut it.",
93
+ });
94
+ export type ActiveContextToolAction = typeof ACTIVE_CONTEXT_TOOL_ACTIONS[number];
95
+ export const USER_RESCUE_MAX_SOURCE_CHARS = 512_000;
96
+ export const DEFAULT_CONTEXT_WINDOW = 272_000;
97
+ export const EXPAND_LEASE_GENERATIONS = 8;
98
+ export const MAX_EXPAND_LEASES = 64;
99
+
149
100
  export const COMMIT_RECLAIM_FLOOR_SHARE = 0.02;
150
101
 
151
102
  /**
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.
103
+ * How far below the commit trigger the agent is told a commit is coming (Shane,
104
+ * 2026-08-22: "around 10% from the commit threshold"). A share of the serving budget
105
+ * rather than a token count, because a token count means a different fraction of every
106
+ * deployment's window. The advisory takes the wider of this and one worst recent inflow
107
+ * step, so a single large result cannot step over the whole band unasked.
157
108
  */
158
109
  export const MAX_PINNED_SHARE = 0.25;
159
110
 
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
111
  export interface ActiveContextThresholds {
184
- /** Occupancy of the serving budget that fires an automatic commit. */
185
112
  maxTarget: number;
186
- /** Occupancy an automatic commit folds down toward. The hysteresis gap is maxTarget - minTarget. */
187
113
  minTarget: number;
188
- /** Newest share of the serving budget where nothing folds, marked or not. */
189
- freshTail: number;
190
- /** Unpinned member folds at or above which placeholders become span material. */
191
114
  consolidateAfter: number;
115
+ minFoldChars: number;
192
116
  }
193
117
 
194
118
  /**
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.
119
+ * The floor a fold has to clear to be worth making, in CHARACTERS (Shane, 2026-08-21).
200
120
  *
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.
121
+ * Characters rather than tokens because characters are provider-agnostic: a token count
122
+ * means a different amount of text on every wire, and this number exists so a person can
123
+ * reason about it. It replaces two constants nobody could tell apart, minToolChars at
124
+ * 2,000 and minChapterChars at 4,000, and it is not specific to tool results or to
125
+ * chapters: everything in the window costs tokens, so everything counts toward the floor.
207
126
  *
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.
127
+ * It is a floor, not a cut. The automatic span accumulates whole messages and stops at
128
+ * the first one that carries it OVER the floor, so a fold is always at least this big
129
+ * and never cuts a message in half to hit the number exactly. The same floor is what a
130
+ * gap between two folds has to clear to survive: anything smaller is absorbed by the
131
+ * fold beside it rather than left as a raw sliver, because a sliver costs its bytes and
132
+ * buys nothing.
213
133
  *
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.
134
+ * 8,000 was chosen against the sealed corpus. Rep 1 of sol-20260814-traps folded 89
135
+ * spans at a median of 57,808 source chars, but nine of them came in under 8,000 and
136
+ * TWO were net negative, 2,726 chars of source replaced by 5,341 of placeholder (196
137
+ * percent) and 6,384 replaced by 10,531 (165 percent). A fold that grows the window is
138
+ * not a fold. The floor sits above every observed inversion with room to spare.
220
139
  */
140
+ export const MINIMUM_FOLD_CHARS_FLOOR = 2_000;
141
+
221
142
  export const DEFAULT_THRESHOLDS: Readonly<ActiveContextThresholds> = Object.freeze({
222
143
  maxTarget: 0.80,
223
- minTarget: 0.35,
224
- freshTail: 0.02,
144
+ // 0.40, up from 0.20 (Shane 2026-08-23: "we change the thresholds to what hits that
145
+ // 100k/250k or so"). This is the only threshold that moved, because the corpus says
146
+ // the top of the band was already where he wants it and only the floor was wrong.
147
+ //
148
+ // Read against the 19 mature sealed pifold runs that share a 251,520-token budget,
149
+ // where the trigger sat at 0.80 of it, 201,216 tokens. Peak occupancy per run came in
150
+ // at a median 237,399, a 0.944 share, p90 247,551, max 257,117: a session runs roughly
151
+ // 36,000 tokens PAST its own trigger before the commit it fired takes effect, because
152
+ // the trigger fires at a measurement and inflow continues while the epoch is computed.
153
+ // So 0.80 already delivers a 250k top, and raising it toward 250k directly would put
154
+ // the median peak over the budget and into the fence.
155
+ //
156
+ // The floor was the miss. Across 206 commit landings the session came to rest at a
157
+ // median 79,034 tokens, and 153 of the 206 landed below 100,000. Landings sit ABOVE
158
+ // the aim rather than at it, since the class law and the available material both
159
+ // outrank it, so moving the aim to 0.40 sets a floor near where it lands rather than
160
+ // far under it: 102,246 tokens against a default 272,000-token descriptor, 100,608
161
+ // against the harness budget.
162
+ //
163
+ // 0.40 also makes the aim reachable for the first time. MAX_PINNED_SHARE is 0.25, so a
164
+ // fully pinned session legally holds more than a 0.20 aim asks for and the target was
165
+ // a number the class law outranked by construction. The epoch receipt still reports
166
+ // targetBudgetShare beside actualFreedBudgetShare, so any remaining shortfall stays a
167
+ // readable fact per epoch rather than a silent miss.
168
+ //
169
+ // The cost is paid in cadence and it is the 2026-08-14 argument running backwards: a
170
+ // shallower cut buys less runway for nearly the same commit, so the refill distance
171
+ // goes from the observed 0.314 -> 0.80 to 0.40 -> 0.80 and the same session shape owes
172
+ // about a fifth more epochs, which is a fifth more full-prefix rewrites. Shane bought
173
+ // that deliberately: 100k of raw context held across the whole cycle is what he is
174
+ // paying for. The validation laws below bind exactly as before.
175
+ minTarget: 0.40,
225
176
  consolidateAfter: 10,
177
+ minFoldChars: 8_000,
226
178
  });
227
179
 
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
180
  export const MINIMUM_SUPPORTED_BUDGET_TOKENS = 10_000;
239
181
 
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
- export const MINIMUM_FRESH_TAIL_TOKENS = 500;
246
-
247
182
  export class ThresholdPolicyError extends Error {
248
183
  readonly invariant: string;
249
184
  constructor(invariant: string, message: string) {
@@ -260,21 +195,13 @@ function thresholdProportion(value: unknown, field: string): number {
260
195
  return value;
261
196
  }
262
197
 
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
198
  export function resolveThresholds(value: unknown): ActiveContextThresholds {
272
199
  if (value === undefined) return { ...DEFAULT_THRESHOLDS };
273
200
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
274
201
  throw new ThresholdPolicyError("shape", "thresholds must be an object with all four fields");
275
202
  }
276
203
  const supplied = value as Record<string, unknown>;
277
- const fields = ["maxTarget", "minTarget", "freshTail", "consolidateAfter"] as const;
204
+ const fields = ["maxTarget", "minTarget", "consolidateAfter", "minFoldChars"] as const;
278
205
  for (const key of Object.keys(supplied)) {
279
206
  if (!(fields as readonly string[]).includes(key)) {
280
207
  throw new ThresholdPolicyError("shape", `thresholds has no ${key} field`);
@@ -287,73 +214,33 @@ export function resolveThresholds(value: unknown): ActiveContextThresholds {
287
214
  }
288
215
  const maxTarget = thresholdProportion(supplied.maxTarget, "maxTarget");
289
216
  const minTarget = thresholdProportion(supplied.minTarget, "minTarget");
290
- const freshTail = thresholdProportion(supplied.freshTail, "freshTail");
291
217
  const consolidateAfter = supplied.consolidateAfter;
292
218
  if (typeof consolidateAfter !== "number" || !Number.isInteger(consolidateAfter) || consolidateAfter < 1) {
293
219
  throw new ThresholdPolicyError("consolidateAfter", "thresholds.consolidateAfter must be a positive integer count");
294
220
  }
295
- // L < M: the thermostat needs a gap to fold down into.
221
+ const minFoldChars = supplied.minFoldChars;
222
+ if (typeof minFoldChars !== "number" || !Number.isInteger(minFoldChars) ||
223
+ minFoldChars < MINIMUM_FOLD_CHARS_FLOOR) {
224
+ throw new ThresholdPolicyError("minFoldChars",
225
+ "thresholds.minFoldChars must be an integer character count of at least " +
226
+ `${MINIMUM_FOLD_CHARS_FLOOR}; below that a fold can cost more placeholder than the ` +
227
+ "source it replaces");
228
+ }
296
229
  if (!(minTarget < maxTarget)) {
297
230
  throw new ThresholdPolicyError("minTarget<maxTarget",
298
231
  "thresholds.minTarget must sit below thresholds.maxTarget, or a commit has no depth to reach");
299
232
  }
300
- // F < M: a fresh tail wider than the trigger is a window that triggers on protection.
301
- if (!(freshTail < maxTarget)) {
302
- throw new ThresholdPolicyError("freshTail<maxTarget",
303
- "thresholds.freshTail must sit below thresholds.maxTarget, or the trigger fires on protected mass alone");
304
- }
305
- // G >= F: one refill of the protected tail must not re-arm the trigger by itself.
306
- if (!(maxTarget - minTarget >= freshTail)) {
307
- throw new ThresholdPolicyError("gap>=freshTail",
308
- "the hysteresis gap (maxTarget - minTarget) must be at least thresholds.freshTail, " +
309
- "or refilling the protected tail alone re-fires the trigger");
310
- }
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
- const pinned = Math.ceil(MAX_PINNED_SHARE * MINIMUM_SUPPORTED_BUDGET_TOKENS);
315
- const fresh = Math.ceil(freshTail * MINIMUM_SUPPORTED_BUDGET_TOKENS);
316
- if (!(pinned + fresh < Math.floor(maxTarget * MINIMUM_SUPPORTED_BUDGET_TOKENS))) {
317
- throw new ThresholdPolicyError("pinnedPlusFreshTail<maxTarget",
318
- `the pin ceiling (${MAX_PINNED_SHARE}) plus thresholds.freshTail must stay below ` +
319
- `thresholds.maxTarget at the ${MINIMUM_SUPPORTED_BUDGET_TOKENS}-token minimum supported budget`);
320
- }
321
- return { maxTarget, minTarget, freshTail, consolidateAfter };
233
+ return { maxTarget, minTarget, consolidateAfter, minFoldChars };
322
234
  }
323
235
 
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
- export function assertThresholdsServable(thresholds: ActiveContextThresholds, budgetTokens: number): void {
236
+ export function assertThresholdsServable(_thresholds: ActiveContextThresholds, budgetTokens: number): void {
330
237
  if (!Number.isFinite(budgetTokens) || budgetTokens < MINIMUM_SUPPORTED_BUDGET_TOKENS) {
331
238
  throw new ThresholdPolicyError("minimumBudget",
332
239
  `a ${Math.floor(budgetTokens)}-token serving budget is below the ` +
333
240
  `${MINIMUM_SUPPORTED_BUDGET_TOKENS}-token minimum this package supports`);
334
241
  }
335
- const freshTokens = Math.floor(thresholds.freshTail * budgetTokens);
336
- if (freshTokens < MINIMUM_FRESH_TAIL_TOKENS) {
337
- throw new ThresholdPolicyError("freshTailTokens",
338
- `thresholds.freshTail is ${freshTokens} tokens of a ${Math.floor(budgetTokens)}-token serving budget, ` +
339
- `below the ${MINIMUM_FRESH_TAIL_TOKENS}-token minimum one foldable unit needs`);
340
- }
341
242
  }
342
243
 
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
244
  export function zoneBytes(share: number, budgetTokens: number): number {
358
245
  if (!Number.isFinite(share) || share <= 0 || !Number.isFinite(budgetTokens) || budgetTokens <= 0) return 0;
359
246
  return Math.floor(share * budgetTokens * ESTIMATED_BYTES_PER_TOKEN);
@@ -365,281 +252,59 @@ export function servingBudgetTokens(window: number): number {
365
252
  }
366
253
 
367
254
  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
255
  export const ESTIMATED_BYTES_PER_TOKEN = 4;
380
- /** Rendered navigation/topology overhead assumed around a brief when estimating a placeholder. */
381
256
  export const ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES = 240;
382
257
 
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
258
  export const PEEK_MIN_SLICE_BYTES = 1_024;
418
259
 
419
- /** How many folds the dieted status payload ranks by what they would reclaim. */
420
- export const STATUS_DIET_SUGGESTIONS = 5;
260
+ export const STATUS_DIET_INDEX_ROWS = 5;
421
261
 
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
- */
446
- 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
- actionResponses: boolean;
451
- }
452
-
453
- export const DEFAULT_GUIDANCE: ActiveContextGuidance = Object.freeze({
454
- thresholdNotices: true,
455
- actionResponses: true,
456
- });
457
-
458
- const GUIDANCE_KEYS: readonly (keyof ActiveContextGuidance)[] = Object.freeze([
459
- "thresholdNotices", "actionResponses",
460
- ]);
461
-
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
- export function resolveGuidance(value: unknown): ActiveContextGuidance {
471
- if (value === undefined) return DEFAULT_GUIDANCE;
472
- if (!value || typeof value !== "object" || Array.isArray(value)) {
473
- throw new Error("guidance must be an object of { thresholdNotices, actionResponses } booleans");
474
- }
475
- for (const key of Object.keys(value)) {
476
- if (!GUIDANCE_KEYS.includes(key as keyof ActiveContextGuidance)) {
477
- throw new Error(`guidance has no ${key} setting: the only keys are ` +
478
- `${GUIDANCE_KEYS.join(", ")}, and both are booleans`);
479
- }
480
- }
481
- const resolved = { ...DEFAULT_GUIDANCE } as ActiveContextGuidance;
482
- for (const key of GUIDANCE_KEYS) {
483
- if (!Object.hasOwn(value, key)) continue;
484
- const setting = (value as Record<string, unknown>)[key];
485
- if (typeof setting !== "boolean") throw new Error(`guidance.${key} must be a boolean`);
486
- resolved[key] = setting;
487
- }
488
- return Object.freeze(resolved);
489
- }
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. */
503
262
  export const MAX_CONTEXT_RECEIPTS = 3;
504
- /** Hard byte cap on the rendered receipt block, so a receipt can never itself bloat. */
505
263
  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. */
522
- export const CONTEXT_MARK_RESPONSE_BYTES = 900;
264
+
523
265
  /**
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.
266
+ * The post-fold notice's bound, and its own rather than the receipt block's.
267
+ *
268
+ * It is larger because it carries a variable-length list the receipt does not: three
269
+ * fixed sentences of instruction plus one entry per unbriefed fold, and the pending set
270
+ * grows past MAX_FRONTIER_CUTS_PER_PASS whenever the frontier stages faster than the
271
+ * agent answers. 1,200 seats the instruction and roughly eight folds; past that the list
272
+ * gives way and states how many it could not name.
530
273
  */
274
+ export const FOLD_NOTICE_BYTES = 1_200;
531
275
  export const CONTEXT_STATUS_RESPONSE_BYTES = 24_000;
532
- /** How many unmarked candidates the awareness block names; the remainder is an aggregate. */
533
276
  export const MAX_UNMARKED_CANDIDATES = 3;
534
277
 
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
278
  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
- 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
279
  export const PEEK_DEFAULT_MAX_BYTES = 16_000;
610
- /** Head share of a truncated peek; the remainder is the tail, where conclusions live. */
611
280
  export const PEEK_HEAD_SHARE = 0.6;
612
281
 
613
- // ---------------------------------------------------------------------------
614
- // Overflow rollback and recovery.
615
- // ---------------------------------------------------------------------------
616
-
617
282
  /**
618
- * How many times one inflow may be recovered before the runtime gives up.
283
+ * A PEEK IS EPHEMERAL UNLESS THE AGENT SAYS OTHERWISE (Shane, 2026-08-22).
619
284
  *
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.
285
+ * The default follows the measured choice rather than the cautious one. On sealed run
286
+ * rep 7, six of seven peeks asked for ephemeral at first organic exposure, and the one
287
+ * durable read was the stage-64 probe, exactly where the bytes were wanted standing.
288
+ * The economics agree: a peek is a pure append and costs the cache nothing, while the
289
+ * withdrawal is a tail operation at the withdrawn result's own index, so the cheap read
290
+ * should be the one you get without asking.
632
291
  *
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.
292
+ * Only an explicit false is durable. A missing value, and anything the validator has
293
+ * already rejected, reads as ephemeral, so a host or model that never learned the
294
+ * argument still gets the cheap behaviour and can still recover the bytes by peeking
295
+ * again, which is lossless.
637
296
  */
297
+ export function peekIsEphemeral(params: Record<string, unknown> | undefined | null): boolean {
298
+ return params?.ephemeral !== false;
299
+ }
300
+
638
301
  export const PEEK_READ_ONLY_CONTEXT_ACTIONS: ReadonlySet<string> = new Set(["status", "peek"]);
639
302
 
640
- export type MarkOrigin = "agent" | "ladder";
303
+ /** "user" is a mark laid down by the human through /fold-editor; it stages and
304
+ * commits through the same validated path as an agent mark and counts toward the
305
+ * commit's coverage the same way. */
306
+ export type MarkOrigin = "agent" | "ladder" | "user";
641
307
 
642
- /** A fold decided but not yet applied: no projection byte has moved for it. */
643
308
  export interface PendingFoldMark {
644
309
  mark: "fold";
645
310
  id: string;
@@ -648,11 +313,9 @@ export interface PendingFoldMark {
648
313
  brief: string;
649
314
  briefProvenance: BriefProvenance;
650
315
  origin: MarkOrigin;
651
- /** Transcript ordinal at mark time; orders the commit. Never a wall clock. */
652
316
  ordinal: number;
653
317
  }
654
318
 
655
- /** An expanded fold decided to return to its placeholder at the next commit. */
656
319
  export interface PendingRefoldMark {
657
320
  mark: "refold";
658
321
  id: string;
@@ -665,33 +328,13 @@ export type PendingMark = PendingFoldMark | PendingRefoldMark;
665
328
  export const ACTIVE_CONTEXT_POLICY = Object.freeze({
666
329
  refoldRatio: 0.85,
667
330
  prepareRatio: 0.90,
668
- warmRatio: 0.55,
669
331
  responseReserve: 16_384,
670
- minToolChars: 2_000,
671
- minChapterChars: 4_000,
672
- maxChapterChars: 128_000,
673
- maxChapterTurns: 4,
332
+ // The most exact source bytes ONE read may put back in the window, whichever verb
333
+ // asks: an expand collapses its biggest children until the render fits, and a peek
334
+ // clamps its `bytes` to it. Was two numbers until 2026-08-22, when the peek ceiling
335
+ // was `maxChapterChars` 128,000 and that constant also claimed to cap a chapter.
674
336
  maxSourceChars: 200_000,
675
- 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
337
  maxBriefChars: 2_000,
694
- briefTimeoutMs: 120_000,
695
338
  orientationMessages: 2,
696
339
  maxOrientationChars: 12_000,
697
340
  });
@@ -709,10 +352,6 @@ export type BriefProvenance =
709
352
  launchContractDigest?: string;
710
353
  };
711
354
 
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
355
  export type BriefOverride = string | { brief: string; provenance: BriefProvenance };
717
356
 
718
357
  export interface ActiveFold {
@@ -744,57 +383,6 @@ export interface PreparedFold {
744
383
  fold: ActiveFold;
745
384
  }
746
385
 
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
386
  export interface ActiveContextState {
799
387
  version: 1;
800
388
  sessionId: string;
@@ -804,17 +392,8 @@ export interface ActiveContextState {
804
392
  protected: EvidenceRef[];
805
393
  tokensSinceToolFold: number;
806
394
  leases: Record<string, number>;
807
- surfacing?: SurfacingRecord[];
808
- /** Epoch scheduling only; omitted when empty so immediate-mode state digests never move. */
809
395
  pendingMarks?: PendingMark[];
810
- /** Retired with peek reclamation; carried only so pre-cut state still parses. */
811
396
  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
397
  briefs?: Record<string, BriefOverride>;
819
398
  prepared?: PreparedFold;
820
399
  advisory?: {
@@ -822,26 +401,7 @@ export interface ActiveContextState {
822
401
  delivered: Record<string, number>;
823
402
  armed?: { milestone: AdvisoryMilestone; threshold: number; scheduleKey: string };
824
403
  };
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
404
  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
405
  }
846
406
 
847
407
  export interface FoldRecordRef {
@@ -868,7 +428,6 @@ export interface ActiveContextCheckpointV2 {
868
428
  prepared: PreparedFold | null;
869
429
  tokensSinceToolFold?: number;
870
430
  leases?: Record<string, number>;
871
- surfacing?: SurfacingRecord[];
872
431
  pendingMarks?: PendingMark[];
873
432
  briefs?: Record<string, BriefOverride>;
874
433
  advisory?: NonNullable<ActiveContextState["advisory"]>;
@@ -892,9 +451,16 @@ export interface ActiveContextDeltaV2 {
892
451
  prepared: PreparedFold | null;
893
452
  tokensSinceToolFold?: number;
894
453
  leases?: Record<string, number>;
895
- surfacing?: SurfacingRecord[];
454
+ // `pendingMarks` and `briefs` on a delta are the WHOLE value: what every delta written
455
+ // before 2026-08-13 carries, and what sealed sessions replay. New deltas carry the change
456
+ // instead. The marks carry an ORDER as well as a membership, so the change is the marks
457
+ // that are new or rewritten plus the whole key order, which states the removals too.
896
458
  pendingMarks?: PendingMark[];
459
+ addPendingMarks?: PendingMark[];
460
+ pendingMarkOrder?: string[];
897
461
  briefs?: Record<string, BriefOverride>;
462
+ addBriefs?: Record<string, BriefOverride>;
463
+ removeBriefIds?: string[];
898
464
  advisory?: NonNullable<ActiveContextState["advisory"]>;
899
465
  rider?: NonNullable<ActiveContextState["rider"]>;
900
466
  lastCall?: NonNullable<ActiveContextState["lastCall"]>;
@@ -928,18 +494,14 @@ export interface ActiveContextSnapshot {
928
494
  branchObjects: BranchObject[];
929
495
  completeTurns: CompleteTurn[];
930
496
  freshBoundary: number;
931
- /** The four thermostat numbers in force for this session. */
932
497
  thresholds: ActiveContextThresholds;
933
- /** The serving budget the thresholds are proportions of. One denominator. */
934
498
  budgetTokens: number;
935
499
  protectedIndices: Set<number>;
936
- toolProtectedIndices: Set<number>;
937
500
  policy: typeof ACTIVE_CONTEXT_POLICY;
938
501
  toolName: string;
939
502
  brandNoun: string;
940
503
  entryTypePrefix: string;
941
504
  blacklistAutoFoldTools: ReadonlySet<string>;
942
- /** Active-context tool actions treated as read-only when classifying tool batches. */
943
505
  readOnlyContextActions: ReadonlySet<string>;
944
506
  contextWindow: number;
945
507
  windowSource: "reported" | "fallback";
@@ -951,15 +513,6 @@ export interface FoldCandidate {
951
513
  sourceRefs: EvidenceRef[];
952
514
  }
953
515
 
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
516
  export const AUTO_FOLD_BLACKLIST_DEFAULT: ReadonlySet<string> = new Set();
964
517
 
965
518
  export type AdvisoryMilestone = "orientation" | "notice" | "tools" | "chapters" | "urgent";