pi-fold 1.0.4 → 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.
- package/README.md +34 -8
- package/extensions/active-context.ts +245 -2355
- package/extensions/evidence.js +0 -10
- package/extensions/index.js +10 -30
- package/extensions/json.ts +3 -29
- package/extensions/lib/canonical.ts +0 -8
- package/extensions/lib/curation.ts +1 -258
- package/extensions/lib/folding.ts +33 -406
- package/extensions/lib/instrumentation.ts +3 -124
- package/extensions/lib/measurement.ts +0 -71
- package/extensions/lib/persistence.ts +216 -197
- package/extensions/lib/policy.ts +29 -558
- package/extensions/lib/rollback.ts +0 -53
- package/extensions/lib/scheduling.ts +36 -367
- package/extensions/lib/selection.ts +206 -312
- package/extensions/lib/tool-surface.ts +5 -18
- package/extensions/lib/transcript.ts +32 -108
- package/package.json +1 -1
- package/extensions/lib/surfacing.ts +0 -604
- package/extensions/summarizer.js +0 -291
package/extensions/lib/policy.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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
|
-
"
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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";
|