@animalabs/context-manager 0.5.5 → 0.5.10

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.
Files changed (113) hide show
  1. package/dist/src/adaptive/kv-control.d.ts +69 -32
  2. package/dist/src/adaptive/kv-control.d.ts.map +1 -1
  3. package/dist/src/adaptive/kv-control.js +471 -172
  4. package/dist/src/adaptive/kv-control.js.map +1 -1
  5. package/dist/src/adaptive/picker.d.ts +8 -0
  6. package/dist/src/adaptive/picker.d.ts.map +1 -1
  7. package/dist/src/adaptive/picker.js.map +1 -1
  8. package/dist/src/adaptive/strategies/kv-stable.d.ts +12 -4
  9. package/dist/src/adaptive/strategies/kv-stable.d.ts.map +1 -1
  10. package/dist/src/adaptive/strategies/kv-stable.js +14 -5
  11. package/dist/src/adaptive/strategies/kv-stable.js.map +1 -1
  12. package/dist/src/context-log.d.ts.map +1 -1
  13. package/dist/src/context-log.js +11 -0
  14. package/dist/src/context-log.js.map +1 -1
  15. package/dist/src/context-manager.d.ts +28 -1
  16. package/dist/src/context-manager.d.ts.map +1 -1
  17. package/dist/src/context-manager.js +42 -0
  18. package/dist/src/context-manager.js.map +1 -1
  19. package/dist/src/index.d.ts +3 -2
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +1 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/message-store.d.ts +60 -0
  24. package/dist/src/message-store.d.ts.map +1 -1
  25. package/dist/src/message-store.js +165 -10
  26. package/dist/src/message-store.js.map +1 -1
  27. package/dist/src/strategies/autobiographical.d.ts +223 -2
  28. package/dist/src/strategies/autobiographical.d.ts.map +1 -1
  29. package/dist/src/strategies/autobiographical.js +1487 -142
  30. package/dist/src/strategies/autobiographical.js.map +1 -1
  31. package/dist/src/strategies/index.d.ts +1 -1
  32. package/dist/src/strategies/index.d.ts.map +1 -1
  33. package/dist/src/strategies/index.js.map +1 -1
  34. package/dist/src/strategies/knowledge.d.ts +8 -0
  35. package/dist/src/strategies/knowledge.d.ts.map +1 -1
  36. package/dist/src/strategies/knowledge.js +23 -1
  37. package/dist/src/strategies/knowledge.js.map +1 -1
  38. package/dist/src/types/strategy.d.ts +89 -5
  39. package/dist/src/types/strategy.d.ts.map +1 -1
  40. package/dist/src/types/strategy.js.map +1 -1
  41. package/dist/test/adaptive/doc-plus-chat.test.js +7 -3
  42. package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -1
  43. package/dist/test/adaptive/hard-fail.test.js +103 -0
  44. package/dist/test/adaptive/hard-fail.test.js.map +1 -1
  45. package/dist/test/adaptive/ingestion.test.js +3 -1
  46. package/dist/test/adaptive/ingestion.test.js.map +1 -1
  47. package/dist/test/adaptive/kv-control.test.js +17 -11
  48. package/dist/test/adaptive/kv-control.test.js.map +1 -1
  49. package/dist/test/adaptive/single-path-solve.test.d.ts +16 -0
  50. package/dist/test/adaptive/single-path-solve.test.d.ts.map +1 -0
  51. package/dist/test/adaptive/single-path-solve.test.js +204 -0
  52. package/dist/test/adaptive/single-path-solve.test.js.map +1 -0
  53. package/dist/test/anti-redundancy-budget.test.d.ts +17 -0
  54. package/dist/test/anti-redundancy-budget.test.d.ts.map +1 -0
  55. package/dist/test/anti-redundancy-budget.test.js +197 -0
  56. package/dist/test/anti-redundancy-budget.test.js.map +1 -0
  57. package/dist/test/chunk-persistence.test.d.ts +29 -0
  58. package/dist/test/chunk-persistence.test.d.ts.map +1 -0
  59. package/dist/test/chunk-persistence.test.js +290 -0
  60. package/dist/test/chunk-persistence.test.js.map +1 -0
  61. package/dist/test/compression-dedup.test.d.ts +20 -0
  62. package/dist/test/compression-dedup.test.d.ts.map +1 -0
  63. package/dist/test/compression-dedup.test.js +92 -0
  64. package/dist/test/compression-dedup.test.js.map +1 -0
  65. package/dist/test/compression-shape-invariants.test.js +40 -3
  66. package/dist/test/compression-shape-invariants.test.js.map +1 -1
  67. package/dist/test/head-window-order.test.d.ts +19 -0
  68. package/dist/test/head-window-order.test.d.ts.map +1 -0
  69. package/dist/test/head-window-order.test.js +159 -0
  70. package/dist/test/head-window-order.test.js.map +1 -0
  71. package/dist/test/image-strip-render-stats.test.js +25 -3
  72. package/dist/test/image-strip-render-stats.test.js.map +1 -1
  73. package/dist/test/knowledge-tool-boundary.test.d.ts +14 -0
  74. package/dist/test/knowledge-tool-boundary.test.d.ts.map +1 -0
  75. package/dist/test/knowledge-tool-boundary.test.js +93 -0
  76. package/dist/test/knowledge-tool-boundary.test.js.map +1 -0
  77. package/dist/test/l1-holdback.test.d.ts +15 -0
  78. package/dist/test/l1-holdback.test.d.ts.map +1 -0
  79. package/dist/test/l1-holdback.test.js +119 -0
  80. package/dist/test/l1-holdback.test.js.map +1 -0
  81. package/dist/test/merge-contiguity.test.d.ts +11 -0
  82. package/dist/test/merge-contiguity.test.d.ts.map +1 -0
  83. package/dist/test/merge-contiguity.test.js +82 -0
  84. package/dist/test/merge-contiguity.test.js.map +1 -0
  85. package/dist/test/message-store-window.test.d.ts +13 -0
  86. package/dist/test/message-store-window.test.d.ts.map +1 -0
  87. package/dist/test/message-store-window.test.js +177 -0
  88. package/dist/test/message-store-window.test.js.map +1 -0
  89. package/dist/test/speculation-cap.test.js +4 -0
  90. package/dist/test/speculation-cap.test.js.map +1 -1
  91. package/dist/test/strategy-persistence.test.js +41 -0
  92. package/dist/test/strategy-persistence.test.js.map +1 -1
  93. package/dist/test/summary-log-consistency.test.d.ts +17 -0
  94. package/dist/test/summary-log-consistency.test.d.ts.map +1 -0
  95. package/dist/test/summary-log-consistency.test.js +102 -0
  96. package/dist/test/summary-log-consistency.test.js.map +1 -0
  97. package/dist/test/tool-pairing-invariant.test.d.ts +15 -0
  98. package/dist/test/tool-pairing-invariant.test.d.ts.map +1 -0
  99. package/dist/test/tool-pairing-invariant.test.js +225 -0
  100. package/dist/test/tool-pairing-invariant.test.js.map +1 -0
  101. package/dist/tsconfig.tsbuildinfo +1 -1
  102. package/package.json +11 -3
  103. package/src/adaptive/kv-control.ts +509 -184
  104. package/src/adaptive/picker.ts +8 -0
  105. package/src/adaptive/strategies/kv-stable.ts +22 -10
  106. package/src/context-log.ts +11 -0
  107. package/src/context-manager.ts +48 -2
  108. package/src/index.ts +3 -2
  109. package/src/message-store.ts +202 -9
  110. package/src/strategies/autobiographical.ts +1576 -166
  111. package/src/strategies/index.ts +1 -1
  112. package/src/strategies/knowledge.ts +25 -1
  113. package/src/types/strategy.ts +97 -5
