pi-fold 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -8
- package/extensions/active-context.ts +255 -2331
- 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
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
} from "./persistence.ts";
|
|
36
36
|
import { currentTurnRefKeys } from "./transcript.ts";
|
|
37
37
|
import {
|
|
38
|
-
EPOCH_MAX_TOPUP_MARKS,
|
|
39
38
|
ESTIMATED_BYTES_PER_TOKEN,
|
|
40
39
|
MAX_WEDGE_ABSORB_TOKENS,
|
|
41
40
|
ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES,
|
|
@@ -65,60 +64,23 @@ import {
|
|
|
65
64
|
resultCall,
|
|
66
65
|
} from "./selection.ts";
|
|
67
66
|
|
|
68
|
-
/**
|
|
69
|
-
* Two-phase fold scheduling.
|
|
70
|
-
*
|
|
71
|
-
* A provider prefix cache is positional: editing the middle of the window
|
|
72
|
-
* invalidates every byte after the edit, so per-turn folding pays a full uncached
|
|
73
|
-
* prefix over and over. Nothing here changes what a fold MEANS. A fold decision
|
|
74
|
-
* becomes a MARK, which moves no projection byte, and a later COMMIT applies every
|
|
75
|
-
* pending mark through the same preparation and commit machinery an immediate fold
|
|
76
|
-
* uses, in one rewrite.
|
|
77
|
-
*
|
|
78
|
-
* Every function is deterministic: transcript ordinals and positions only, never a
|
|
79
|
-
* wall clock and never randomness.
|
|
80
|
-
*/
|
|
81
|
-
|
|
82
|
-
/** The empty read, shared and frozen, so a state with no marks allocates nothing. */
|
|
83
67
|
const NO_PENDING_MARKS: readonly PendingMark[] = Object.freeze([]);
|
|
84
68
|
|
|
85
|
-
/**
|
|
86
|
-
* The pending marks as a READ VIEW, not a copy.
|
|
87
|
-
*
|
|
88
|
-
* Every caller here reads: membership tests, accounting sums, and filters that build
|
|
89
|
-
* a fresh array anyway. The copy belongs on the WRITE path, and `withPendingMarks`
|
|
90
|
-
* already takes it, so taking a second one on every read bought nothing and cost a
|
|
91
|
-
* great deal: `markSpanRefs` calls this once per mark and is itself called once per
|
|
92
|
-
* mark by the accounting, eligibility, staleness, current-turn and absorption
|
|
93
|
-
* readings, which made a deep clone of the whole array quadratic in the mark count
|
|
94
|
-
* per pass. Profiled at 21 percent of a 120-turn session, the single largest cost in
|
|
95
|
-
* the runtime, ahead of every projection and hash.
|
|
96
|
-
*
|
|
97
|
-
* The view is safe because the state is replaced rather than edited: no code path
|
|
98
|
-
* mutates `state.pendingMarks` in place, and the readonly type is what keeps it so.
|
|
99
|
-
*/
|
|
100
69
|
export function pendingMarks(state: Pick<ActiveContextState, "pendingMarks">): readonly PendingMark[] {
|
|
101
70
|
return state.pendingMarks ?? NO_PENDING_MARKS;
|
|
102
71
|
}
|
|
103
72
|
|
|
104
|
-
/**
|
|
105
|
-
* Property ORDER matters here: the state digest is a stable stringify, so an
|
|
106
|
-
* appended key would replay as digest drift. Rebuild the tail of the record in the
|
|
107
|
-
* same order `parseActiveContextState` produces.
|
|
108
|
-
*/
|
|
109
73
|
export function withPendingMarks(
|
|
110
74
|
state: ActiveContextState,
|
|
111
75
|
marks: readonly PendingMark[],
|
|
112
76
|
): ActiveContextState {
|
|
113
77
|
const next = marks.length ? parsePendingMarks(clone(marks) as PendingMark[]) : [];
|
|
114
78
|
const head = { ...state };
|
|
115
|
-
delete head.surfacing;
|
|
116
79
|
delete head.pendingMarks;
|
|
117
80
|
delete head.advisory;
|
|
118
81
|
delete head.prepared;
|
|
119
82
|
return {
|
|
120
83
|
...head,
|
|
121
|
-
...(state.surfacing?.length ? { surfacing: clone(state.surfacing) } : {}),
|
|
122
84
|
...(next.length ? { pendingMarks: next } : {}),
|
|
123
85
|
...(state.advisory ? { advisory: clone(state.advisory) } : {}),
|
|
124
86
|
...(state.prepared ? { prepared: clone(state.prepared) } : {}),
|
|
@@ -131,7 +93,6 @@ export interface MarkAddition {
|
|
|
131
93
|
reason: string | null;
|
|
132
94
|
}
|
|
133
95
|
|
|
134
|
-
/** Idempotent by mark key, bounded, and never silently dropping the oldest mark. */
|
|
135
96
|
export function addPendingMark(state: ActiveContextState, mark: PendingMark): MarkAddition {
|
|
136
97
|
const marks = pendingMarks(state);
|
|
137
98
|
if (marks.some((existing) => pendingMarkKey(existing) === pendingMarkKey(mark))) {
|
|
@@ -151,43 +112,15 @@ export function estimatedTokens(sizeInBytes: number): number {
|
|
|
151
112
|
return Math.max(0, Math.ceil(sizeInBytes / ESTIMATED_BYTES_PER_TOKEN));
|
|
152
113
|
}
|
|
153
114
|
|
|
154
|
-
/** Transcript-position ordering used for marks; identical to the surfacing ordinal. */
|
|
155
115
|
export function markOrdinal(snapshot: Pick<ActiveContextSnapshot, "mapped">): number {
|
|
156
116
|
return snapshot.mapped.length;
|
|
157
117
|
}
|
|
158
118
|
|
|
159
119
|
export interface MarkSpanRefs {
|
|
160
|
-
/** Every evidence ref the mark's parts resolve to against the state in hand. */
|
|
161
120
|
refs: EvidenceRef[];
|
|
162
|
-
/**
|
|
163
|
-
* The first part naming a fold the state does not hold, or null when the span reads
|
|
164
|
-
* in full. `pending` is the difference between a defer and a drop: a mark still
|
|
165
|
-
* standing carries the id its committed fold will have, so that fold is about to
|
|
166
|
-
* exist, and where no mark names it nothing ever will.
|
|
167
|
-
*/
|
|
168
121
|
unresolved: { foldId: string; pending: boolean } | null;
|
|
169
122
|
}
|
|
170
123
|
|
|
171
|
-
/**
|
|
172
|
-
* THE ONE READING OF A MARK'S SPAN, AND IT ANSWERS RATHER THAN THROWS.
|
|
173
|
-
*
|
|
174
|
-
* A mark's parts name folds by id. Between the mark and the commit the state can stop
|
|
175
|
-
* holding one: `reboundary` dissolves a root while consulting only `fold.parentId`, so
|
|
176
|
-
* nothing there consults the pending marks, and the mark is left naming a fold that is
|
|
177
|
-
* gone. `candidateSourceRefs` answers that with `Missing candidate child`, which is
|
|
178
|
-
* right for a candidate being prepared and wrong for every reading that merely reports
|
|
179
|
-
* on a mark: accounting, eligibility, staleness, the current-turn guard, the claimed
|
|
180
|
-
* keys and wedge absorption all ran that throw out of a status call or a commit pass,
|
|
181
|
-
* on state the runtime itself produced (Shane 2026-08-10).
|
|
182
|
-
*
|
|
183
|
-
* So the readings share this, and it is total. Raw parts resolve; a held child is
|
|
184
|
-
* flattened; an unheld child is REPORTED, with the refs either side of it still
|
|
185
|
-
* collected, because a partial span is the honest reading of a partly resolvable mark
|
|
186
|
-
* and a guard that can see half the evidence is better than one that sees none. A
|
|
187
|
-
* repeated fold on the path reads as unresolved and not pending: a corrupt forest costs
|
|
188
|
-
* one mark the ordinary drop rather than hanging the caller, which is the same stop the
|
|
189
|
-
* apply order's `placing` set carries.
|
|
190
|
-
*/
|
|
191
124
|
export function markSpanRefs(state: ActiveContextState, mark: PendingMark): MarkSpanRefs {
|
|
192
125
|
const byId = new Map(state.folds.map((fold) => [fold.id, fold] as const));
|
|
193
126
|
const marks = pendingMarks(state);
|
|
@@ -230,11 +163,6 @@ export function markSpanRefs(state: ActiveContextState, mark: PendingMark): Mark
|
|
|
230
163
|
return { refs, unresolved };
|
|
231
164
|
}
|
|
232
165
|
|
|
233
|
-
/**
|
|
234
|
-
* Estimated bytes a mark would remove from the projection. Fold marks use a
|
|
235
|
-
* placeholder estimate rather than a trial fold: the number is presentational, so
|
|
236
|
-
* paying a full preparation per mark on every status call is not worth exactness.
|
|
237
|
-
*/
|
|
238
166
|
export function markFreedBytes(
|
|
239
167
|
snapshot: ActiveContextSnapshot,
|
|
240
168
|
state: ActiveContextState,
|
|
@@ -257,14 +185,6 @@ export function markFreedBytes(
|
|
|
257
185
|
return Math.max(0, bytes(source) - placeholder);
|
|
258
186
|
}
|
|
259
187
|
|
|
260
|
-
/**
|
|
261
|
-
* Whether a pending mark covers any evidence the current excursion just gathered.
|
|
262
|
-
*
|
|
263
|
-
* Read off whatever the span resolves to. A mark naming an unheld fold still answers
|
|
264
|
-
* here, on the parts that DO resolve, so a span holding both fresh raw evidence and a
|
|
265
|
-
* child that has not landed is guarded on the fresh evidence instead of throwing the
|
|
266
|
-
* whole commit.
|
|
267
|
-
*/
|
|
268
188
|
export function markTouchesCurrentTurn(
|
|
269
189
|
state: ActiveContextState,
|
|
270
190
|
mark: PendingMark,
|
|
@@ -274,24 +194,6 @@ export function markTouchesCurrentTurn(
|
|
|
274
194
|
return markSpanRefs(state, mark).refs.some((ref) => currentTurn.has(objectRefKey(ref)));
|
|
275
195
|
}
|
|
276
196
|
|
|
277
|
-
/**
|
|
278
|
-
* The real staleness of a mark: the earliest window index its span covers, in branch
|
|
279
|
-
* order.
|
|
280
|
-
*
|
|
281
|
-
* A mark's `ordinal` is the transcript position at MARK time, so across epochs it
|
|
282
|
-
* carries when the DECISION was made and nothing about the age of what the decision
|
|
283
|
-
* covers, and inside one epoch every mark shares one value. Ordering a release by it
|
|
284
|
-
* therefore fell through to comparing mark ids, which are content hashes. Measured on the
|
|
285
|
-
* sealed rep-3 state 2026-08-10: the bounded release held back the marks covering window
|
|
286
|
-
* indices 101 and 111 and surrendered the one covering 117, the newest material on the
|
|
287
|
-
* table, so the two marks the guard kept were not the two it exists to keep.
|
|
288
|
-
*
|
|
289
|
-
* A span that no longer maps sorts past the newest entry: there is no staleness to read
|
|
290
|
-
* on material that has left the branch, and releasing such a mark frees no window byte.
|
|
291
|
-
* A span that does not resolve in full sorts there too, for the same reason and by the
|
|
292
|
-
* same number: while a named fold is missing there is no start to read, and the apply
|
|
293
|
-
* order places such a mark off its naming rather than off this reading.
|
|
294
|
-
*/
|
|
295
197
|
export function markSpanStart(
|
|
296
198
|
snapshot: ActiveContextSnapshot,
|
|
297
199
|
state: ActiveContextState,
|
|
@@ -308,71 +210,48 @@ export function markSpanStart(
|
|
|
308
210
|
return indices.length ? Math.min(...indices) : snapshot.mapped.length;
|
|
309
211
|
}
|
|
310
212
|
|
|
311
|
-
/**
|
|
312
|
-
* How many of the newest guarded marks the pressure waiver still protects. The agent's
|
|
313
|
-
* MOST RECENT reads are the ones an in-flight excursion is about to work from, so they
|
|
314
|
-
* are the last thing surrendered; everything older is fair game once the window is the
|
|
315
|
-
* bigger risk.
|
|
316
|
-
*/
|
|
317
213
|
export const GUARD_WAIVER_PROTECTED_MARKS = 2;
|
|
318
214
|
|
|
319
|
-
/**
|
|
320
|
-
* How many marks the guard must be holding before the pressure waiver releases any.
|
|
321
|
-
* Without a floor the waiver degrades into exactly the per-pass folding epoch
|
|
322
|
-
* scheduling exists to prevent: the batch drains, one new mark is guarded on the next
|
|
323
|
-
* pass, and releasing that one mark buys a whole prefix invalidation. Below the fence
|
|
324
|
-
* the waiver only ever releases a BATCH. The fence itself ignores this floor.
|
|
325
|
-
*/
|
|
326
215
|
export const GUARD_WAIVER_MINIMUM_MARKS = 4;
|
|
327
216
|
|
|
328
|
-
/**
|
|
329
|
-
* Survivability outranks the guard.
|
|
330
|
-
*
|
|
331
|
-
* The current-turn guard is correct while there is room to wait, and fatal once there
|
|
332
|
-
* is not: a workload that never emits a terminal assistant message has ONE turn, so
|
|
333
|
-
* the boundary never advances and every mark is guarded forever. Measured 2026-08-06
|
|
334
|
-
* (rep 11): 58 assistant messages, all stopReason "toolUse", ZERO terminal ones, so
|
|
335
|
-
* the automatic commit applied nothing all run; the only marks that ever landed came
|
|
336
|
-
* from the agent's own two explicit commits. The window reached 359,625 tokens and the
|
|
337
|
-
* next request went out at ~458k estimated and was rejected by the provider.
|
|
338
|
-
*
|
|
339
|
-
* So: the guard holds in full while the commit has other work. It is waived only when
|
|
340
|
-
* it would STARVE the commit -- no other mark could be applied -- and pressure has
|
|
341
|
-
* reached the backstop, and then only for the OLDEST guarded marks. At the hard fence
|
|
342
|
-
* every guarded mark is waived, because the only useful action left is the fold that
|
|
343
|
-
* keeps the request transmissible.
|
|
344
|
-
*/
|
|
345
217
|
export function guardWaiverCount(input: {
|
|
346
218
|
snapshot: ActiveContextSnapshot;
|
|
347
219
|
ratio: number | null;
|
|
348
220
|
guardedMarks: number;
|
|
349
|
-
/** Marks this commit can apply WITHOUT the waiver. While any exist, the guard holds. */
|
|
350
221
|
otherApplicableMarks: number;
|
|
222
|
+
/**
|
|
223
|
+
* THE BOUNDARY IS ITS OWN SIGNAL, and a better one than the occupancy anchor.
|
|
224
|
+
*
|
|
225
|
+
* The anchor exists to answer "is this session in enough trouble to spend the open
|
|
226
|
+
* turn's own evidence": a proxy, because nothing else was asking. At a compaction
|
|
227
|
+
* boundary Pi has already answered it. Cancelling the compaction and then applying
|
|
228
|
+
* nothing hands Pi's own threshold a byte-identical projection, which is worse than
|
|
229
|
+
* letting it compact, because compaction would at least have shed something.
|
|
230
|
+
*
|
|
231
|
+
* Measured on the sol-20260813 smoke: the boundary fired, cancelled the compaction and
|
|
232
|
+
* retained all seven marks at 0.20 occupancy, freeing nothing. The full run reaches the
|
|
233
|
+
* anchor 12,576 tokens past its own trigger, so it self-corrects there and this is
|
|
234
|
+
* about what happens in between; a smoke, or any session whose budget it never
|
|
235
|
+
* approaches, never corrects at all.
|
|
236
|
+
*
|
|
237
|
+
* Everything else about the waiver is unchanged, and deliberately: the newest marks
|
|
238
|
+
* still stay raw, a small guarded set still holds whole, and other applicable work
|
|
239
|
+
* still takes precedence over spending the open turn.
|
|
240
|
+
*/
|
|
241
|
+
boundary?: boolean;
|
|
351
242
|
}): number {
|
|
352
|
-
const { snapshot, ratio, guardedMarks, otherApplicableMarks } = input;
|
|
243
|
+
const { snapshot, ratio, guardedMarks, otherApplicableMarks, boundary } = input;
|
|
353
244
|
if (guardedMarks <= 0 || typeof ratio !== "number" || !Number.isFinite(ratio)) return 0;
|
|
354
245
|
if (ratio >= hardFenceRatio(snapshot)) return guardedMarks;
|
|
355
246
|
const occupancy = budgetOccupancy(snapshot, ratio);
|
|
356
|
-
if (occupancy === null ||
|
|
247
|
+
if (occupancy === null || otherApplicableMarks > 0) return 0;
|
|
248
|
+
if (occupancy < snapshot.policy.refoldRatio && !boundary) return 0;
|
|
357
249
|
if (guardedMarks < GUARD_WAIVER_MINIMUM_MARKS) return 0;
|
|
358
250
|
return Math.max(1, guardedMarks - GUARD_WAIVER_PROTECTED_MARKS);
|
|
359
251
|
}
|
|
360
252
|
|
|
361
253
|
export type MarkEligibility = "eligible" | "protected" | "unfulfillable";
|
|
362
254
|
|
|
363
|
-
/**
|
|
364
|
-
* Whether a pending mark can be applied right now.
|
|
365
|
-
*
|
|
366
|
-
* "protected" is a WAITING state, not a failure: the span is fresh or the agent
|
|
367
|
-
* protected it, and both conditions expire. "unfulfillable" is terminal: the evidence
|
|
368
|
-
* or the fold the mark names has left the branch, so no later commit can honour it.
|
|
369
|
-
* Collapsing the two is what made a refusal look like a drop.
|
|
370
|
-
*
|
|
371
|
-
* A mark naming a fold the state does not hold is judged by the same two words, and
|
|
372
|
-
* the classification is the whole answer: a fold another pending mark is about to mint
|
|
373
|
-
* is a span still WAITING to be readable, and a fold nothing will mint has left the
|
|
374
|
-
* branch exactly as lost evidence has. No third word (Shane 2026-08-10).
|
|
375
|
-
*/
|
|
376
255
|
export function markEligibility(
|
|
377
256
|
snapshot: ActiveContextSnapshot,
|
|
378
257
|
state: ActiveContextState,
|
|
@@ -403,25 +282,15 @@ export interface MarkAccounting {
|
|
|
403
282
|
freedTokens: number;
|
|
404
283
|
freedBudgetShare: number;
|
|
405
284
|
rewriteTokens: number;
|
|
406
|
-
/** Marks a commit could apply right now. */
|
|
407
285
|
eligibleMarks: number;
|
|
408
|
-
/** Marks whose span is still fresh or protected, waiting rather than lost. */
|
|
409
286
|
retainedMarks: number;
|
|
410
287
|
eligibleFreedBytes: number;
|
|
411
288
|
eligibleFreedTokens: number;
|
|
412
|
-
/** The ROI signal: eligible marked mass as a share of the truthful window. */
|
|
413
289
|
eligibleFreedBudgetShare: number;
|
|
414
|
-
/** Raw peek mass no reclamation can take: pinned reads and protected results. */
|
|
415
290
|
pinnedBytes: number;
|
|
416
|
-
/** How many peek results that mass is spread over. */
|
|
417
291
|
pinnedResults: number;
|
|
418
292
|
}
|
|
419
293
|
|
|
420
|
-
/**
|
|
421
|
-
* What a commit of the current marks would cost and free. `rewriteTokens` is the
|
|
422
|
-
* prefix a commit invalidates: everything from the earliest marked message to the
|
|
423
|
-
* end of the window, which is exactly what a positional cache must re-send.
|
|
424
|
-
*/
|
|
425
294
|
export function markAccounting(
|
|
426
295
|
snapshot: ActiveContextSnapshot,
|
|
427
296
|
state: ActiveContextState,
|
|
@@ -474,22 +343,6 @@ export function markAccounting(
|
|
|
474
343
|
};
|
|
475
344
|
}
|
|
476
345
|
|
|
477
|
-
/**
|
|
478
|
-
* THE commit trigger. There is exactly one.
|
|
479
|
-
*
|
|
480
|
-
* Occupancy of the serving budget reaches maxTarget and a commit fires. Nothing else
|
|
481
|
-
* fires one automatically: the user command and the fence/recovery lane are the only
|
|
482
|
-
* other paths into a commit, and both are authority rather than economics.
|
|
483
|
-
*
|
|
484
|
-
* Two arms used to sit here. The eligible-share arm asked whether accumulated marks
|
|
485
|
-
* were worth a rewrite and fired 164 commits from ordinal 17 in rep 15, each a fresh
|
|
486
|
-
* cache rebuild bought with marks that had not yet earned one; worse, it could fire
|
|
487
|
-
* BELOW maxTarget, which is the quiet-runtime law it was sitting underneath. The
|
|
488
|
-
* pressure arm read a window ratio against the refold rung, which after
|
|
489
|
-
* re-denomination is simply a second line above this one. Both are gone. Marking is
|
|
490
|
-
* doorless below the commit line and costs nothing, so what a pass accumulates is never a
|
|
491
|
-
* reason to commit; crossing the band top is.
|
|
492
|
-
*/
|
|
493
346
|
export function epochCommitDue(snapshot: ActiveContextSnapshot, ratio: number | null): boolean {
|
|
494
347
|
const occupancy = budgetOccupancy(snapshot, ratio);
|
|
495
348
|
return occupancy !== null && occupancy >= snapshot.thresholds.maxTarget;
|
|
@@ -505,8 +358,6 @@ export function foldMarkFor(input: {
|
|
|
505
358
|
const { candidate } = input;
|
|
506
359
|
return {
|
|
507
360
|
mark: "fold",
|
|
508
|
-
// The same identity the committed fold will get, so a repeated decision is
|
|
509
|
-
// inert rather than a second fold of the same span.
|
|
510
361
|
id: foldIdFor(candidate.kind, candidate.parts),
|
|
511
362
|
kind: candidate.kind,
|
|
512
363
|
parts: clone(candidate.parts),
|
|
@@ -517,25 +368,18 @@ export function foldMarkFor(input: {
|
|
|
517
368
|
};
|
|
518
369
|
}
|
|
519
370
|
|
|
520
|
-
/** The deterministic brief a ladder decision would have folded with. */
|
|
521
371
|
export function ladderBrief(
|
|
522
372
|
snapshot: ActiveContextSnapshot,
|
|
523
373
|
state: ActiveContextState,
|
|
524
374
|
candidate: FoldCandidate,
|
|
525
375
|
): string {
|
|
526
376
|
if (candidate.kind === "tool-result") return automaticToolBrief(snapshot, candidate);
|
|
527
|
-
if (candidate.kind === "consolidation")
|
|
377
|
+
if (candidate.kind === "consolidation") {
|
|
378
|
+
return deterministicConsolidationBrief(candidate, state, snapshot.toolName, snapshot);
|
|
379
|
+
}
|
|
528
380
|
return deterministicChapterCandidateBrief(snapshot, candidate);
|
|
529
381
|
}
|
|
530
382
|
|
|
531
|
-
/**
|
|
532
|
-
* The brief and provenance an automatic mark carries.
|
|
533
|
-
*
|
|
534
|
-
* A warmed chapter brief the runtime already paid a model call for is REUSED when the
|
|
535
|
-
* prepared fold is exactly this candidate. Under epoch scheduling every automatic fold
|
|
536
|
-
* arrives through a mark, so a preparation whose brief the mark path ignored would be a
|
|
537
|
-
* model call spent on nothing.
|
|
538
|
-
*/
|
|
539
383
|
export function automaticMarkBrief(
|
|
540
384
|
snapshot: ActiveContextSnapshot,
|
|
541
385
|
state: ActiveContextState,
|
|
@@ -548,11 +392,6 @@ export function automaticMarkBrief(
|
|
|
548
392
|
return { brief: ladderBrief(snapshot, state, candidate), briefProvenance: { kind: "deterministic" } };
|
|
549
393
|
}
|
|
550
394
|
|
|
551
|
-
/**
|
|
552
|
-
* Turn one automatic rung selection into a mark. Prepared chapters are excluded on
|
|
553
|
-
* purpose: that rung only fires at the hard provider fence, which is already inside
|
|
554
|
-
* a commit epoch, and deferring it would strand a model-generated brief.
|
|
555
|
-
*/
|
|
556
395
|
export function ladderSelectionMark(input: {
|
|
557
396
|
snapshot: ActiveContextSnapshot;
|
|
558
397
|
state: ActiveContextState;
|
|
@@ -574,24 +413,6 @@ export function ladderSelectionMark(input: {
|
|
|
574
413
|
});
|
|
575
414
|
}
|
|
576
415
|
|
|
577
|
-
/**
|
|
578
|
-
* PEEK COPIES ARE EPHEMERAL BY CONTRACT.
|
|
579
|
-
*
|
|
580
|
-
* A peek returns a fold's exact stored source, so the copy sits in the window as raw
|
|
581
|
-
* mass beside that fold's own placeholder: the same bytes held twice, with the second
|
|
582
|
-
* copy re-retrievable forever from the id the agent already used. So a completed peek
|
|
583
|
-
* read is reclaimed at the NEXT commit rather than aging through the ladder, and the
|
|
584
|
-
* window being append-only is why the reclaim can only happen at a commit boundary.
|
|
585
|
-
*
|
|
586
|
-
* Two survival paths, and only two. Expanding the fold it peeked means the agent asked
|
|
587
|
-
* for those bytes in place, and pinning is the veto: a pin holds the copy raw through
|
|
588
|
-
* every commit, exactly as it holds any other evidence, and lifting it hands the copy
|
|
589
|
-
* back to the next commit. Both are read here at marking time, and a pin made AFTER the
|
|
590
|
-
* mark exists still vetoes, because a protected mark waits instead of applying.
|
|
591
|
-
*
|
|
592
|
-
* These marks are minted at the last-call exposure as well as at the commit, so the
|
|
593
|
-
* pending disposal is visible during the gated round the pin has to fit inside.
|
|
594
|
-
*/
|
|
595
416
|
export function ephemeralPeekMarks(input: {
|
|
596
417
|
snapshot: ActiveContextSnapshot;
|
|
597
418
|
state: ActiveContextState;
|
|
@@ -619,7 +440,6 @@ export function ephemeralPeekMarks(input: {
|
|
|
619
440
|
.map(({ index }) => snapshot.mapped[index].ref!);
|
|
620
441
|
const peeked = peekedSourceFoldIds(snapshot, refs);
|
|
621
442
|
if (!peeked) continue;
|
|
622
|
-
// The agent committed to what it peeked: leave the read in the raw window.
|
|
623
443
|
if (peeked.some((id) => state.expanded.includes(id) || peekedFoldProtected(snapshot, state, id))) continue;
|
|
624
444
|
if (refs.some((ref) => claimed.has(objectRefKey(ref))) ||
|
|
625
445
|
refs.length > snapshot.policy.maxFoldSourceRefs ||
|
|
@@ -633,8 +453,6 @@ export function ephemeralPeekMarks(input: {
|
|
|
633
453
|
candidate,
|
|
634
454
|
brief: automaticToolBrief(snapshot, candidate),
|
|
635
455
|
briefProvenance: { kind: "deterministic" },
|
|
636
|
-
// A peek is an agent-initiated read; attributing its disposal to the ladder
|
|
637
|
-
// would deflate the agent-vs-ladder mark ratio the adjudication reads.
|
|
638
456
|
origin: "agent",
|
|
639
457
|
ordinal: input.ordinal,
|
|
640
458
|
}));
|
|
@@ -655,7 +473,6 @@ function peekedFoldProtected(
|
|
|
655
473
|
: refsProtected(refs, state, snapshot);
|
|
656
474
|
}
|
|
657
475
|
|
|
658
|
-
/** Every evidence key already spoken for by a fold or a pending fold mark. */
|
|
659
476
|
export function claimedRefKeys(state: ActiveContextState): Set<string> {
|
|
660
477
|
const keys = new Set<string>();
|
|
661
478
|
for (const fold of state.folds) {
|
|
@@ -668,35 +485,13 @@ export function claimedRefKeys(state: ActiveContextState): Set<string> {
|
|
|
668
485
|
return keys;
|
|
669
486
|
}
|
|
670
487
|
|
|
671
|
-
/** What is still on the table after the marks in hand: the steering number and its parts. */
|
|
672
488
|
export interface UnmarkedRemainder {
|
|
673
|
-
/** Tool results in a MEMBER batch that no fold and no pending mark owns. */
|
|
674
489
|
spans: number;
|
|
675
490
|
tokens: number;
|
|
676
|
-
/** Unmarked member tokens as a share of the member tool mass. The steering number. */
|
|
677
491
|
share: number;
|
|
678
|
-
/** The largest few, by reclaim value, so the next batch is one read away. */
|
|
679
492
|
candidates: Array<{ id: string; tokens: number }>;
|
|
680
493
|
}
|
|
681
494
|
|
|
682
|
-
/**
|
|
683
|
-
* The unmarked remainder, as an AGGREGATE plus a bounded head.
|
|
684
|
-
*
|
|
685
|
-
* An exhaustive list is what the status index already is, and re-rendering it is what
|
|
686
|
-
* this build removed from the projection. What an agent needs to decide the NEXT batch
|
|
687
|
-
* is one percentage and the few largest names; everything else is a total.
|
|
688
|
-
*
|
|
689
|
-
* ONE DEFINITION OF STALE, AND IT IS THE SELECTOR'S OWN. The scope is class membership:
|
|
690
|
-
* every result inside a batch `automaticToolBatches` admits, which is the same
|
|
691
|
-
* enumeration `selectAutomaticToolBatch` picks its proposal out of. So the last call and
|
|
692
|
-
* the deferred-commit record announce mass a commit can actually take, by construction
|
|
693
|
-
* rather than by two filters kept in step. Counting whole-window tool results instead
|
|
694
|
-
* announced material no rung could ever propose: rep 3 named 280k tokens while every
|
|
695
|
-
* selector had nothing, which reads as a runtime refusing work it had just described.
|
|
696
|
-
*
|
|
697
|
-
* Membership is not ownership: the denominator is every member result, taken or not, so
|
|
698
|
-
* the share reads as the fraction of reachable tool mass still unspoken for.
|
|
699
|
-
*/
|
|
700
495
|
export function unmarkedRemainder(
|
|
701
496
|
snapshot: ActiveContextSnapshot,
|
|
702
497
|
state: ActiveContextState,
|
|
@@ -729,24 +524,12 @@ export function unmarkedRemainder(
|
|
|
729
524
|
};
|
|
730
525
|
}
|
|
731
526
|
|
|
732
|
-
/** Every fold id already spoken for by a pending refold mark. */
|
|
733
527
|
export function markedFoldIds(state: ActiveContextState): Set<string> {
|
|
734
528
|
const ids = new Set<string>();
|
|
735
529
|
for (const mark of pendingMarks(state)) if (mark.mark === "refold") ids.add(mark.id);
|
|
736
530
|
return ids;
|
|
737
531
|
}
|
|
738
532
|
|
|
739
|
-
/**
|
|
740
|
-
* THE COUNT LAW, AS MARKS. Every parent the eligible-root count owes right now, all of
|
|
741
|
-
* them, in the epoch that notices.
|
|
742
|
-
*
|
|
743
|
-
* Separate from the top-up on purpose. The top-up is pressure arithmetic: it stops the
|
|
744
|
-
* moment the marks in hand reach the freeing target, which is the right rule for how
|
|
745
|
-
* DEEP a commit cuts and the wrong rule entirely for a law that says a count of eligible
|
|
746
|
-
* roots at or above the width is a state that must not persist. So this runs at every
|
|
747
|
-
* commit epoch and it runs first, whatever the pressure and whatever the top-up would
|
|
748
|
-
* have proposed (Shane 2026-08-10).
|
|
749
|
-
*/
|
|
750
533
|
export function consolidationMarks(input: {
|
|
751
534
|
snapshot: ActiveContextSnapshot;
|
|
752
535
|
state: ActiveContextState;
|
|
@@ -760,22 +543,12 @@ export function consolidationMarks(input: {
|
|
|
760
543
|
}));
|
|
761
544
|
}
|
|
762
545
|
|
|
763
|
-
/**
|
|
764
|
-
* Agent judgment leads; automation guarantees the floor. If the marks the agent made
|
|
765
|
-
* would free less than the target share of the window, automation adds the stalest
|
|
766
|
-
* unprotected eligible SPANS until they do: completed tool batches and raw narrative
|
|
767
|
-
* chapters. Placeholders are not on that list any more; a placeholder gets a parent from
|
|
768
|
-
* the count law above, which is not pressure arithmetic and does not stop at a target.
|
|
769
|
-
*/
|
|
770
546
|
export function topUpMarks(input: {
|
|
771
547
|
snapshot: ActiveContextSnapshot;
|
|
772
548
|
state: ActiveContextState;
|
|
773
549
|
ordinal: number;
|
|
774
|
-
/** Freeing target as a share of the SERVING BUDGET. Required: there is no second default. */
|
|
775
550
|
targetShare: number;
|
|
776
|
-
/** Evidence keys the top-up may never propose, e.g. the current excursion's reads. */
|
|
777
551
|
excludeRefKeys?: ReadonlySet<string>;
|
|
778
|
-
/** Measure progress against ELIGIBLE mass, so pinned or protected marks never count. */
|
|
779
552
|
eligibleOnly?: boolean;
|
|
780
553
|
}): PendingFoldMark[] {
|
|
781
554
|
const { snapshot } = input;
|
|
@@ -789,7 +562,7 @@ export function topUpMarks(input: {
|
|
|
789
562
|
return input.eligibleOnly ? accounting.eligibleFreedBudgetShare : accounting.freedBudgetShare;
|
|
790
563
|
};
|
|
791
564
|
let share = progress(state);
|
|
792
|
-
|
|
565
|
+
while (share < target) {
|
|
793
566
|
const candidate = selectAutomaticSpan(snapshot, state, claimed);
|
|
794
567
|
if (!candidate) break;
|
|
795
568
|
const mark = foldMarkFor({
|
|
@@ -808,15 +581,8 @@ export function topUpMarks(input: {
|
|
|
808
581
|
return marks;
|
|
809
582
|
}
|
|
810
583
|
|
|
811
|
-
/** One sliver a commit swallowed into its later neighbour, reported never inferred. */
|
|
812
584
|
export interface AbsorbedWedge {
|
|
813
|
-
/** The mark that grew backward to cover it. */
|
|
814
585
|
intoMarkId: string;
|
|
815
|
-
/**
|
|
816
|
-
* The id that mark carried BEFORE it grew. A mark's id is derived from its span, so
|
|
817
|
-
* growing the span mints a new one; without this the trail from the decision the
|
|
818
|
-
* agent made to the fold that carries it is broken, and a mark appears to vanish.
|
|
819
|
-
*/
|
|
820
586
|
fromMarkId: string;
|
|
821
587
|
startId: string;
|
|
822
588
|
endId: string;
|
|
@@ -824,28 +590,10 @@ export interface AbsorbedWedge {
|
|
|
824
590
|
tokens: number;
|
|
825
591
|
}
|
|
826
592
|
|
|
827
|
-
/**
|
|
828
|
-
* Wedge absorption. THE ANTI-LCM PIN LIVES HERE.
|
|
829
|
-
*
|
|
830
|
-
* A sliver of stale raw content hugging a fold boundary is a crumb nothing will ever
|
|
831
|
-
* reclaim: it is below the minimum chapter size, so no chapter rung will take it, and
|
|
832
|
-
* it is not a tool batch, so no tool rung will either. Absorbing it costs nothing
|
|
833
|
-
* because it happens INSIDE a commit the epoch has already paid for -- it grows a
|
|
834
|
-
* pending mark backward rather than performing a mutation of its own.
|
|
835
|
-
*
|
|
836
|
-
* What it must never do is erode deliberate non-sequential curation. An agent holding
|
|
837
|
-
* folds at 10:20, 40:55 and 60:70 with raw spans between them chose that shape, and a
|
|
838
|
-
* mechanism that swallowed those gaps would delete the curation element outright. So
|
|
839
|
-
* the threshold is tiny, measured in the session's own calibrated tokens, and NOT
|
|
840
|
-
* pressure-scaled: above it, a gap stays raw permanently no matter how full the window
|
|
841
|
-
* gets or how ragged the projection looks.
|
|
842
|
-
*/
|
|
843
593
|
export function absorbWedgeMarks(input: {
|
|
844
594
|
snapshot: ActiveContextSnapshot;
|
|
845
595
|
state: ActiveContextState;
|
|
846
|
-
/** The session's measured serialized chars per token; never a fixed constant. */
|
|
847
596
|
charsPerToken: number;
|
|
848
|
-
/** Evidence absorption may never touch, e.g. the current excursion's own reads. */
|
|
849
597
|
excludeRefKeys?: ReadonlySet<string>;
|
|
850
598
|
}): { state: ActiveContextState; absorbed: AbsorbedWedge[] } {
|
|
851
599
|
const { snapshot } = input;
|
|
@@ -863,9 +611,7 @@ export function absorbWedgeMarks(input: {
|
|
|
863
611
|
.filter((index): index is number => index !== undefined);
|
|
864
612
|
return indices.length ? { start: Math.min(...indices), end: Math.max(...indices) } : null;
|
|
865
613
|
};
|
|
866
|
-
|
|
867
|
-
// has already grown is not a candidate to grow again in the same commit.
|
|
868
|
-
for (let guard = 0; guard < MAX_PENDING_MARKS; guard += 1) {
|
|
614
|
+
for (;;) {
|
|
869
615
|
const marks = pendingMarks(state);
|
|
870
616
|
const occupied = [
|
|
871
617
|
...orderedRoots(state, snapshot).map((root) => ({ start: root.start, end: root.end })),
|
|
@@ -886,7 +632,6 @@ export function absorbWedgeMarks(input: {
|
|
|
886
632
|
if (gapStart < 0 || gapEnd < gapStart) continue;
|
|
887
633
|
if (absorbed.some((item) => item.intoMarkId === mark.id)) continue;
|
|
888
634
|
const gapTokens = Math.ceil(spanBytes(snapshot, gapStart, gapEnd + 1) / charsPerToken);
|
|
889
|
-
// The whole discriminator, in one line: above this, the gap is the agent's.
|
|
890
635
|
if (gapTokens > MAX_WEDGE_ABSORB_TOKENS) continue;
|
|
891
636
|
const gapRefs = [];
|
|
892
637
|
let usable = true;
|
|
@@ -897,15 +642,19 @@ export function absorbWedgeMarks(input: {
|
|
|
897
642
|
}
|
|
898
643
|
if (!usable || !gapRefs.length) continue;
|
|
899
644
|
if (refsProtected(gapRefs, state, snapshot)) continue;
|
|
900
|
-
// Structural safety: the grown span must still be a valid closed chapter, which
|
|
901
|
-
// is what keeps every tool call paired with its result in the projection.
|
|
902
645
|
if (!chapterRangeIsUnitAligned(snapshot, gapStart, interval.end)) continue;
|
|
903
646
|
const parts = partsForRange(snapshot, state, gapStart, interval.end, new Set<FoldKind>(["tool-result"]));
|
|
904
647
|
if (!parts) continue;
|
|
648
|
+
// ELIGIBILITY, not truncation (Shane 2026-08-14): the grown fold's brief
|
|
649
|
+
// must name what it absorbed, and a brief already near the policy cap has
|
|
650
|
+
// no room for that sentence. Slicing dropped exactly the words that made
|
|
651
|
+
// the absorption honest, so a wedge whose truthful suffix does not fit is
|
|
652
|
+
// simply not absorbed; the gap stays raw for a later commit.
|
|
653
|
+
const absorbSuffix = ` It also holds ${gapRefs.length} short adjacent entry(s) absorbed at commit.`;
|
|
654
|
+
if (mark.brief.length + absorbSuffix.length > snapshot.policy.maxBriefChars) continue;
|
|
905
655
|
const grown = foldMarkFor({
|
|
906
656
|
candidate: { kind: "chapter", parts, sourceRefs: candidateSourceRefs(parts, state) },
|
|
907
|
-
brief: `${mark.brief}
|
|
908
|
-
.slice(0, snapshot.policy.maxBriefChars),
|
|
657
|
+
brief: `${mark.brief}${absorbSuffix}`,
|
|
909
658
|
briefProvenance: mark.briefProvenance,
|
|
910
659
|
origin: mark.origin,
|
|
911
660
|
ordinal: mark.ordinal,
|
|
@@ -941,7 +690,6 @@ export interface RefusedMark {
|
|
|
941
690
|
id: string;
|
|
942
691
|
origin: MarkOrigin;
|
|
943
692
|
reason: string;
|
|
944
|
-
/** Whether the mark survives as pending rather than being discarded. */
|
|
945
693
|
retained: boolean;
|
|
946
694
|
}
|
|
947
695
|
|
|
@@ -950,38 +698,16 @@ export interface CommitEpochResult {
|
|
|
950
698
|
applied: AppliedMark[];
|
|
951
699
|
refused: RefusedMark[];
|
|
952
700
|
retained: PendingMark[];
|
|
953
|
-
/** Guarded marks the pressure waiver released into this commit, oldest material first. */
|
|
954
701
|
waived: PendingMark[];
|
|
955
702
|
}
|
|
956
703
|
|
|
957
|
-
/**
|
|
958
|
-
* Apply every pending mark. This is the ONE mutation of the epoch: it reuses
|
|
959
|
-
* prepareFold/commitPreparedFold exactly as the immediate path does, so a committed
|
|
960
|
-
* mark and an immediate fold produce the same fold record.
|
|
961
|
-
*/
|
|
962
704
|
export async function commitPendingMarks(input: {
|
|
963
705
|
snapshot: ActiveContextSnapshot;
|
|
964
706
|
state: ActiveContextState;
|
|
965
707
|
generation: number;
|
|
966
708
|
now?: () => number;
|
|
967
|
-
/**
|
|
968
|
-
* Keep marks the commit could not apply YET. A mark is a standing decision; dropping
|
|
969
|
-
* it because the span happened to be fresh at commit time silently discards the
|
|
970
|
-
* agent's judgment and leaves nothing for the next commit to act on.
|
|
971
|
-
*/
|
|
972
709
|
retainIneligible?: boolean;
|
|
973
|
-
/**
|
|
974
|
-
* Never fold evidence the CURRENT excursion gathered. A commit that runs between
|
|
975
|
-
* an agent's read and its use of that read hands the agent a window where its own
|
|
976
|
-
* just-gathered evidence is a placeholder. Such marks stay pending for the next
|
|
977
|
-
* commit rather than being applied or dropped.
|
|
978
|
-
*/
|
|
979
710
|
guardCurrentTurn?: boolean;
|
|
980
|
-
/**
|
|
981
|
-
* How many guarded marks the pressure waiver releases anyway, oldest material first. The
|
|
982
|
-
* guard protects an in-flight excursion; it may never cost the session its ability
|
|
983
|
-
* to send a request at all. See `guardWaiverCount`.
|
|
984
|
-
*/
|
|
985
711
|
guardWaiver?: number;
|
|
986
712
|
}): Promise<CommitEpochResult> {
|
|
987
713
|
const marks = pendingMarks(input.state);
|
|
@@ -990,13 +716,6 @@ export async function commitPendingMarks(input: {
|
|
|
990
716
|
const guardReasons = new Map<string, string>();
|
|
991
717
|
const held = new Set(input.state.folds.map((fold) => fold.id));
|
|
992
718
|
const deferred: PendingMark[] = [];
|
|
993
|
-
/**
|
|
994
|
-
* Whether the mark names a fold some other standing mark is about to mint. Until that
|
|
995
|
-
* fold exists there is no eligibility worth judging: the question is about material
|
|
996
|
-
* the child owns, and the apply loop is where it gets asked, after the child has
|
|
997
|
-
* landed. A mark naming a fold NOBODY will mint is not this case; it is judged
|
|
998
|
-
* normally, and the apply loop drops it by name.
|
|
999
|
-
*/
|
|
1000
719
|
const awaitingMintedChild = (mark: PendingMark): boolean =>
|
|
1001
720
|
markSpanRefs(input.state, mark).unresolved?.pending === true;
|
|
1002
721
|
const currentTurn = input.guardCurrentTurn
|
|
@@ -1009,11 +728,6 @@ export async function commitPendingMarks(input: {
|
|
|
1009
728
|
if (markTouchesCurrentTurn(input.state, mark, currentTurn)) guarded.push(mark);
|
|
1010
729
|
else applicable.push(mark);
|
|
1011
730
|
}
|
|
1012
|
-
// Oldest MATERIAL first: the newest reads are the ones the excursion is about to
|
|
1013
|
-
// use, so they are the last evidence the waiver surrenders. Staleness is a property
|
|
1014
|
-
// of the span, never of when the mark was proposed; see `markSpanStart`. The id
|
|
1015
|
-
// comparison is the final tiebreak between marks covering the same start, and
|
|
1016
|
-
// nothing else.
|
|
1017
731
|
const guardedStart = new Map(guarded.map((mark) =>
|
|
1018
732
|
[pendingMarkKey(mark), markSpanStart(input.snapshot, input.state, mark)] as const));
|
|
1019
733
|
guarded.sort((left, right) =>
|
|
@@ -1038,10 +752,6 @@ export async function commitPendingMarks(input: {
|
|
|
1038
752
|
if (input.retainIneligible) {
|
|
1039
753
|
const applicable: PendingMark[] = [];
|
|
1040
754
|
for (const mark of marks) {
|
|
1041
|
-
// A mark awaiting a child another mark will mint is not judged here. Its
|
|
1042
|
-
// eligibility is a question about material the child fold owns, and that fold is
|
|
1043
|
-
// what this commit is about to create. The apply loop asks it once the child has
|
|
1044
|
-
// landed, and answers by name when it does not.
|
|
1045
755
|
if (!awaitingMintedChild(mark) &&
|
|
1046
756
|
markEligibility(input.snapshot, input.state, mark) === "protected") retained.push(mark);
|
|
1047
757
|
else applicable.push(mark);
|
|
@@ -1059,23 +769,6 @@ export async function commitPendingMarks(input: {
|
|
|
1059
769
|
"span is still fresh or protected; the mark stays pending until it is eligible",
|
|
1060
770
|
retained: true,
|
|
1061
771
|
}));
|
|
1062
|
-
// CHILDREN BEFORE THE SPANS THAT ABSORB THEM, AND TRANSCRIPT ORDER IS NOT THAT ORDER.
|
|
1063
|
-
//
|
|
1064
|
-
// The invariant is real: a span whose parts name another mark's fold must find that
|
|
1065
|
-
// fold in the forest when it applies. `foldMarkFor` gives a mark the id its committed
|
|
1066
|
-
// fold will carry, so the naming is exact and readable off the parts. What it is NOT is
|
|
1067
|
-
// geometry. A parent CONTAINS its child, so the earliest window index a parent covers
|
|
1068
|
-
// is at or before its child's, and ordering oldest material first applies the parent
|
|
1069
|
-
// FIRST in exactly the case the invariant exists for. The cost is not a reordering:
|
|
1070
|
-
// `candidateSourceRefs` throws `Missing candidate child` on the unresolved part, the
|
|
1071
|
-
// apply loop catches it, and the mark is refused with `retained: false`, so the parent
|
|
1072
|
-
// applied one place too early loses the agent's decision outright rather than waiting.
|
|
1073
|
-
//
|
|
1074
|
-
// So the dependency is the law and the sort key is the tiebreak. Among marks with no
|
|
1075
|
-
// such tie, real transcript order: the earliest window index the span covers, oldest
|
|
1076
|
-
// material first, id last. The `ordinal` this used to sort on is the transcript position
|
|
1077
|
-
// at MARK time, one value shared by every mark an epoch proposes, so within an epoch it
|
|
1078
|
-
// decided nothing and the order fell through to comparing content hashes.
|
|
1079
772
|
const minting = new Map(marks.flatMap((mark) =>
|
|
1080
773
|
mark.mark === "fold" && !held.has(mark.id) ? [[mark.id, mark] as const] : []));
|
|
1081
774
|
const applyStart = new Map(marks.map((mark) =>
|
|
@@ -1088,9 +781,6 @@ export async function commitPendingMarks(input: {
|
|
|
1088
781
|
const placing = new Set<string>();
|
|
1089
782
|
const place = (mark: PendingMark): void => {
|
|
1090
783
|
const key = pendingMarkKey(mark);
|
|
1091
|
-
// `placing` is the cycle stop. Fold ids are hashes OF the parts, so a cycle would
|
|
1092
|
-
// need a hash to name its own ancestor; the guard is here so a corrupt state loses
|
|
1093
|
-
// one mark to the ordinary refusal rather than hanging the commit.
|
|
1094
784
|
if (placed.has(key) || placing.has(key)) return;
|
|
1095
785
|
placing.add(key);
|
|
1096
786
|
if (mark.mark === "fold") {
|
|
@@ -1117,15 +807,6 @@ export async function commitPendingMarks(input: {
|
|
|
1117
807
|
continue;
|
|
1118
808
|
}
|
|
1119
809
|
const parts = clone(mark.parts);
|
|
1120
|
-
// THE ANSWER TO A NAME THE STATE CANNOT RESOLVE, AND IT IS TWO ANSWERS.
|
|
1121
|
-
//
|
|
1122
|
-
// A mark still standing will mint the fold this one names, so the span becomes
|
|
1123
|
-
// readable at the next commit: that is a DEFER, and the mark stays pending. This
|
|
1124
|
-
// is the child a guard held while its parent was applicable, which the epoch used
|
|
1125
|
-
// to discard outright. Nothing standing names it and it is a DROP: `reboundary`
|
|
1126
|
-
// dissolved the fold, or the mark that would have minted it was itself refused,
|
|
1127
|
-
// and no later commit can honour the decision. Same two words the eligibility
|
|
1128
|
-
// reading uses, and the same receipt shape a dropped brief upgrade gets.
|
|
1129
810
|
const span = markSpanRefs(state, mark);
|
|
1130
811
|
if (span.unresolved) {
|
|
1131
812
|
const { foldId, pending } = span.unresolved;
|
|
@@ -1169,9 +850,6 @@ export async function commitPendingMarks(input: {
|
|
|
1169
850
|
generation: input.generation,
|
|
1170
851
|
});
|
|
1171
852
|
applied.push({ mark: "fold", id: mark.id, origin: mark.origin, foldId: prepared.id });
|
|
1172
|
-
// A warmed preparation the commit just materialized through its mark is spent:
|
|
1173
|
-
// leaving it standing would hold a prepared fold whose id already exists in the
|
|
1174
|
-
// forest, and every later drift check would refuse it by name.
|
|
1175
853
|
if (state.prepared && state.folds.some((fold) => fold.id === state.prepared!.id)) {
|
|
1176
854
|
state = clearPrepared(state);
|
|
1177
855
|
}
|
|
@@ -1185,9 +863,6 @@ export async function commitPendingMarks(input: {
|
|
|
1185
863
|
});
|
|
1186
864
|
}
|
|
1187
865
|
}
|
|
1188
|
-
// Deferrals rejoin the pending set. A mark the loop could not read yet is a standing
|
|
1189
|
-
// decision exactly as a guard-held one is, and `retained` is what the commit record
|
|
1190
|
-
// counts as deferred, so both live in the same list.
|
|
1191
866
|
if (deferred.length) {
|
|
1192
867
|
state = withPendingMarks(state, [...pendingMarks(state), ...deferred]);
|
|
1193
868
|
retained.push(...deferred);
|
|
@@ -1195,7 +870,6 @@ export async function commitPendingMarks(input: {
|
|
|
1195
870
|
return { state, applied, refused, retained, waived };
|
|
1196
871
|
}
|
|
1197
872
|
|
|
1198
|
-
/** The scheduling block reported by the status action. */
|
|
1199
873
|
export function schedulingStatus(input: {
|
|
1200
874
|
snapshot: ActiveContextSnapshot;
|
|
1201
875
|
state: ActiveContextState;
|
|
@@ -1203,16 +877,11 @@ export function schedulingStatus(input: {
|
|
|
1203
877
|
}): Record<string, unknown> {
|
|
1204
878
|
const accounting = markAccounting(input.snapshot, input.state);
|
|
1205
879
|
return {
|
|
1206
|
-
// A literal, not a setting: epoch is the only scheduler there is.
|
|
1207
880
|
mode: "epoch",
|
|
1208
881
|
...accounting,
|
|
1209
|
-
// The declared policy, reported never mutable.
|
|
1210
882
|
thresholds: { ...input.snapshot.thresholds },
|
|
1211
883
|
budgetTokens: input.snapshot.budgetTokens,
|
|
1212
884
|
commitDue: epochCommitDue(input.snapshot, input.ratio),
|
|
1213
|
-
// One trigger, reported as one line. The eligible share is still reported above in
|
|
1214
|
-
// the accounting because it is what a commit WOULD free; it is no longer a reason
|
|
1215
|
-
// to fire one.
|
|
1216
885
|
commitTrigger: {
|
|
1217
886
|
mode: "band-top",
|
|
1218
887
|
commitOccupancy: input.snapshot.thresholds.maxTarget,
|