pi-fold 1.0.2 → 1.0.4

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.
@@ -137,24 +137,44 @@ export type ContextEventKind =
137
137
  | "context.attempt"
138
138
  /** One auto-snap applied to a requested span, referencing its attempt. */
139
139
  | "context.correction"
140
+ /** One protect or unprotect: durable pin bookkeeping; no window bytes move. */
141
+ | "context.protect"
140
142
  /** The resolved serving budget, stated once at startup so a live run can prove it. */
141
143
  | "context.capacity"
142
- /** One evaluation of the guided-curation last-call gate. */
143
- | "context.gate"
144
- /** One sparse occupancy reminder delivered into the window. */
145
- | "context.reminder"
146
144
  /** One receipt delivered into the window. */
147
145
  | "context.receipt"
146
+ /** One post-commit rider composed and persisted: at most one per fold epoch. */
147
+ | "context.rider"
148
+ /** One pre-commit last-call exposure: the band-top prompt, with its telemetry. */
149
+ | "context.lastcall"
150
+ /** One gated round's outcome, attributed to the exposure it answers. */
151
+ | "context.response"
152
+ /** One surfacing suggestion issued: both channel scores, the divergence, the carrier. */
153
+ | "context.suggestion"
154
+ /** One surfacing suggestion graded: the acted/used/ignored join and its transition. */
155
+ | "context.outcome"
156
+ /** One append-once occupancy threshold notice delivered into the window. */
157
+ | "context.notice"
148
158
  /** One commit epoch: the single mutation an epoch pays for. */
149
159
  | "context.commit"
150
160
  /** One fold created, by the ladder or by the agent. */
151
161
  | "context.fold"
162
+ /**
163
+ * One generator call for a brief: what it read, what it wrote, and what it cost.
164
+ *
165
+ * The generator runs outside the session, so its spend appears in no provider ledger
166
+ * and its latency appears in no turn. Without this record a deployment cannot say what
167
+ * briefing costs it, which is the question the async lane exists to answer.
168
+ */
169
+ | "context.brief"
152
170
  /** One boundary sliver absorbed into its later neighbour. */
153
171
  | "context.absorb"
154
172
  /** One oversized span split into sequential bite-sized folds. */
155
173
  | "context.split"
156
174
  /** One overflow rollback and the recovery that followed it. */
157
175
  | "context.recovery"
176
+ /** One tree rollback: what left the branch, and whether the request replayed. */
177
+ | "context.rollback"
158
178
  /** One projection built, classified against the one before it. */
159
179
  | "context.projection"
160
180
  /** One provider response, which is where this stream joins provider usage. */
@@ -7,6 +7,7 @@ import {
7
7
  sha256Value,
8
8
  } from "../json.ts";