@@ -1,32 +1,44 @@
1
1
  /**
2
- * KV-stable context controller (see `docs/kv-stable-context-control.md`).
2
+ * KV-stable context controller the rev 5.0 single-path solve
3
+ * (see `docs/adaptive-resolution-design.md` §13; supersedes the two-path
4
+ * fold-within-reach + emergency design of §12, and the older prose in
5
+ * `docs/kv-stable-context-control.md` where they conflict).
3
6
  *
4
- * A receding-horizon policy that replaces the `value − λ·KVcost` solve. One
5
- * objective — minimize real billed recompute — under a constraint hierarchy with
6
- * a single hard wall (the physical window W) and shaped soft constraints
7
- * (flat zone, perturbation cap, pins/saliency, operating budget). State (the
8
- * carried frontier + the persistent cache) makes it a controller, not a
9
- * per-turn solve.
7
+ * One algorithm per turn, one hard constraint:
10
8
  *
11
- * This prototype uses a hysteresis band [LW, HW] as the controllable operating
12
- * point on the cost↔continuity frontier:
13
- * - append every turn; if rendered tokens exceed HW, shed down to LW;
14
- * otherwise do nothing (pure append → zero perturbation, full cache hit);
15
- * - shed oldest-first, leveled (L1 groups, then L2, then L3), each chunk
16
- * capped by the saliency field (log-age + flat zone + pins) — base-k
17
- * grouping driven by budget, not a fixed clock;
18
- * - W is the only hard wall; if folding everything to its cap can't fit under
19
- * W, escalate (don't thrash).
9
+ * minimize relevance_loss(F) + perturbation(F, F_prev) [lexicographic]
10
+ * subject to render(F) W — the only physics
11
+ * perturbation(F, F_prev) P — trust region; overridable
12
+ *
13
+ * - The IDEAL CUT is built from scratch each turn by pure relevance: fold in
14
+ * salience-then-age priority order, level by level, under the soft shape
15
+ * prior (`foldDepthCap`), past it if W demands, never past the hard
16
+ * protections (flat zone / pins / locked); then pack youngest-first back
17
+ * toward the target. Age/salience-monotone by construction.
18
+ * - PERTURBATION is exact, not modeled: `kvCost` — the tokens the provider
19
+ * will re-read from the earliest layout divergence to the end.
20
+ * - P (`reachTokens`, re-priced) is a TRUST REGION on per-turn divergence,
21
+ * not a spatial eligibility gate. Within it the solver adopts the ideal;
22
+ * beyond it it adopts the ideal's newest changes only (suffix adoption —
23
+ * perturbation is prefix-based, so partial adoption is exactly a suffix
24
+ * cut) and converges over turns, receding-horizon style.
25
+ * - OVERRIDES (same code path, recorded on the plan, logged by the caller):
26
+ * 'bootstrap' (no carried frontier — nothing to preserve), 'infeasible'
27
+ * (nothing within P fits under W), 'quality-gap' (the best plan within P
28
+ * is certifiably bad vs the ideal). There is no emergency path.
20
29
  *
21
30
  * Pure and deterministic.
22
31
  */
23
32
  import { SummaryTree } from './summary-tree.js';
24
- import { renderLayout } from './render-offsets.js';
33
+ import { renderLayout, kvCost } from './render-offsets.js';
25
34
  import { placeMarkers, CacheStore } from './kv-cache-sim.js';
26
35
  /** Provider price multipliers relative to base input tokens (Anthropic-ish). */
27
36
  export const PRICE = { miss: 1.0, cacheRead: 0.1, cacheWrite: 1.25 };
