pi-fold 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -7
- package/extensions/active-context.ts +2252 -182
- package/extensions/lib/advisory.ts +171 -14
- package/extensions/lib/canonical.ts +20 -0
- package/extensions/lib/curation.ts +437 -0
- package/extensions/lib/folding.ts +468 -22
- package/extensions/lib/instrumentation.ts +333 -0
- package/extensions/lib/measurement.ts +111 -0
- package/extensions/lib/persistence.ts +158 -2
- package/extensions/lib/policy.ts +380 -4
- package/extensions/lib/scheduling.ts +923 -0
- package/extensions/lib/selection.ts +437 -12
- package/extensions/lib/surfacing.ts +349 -0
- package/extensions/lib/tool-surface.ts +52 -3
- package/extensions/lib/transcript.ts +58 -4
- package/package.json +2 -2
package/extensions/lib/policy.ts
CHANGED
|
@@ -8,6 +8,9 @@ export const DEFAULT_ACTIVE_CONTEXT_COMMAND_NAMES = Object.freeze({
|
|
|
8
8
|
status: "context",
|
|
9
9
|
fold: "fold-context",
|
|
10
10
|
});
|
|
11
|
+
export const GUIDANCE_PROFILES = Object.freeze(["pressure", "curation", "minimal"] as const);
|
|
12
|
+
export type GuidanceProfile = typeof GUIDANCE_PROFILES[number];
|
|
13
|
+
export const DEFAULT_GUIDANCE_PROFILE: GuidanceProfile = "pressure";
|
|
11
14
|
|
|
12
15
|
export function entryTypeNamespace(entryTypePrefix: string): string {
|
|
13
16
|
const suffix = "-active-context";
|
|
@@ -37,9 +40,26 @@ export const NATIVE_COMPACTION_DECISION_ENTRY = `${DEFAULT_ENTRY_NAMESPACE}-nati
|
|
|
37
40
|
export const PROVIDER_CONTEXT_MEASUREMENT_ENTRY = `${DEFAULT_ENTRY_NAMESPACE}-provider-context-measurement`;
|
|
38
41
|
export const ACTIVE_CONTEXT_STATUS_KEY = DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX;
|
|
39
42
|
export const ACTIVE_CONTEXT_TOOL_ACTIONS = Object.freeze([
|
|
40
|
-
"status", "fold", "expand", "refold", "protect", "unprotect",
|
|
43
|
+
"status", "peek", "fold", "expand", "refold", "protect", "unprotect",
|
|
44
|
+
// The correction verbs. Curation the agent cannot fix afterwards is curation it
|
|
45
|
+
// will not risk making, so re-briefing a fold and dissolving a mis-cut boundary
|
|
46
|
+
// are part of the ordinary surface, not an epoch-only extra.
|
|
47
|
+
"rebrief", "reboundary",
|
|
41
48
|
] as const);
|
|
42
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The epoch surface: the immediate actions plus `unmark`. A mark is a standing decision
|
|
51
|
+
* rather than a one-shot attempt, so withdrawing one needs a verb of its own.
|
|
52
|
+
*
|
|
53
|
+
* There is deliberately NO agent-callable commit verb. Marking is the agent's job and
|
|
54
|
+
* folding is the runtime's, and a verb the runtime is entitled to overrule is surface
|
|
55
|
+
* without function: measured 2026-08-07 (rep 17), the agent called commit twice and the
|
|
56
|
+
* runtime correctly held it both times. The internal commit paths -- the epoch commit,
|
|
57
|
+
* the gated commit, the fence and the recovery lane -- are unchanged and unexposed.
|
|
58
|
+
*/
|
|
59
|
+
export const EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS = Object.freeze([
|
|
60
|
+
...ACTIVE_CONTEXT_TOOL_ACTIONS, "unmark",
|
|
61
|
+
] as const);
|
|
62
|
+
export type ActiveContextToolAction = typeof EPOCH_ACTIVE_CONTEXT_TOOL_ACTIONS[number];
|
|
43
63
|
export const USER_RESCUE_MAX_SOURCE_CHARS = 512_000;
|
|
44
64
|
export const DEFAULT_CONTEXT_WINDOW = 272_000;
|
|
45
65
|
export const TOOL_FOLD_CADENCE_MIN_TOKENS = 12_000;
|
|
@@ -49,10 +69,308 @@ export const MAX_EXPAND_LEASES = 64;
|
|
|
49
69
|
export const CONSOLIDATION_WIDTH_THRESHOLD = 10;
|
|
50
70
|
export const MAX_ADVISORY_DELIVERIES_PER_MILESTONE = 16;
|
|
51
71
|
|
|
72
|
+
// Ephemeral surfacing structure. These stay INTERNAL constants rather than public
|
|
73
|
+
// options: they are the knobs the surfacing experiment is meant to settle, and an
|
|
74
|
+
// option surface fixed before the accept/reject data exists would freeze a guess.
|
|
75
|
+
export const DEFAULT_SURFACING_ENABLED = true;
|
|
76
|
+
export const SURFACING_TOP_K = 3;
|
|
77
|
+
export const SURFACING_MIN_SCORE = 0.30;
|
|
78
|
+
export const SURFACING_CHAR_BUDGET = 1_000;
|
|
79
|
+
export const SURFACING_COOLDOWN_ORDINALS = 8;
|
|
80
|
+
export const SURFACING_OUTCOME_WINDOW_ORDINALS = 12;
|
|
81
|
+
export const SURFACING_RECENT_TASK_SPANS = 6;
|
|
82
|
+
export const SURFACING_MAX_TASK_CHARS = 12_000;
|
|
83
|
+
export const SURFACING_MAX_LOG_RECORDS = 128;
|
|
84
|
+
export const SURFACING_LEXICAL_WEIGHT = 0.80;
|
|
85
|
+
export const SURFACING_RECENCY_WEIGHT = 0.14;
|
|
86
|
+
export const SURFACING_DEPTH_WEIGHT = 0.06;
|
|
87
|
+
export const SURFACING_MAX_DEPTH = 4;
|
|
88
|
+
export const SURFACING_MAX_TEXT_CHARS = 220;
|
|
89
|
+
export const SURFACING_SOURCE_ID = "fold-brief";
|
|
90
|
+
|
|
52
91
|
// Conservative LOWER bound on UTF-8 bytes per provider token, used only to cap
|
|
53
|
-
// the protected byte tail as a share of a small window
|
|
92
|
+
// the protected byte tail as a share of a small window; never to estimate usage.
|
|
54
93
|
export const BYTES_PER_TOKEN_FLOOR = 2;
|
|
55
94
|
|
|
95
|
+
// Two-phase fold scheduling. A provider prefix cache is positional: any mid-window
|
|
96
|
+
// edit invalidates every byte after it, so the cost of folding is dominated by how
|
|
97
|
+
// OFTEN the projection changes, not by how much it saves. In "epoch" mode a fold
|
|
98
|
+
// decision becomes a free MARK and a later COMMIT applies every pending mark in one
|
|
99
|
+
// rewrite. "immediate" is the default and is byte-identical to pre-0.1.2 behavior.
|
|
100
|
+
export const FOLD_SCHEDULING_MODES = Object.freeze(["immediate", "epoch"] as const);
|
|
101
|
+
export type FoldSchedulingMode = typeof FOLD_SCHEDULING_MODES[number];
|
|
102
|
+
export const DEFAULT_FOLD_SCHEDULING: FoldSchedulingMode = "immediate";
|
|
103
|
+
|
|
104
|
+
// Epoch knobs stay INTERNAL constants for the same reason the surfacing knobs do:
|
|
105
|
+
// they are what the round-2 cost measurement exists to settle, and an option surface
|
|
106
|
+
// fixed before that data would freeze a guess.
|
|
107
|
+
/**
|
|
108
|
+
* An automatic commit tops up with stale tool batches until it would free this share
|
|
109
|
+
* of the window. It is a FLOOR under an epoch's accumulated marks, not the driver of
|
|
110
|
+
* one, so it is set where a commit is unambiguously worth its single rewrite.
|
|
111
|
+
*/
|
|
112
|
+
export const EPOCH_COMMIT_TARGET_WINDOW_SHARE = 0.40;
|
|
113
|
+
/**
|
|
114
|
+
* Commit when the ELIGIBLE marked mass is worth its one rewrite. Window pressure stays
|
|
115
|
+
* underneath as the safety backstop; this is the economic trigger above it.
|
|
116
|
+
*/
|
|
117
|
+
export const EPOCH_ELIGIBLE_SHARE_COMMIT_THRESHOLD = 0.30;
|
|
118
|
+
/**
|
|
119
|
+
* The reclaim floor, and the single constant that decides whether a commit is worth
|
|
120
|
+
* firing at all.
|
|
121
|
+
*
|
|
122
|
+
* Measured on memex (19,082 calls, 254 sessions): a token-weighted pooled cache share
|
|
123
|
+
* of 89.9% across two provider wires, with steady-state requests at 91.6% and any
|
|
124
|
+
* request following a mutation at 15.7%. A fold costs exactly what one user message
|
|
125
|
+
* costs. Their mutations are 5.3% of requests; rep 14's were about 34%, and that
|
|
126
|
+
* FREQUENCY difference is the entire gap between their 90% and our 64% -- the
|
|
127
|
+
* per-mutation penalty is the same on both.
|
|
128
|
+
*
|
|
129
|
+
* So a commit that would free less than this share of the truthful budget does not
|
|
130
|
+
* fire: it defers and accumulates until it is worth a full prefix. Safety outranks
|
|
131
|
+
* economics, so the hard fence and overflow recovery fire regardless.
|
|
132
|
+
*/
|
|
133
|
+
export const COMMIT_RECLAIM_FLOOR_SHARE = 0.02;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The thermostat's lower line.
|
|
137
|
+
*
|
|
138
|
+
* The floor above says what is worth firing; this says how DEEP a firing goes. The
|
|
139
|
+
* failure shape it removes is rep 13's crumb ratchet: a trigger that fires, frees a
|
|
140
|
+
* little, and re-fires almost immediately, so the window climbs a staircase of
|
|
141
|
+
* mutations. Firing at the trigger line and folding down to a target line makes the
|
|
142
|
+
* spacing between events structural -- the next event cannot arrive until inflow has
|
|
143
|
+
* refilled the gap -- so the window saws instead of climbing.
|
|
144
|
+
*
|
|
145
|
+
* The gap here is 15 points of a truthful budget. Rep 13's largest measured inflow step
|
|
146
|
+
* was 27,815 tokens against a 400,000 window, about 7 points, so at least two full
|
|
147
|
+
* worst-case steps must land before the trigger can re-arm, and the agent keeps roughly
|
|
148
|
+
* two thirds of its budget as working room after every event. Eligibility still binds:
|
|
149
|
+
* if the eligible mass cannot reach the target, the commit takes what it may and
|
|
150
|
+
* records the shortfall rather than loosening a protection to hit a number.
|
|
151
|
+
*/
|
|
152
|
+
export const CURATION_TARGET_OCCUPANCY_SHARE = 0.35;
|
|
153
|
+
export const MAX_PENDING_MARKS = 256;
|
|
154
|
+
/** Enough batches to actually reach the floor on a wide window before the loop exits. */
|
|
155
|
+
export const EPOCH_MAX_TOPUP_MARKS = 64;
|
|
156
|
+
/** Estimate only; provider token accounting always comes from a measured response. */
|
|
157
|
+
export const ESTIMATED_BYTES_PER_TOKEN = 4;
|
|
158
|
+
/** Rendered navigation/topology overhead assumed around a brief when estimating a placeholder. */
|
|
159
|
+
export const ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES = 240;
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// The reliability spine. Every behavior below shipped as an off-by-default lever
|
|
163
|
+
// through iterations 2 and 3 and was sealed ON by rep 14 (64/64), so it is now
|
|
164
|
+
// simply how pi-fold works: the flags and their conditional twins are gone, and
|
|
165
|
+
// git history is the lineage. What stays configurable is only what is genuinely a
|
|
166
|
+
// deployment fact (`providerTotalWindow`) or an experiment condition (`guidance`,
|
|
167
|
+
// `foldScheduling`, `foldPeekResults`, `guidedCuration`).
|
|
168
|
+
//
|
|
169
|
+
// Sealed unconditional: admission control, retained pending marks, the eligible-share
|
|
170
|
+
// commit trigger, stage-identified briefs, the current-turn commit guard, the
|
|
171
|
+
// pinned-mass backstop, the status index diet, delivery-counted advisories, and
|
|
172
|
+
// projection instrumentation.
|
|
173
|
+
//
|
|
174
|
+
// NOT sealed, deleted: ephemeral peek and its per-call `ephemeral` override. It rewrote
|
|
175
|
+
// a consumed peek result in place on the theory the edit was tail-local. It is not: the
|
|
176
|
+
// rewrite waits for a later assistant message to exist, by which point the window has
|
|
177
|
+
// grown over it and the edit lands mid-prefix. Rep 22 measured two of them costing 100k
|
|
178
|
+
// fresh tokens. A peek is append-only; the tool-fold rung reclaims the duplicate bytes
|
|
179
|
+
// at the next commit, which is the one moment a rewrite is already being paid for.
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The provider serving window is the TOTAL admission budget minus whatever output
|
|
184
|
+
* reservation the deployment actually asked for; the per-request max-input descriptor
|
|
185
|
+
* assumes a full output reservation and understates the real ceiling. Measured
|
|
186
|
+
* 2026-08-06: a run aborted at ~297k projected tokens against a 272k descriptor while
|
|
187
|
+
* the same provider had just accepted 339,689 tokens. Declaring the total window is
|
|
188
|
+
* the deployment's own fact, so it is the option; there is no separate boolean, and
|
|
189
|
+
* an undeclared window falls back to the descriptor and SAYS so in the accounting.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
/** Narrowing is what makes a refusal governance instead of denial. */
|
|
193
|
+
export const PEEK_MIN_SLICE_BYTES = 1_024;
|
|
194
|
+
|
|
195
|
+
/** How many folds the dieted status payload ranks by what they would reclaim. */
|
|
196
|
+
export const STATUS_DIET_SUGGESTIONS = 5;
|
|
197
|
+
|
|
198
|
+
export const MAX_PROJECTION_HASH_RECORDS = 64;
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Guided curation. The one iteration-4 experiment condition: the two-signal
|
|
202
|
+
// curation trigger, its bounded last-call gate, and the reactive receipt block.
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
export const DEFAULT_GUIDED_CURATION = false;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Signal one: measured occupancy of the truthful serving budget.
|
|
209
|
+
*
|
|
210
|
+
* Four fifths, not half (Shane RULING 2026-08-06, after rep 15): below this line the
|
|
211
|
+
* runtime is QUIET -- nothing folds automatically at all -- so the threshold is where
|
|
212
|
+
* the fold event should happen, and the remaining fifth of the budget is the runway the
|
|
213
|
+
* gate rounds and the commit itself spend. An earlier line buys announcement room by
|
|
214
|
+
* spending window the agent still wants for the task.
|
|
215
|
+
*/
|
|
216
|
+
export const CURATION_OCCUPANCY_SHARE = 0.80;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The two sparse reminders, as shares of the truthful serving budget.
|
|
220
|
+
*
|
|
221
|
+
* Exactly two per window cycle, one line each, informatory: mark chapters as you go so
|
|
222
|
+
* the folds are neat when the fold event triggers. They are the ONLY pre-gate guidance
|
|
223
|
+
* in guided curation -- recurring pressure nags trained rep 15's agent to ignore the
|
|
224
|
+
* one message that mattered -- and they re-arm after each commit cycle, so a window that
|
|
225
|
+
* climbs twice is reminded twice.
|
|
226
|
+
*/
|
|
227
|
+
export const CURATION_REMINDER_SHARES: readonly number[] = Object.freeze([0.45, 0.65]);
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Signal two: tool-result mass OUTSIDE the fresh tail, as a share of the window.
|
|
231
|
+
* Occupancy alone announces commits a commit cannot serve -- a window full of
|
|
232
|
+
* conversation has nothing stale to fold. Rep 14 ran 138 tool results over ~4.2MB of
|
|
233
|
+
* payload against a 400k window, so a fifth of the window (~80k tokens, ~320KB) is
|
|
234
|
+
* roughly a dozen ordinary results: reached early in a tool-heavy run, and never
|
|
235
|
+
* reached by a run whose window is not actually made of stale tool output. It is half
|
|
236
|
+
* the commit's own top-up target, so the announcement always precedes a commit that
|
|
237
|
+
* has at least half a target of real mass to work with.
|
|
238
|
+
*/
|
|
239
|
+
export const CURATION_STALE_TOOL_SHARE = 0.20;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The gate's hard round cap. Every context pass with the gate open consumes one round,
|
|
243
|
+
* so the commit proceeds after at most this many passes NO MATTER WHAT the agent does;
|
|
244
|
+
* a pass whose response was not a context-tool call proceeds immediately. Two is a
|
|
245
|
+
* bounded conversation, not a negotiation, and it makes a stall non-constructible.
|
|
246
|
+
*/
|
|
247
|
+
export const CURATION_GATE_MAX_ROUNDS = 2;
|
|
248
|
+
|
|
249
|
+
/** How many automatic-action receipts stay in the window; the oldest ages out. */
|
|
250
|
+
export const MAX_CONTEXT_RECEIPTS = 3;
|
|
251
|
+
/** Hard byte cap on the rendered receipt block, so a receipt can never itself bloat. */
|
|
252
|
+
export const CONTEXT_RECEIPT_BLOCK_BYTES = 900;
|
|
253
|
+
/** Durable instrumentation records kept in the in-memory ledger for status. */
|
|
254
|
+
export const MAX_CONTEXT_ATTEMPT_RECORDS = 128;
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// The mark response.
|
|
258
|
+
//
|
|
259
|
+
// Measured 2026-08-07 (rep 17): every context action was followed by a projection
|
|
260
|
+
// REWRITE, never an append, because the surface answered a mark by refreshing blocks
|
|
261
|
+
// the agent had already paid to cache. A tail-appended tool result is the one place
|
|
262
|
+
// left that can inform the agent for free, so the awareness moved THERE and the
|
|
263
|
+
// projection went byte-frozen between fold events. The result is bounded on the same
|
|
264
|
+
// principle as the receipt block: a report about bloat that becomes bloat has argued
|
|
265
|
+
// against itself.
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
/** Hard byte cap on the rendered awareness block a mark call answers with. */
|
|
269
|
+
export const CONTEXT_MARK_RESPONSE_BYTES = 900;
|
|
270
|
+
/**
|
|
271
|
+
* Hard byte cap on one serialized status payload, every detail variant included.
|
|
272
|
+
* Measured 2026-08-07 (rep 19): six paged status results accumulated to ~254k tokens
|
|
273
|
+
* and the seventh answered with 526KB, 6.2x the remaining headroom, aborting the
|
|
274
|
+
* request. Paging already existed; nothing FORCED it. Above the cap the listing is
|
|
275
|
+
* truncated at a unit boundary and a continuation names the next offset, so the
|
|
276
|
+
* biggest possible status answer is about 6k tokens and the rest is another page.
|
|
277
|
+
*/
|
|
278
|
+
export const CONTEXT_STATUS_RESPONSE_BYTES = 24_000;
|
|
279
|
+
/** How many unmarked candidates the awareness block names; the remainder is an aggregate. */
|
|
280
|
+
export const MAX_UNMARKED_CANDIDATES = 3;
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Bite-sized folds and bounded peeks. These two constants are one decision read
|
|
284
|
+
// from two ends, so they are stated together and kept equal.
|
|
285
|
+
//
|
|
286
|
+
// Measured 2026-08-06 (rep 6): a single 60,432-byte chapter fold hid the needed
|
|
287
|
+
// fact in its tail, and every peek of it was either truncated short of the answer
|
|
288
|
+
// or too expensive to widen. A fold is only navigable if reading one back is cheap,
|
|
289
|
+
// so a fold's source is capped and an oversized span is SPLIT into sequential
|
|
290
|
+
// bounded folds, each with its own brief. The peek bound is the same number, so a
|
|
291
|
+
// default peek of one bite-sized fold returns it whole -- about 4,000 tokens, a
|
|
292
|
+
// read any session can afford -- and only a legacy oversized fold is ever truncated.
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
/** A fold whose exact source exceeds this is split into sequential bounded folds. */
|
|
296
|
+
export const MAX_FOLD_SPAN_CHARS = 16_000;
|
|
297
|
+
/**
|
|
298
|
+
* The other bound: a SLIVER of stale raw content hugging a fold boundary is absorbed
|
|
299
|
+
* into its later neighbour inside a commit the epoch already paid for.
|
|
300
|
+
*
|
|
301
|
+
* The threshold is deliberately tiny, measured in the session's own calibrated TOKENS,
|
|
302
|
+
* and it is not pressure-scaled. Non-sequential curation is a first-class state: an
|
|
303
|
+
* agent may hold folds at 10:20, 40:55 and 60:70 with raw spans between them ON
|
|
304
|
+
* PURPOSE, and a mechanism that swallowed those gaps would quietly turn pi-fold into
|
|
305
|
+
* system-controlled compaction with the curation element deleted. 256 tokens is about
|
|
306
|
+
* one short prompt or one one-line result -- nobody deliberately keeps a sliver that
|
|
307
|
+
* small as standing context -- and it sits an order of magnitude under the minimum
|
|
308
|
+
* chapter size the ladder itself will fold. Anything larger stays raw permanently,
|
|
309
|
+
* however ragged the projection looks; the ladder's ordinary fold eligibility is the
|
|
310
|
+
* only thing that ever touches it, and absorption never becomes a second folding path.
|
|
311
|
+
*
|
|
312
|
+
* Later, not earlier, because extending the LATER fold backward mutates at a shallower
|
|
313
|
+
* prefix position and preserves more of the cache. There is no content-affinity logic,
|
|
314
|
+
* every absorption is receipted, and a wrong grouping is one `reboundary` away.
|
|
315
|
+
*/
|
|
316
|
+
export const MAX_WEDGE_ABSORB_TOKENS = 256;
|
|
317
|
+
/** What a peek returns without an explicit widening argument. */
|
|
318
|
+
export const PEEK_DEFAULT_MAX_BYTES = 16_000;
|
|
319
|
+
/** Head share of a truncated peek; the remainder is the tail, where conclusions live. */
|
|
320
|
+
export const PEEK_HEAD_SHARE = 0.6;
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Overflow rollback and recovery.
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* How many times one inflow may be recovered before the runtime gives up.
|
|
328
|
+
*
|
|
329
|
+
* A provider context-overflow rejection mutates nothing durable -- the assistant
|
|
330
|
+
* message never lands and the projection is rebuilt per request -- so recovery is a
|
|
331
|
+
* reduction plus a rebuild, not a branch rewind. Two attempts is the cap: the first
|
|
332
|
+
* folds at fence pressure with the guard fully waived, the second reaches into the
|
|
333
|
+
* fresh tail, and a third would be asking the same question of the same evidence.
|
|
334
|
+
* Past it the run fails LOUDLY, because an inflow that cannot fit after maximal
|
|
335
|
+
* folding is a genuine impossibility rather than a recoverable state.
|
|
336
|
+
*/
|
|
337
|
+
export const OVERFLOW_RECOVERY_MAX_ATTEMPTS = 2;
|
|
338
|
+
|
|
339
|
+
/** Active-context tool actions that read without mutating, so their results may fold. */
|
|
340
|
+
export const READ_ONLY_CONTEXT_ACTIONS_DEFAULT: ReadonlySet<string> = new Set(["status"]);
|
|
341
|
+
/**
|
|
342
|
+
* The classification that also lets a `peek` result be reclaimed by the tool-fold rung.
|
|
343
|
+
* A peek copies a fold's own stored source back into the window, so leaving it raw is a
|
|
344
|
+
* pure duplicate of evidence the runtime already holds; epoch scheduling adopts this set
|
|
345
|
+
* unconditionally, and immediate scheduling reaches it through `foldPeekResults`.
|
|
346
|
+
*/
|
|
347
|
+
export const PEEK_READ_ONLY_CONTEXT_ACTIONS: ReadonlySet<string> = new Set(["status", "peek"]);
|
|
348
|
+
|
|
349
|
+
export type MarkOrigin = "agent" | "ladder";
|
|
350
|
+
|
|
351
|
+
/** A fold decided but not yet applied: no projection byte has moved for it. */
|
|
352
|
+
export interface PendingFoldMark {
|
|
353
|
+
mark: "fold";
|
|
354
|
+
id: string;
|
|
355
|
+
kind: FoldKind;
|
|
356
|
+
parts: FoldPart[];
|
|
357
|
+
brief: string;
|
|
358
|
+
briefProvenance: BriefProvenance;
|
|
359
|
+
origin: MarkOrigin;
|
|
360
|
+
/** Transcript ordinal at mark time; orders the commit. Never a wall clock. */
|
|
361
|
+
ordinal: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** An expanded fold decided to return to its placeholder at the next commit. */
|
|
365
|
+
export interface PendingRefoldMark {
|
|
366
|
+
mark: "refold";
|
|
367
|
+
id: string;
|
|
368
|
+
origin: MarkOrigin;
|
|
369
|
+
ordinal: number;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export type PendingMark = PendingFoldMark | PendingRefoldMark;
|
|
373
|
+
|
|
56
374
|
export const ACTIVE_CONTEXT_POLICY = Object.freeze({
|
|
57
375
|
freshTurns: 3,
|
|
58
376
|
freshBytes: 24_000,
|
|
@@ -120,6 +438,44 @@ export interface PreparedFold {
|
|
|
120
438
|
fold: ActiveFold;
|
|
121
439
|
}
|
|
122
440
|
|
|
441
|
+
export type SurfacingOutcome = "shown" | "accept" | "reject";
|
|
442
|
+
|
|
443
|
+
/** One durable surfacing observation: what was shown, how it scored, and what the agent did. */
|
|
444
|
+
export interface SurfacingRecord {
|
|
445
|
+
source: string;
|
|
446
|
+
id: string;
|
|
447
|
+
score: number;
|
|
448
|
+
ordinal: number;
|
|
449
|
+
outcome: SurfacingOutcome;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** One item a suggestion source offers for the shared carrier. */
|
|
453
|
+
export interface SurfacingCandidate {
|
|
454
|
+
source: string;
|
|
455
|
+
id: string;
|
|
456
|
+
text: string;
|
|
457
|
+
route: string;
|
|
458
|
+
alternateRoute?: string;
|
|
459
|
+
/** Transcript position used for recency; higher is more recent. Never a wall clock. */
|
|
460
|
+
position?: number;
|
|
461
|
+
depth?: number;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export interface SurfacingSuggestion extends SurfacingCandidate {
|
|
465
|
+
score: number;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface SuggestionSourceInput {
|
|
469
|
+
state: ActiveContextState;
|
|
470
|
+
snapshot: ActiveContextSnapshot;
|
|
471
|
+
toolName: string;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export interface SuggestionSource {
|
|
475
|
+
id: string;
|
|
476
|
+
candidates: (input: SuggestionSourceInput) => SurfacingCandidate[];
|
|
477
|
+
}
|
|
478
|
+
|
|
123
479
|
export interface ActiveContextState {
|
|
124
480
|
version: 1;
|
|
125
481
|
sessionId: string;
|
|
@@ -129,6 +485,18 @@ export interface ActiveContextState {
|
|
|
129
485
|
protected: EvidenceRef[];
|
|
130
486
|
tokensSinceToolFold: number;
|
|
131
487
|
leases: Record<string, number>;
|
|
488
|
+
surfacing?: SurfacingRecord[];
|
|
489
|
+
/** Epoch scheduling only; omitted when empty so immediate-mode state digests never move. */
|
|
490
|
+
pendingMarks?: PendingMark[];
|
|
491
|
+
/** Retired with peek reclamation; carried only so pre-cut state still parses. */
|
|
492
|
+
pinnedPeeks?: never;
|
|
493
|
+
/**
|
|
494
|
+
* Agent-corrected fold briefs, by fold id. A fold RECORD is content-addressed and
|
|
495
|
+
* immutable -- rewriting its brief in place would report a conflicting durable fold
|
|
496
|
+
* and suspend automatic management -- so a re-brief is durable state beside the
|
|
497
|
+
* record rather than a mutation of it. Omitted when empty.
|
|
498
|
+
*/
|
|
499
|
+
briefs?: Record<string, string>;
|
|
132
500
|
prepared?: PreparedFold;
|
|
133
501
|
advisory?: {
|
|
134
502
|
highWater: number;
|
|
@@ -161,6 +529,9 @@ export interface ActiveContextCheckpointV2 {
|
|
|
161
529
|
prepared: PreparedFold | null;
|
|
162
530
|
tokensSinceToolFold?: number;
|
|
163
531
|
leases?: Record<string, number>;
|
|
532
|
+
surfacing?: SurfacingRecord[];
|
|
533
|
+
pendingMarks?: PendingMark[];
|
|
534
|
+
briefs?: Record<string, string>;
|
|
164
535
|
advisory?: NonNullable<ActiveContextState["advisory"]>;
|
|
165
536
|
stateSha256: string;
|
|
166
537
|
}
|
|
@@ -179,6 +550,9 @@ export interface ActiveContextDeltaV2 {
|
|
|
179
550
|
prepared: PreparedFold | null;
|
|
180
551
|
tokensSinceToolFold?: number;
|
|
181
552
|
leases?: Record<string, number>;
|
|
553
|
+
surfacing?: SurfacingRecord[];
|
|
554
|
+
pendingMarks?: PendingMark[];
|
|
555
|
+
briefs?: Record<string, string>;
|
|
182
556
|
advisory?: NonNullable<ActiveContextState["advisory"]>;
|
|
183
557
|
stateSha256: string;
|
|
184
558
|
}
|
|
@@ -216,6 +590,8 @@ export interface ActiveContextSnapshot {
|
|
|
216
590
|
brandNoun: string;
|
|
217
591
|
entryTypePrefix: string;
|
|
218
592
|
readOnlyTools: ReadonlySet<string>;
|
|
593
|
+
/** Active-context tool actions treated as read-only when classifying tool batches. */
|
|
594
|
+
readOnlyContextActions: ReadonlySet<string>;
|
|
219
595
|
contextWindow: number;
|
|
220
596
|
windowSource: "reported" | "fallback";
|
|
221
597
|
}
|
|
@@ -230,4 +606,4 @@ export const READ_ONLY_TOOLS_DEFAULT = new Set([
|
|
|
230
606
|
"read", "grep", "find", "ls",
|
|
231
607
|
]);
|
|
232
608
|
|
|
233
|
-
export type AdvisoryMilestone = "notice" | "tools" | "chapters" | "urgent";
|
|
609
|
+
export type AdvisoryMilestone = "orientation" | "notice" | "tools" | "chapters" | "urgent";
|