9
9
  import {
10
+ bytes,
10
11
  clone,
11
12
  exactRecord,
12
13
  messageRole,
@@ -22,8 +23,7 @@ import {
22
23
  import {
23
24
  ACTIVE_CONTEXT_POLICY,
24
25
  DEFAULT_CONTEXT_WINDOW,
25
- TOOL_FOLD_CADENCE_MIN_TOKENS,
26
- TOOL_FOLD_CADENCE_WINDOW_FRACTION,
26
+ servingBudgetTokens,
27
27
  } from "./policy.ts";
28
28
  import type {
29
29
  ActiveContextSnapshot,
@@ -35,7 +35,7 @@ import type {
35
35
  export { protectionSha256, topologySha256 } from "./persistence.ts";
36
36
  export { clone } from "./canonical.ts";
37
37
  export { sha256Value } from "../json.ts";
38
- export { ADVISORY_BUDGETS, validAdvisoryState } from "./persistence.ts";
38
+ export { validAdvisoryState } from "./persistence.ts";
39
39
 
40
40
  export const MAPPED_BY_KEY = new WeakMap<ActiveContextSnapshot, Map<string, MappedMessage>>();
41
41
 
@@ -89,6 +89,32 @@ export function contextUsageRatio(value: unknown): number | null {
89
89
  return tokens / contextWindow;
90
90
  }
91
91
 
92
+ /**
93
+ * A measured WINDOW ratio, restated as occupancy of the serving budget.
94
+ *
95
+ * The provider reports tokens against its total window and the thermostat is declared
96
+ * against the serving budget, so exactly one conversion stands between them and it is
97
+ * written here rather than inferred at each reader. Before this build the trigger
98
+ * divided by the budget while the freeing target and the backstop divided by the
99
+ * window, which at a 383,616-token budget behind a 400,000-token window is a 4.5-point
100
+ * disagreement, and 10 points at a 100,000-token fixture.
101
+ */
102
+ export function budgetOccupancy(snapshot: ActiveContextSnapshot, windowRatio: number | null): number | null {
103
+ if (typeof windowRatio !== "number" || !Number.isFinite(windowRatio)) return null;
104
+ if (!(snapshot.budgetTokens > 0) || !(snapshot.contextWindow > 0)) return null;
105
+ return windowRatio * snapshot.contextWindow / snapshot.budgetTokens;
106
+ }
107
+
108
+ /**
109
+ * The fence, as a ratio of the window the measured ratio is denominated against.
110
+ *
111
+ * A measured ratio is tokens over `contextWindow`, so "the request reached its serving
112
+ * budget" is exactly `ratio >= budgetTokens / contextWindow`. When the snapshot carries
113
+ * a budget the runtime uses it verbatim: a deployment that DECLARED its input budget has
114
+ * already netted out its output reservation, and re-subtracting one here would fence it
115
+ * roughly four points early. Only the ctx fallback, where nothing but a raw provider
116
+ * window is known, still estimates the reservation.
117
+ */
92
118
  export function hardFenceRatio(value?: unknown, ctx?: any): number {
93
119
  const direct = ownValue(value, "contextWindow");
94
120
  const reported = ownValue(ctx?.getContextUsage?.(), "contextWindow");
@@ -97,6 +123,10 @@ export function hardFenceRatio(value?: unknown, ctx?: any): number {
97
123
  : typeof reported === "number" && Number.isFinite(reported) && reported > 0
98
124
  ? reported
99
125
  : DEFAULT_CONTEXT_WINDOW;
126
+ const budgetTokens = ownValue(value, "budgetTokens");
127
+ if (typeof budgetTokens === "number" && Number.isFinite(budgetTokens) && budgetTokens > 0) {
128
+ return Math.min(1, budgetTokens / contextWindow);
129
+ }
100
130
  const reserve = Math.min(
101
131
  ACTIVE_CONTEXT_POLICY.responseReserve,
102
132
  Math.floor(contextWindow * 0.1),
@@ -118,7 +148,7 @@ export function hardFenceRatio(value?: unknown, ctx?: any): number {
118
148
  * is how a 25 percent error survived four instrumented runs.
119
149
  */
120
150
  export interface CapacityAccounting {
121
- /** "truthful" once the deployment declares its total serving window. */
151
+ /** "truthful" once the deployment declares its own serving budget. */
122
152
  mode: "descriptor" | "truthful";
123
153
  /** The denominator every pressure ratio and fence is computed against. */
124
154
  window: number;
@@ -131,6 +161,18 @@ export interface CapacityAccounting {
131
161
  descriptorWindow: number | null;
132
162
  }
133
163
 
164
+ /**
165
+ * ONE denominator, and no guess in front of it.
166
+ *
167
+ * `truthful` means the deployment stated the budget it may actually fill, already net of
168
+ * whatever output reservation it holds back, so the value passes through untouched and
169
+ * the reservation this runtime reports is zero because it withheld nothing. Only the
170
+ * descriptor fallback still subtracts, because a per-request max-input descriptor is a
171
+ * total-window claim and the reservation inside it has to be estimated. That estimate was
172
+ * the defect the reshape removed: the runtime subtracted a fixed 16,384 tokens capped at
173
+ * a tenth of the window while the documentation claimed it subtracted the reservation
174
+ * actually in force.
175
+ */
134
176
  export function capacityAccounting(input: {
135
177
  window: number;
136
178
  truthful: boolean;
@@ -138,11 +180,8 @@ export function capacityAccounting(input: {
138
180
  usedTokens: number | null;
139
181
  }): CapacityAccounting {
140
182
  const window = Number.isFinite(input.window) && input.window > 0 ? input.window : DEFAULT_CONTEXT_WINDOW;
141
- const outputReservation = Math.min(
142
- ACTIVE_CONTEXT_POLICY.responseReserve,
143
- Math.floor(window * 0.1),
144
- );
145
- const budgetTokens = window - outputReservation;
183
+ const budgetTokens = input.truthful ? window : servingBudgetTokens(window);
184
+ const outputReservation = window - budgetTokens;
146
185
  const usedTokens = typeof input.usedTokens === "number" && Number.isFinite(input.usedTokens) &&
147
186
  input.usedTokens >= 0 ? input.usedTokens : null;
148
187
  return {
@@ -270,13 +309,6 @@ export function contextWindowFor(ctx: any): number | null {
270
309
  return typeof modelWindow === "number" && Number.isFinite(modelWindow) && modelWindow > 0 ? modelWindow : null;
271
310
  }
272
311
 
273
- export function toolFoldCadence(contextWindow: number): number {
274
- if (!Number.isFinite(contextWindow) || contextWindow <= 0) {
275
- throw new Error("Tool-fold cadence requires a positive context window");
276
- }
277
- return Math.max(TOOL_FOLD_CADENCE_MIN_TOKENS, TOOL_FOLD_CADENCE_WINDOW_FRACTION * contextWindow);
278
- }
279
-
280
312
  export function rootFolds(state: ActiveContextState): ActiveFold[] {
281
313
  return state.folds.filter((fold) => fold.parentId === null);
282
314
  }
@@ -370,6 +402,50 @@ export function toolRefsProtected(
370
402
  });
371
403
  }
372
404
 
405
+ /**
406
+ * Mass the agent's pins hold out of the ladder's reach: explicitly protected evidence
407
+ * outside the fresh tail. A pin's cost must be a number the agent sees on receipts,
408
+ * never a silent commit shortfall.
409
+ */
410
+ export function protectedStaleMass(
411
+ snapshot: ActiveContextSnapshot,
412
+ state: ActiveContextState,
413
+ ): { bytes: number; refs: number } {
414
+ const explicit = explicitProtectedKeys(state);
415
+ if (!explicit.size) return { bytes: 0, refs: 0 };
416
+ let total = 0;
417
+ let refs = 0;
418
+ for (const item of snapshot.mapped) {
419
+ if (!item.ref || !explicit.has(objectRefKey(item.ref))) continue;
420
+ if (snapshot.protectedIndices.has(item.index)) continue;
421
+ total += bytes(item.message);
422
+ refs += 1;
423
+ }
424
+ return { bytes: total, refs };
425
+ }
426
+
427
+ /**
428
+ * Every byte protect currently holds raw, fresh or stale: the mass the pinned-share
429
+ * cap is measured against. protectedStaleMass narrows to what a commit could not
430
+ * reclaim TODAY; the cap has to see the whole promise, because a pin inside the
431
+ * fresh tail outlives the tail.
432
+ */
433
+ export function explicitProtectedMass(
434
+ snapshot: ActiveContextSnapshot,
435
+ state: ActiveContextState,
436
+ ): { bytes: number; refs: number } {
437
+ const explicit = explicitProtectedKeys(state);
438
+ if (!explicit.size) return { bytes: 0, refs: 0 };
439
+ let total = 0;
440
+ let refs = 0;
441
+ for (const item of snapshot.mapped) {
442
+ if (!item.ref || !explicit.has(objectRefKey(item.ref))) continue;
443
+ total += bytes(item.message);
444
+ refs += 1;
445
+ }
446
+ return { bytes: total, refs };
447
+ }
448
+
373
449
  export interface AdmissionVerdict {
374
450
  admitted: boolean;
375
451
  reason: "admitted" | "unmeasured" | "would-cross-fence";