28
- /** Deepest fold level the saliency field will ever allow. */
29
- export const MAX_FOLD_LEVEL = 3;
37
+ /** Safety ceiling on saliency fold depth. The *effective* cap is
38
+ * `maxAvailableLevel(tree)` (fold only as deep as summaries actually exist),
39
+ * which every caller passes in; this constant is just a high sane upper bound so
40
+ * L4/L5/… are allowed as the merge pipeline builds them — never a pin at L3. */
41
+ export const MAX_FOLD_LEVEL = 8;
30
42
  /**
31
43
  * Per-chunk maximum fold depth from the saliency field: flat zone and pins are
32
44
  * raw (0); otherwise log-age banded against the base-k geometry. The "finest
@@ -53,60 +65,161 @@ function maxAvailableLevel(tree) {
53
65
  m = s.level;
54
66
  return m;
55
67
  }
68
+ /** Per-chunk salience: the coefficient on information loss (design §13.3).
69
+ * Defaults to 1. Lower = folds earlier and costs less when folded (code-heavy /
70
+ * tool-output / externalized content). Never overrides hard protections. */
71
+ function salienceOf(c) {
72
+ const s = c.salience;
73
+ if (s === undefined || !Number.isFinite(s))
74
+ return 1;
75
+ return Math.min(1, Math.max(0, s));
76
+ }
77
+ /**
78
+ * Salience-weighted MISALLOCATION loss of a frontier: Σ salience(c) ·
79
+ * max(0, level(c) − shapeCap(c)) over foldable chunks — the excess fold depth
80
+ * beyond what the relevance shape prior (log-age band) would assign. Zero for
81
+ * any cut that folds nothing deeper than its prior; positive exactly where
82
+ * fidelity is spent in the wrong place. Recency-aware by construction (the
83
+ * cap grows with age), so an INVERTED profile — recent content deep, ancient
84
+ * content fine — scores high even when a naive Σ level would tie it with the
85
+ * correct gradient. Used only to compare cuts of the same tree (quality-gap
86
+ * tests, §13.4).
87
+ */
88
+ function relevanceLoss(F, ordered, rawZone, caps) {
89
+ let loss = 0;
90
+ for (const c of ordered) {
91
+ if (rawZone.has(c.id))
92
+ continue;
93
+ const lvl = F.get(c.id) ?? 0;
94
+ if (lvl <= 0)
95
+ continue;
96
+ const cap = Math.max(0, caps.get(c.id) ?? 0);
97
+ if (lvl > cap)
98
+ loss += salienceOf(c) * (lvl - cap);
99
+ }
100
+ return loss;
101
+ }
56
102
  /**
57
- * Deepen `frontier` in place to bring rendered tokens ≤ `target`, leveled
58
- * (L1 groups, then L2, then L3) and oldest-first *within the reach window*. A
59
- * chunk is editable this turn only if the raw tokens newer than it are <
60
- * `reachTokens` — this bounds the divergence depth (and thus the per-turn KV
61
- * perturbation) to ≈ reach. Each chunk is bounded by its saliency cap;
62
- * flat-zone/pinned chunks (cap 0) are never touched.
103
+ * Incremental token accounting for whole-group fold/unfold moves.
63
104
  *
64
- * `reachTokens` is the soft cap P; `windowTokens` is the hard wall W. If folding
65
- * within reach can't fit under W, reach is lifted (emergency) and folding
66
- * continues to caps yielding P minimally to keep W. Returns final tokens.
105
+ * `renderLayout` is O(n); calling it per accepted fold makes the ideal-cut
106
+ * construction O(n·folds) O(n²) seconds on a 4k-chunk store, paid on
107
+ * EVERY compile under the single-path solve. The ledger tracks rendered units
108
+ * (raw shards / recall pairs) as a multiset keyed exactly like renderLayout's
109
+ * units, so a group fold/unfold is O(group) and `tokens` stays equal to
110
+ * `renderLayout(...).totalTokens` for the same frontier (both share the
111
+ * "missing ancestor → raw" fallback; siblings share one recall unit).
67
112
  */
68
- function shedToTarget(frontier, inputs, tree, ordered, caps, target, reachTokens, windowTokens, maxFoldLevel, pinCaps) {
69
- // Raw tokens strictly newer than each chunk (its distance from the live end).
70
- const newerTokens = new Map();
71
- let acc = 0;
72
- for (let i = ordered.length - 1; i >= 0; i--) {
73
- newerTokens.set(ordered[i].id, acc);
74
- acc += ordered[i].rawTokens;
113
+ class TokenLedger {
114
+ tree;
115
+ inputs;
116
+ unitTokens = new Map(); // unitKey → tokens
117
+ unitRefs = new Map(); // unitKey leaf count
118
+ tokens = 0;
119
+ constructor(tree, inputs, ordered, F) {
120
+ this.tree = tree;
121
+ this.inputs = inputs;
122
+ this.tokens = inputs.headTokens + inputs.tailTokens;
123
+ for (const c of ordered) {
124
+ if (inputs.headChunkIds.has(c.id) || inputs.tailChunkIds.has(c.id))
125
+ continue;
126
+ const { key, tokens } = this.unitFor(c, F.get(c.id) ?? 0);
127
+ this.addRef(key, tokens);
128
+ }
75
129
  }
76
- const foldWithinReach = (reach, ignoreCaps = false) => {
130
+ /** Unit identity + cost for a leaf at a level — mirrors renderLayout. */
131
+ unitFor(c, level) {
132
+ const effective = c.pinned ? 0 : level;
133
+ if (effective > 0) {
134
+ const ancestor = this.tree.ancestorAt(c.id, effective);
135
+ if (ancestor)
136
+ return { key: `recall:${ancestor.id}`, tokens: ancestor.recallTokens };
137
+ }
138
+ return { key: `raw:${c.id}`, tokens: c.rawTokens };
139
+ }
140
+ addRef(key, tokens) {
141
+ const refs = this.unitRefs.get(key) ?? 0;
142
+ if (refs === 0) {
143
+ this.unitTokens.set(key, tokens);
144
+ this.tokens += tokens;
145
+ }
146
+ this.unitRefs.set(key, refs + 1);
147
+ }
148
+ dropRef(key) {
149
+ const refs = this.unitRefs.get(key) ?? 0;
150
+ if (refs <= 1) {
151
+ this.unitRefs.delete(key);
152
+ this.tokens -= this.unitTokens.get(key) ?? 0;
153
+ this.unitTokens.delete(key);
154
+ }
155
+ else {
156
+ this.unitRefs.set(key, refs - 1);
157
+ }
158
+ }
159
+ /** Move one leaf from `fromLevel` to `toLevel`. Callers move whole groups
160
+ * (every leaf of the target node), which keeps units exact. */
161
+ move(c, byId, fromLevel, toLevel) {
162
+ if (this.inputs.headChunkIds.has(c.id) || this.inputs.tailChunkIds.has(c.id))
163
+ return;
164
+ void byId;
165
+ this.dropRef(this.unitFor(c, fromLevel).key);
166
+ const next = this.unitFor(c, toLevel);
167
+ this.addRef(next.key, next.tokens);
168
+ }
169
+ }
170
+ /**
171
+ * Build the IDEAL CUT from scratch — pure relevance, no P anywhere (§13.4).
172
+ *
173
+ * Fold order is (salience ascending, then oldest-first), level by level, so the
174
+ * profile is salience/age-monotone by construction: with uniform salience it
175
+ * reduces to the classic oldest-first shed. Two phases against the shape prior:
176
+ * A. fold under the soft `caps` (log-age shape + pin caps) until ≤ target;
177
+ * B. if still over W, keep folding past the shape caps (never past the
178
+ * hard-protected −1 sentinel or a pin cap) — the prior is a relevance
179
+ * shaper, never a feasibility wall;
180
+ * C. pack: un-fold youngest-first back toward `target` (accept-if-fit), so
181
+ * headroom is spent on the most recent foldable content.
182
+ * Ends projected to a valid group-consistent tree cut.
183
+ */
184
+ function relevanceCut(inputs, tree, ordered, base, caps, pinCaps, rawZone, immovable, frozen, fixedLevels, target, windowTokens, maxFoldLevel) {
185
+ const F = new Map(base);
186
+ const byId = new Map(ordered.map((c) => [c.id, c]));
187
+ // Fold priority: cheapest information first — ascending salience, then oldest.
188
+ const priority = [...ordered].sort((a, b) => {
189
+ const sa = salienceOf(a);
190
+ const sb = salienceOf(b);
191
+ if (sa !== sb)
192
+ return sa - sb;
193
+ return a.sequence - b.sequence;
194
+ });
195
+ // Incremental accounting: O(group) per move instead of O(n) renderLayout.
196
+ const _tl = typeof process !== 'undefined' && process.env?.KV_TIMING ? Date.now() : 0;
197
+ const ledger = new TokenLedger(tree, inputs, ordered, F);
198
+ if (_tl)
199
+ console.error(`[kv-timing] ledger-init ${Date.now() - _tl}ms tokens=${ledger.tokens}`);
200
+ const foldPass = (ignoreShapeCaps, stopAt) => {
77
201
  for (let level = 1; level <= maxFoldLevel; level++) {
78
- for (const c of ordered) {
79
- // oldest-first within reach
80
- if ((newerTokens.get(c.id) ?? 0) >= reach)
81
- continue;
82
- if ((frontier.get(c.id) ?? 0) >= level)
202
+ for (const c of priority) {
203
+ if (ledger.tokens <= stopAt)
204
+ return true;
205
+ if ((F.get(c.id) ?? 0) >= level)
83
206
  continue;
84
207
  const ancestor = tree.ancestorAt(c.id, level);
85
208
  if (!ancestor)
86
209
  continue; // summary not produced yet → can't fold here
87
- // Group-consistency + reach: fold the WHOLE group to `level` only if
88
- // every covered leaf permits it (within the saliency cap unless
89
- // `ignoreCaps`, and within reach). The picker raises whole groups, so a
90
- // partially-eligible group would be an unreachable target (it
91
- // oscillates). A uniform fold keeps the frontier group-consistent and
92
- // the picker convergent. `rawZone`/`frozen` always block (their cap is 0
93
- // and stays 0 even under ignoreCaps).
210
+ // Group-atomicity: fold the WHOLE group to `level` only if every
211
+ // covered leaf permits it. Hard protections (−1 sentinel: flat zone /
212
+ // pins / locked) always block; pin-max-level always blocks past its
213
+ // bound; the soft shape cap blocks only in phase A.
94
214
  let eligible = true;
95
215
  for (const leafId of ancestor.leafChunkIds) {
96
216
  const cap = caps.get(leafId) ?? 0;
97
- // A V2 pin-max-level is a HARD cap: never fold a pinned chunk deeper
98
- // than its bound, even in the W emergency (pins are hard-protected).
99
217
  const pinCap = pinCaps.get(leafId);
100
218
  if (pinCap !== undefined && level > pinCap) {
101
219
  eligible = false;
102
220
  break;
103
221
  }
104
- // Normal: respect the saliency depth cap. Emergency (ignoreCaps): only
105
- // the hard-protected set (cap = −1: flat zone / pins / locked) blocks;
106
- // the age-extended-raw band (cap 0) and shallower-than-cap content all
107
- // become foldable to fit under W.
108
- const capBlocks = ignoreCaps ? cap < 0 : cap < level;
109
- if (capBlocks || (newerTokens.get(leafId) ?? 0) >= reach) {
222
+ if (ignoreShapeCaps ? cap < 0 : cap < level) {
110
223
  eligible = false;
111
224
  break;
112
225
  }
@@ -114,85 +227,186 @@ function shedToTarget(frontier, inputs, tree, ordered, caps, target, reachTokens
114
227
  if (!eligible)
115
228
  continue;
116
229
  for (const leafId of ancestor.leafChunkIds) {
117
- if ((frontier.get(leafId) ?? 0) < level)
118
- frontier.set(leafId, level);
230
+ const leaf = byId.get(leafId);
231
+ const from = F.get(leafId) ?? 0;
232
+ if (from < level) {
233
+ if (leaf)
234
+ ledger.move(leaf, byId, from, level);
235
+ F.set(leafId, level);
236
+ }
119
237
  }
120
- if (renderLayout(inputs, tree, frontier).totalTokens <= target)
238
+ if (ledger.tokens <= stopAt)
121
239
  return true;
122
240
  }
123
241
  }
124
242
  return false;
125
243
  };
126
- foldWithinReach(reachTokens);
127
- let tokens = renderLayout(inputs, tree, frontier).totalTokens;
128
- // Hard wall: W is the only hard constraint. If folding within reach + caps
129
- // still exceeds W, the emergency lifts BOTH the reach cap (P) AND the saliency
130
- // depth cap — the cap is a soft *relevance* shaper, never a feasibility wall.
131
- // It still never folds rawZone/frozen (their cap stays 0). (docs
132
- // adaptive-resolution-design.md §12.4: fixes OverBudgetError-at-L2 when deeper
133
- // summaries exist and would fit.)
134
- if (tokens > windowTokens) {
135
- foldWithinReach(Infinity, /* ignoreCaps */ true);
136
- tokens = renderLayout(inputs, tree, frontier).totalTokens;
244
+ // Phase A: fold under the shape prior toward the target.
245
+ if (ledger.tokens > target)
246
+ foldPass(false, target);
247
+ // Phase B: the prior yields to W (only hard protections stand).
248
+ if (ledger.tokens > windowTokens)
249
+ foldPass(true, target);
250
+ // Phase C: pack youngest-first back toward the target (skip if infeasible).
251
+ if (ledger.tokens <= windowTokens && ledger.tokens < target) {
252
+ for (let level = maxFoldLevel; level >= 1 && ledger.tokens < target; level--) {
253
+ for (let oi = ordered.length - 1; oi >= 0; oi--) {
254
+ if (ledger.tokens >= target)
255
+ break;
256
+ const c = ordered[oi]; // youngest-first
257
+ if ((F.get(c.id) ?? 0) !== level)
258
+ continue;
259
+ if (rawZone.has(c.id) || immovable.has(c.id))
260
+ continue;
261
+ const node = tree.ancestorAt(c.id, level);
262
+ if (!node)
263
+ continue;
264
+ let eligible = true;
265
+ for (const leafId of node.leafChunkIds) {
266
+ if ((F.get(leafId) ?? 0) !== level || rawZone.has(leafId) || immovable.has(leafId)) {
267
+ eligible = false;
268
+ break;
269
+ }
270
+ }
271
+ if (!eligible)
272
+ continue;
273
+ const before = ledger.tokens;
274
+ for (const leafId of node.leafChunkIds) {
275
+ const leaf = byId.get(leafId);
276
+ if (leaf)
277
+ ledger.move(leaf, byId, level, level - 1);
278
+ F.set(leafId, level - 1);
279
+ }
280
+ // Accept an un-fold that lands PAST the target when it still gets
281
+ // CLOSER to it (and stays under W). Merge groups are coarse quanta
282
+ // (8-15k on production trees); accept-only-if-under-target strands
283
+ // real headroom un-spent whenever every remaining quantum overshoots
284
+ // (mythos 2026-07-12: 134k rendered of a 183.6k hard budget). The
285
+ // target stays the attractor — W stays the only wall (§13.2).
286
+ const nt = ledger.tokens;
287
+ const accept = nt <= windowTokens && Math.abs(nt - target) < Math.abs(before - target);
288
+ if (!accept) {
289
+ // moves away from the target (or breaches W) → revert
290
+ for (const leafId of node.leafChunkIds) {
291
+ const leaf = byId.get(leafId);
292
+ if (leaf)
293
+ ledger.move(leaf, byId, level - 1, level);
294
+ F.set(leafId, level);
295
+ }
296
+ }
297
+ }
298
+ }
137
299
  }
138
- return tokens;
300
+ const _tp = typeof process !== 'undefined' && process.env?.KV_TIMING ? Date.now() : 0;
301
+ projectToValidCut(F, tree, ordered, frozen, fixedLevels);
302
+ if (_tp)
303
+ console.error(`[kv-timing] cut-projection ${Date.now() - _tp}ms`);
304
+ // One authoritative render at the end (the ledger tracks it exactly, but the
305
+ // projection may have moved leaves — recompute once, not per move).
306
+ const tokens = renderLayout(inputs, tree, F).totalTokens;
307
+ return { F, tokens };
139
308
  }
140
309
  /**
141
- * Raise `frontier` resolution in place (UN-fold toward raw) to fill up to
142
- * `target`, leveled (L3→L2, L2→L1, L1→raw) and **youngest-first within the reach
143
- * window** the mirror image of `shedToTarget`. Un-folding is a prefix
144
- * perturbation too, so it obeys the same reach cap (bounded divergence) and only
145
- * lowers whole groups (group-consistent the picker reaches it via `lower`
146
- * ops). Youngest-first spends the budget on the most recent foldable content
147
- * (highest value, shallowest divergence). Stops before exceeding `target`.
148
- * rawZone/frozen chunks are never touched. Returns final tokens.
310
+ * Partial adoption of the ideal under the trust region (§13.4): keep `prev`
311
+ * before a boundary sequence, take `ideal` at and after it. Perturbation is
312
+ * prefix-based, so the cheapest partial plans are exactly the suffix cuts;
313
+ * binary-search the oldest boundary whose perturbation stays P (perturbation
314
+ * is monotone non-increasing as the boundary moves newer). Each candidate is
315
+ * projected to a valid cut before costing (mixing two cuts can bisect a group).
149
316
  */
150
- function expandToTarget(frontier, inputs, tree, ordered, target, reachTokens, rawZone, immovable, maxFoldLevel) {
151
- const newerTokens = new Map();
152
- let acc = 0;
153
- for (let i = ordered.length - 1; i >= 0; i--) {
154
- newerTokens.set(ordered[i].id, acc);
155
- acc += ordered[i].rawTokens;
317
+ function suffixAdopt(inputs, tree, ordered, prev, prevLayout, ideal, frozen, fixedLevels, P) {
318
+ const changedSeqs = [];
319
+ for (const c of ordered) {
320
+ if ((prev.get(c.id) ?? 0) !== (ideal.get(c.id) ?? 0))
321
+ changedSeqs.push(c.sequence);
156
322
  }
157
- let tokens = renderLayout(inputs, tree, frontier).totalTokens;
158
- for (let level = maxFoldLevel; level >= 1 && tokens < target; level--) {
159
- for (let oi = ordered.length - 1; oi >= 0; oi--) {
160
- if (tokens >= target)
161
- break;
162
- const c = ordered[oi]; // youngest-first
163
- if ((frontier.get(c.id) ?? 0) !== level)
164
- continue;
165
- if (rawZone.has(c.id) || immovable.has(c.id))
166
- continue;
167
- if ((newerTokens.get(c.id) ?? 0) >= reachTokens)
168
- continue;
169
- const node = tree.ancestorAt(c.id, level);
170
- if (!node)
323
+ const build = (boundary) => {
324
+ const F = new Map(prev);
325
+ for (const c of ordered) {
326
+ if (c.sequence >= boundary)
327
+ F.set(c.id, ideal.get(c.id) ?? 0);
328
+ }
329
+ projectToValidCut(F, tree, ordered, frozen, fixedLevels);
330
+ const layout = renderLayout(inputs, tree, F);
331
+ return { F, layout, pert: kvCost(prevLayout, layout) };
332
+ };
333
+ if (changedSeqs.length === 0) {
334
+ const layout = renderLayout(inputs, tree, new Map(prev));
335
+ return { F: new Map(prev), tokens: layout.totalTokens, perturbation: 0 };
336
+ }
337
+ // Binary search: smallest index (oldest boundary) with perturbation ≤ P.
338
+ let lo = 0;
339
+ let hi = changedSeqs.length; // hi = adopt nothing (pert 0, always feasible)
340
+ let best = build(Number.MAX_SAFE_INTEGER);
341
+ let bestIdx = hi;
342
+ while (lo < hi) {
343
+ const mid = (lo + hi) >> 1;
344
+ const cand = build(changedSeqs[mid]);
345
+ if (cand.pert <= P) {
346
+ best = cand;
347
+ bestIdx = mid;
348
+ hi = mid;
349
+ }
350
+ else {
351
+ lo = mid + 1;
352
+ }
353
+ }
354
+ void bestIdx;
355
+ return { F: best.F, tokens: best.layout.totalTokens, perturbation: best.pert };
356
+ }
357
+ /**
358
+ * Project a per-chunk frontier onto the nearest VALID TREE CUT (group-consistent).
359
+ *
360
+ * A leaf folded to level k renders as part of its L_k ancestor node, and that
361
+ * node is atomic over its whole covered range — so EVERY leaf under it must share
362
+ * level k, or the node is unrenderable. The picker walks group-atomic raise/lower
363
+ * ops, so a frontier that folds only SOME leaves under a node is an unreachable
364
+ * target: it raises the group (over-folding a sibling), lowers it (under-folding
365
+ * another), forever — non-convergence (the wedge this fixes).
366
+ *
367
+ * Frontiers seeded per-chunk from the carried `previous` frontier can violate
368
+ * this: as a summary group accretes new raw leaves over turns while older leaves
369
+ * stay folded, its leaves end up at mixed levels. shed/expand only re-consolidate
370
+ * groups they actively move; a mixed group sitting in the budget dead-band passes
371
+ * through untouched. This pass repairs it.
372
+ *
373
+ * Shallowest-first: where a group disagrees, UN-fold the folded leaves one level
374
+ * toward raw (preserving continuity), cascading until every folded leaf's group
375
+ * is unanimous. Monotone (only lowers) → terminates. Any budget breach from the
376
+ * extra raw is re-folded whole-group by the shed pass that runs afterward.
377
+ *
378
+ * Pinned (`fixedLevels`) and frozen (locked) leaves are already made
379
+ * group-consistent by the caller (pin-at-level fixes a whole node's leaves), so
380
+ * they are held here rather than lowered.
381
+ */
382
+ function projectToValidCut(F, tree, ordered, frozen, fixedLevels) {
383
+ const maxPasses = (ordered.length + 1) * (maxAvailableLevel(tree) + 2);
384
+ let changed = true;
385
+ let pass = 0;
386
+ while (changed && pass++ < maxPasses) {
387
+ changed = false;
388
+ for (const c of ordered) {
389
+ const lvl = F.get(c.id) ?? 0;
390
+ if (lvl <= 0)
171
391
  continue;
172
- // Lower the WHOLE group to level-1 only if every covered leaf is at this
173
- // level and within reach (group-consistent + reach-bounded).
174
- let eligible = true;
175
- for (const leafId of node.leafChunkIds) {
176
- if ((frontier.get(leafId) ?? 0) !== level ||
177
- rawZone.has(leafId) || immovable.has(leafId) ||
178
- (newerTokens.get(leafId) ?? 0) >= reachTokens) {
179
- eligible = false;
180
- break;
392
+ if (frozen.has(c.id) || fixedLevels.has(c.id))
393
+ continue; // held; consistent by construction
394
+ const node = tree.ancestorAt(c.id, lvl);
395
+ let unanimous = node != null;
396
+ if (node) {
397
+ for (const leafId of node.leafChunkIds) {
398
+ if ((F.get(leafId) ?? 0) !== lvl) {
399
+ unanimous = false;
400
+ break;
401
+ }
181
402
  }
182
403
  }
183
- if (!eligible)
184
- continue;
185
- for (const leafId of node.leafChunkIds)
186
- frontier.set(leafId, level - 1);
187
- const nt = renderLayout(inputs, tree, frontier).totalTokens;
188
- if (nt <= target)
189
- tokens = nt; // accept the un-fold
190
- else
191
- for (const leafId of node.leafChunkIds)
192
- frontier.set(leafId, level); // overshoot → revert
404
+ if (!unanimous) {
405
+ F.set(c.id, lvl - 1);
406
+ changed = true;
407
+ } // un-fold one level (shallowest-first)
193
408
  }
194
409
  }
195
- return tokens;
196
410
  }
197
411
  /** Earliest source sequence whose resolution changed `prev → next`. */
198
412
  function earliestChangedSequence(prev, next, ordered) {
@@ -232,16 +446,22 @@ function rawTailSet(ordered, tailTokens) {
232
446
  const EMPTY_SET = new Set();
233
447
  const EMPTY_LEVEL_MAP = new Map();
234
448
  /**
235
- * Plan one turn's frontier under the controller policy shared by the replay
236
- * harness (`replayControlled`) and `KvStableStrategy`. Carries F_prev forward,
237
- * holds the raw zone raw and the frozen set fixed, then drives the rendered size
238
- * toward `targetTokens` from EITHER side, both bounded by the `reachTokens`
239
- * divergence cap (KV-continuity preserving in both directions):
240
- * - over `foldAtTokens` → deepen, oldest-first, under the log-age saliency
241
- * caps, yielding the reach cap only as needed to stay under the hard wall W;
242
- * - under `expandAtTokens` un-fold, youngest-first, to use budget headroom.
243
- * The [expandAt, foldAt] dead band leaves F_prev untouched (zero perturbation)
244
- * when already in range. Pure and deterministic.
449
+ * Plan one turn's frontier the rev 5.0 single-path solve (§13.4), shared by
450
+ * the replay harness (`replayControlled`) and `KvStableStrategy`.
451
+ *
452
+ * 1. Build the IDEAL CUT from scratch (pure relevance; W the only wall).
453
+ * 2. No carried frontier → adopt it (bootstrap; P not applicable).
454
+ * 3. Carried frontier in the [expandAt, foldAt] dead band with a small
455
+ * quality gap hold it (zero perturbation). A LARGE gap falls through:
456
+ * a stuck misallocated profile self-heals instead of fossilizing.
457
+ * 4. Ideal within the trust region P (exact kvCost) adopt it.
458
+ * 5. Otherwise adopt the ideal's newest changes only (suffix adoption) if
459
+ * that fits W and is not certifiably bad; else adopt the ideal and record
460
+ * the override ('infeasible' / 'quality-gap').
461
+ *
462
+ * There is no emergency path and no spatial eligibility gate: cache continuity
463
+ * is priced (and bounded by P) — never a reason a chunk can't move.
464
+ * Pure and deterministic.
245
465
  */
246
466
  export function planControlledFrontier(inputs, tree, p) {
247
467
  const ordered = [...inputs.chunks].sort((a, b) => a.sequence - b.sequence);
@@ -249,72 +469,151 @@ export function planControlledFrontier(inputs, tree, p) {
249
469
  const fixedLevels = p.fixedLevels ?? EMPTY_LEVEL_MAP;
250
470
  const pinCaps = p.pinCaps ?? EMPTY_LEVEL_MAP;
251
471
  const mergeThreshold = p.mergeThreshold ?? 6;
252
- const reach = p.reachTokens ?? p.windowTokens;
472
+ const P = p.reachTokens ?? p.windowTokens;
253
473
  const expandAt = p.expandAtTokens ?? p.targetTokens;
474
+ const gapRatio = p.qualityGapRatio ?? 0.35;
254
475
  // Fold as deep as the tree actually goes (L4/L5… when produced), not a constant.
255
476
  const maxFoldLevel = maxAvailableLevel(tree);
256
- // Immovable = never folded OR un-folded by the shed/expand passes: the locked
257
- // (frozen) set plus V2 pin-at-level-k chunks (held at their fixed level).
477
+ // Immovable = never folded OR un-folded by the solve: the locked (frozen)
478
+ // set plus V2 pin-at-level-k chunks (held at their fixed level).
258
479
  const immovable = new Set(frozen);
259
480
  for (const id of fixedLevels.keys())
260
481
  immovable.add(id);
261
- const F = new Map();
482
+ // Base = the non-negotiable part of every cut: raw zone raw, pins at their
483
+ // level, frozen at their carried resolution. Everything else starts raw here;
484
+ // the carried frontier and the ideal cut both build on this base.
485
+ const base = new Map();
262
486
  for (const c of ordered) {
263
487
  if (p.rawZone.has(c.id)) {
264
- F.set(c.id, 0);
488
+ base.set(c.id, 0);
265
489
  }
266
490
  else if (fixedLevels.has(c.id)) {
267
491
  // Pin-at-k: fix to exactly k, clamped to the deepest produced level for
268
492
  // this chunk (can't render at a level whose summary doesn't exist yet).
269
493
  const k = Math.max(0, fixedLevels.get(c.id));
270
- F.set(c.id, k === 0 ? 0 : Math.min(k, tree.maxLevel(c.id)));
494
+ base.set(c.id, k === 0 ? 0 : Math.min(k, tree.maxLevel(c.id)));
271
495
  }
272
496
  else if (frozen.has(c.id)) {
273
- F.set(c.id, p.previous.get(c.id) ?? c.currentResolution);
497
+ base.set(c.id, p.previous.get(c.id) ?? c.currentResolution);
498
+ }
499
+ else {
500
+ base.set(c.id, 0);
501
+ }
502
+ }
503
+ // Carried frontier (F_prev with the base enforced): what is actually
504
+ // rendered right now, and the reference for perturbation.
505
+ const carried = new Map(base);
506
+ let carriedNonEmpty = false;
507
+ for (const c of ordered) {
508
+ if (p.rawZone.has(c.id) || fixedLevels.has(c.id) || frozen.has(c.id))
509
+ continue;
510
+ // Enforce a pin-max-level immediately: un-fold anything carried deeper than
511
+ // its cap down to the cap (shallower summaries — its ancestors — are
512
+ // guaranteed to exist). The intended divergence cost of tightening a pin.
513
+ let lvl = p.previous.get(c.id) ?? 0;
514
+ const cap = pinCaps.get(c.id);
515
+ if (cap !== undefined && lvl > cap)
516
+ lvl = Math.max(0, cap);
517
+ carried.set(c.id, lvl);
518
+ }
519
+ for (const v of p.previous.values()) {
520
+ if (v !== 0) {
521
+ carriedNonEmpty = true;
522
+ break;
523
+ }
524
+ }
525
+ projectToValidCut(carried, tree, ordered, frozen, fixedLevels);
526
+ const carriedLayout = renderLayout(inputs, tree, carried);
527
+ const carriedTokens = carriedLayout.totalTokens;
528
+ // Shape prior: soft per-chunk depth caps (log-age band + pin caps), with the
529
+ // −1 sentinel marking the hard-protected set (never folded by anything).
530
+ const flatZoneChunks = p.rawZone.size;
531
+ const caps = new Map();
532
+ for (const c of ordered) {
533
+ let cap;
534
+ if (p.rawZone.has(c.id) || immovable.has(c.id)) {
535
+ cap = -1; // hard-protected sentinel: never folded, W notwithstanding
274
536
  }
275
537
  else {
276
- // Carry F_prev, but enforce a pin-max-level immediately: un-fold anything
277
- // carried deeper than its cap down to the cap (shallower summaries — its
278
- // ancestors are guaranteed to exist). This is the intended divergence
279
- // cost of adding/tightening a pin (design §7).
280
- let lvl = p.previous.get(c.id) ?? 0;
281
- const cap = pinCaps.get(c.id);
282
- if (cap !== undefined && lvl > cap)
283
- lvl = Math.max(0, cap);
284
- F.set(c.id, lvl);
538
+ cap = foldDepthCap(c, p.now, p.rawZone, flatZoneChunks, mergeThreshold, maxFoldLevel);
539
+ const pinCap = pinCaps.get(c.id);
540
+ if (pinCap !== undefined)
541
+ cap = Math.min(cap, Math.max(0, pinCap));
285
542
  }
543
+ caps.set(c.id, cap);
286
544
  }
287
- let tokens = renderLayout(inputs, tree, F).totalTokens;
288
- const before = tokens;
289
- let folded = false;
290
- let expanded = false;
291
- let escalated = false;
292
- if (tokens > p.foldAtTokens) {
293
- const flatZoneChunks = p.rawZone.size;
294
- const caps = new Map();
545
+ // 1. The ideal cut — pure relevance, no P anywhere.
546
+ const _t0 = typeof process !== 'undefined' && process.env?.KV_TIMING ? Date.now() : 0;
547
+ const ideal = relevanceCut(inputs, tree, ordered, base, caps, pinCaps, p.rawZone, immovable, frozen, fixedLevels, p.targetTokens, p.windowTokens, maxFoldLevel);
548
+ if (_t0)
549
+ console.error(`[kv-timing] relevanceCut ${Date.now() - _t0}ms idealTokens=${ideal.tokens}`);
550
+ const idealLayout = renderLayout(inputs, tree, ideal.F);
551
+ const idealLoss = relevanceLoss(ideal.F, ordered, p.rawZone, caps);
552
+ const gapCeiling = gapRatio * Math.max(1, idealLoss);
553
+ const flags = (F) => {
554
+ let folded = false;
555
+ let expanded = false;
295
556
  for (const c of ordered) {
296
- let cap;
297
- if (p.rawZone.has(c.id) || immovable.has(c.id)) {
298
- cap = -1; // hard-protected sentinel: never fold, even in the W emergency
299
- }
300
- else {
301
- cap = foldDepthCap(c, p.now, p.rawZone, flatZoneChunks, mergeThreshold, maxFoldLevel);
302
- const pinCap = pinCaps.get(c.id);
303
- if (pinCap !== undefined)
304
- cap = Math.min(cap, Math.max(0, pinCap));
305
- }
306
- caps.set(c.id, cap);
557
+ const prev = carried.get(c.id) ?? 0;
558
+ const next = F.get(c.id) ?? 0;
559
+ if (next > prev)
560
+ folded = true;
561
+ else if (next < prev)
562
+ expanded = true;
563
+ if (folded && expanded)
564
+ break;
307
565
  }
308
- tokens = shedToTarget(F, inputs, tree, ordered, caps, p.targetTokens, reach, p.windowTokens, maxFoldLevel, pinCaps);
309
- folded = tokens !== before; // a real fold only if it actually changed the render
310
- if (tokens > p.windowTokens)
311
- escalated = true;
566
+ return { folded, expanded };
567
+ };
568
+ const adoptIdeal = (override) => ({
569
+ resolutions: ideal.F,
570
+ tokens: ideal.tokens,
571
+ ...flags(ideal.F),
572
+ escalated: ideal.tokens > p.windowTokens,
573
+ perturbation: kvCost(carriedLayout, idealLayout),
574
+ ...(override ? { override } : {}),
575
+ });
576
+ // 2. Bootstrap: nothing carried → nothing to preserve; pure relevance solve.
577
+ if (!carriedNonEmpty && p.previous.size === 0)
578
+ return adoptIdeal('bootstrap');
579
+ // 3. Dead band with self-heal: hold the carried frontier only when it is
580
+ // feasible, in band, AND not certifiably misallocated.
581
+ const carriedLoss = relevanceLoss(carried, ordered, p.rawZone, caps);
582
+ const inBand = carriedTokens <= p.foldAtTokens && carriedTokens >= expandAt;
583
+ if (inBand && carriedTokens <= p.windowTokens && carriedLoss - idealLoss <= gapCeiling) {
584
+ return {
585
+ resolutions: carried,
586
+ tokens: carriedTokens,
587
+ ...flags(carried), // vs itself → both false unless projection moved it
588
+ escalated: false,
589
+ perturbation: 0,
590
+ };
312
591
  }
313
- else if (tokens < expandAt) {
314
- tokens = expandToTarget(F, inputs, tree, ordered, p.targetTokens, reach, p.rawZone, immovable, maxFoldLevel);
315
- expanded = tokens !== before; // a real un-fold only if it actually changed
592
+ // 4. Within the trust region → adopt the ideal outright.
593
+ const idealPert = kvCost(carriedLayout, idealLayout);
594
+ if (idealPert <= P)
595
+ return adoptIdeal();
596
+ // 5. Suffix adoption: the ideal's newest changes only, within P.
597
+ const partial = suffixAdopt(inputs, tree, ordered, carried, carriedLayout, ideal.F, frozen, fixedLevels, P);
598
+ const partialLoss = relevanceLoss(partial.F, ordered, p.rawZone, caps);
599
+ // Progress guard: when a shed is REQUIRED (carried over foldAt), a partial
600
+ // plan that neither reaches the band nor moves at all is not acceptable —
601
+ // an under-folded profile scores zero misallocation loss, so the quality
602
+ // gap alone cannot reject "do nothing forever". (P below the physical floor
603
+ // — the tail that any fold must invalidate — lands here and overrides.)
604
+ const mustShed = carriedTokens > p.foldAtTokens;
605
+ const madeProgress = !mustShed || partial.tokens <= p.foldAtTokens || partial.perturbation > 0;
606
+ if (partial.tokens <= p.windowTokens && partialLoss - idealLoss <= gapCeiling && madeProgress) {
607
+ return {
608
+ resolutions: partial.F,
609
+ tokens: partial.tokens,
610
+ ...flags(partial.F),
611
+ escalated: false,
612
+ perturbation: partial.perturbation,
613
+ };
316
614
  }
317
- return { resolutions: F, tokens, folded, expanded, escalated };
615
+ // Trust region priced out: feasibility or quality demands the ideal.
616
+ return adoptIdeal(partial.tokens > p.windowTokens ? 'infeasible' : 'quality-gap');
318
617
  }
319
618
  /**
320
619
  * Run the controller over a session (the exported PickerInputs snapshot) as