@animalabs/context-manager 0.5.4 → 0.5.9

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 (128) hide show
  1. package/dist/src/adaptive/kv-control.d.ts +84 -33
  2. package/dist/src/adaptive/kv-control.d.ts.map +1 -1
  3. package/dist/src/adaptive/kv-control.js +510 -145
  4. package/dist/src/adaptive/kv-control.js.map +1 -1
  5. package/dist/src/adaptive/picker.d.ts +21 -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 +67 -7
  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 +38 -6
  16. package/dist/src/context-manager.d.ts.map +1 -1
  17. package/dist/src/context-manager.js +51 -0
  18. package/dist/src/context-manager.js.map +1 -1
  19. package/dist/src/index.d.ts +4 -2
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +3 -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/phase-channel.d.ts +13 -0
  28. package/dist/src/phase-channel.d.ts.map +1 -0
  29. package/dist/src/phase-channel.js +55 -0
  30. package/dist/src/phase-channel.js.map +1 -0
  31. package/dist/src/strategies/autobiographical.d.ts +247 -8
  32. package/dist/src/strategies/autobiographical.d.ts.map +1 -1
  33. package/dist/src/strategies/autobiographical.js +1419 -141
  34. package/dist/src/strategies/autobiographical.js.map +1 -1
  35. package/dist/src/strategies/index.d.ts +1 -1
  36. package/dist/src/strategies/index.d.ts.map +1 -1
  37. package/dist/src/strategies/index.js.map +1 -1
  38. package/dist/src/strategies/knowledge.d.ts +8 -0
  39. package/dist/src/strategies/knowledge.d.ts.map +1 -1
  40. package/dist/src/strategies/knowledge.js +23 -1
  41. package/dist/src/strategies/knowledge.js.map +1 -1
  42. package/dist/src/types/index.d.ts +1 -1
  43. package/dist/src/types/index.d.ts.map +1 -1
  44. package/dist/src/types/index.js.map +1 -1
  45. package/dist/src/types/strategy.d.ts +112 -11
  46. package/dist/src/types/strategy.d.ts.map +1 -1
  47. package/dist/src/types/strategy.js.map +1 -1
  48. package/dist/test/adaptive/doc-plus-chat.test.js +7 -3
  49. package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -1
  50. package/dist/test/adaptive/hard-fail.test.js +22 -0
  51. package/dist/test/adaptive/hard-fail.test.js.map +1 -1
  52. package/dist/test/adaptive/ingestion.test.js +3 -1
  53. package/dist/test/adaptive/ingestion.test.js.map +1 -1
  54. package/dist/test/adaptive/kv-control.test.js +17 -11
  55. package/dist/test/adaptive/kv-control.test.js.map +1 -1
  56. package/dist/test/adaptive/pin-at-level.test.d.ts +16 -0
  57. package/dist/test/adaptive/pin-at-level.test.d.ts.map +1 -0
  58. package/dist/test/adaptive/pin-at-level.test.js +133 -0
  59. package/dist/test/adaptive/pin-at-level.test.js.map +1 -0
  60. package/dist/test/adaptive/single-path-solve.test.d.ts +16 -0
  61. package/dist/test/adaptive/single-path-solve.test.d.ts.map +1 -0
  62. package/dist/test/adaptive/single-path-solve.test.js +204 -0
  63. package/dist/test/adaptive/single-path-solve.test.js.map +1 -0
  64. package/dist/test/anti-redundancy-budget.test.d.ts +17 -0
  65. package/dist/test/anti-redundancy-budget.test.d.ts.map +1 -0
  66. package/dist/test/anti-redundancy-budget.test.js +197 -0
  67. package/dist/test/anti-redundancy-budget.test.js.map +1 -0
  68. package/dist/test/chunk-persistence.test.d.ts +29 -0
  69. package/dist/test/chunk-persistence.test.d.ts.map +1 -0
  70. package/dist/test/chunk-persistence.test.js +290 -0
  71. package/dist/test/chunk-persistence.test.js.map +1 -0
  72. package/dist/test/compression-dedup.test.d.ts +20 -0
  73. package/dist/test/compression-dedup.test.d.ts.map +1 -0
  74. package/dist/test/compression-dedup.test.js +92 -0
  75. package/dist/test/compression-dedup.test.js.map +1 -0
  76. package/dist/test/compression-shape-invariants.test.js +40 -3
  77. package/dist/test/compression-shape-invariants.test.js.map +1 -1
  78. package/dist/test/head-window-order.test.d.ts +19 -0
  79. package/dist/test/head-window-order.test.d.ts.map +1 -0
  80. package/dist/test/head-window-order.test.js +159 -0
  81. package/dist/test/head-window-order.test.js.map +1 -0
  82. package/dist/test/image-strip-render-stats.test.js +25 -3
  83. package/dist/test/image-strip-render-stats.test.js.map +1 -1
  84. package/dist/test/knowledge-tool-boundary.test.d.ts +14 -0
  85. package/dist/test/knowledge-tool-boundary.test.d.ts.map +1 -0
  86. package/dist/test/knowledge-tool-boundary.test.js +93 -0
  87. package/dist/test/knowledge-tool-boundary.test.js.map +1 -0
  88. package/dist/test/l1-holdback.test.d.ts +15 -0
  89. package/dist/test/l1-holdback.test.d.ts.map +1 -0
  90. package/dist/test/l1-holdback.test.js +119 -0
  91. package/dist/test/l1-holdback.test.js.map +1 -0
  92. package/dist/test/merge-contiguity.test.d.ts +11 -0
  93. package/dist/test/merge-contiguity.test.d.ts.map +1 -0
  94. package/dist/test/merge-contiguity.test.js +74 -0
  95. package/dist/test/merge-contiguity.test.js.map +1 -0
  96. package/dist/test/message-store-window.test.d.ts +13 -0
  97. package/dist/test/message-store-window.test.d.ts.map +1 -0
  98. package/dist/test/message-store-window.test.js +177 -0
  99. package/dist/test/message-store-window.test.js.map +1 -0
  100. package/dist/test/pins.test.js +40 -0
  101. package/dist/test/pins.test.js.map +1 -1
  102. package/dist/test/speculation-cap.test.d.ts +9 -3
  103. package/dist/test/speculation-cap.test.d.ts.map +1 -1
  104. package/dist/test/speculation-cap.test.js +179 -3
  105. package/dist/test/speculation-cap.test.js.map +1 -1
  106. package/dist/test/summary-log-consistency.test.d.ts +18 -0
  107. package/dist/test/summary-log-consistency.test.d.ts.map +1 -0
  108. package/dist/test/summary-log-consistency.test.js +97 -0
  109. package/dist/test/summary-log-consistency.test.js.map +1 -0
  110. package/dist/test/tool-pairing-invariant.test.d.ts +15 -0
  111. package/dist/test/tool-pairing-invariant.test.d.ts.map +1 -0
  112. package/dist/test/tool-pairing-invariant.test.js +225 -0
  113. package/dist/test/tool-pairing-invariant.test.js.map +1 -0
  114. package/dist/tsconfig.tsbuildinfo +1 -1
  115. package/package.json +11 -3
  116. package/src/adaptive/kv-control.ts +554 -152
  117. package/src/adaptive/picker.ts +21 -0
  118. package/src/adaptive/strategies/kv-stable.ts +62 -12
  119. package/src/context-log.ts +11 -0
  120. package/src/context-manager.ts +61 -4
  121. package/src/index.ts +6 -2
  122. package/src/message-store.ts +202 -9
  123. package/src/phase-channel.ts +57 -0
  124. package/src/strategies/autobiographical.ts +1482 -167
  125. package/src/strategies/index.ts +1 -1
  126. package/src/strategies/knowledge.ts +25 -1
  127. package/src/types/index.ts +1 -0
  128. package/src/types/strategy.ts +120 -7
@@ -7,9 +7,15 @@
7
7
  * speculatively-built L1s before there was any real budget pressure.
8
8
  *
9
9
  * Post-fix:
10
- * - `maxSpeculativeL1s` caps the count of (unmerged L1s + queued chunks).
11
- * When at cap, `onNewMessage` defers auto-tick — chunks still queue,
12
- * but compression doesn't fire until an explicit tick() / compile().
10
+ * - `maxSpeculativeL1s` caps the count of *produced, unmerged* L1 summaries.
11
+ * When at cap, `onNewMessage` defers auto-tick L1 production — chunks still
12
+ * queue, but compression doesn't fire until merges bring the unmerged count
13
+ * back under the cap (or an explicit tick() / compile()).
14
+ * - The cap does NOT count the pending compression backlog and does NOT gate
15
+ * merges: merges consolidate L1s into L_{k+1} and REDUCE the unmerged count,
16
+ * so gating them would deadlock (too many unmerged L1s blocks the merges that
17
+ * would relieve the cap, and a large backlog blocks the compression that
18
+ * would drain it). See the "does not deadlock" regression suite below.
13
19
  * - `shouldCompressPreflight()` is overridable so subclasses can add
14
20
  * custom predictive scheduling.
15
21
  */
@@ -83,6 +89,7 @@ describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
83
89
  recentWindowTokens: 5,
84
90
  targetChunkTokens: 20,
85
91
  autoTickOnNewMessage: true,
92
+ l1HoldbackChunks: 0, // this suite tests cap/preflight gating at chunk close
86
93
  });
87
94
  const manager = await setupWithQueuedChunk(strategy);
88
95
  assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk should have formed in compressible region');
@@ -95,6 +102,7 @@ describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
95
102
  recentWindowTokens: 5,
96
103
  targetChunkTokens: 20,
97
104
  autoTickOnNewMessage: true,
105
+ l1HoldbackChunks: 0, // this suite tests cap/preflight gating at chunk close
98
106
  maxSpeculativeL1s: 2,
99
107
  });
100
108
  const manager = await ContextManager.open({
@@ -121,6 +129,7 @@ describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
121
129
  recentWindowTokens: 5,
122
130
  targetChunkTokens: 20,
123
131
  autoTickOnNewMessage: true,
132
+ l1HoldbackChunks: 0, // this suite tests cap/preflight gating at chunk close
124
133
  maxSpeculativeL1s: 5,
125
134
  });
126
135
  const manager = await ContextManager.open({
@@ -147,6 +156,7 @@ describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
147
156
  recentWindowTokens: 5,
148
157
  targetChunkTokens: 20,
149
158
  autoTickOnNewMessage: true,
159
+ l1HoldbackChunks: 0, // this suite tests cap/preflight gating at chunk close
150
160
  });
151
161
  const manager = await setupWithQueuedChunk(strategy);
152
162
  assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
@@ -154,4 +164,170 @@ describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
154
164
  manager.close();
155
165
  });
156
166
  });
167
+ /**
168
+ * Regression: the speculation cap must throttle L1 *production* without
169
+ * deadlocking. Before this fix, `isAtSpeculativeCap()` counted
170
+ * `unmergedL1s + compressionQueue.length` and gated the *entire* drain. A store
171
+ * that accumulated more unmerged L1s than the cap (e.g. a manual backfill) could
172
+ * never recover: the cap blocked the merges that would reduce the count, and the
173
+ * backlog blocked the compression that would drain it. (Observed live: an agent
174
+ * with 79 unmerged L1s, cap 36, and 13 queued merges sat frozen indefinitely.)
175
+ */
176
+ describe('AutobiographicalStrategy — speculation cap does not deadlock (regression)', () => {
177
+ before(cleanup);
178
+ after(cleanup);
179
+ beforeEach(cleanup);
180
+ class DrainStrategy extends AutobiographicalStrategy {
181
+ mergeRuns = 0;
182
+ seedL1(content) {
183
+ const entry = {
184
+ id: `L1-${this.nextSummaryIdCounter()}`,
185
+ level: 1,
186
+ content,
187
+ tokens: Math.ceil(content.length / 4),
188
+ sourceLevel: 0,
189
+ sourceIds: ['x'],
190
+ sourceRange: { first: 'x', last: 'x' },
191
+ created: Date.now(),
192
+ };
193
+ this.pushSummary(entry);
194
+ return entry;
195
+ }
196
+ queueMerge(ids) {
197
+ this.enqueueMerge({ level: 2, sourceIds: ids });
198
+ }
199
+ atCap() { return this.isAtSpeculativeCap(); }
200
+ unmergedL1() {
201
+ return this.summaries.filter((s) => s.level === 1 && !s.mergedInto).length;
202
+ }
203
+ get mergeQ() { return this.mergeQueue; }
204
+ get compQ() { return this.compressionQueue; }
205
+ // Isolate the drain/cap gating from executeMerge's message-store coupling:
206
+ // simulate a successful merge by marking the sources merged.
207
+ async executeMerge(_level, sourceIds) {
208
+ this.mergeRuns++;
209
+ for (const id of sourceIds) {
210
+ const s = this.summaries.find((x) => x.id === id);
211
+ if (s)
212
+ s.mergedInto = 'L2-test';
213
+ }
214
+ }
215
+ }
216
+ it('cap counts produced unmerged L1s only — a compression backlog must not trip it', async () => {
217
+ const strategy = new DrainStrategy({
218
+ headWindowTokens: 0,
219
+ recentWindowTokens: 5,
220
+ maxSpeculativeL1s: 5,
221
+ hierarchical: true,
222
+ });
223
+ const manager = await ContextManager.open({ path: TEST_STORE_PATH, strategy });
224
+ strategy.seedL1('only one'); // 1 produced unmerged L1
225
+ for (let i = 0; i < 100; i++)
226
+ strategy.compQ.push(i); // huge pending backlog
227
+ assert.equal(strategy.atCap(), false, 'a 100-deep compression backlog with 1 produced L1 must NOT trip a cap of 5 (pre-fix deadlock)');
228
+ for (let i = 0; i < 5; i++)
229
+ strategy.seedL1(`x${i}`); // now 6 produced unmerged > 5
230
+ assert.equal(strategy.atCap(), true, 'six produced unmerged L1s exceed the cap of 5');
231
+ manager.close();
232
+ });
233
+ it('over-cap with queued merges still drains merges, then the cap clears', async () => {
234
+ const membrane = { complete: async () => ({ content: [{ type: 'text', text: '[mock]' }] }) };
235
+ const strategy = new DrainStrategy({
236
+ headWindowTokens: 0,
237
+ recentWindowTokens: 5,
238
+ maxSpeculativeL1s: 3,
239
+ hierarchical: true,
240
+ });
241
+ const manager = await ContextManager.open({
242
+ path: TEST_STORE_PATH,
243
+ strategy,
244
+ membrane: membrane,
245
+ });
246
+ const ids = [];
247
+ for (let i = 0; i < 8; i++)
248
+ ids.push(strategy.seedL1(`s${i}`).id); // 8 unmerged > cap 3
249
+ strategy.queueMerge(ids.slice(0, 6)); // one L2 merge over 6 of them
250
+ assert.equal(strategy.atCap(), true, 'precondition: over the cap');
251
+ assert.equal(strategy.mergeQ.length, 1, 'precondition: a merge is queued');
252
+ await manager.tick(); // pre-fix: bailed at the cap → permanent deadlock
253
+ assert.equal(strategy.mergeRuns, 1, 'merge must run even while over the speculative cap');
254
+ assert.equal(strategy.mergeQ.length, 0, 'merge dequeued after success');
255
+ assert.equal(strategy.unmergedL1(), 2, '6 of 8 L1s consolidated');
256
+ assert.equal(strategy.atCap(), false, 'cap cleared → L1 compression can resume');
257
+ manager.close();
258
+ });
259
+ });
260
+ /**
261
+ * Regression: `driveSpeculativeDrain` must keep recursing as long as ticks do
262
+ * real work — using a monotonic work counter, NOT a queue-length delta. A
263
+ * productive tick can ALSO enqueue a follow-on item (e.g. a merge that schedules
264
+ * the next-level merge), leaving the queue length unchanged. The old
265
+ * length-delta check read that as "no progress" and halted the drain with the
266
+ * backlog only partly cleared — observed live: a single trigger drained ~4 of
267
+ * Lena's 13 queued merges, then stalled until the next message.
268
+ */
269
+ describe('AutobiographicalStrategy — drain progresses past flat-length ticks (regression)', () => {
270
+ before(cleanup);
271
+ after(cleanup);
272
+ beforeEach(cleanup);
273
+ class FlatQueueStrategy extends AutobiographicalStrategy {
274
+ mergeRuns = 0;
275
+ refills = 4;
276
+ used = new Set();
277
+ seedL1(content) {
278
+ const entry = {
279
+ id: `L1-${this.nextSummaryIdCounter()}`,
280
+ level: 1, content, tokens: Math.ceil(content.length / 4),
281
+ sourceLevel: 0, sourceIds: ['x'], sourceRange: { first: 'x', last: 'x' },
282
+ created: Date.now(),
283
+ };
284
+ this.pushSummary(entry);
285
+ return entry;
286
+ }
287
+ qMerge(ids) { this.enqueueMerge({ level: 2, sourceIds: ids }); }
288
+ get mergeQ() { return this.mergeQueue; }
289
+ // Each merge consolidates its sources AND (for the first few) enqueues a
290
+ // follow-on merge — so the queue length nets out unchanged that tick.
291
+ async executeMerge(_level, sourceIds) {
292
+ this.mergeRuns++;
293
+ for (const id of sourceIds) {
294
+ const s = this.summaries.find((x) => x.id === id);
295
+ if (s)
296
+ s.mergedInto = 'L2-x';
297
+ this.used.add(id);
298
+ }
299
+ if (this.refills-- > 0) {
300
+ const fresh = this.summaries
301
+ .filter((s) => s.level === 1 && !s.mergedInto && !this.used.has(s.id))
302
+ .slice(0, 6).map((s) => s.id);
303
+ if (fresh.length >= 2)
304
+ this.qMerge(fresh);
305
+ }
306
+ }
307
+ }
308
+ it('a single trigger drains all merges even when each enqueues a follow-on (flat length)', async () => {
309
+ const strategy = new FlatQueueStrategy({
310
+ headWindowTokens: 0, recentWindowTokens: 5, hierarchical: true, // no cap → merges always allowed
311
+ });
312
+ const manager = await ContextManager.open({
313
+ path: TEST_STORE_PATH,
314
+ strategy,
315
+ membrane: { complete: async () => ({ content: [{ type: 'text', text: 'x' }] }) },
316
+ });
317
+ const ids = [];
318
+ for (let i = 0; i < 60; i++)
319
+ ids.push(strategy.seedL1(`s${i}`).id);
320
+ strategy.qMerge(ids.slice(0, 6)); // 1 queued; 4 refills → 5 merges total expected
321
+ // One trigger, as a single onNewMessage would do.
322
+ strategy.driveSpeculativeDrain(manager.createStrategyContext());
323
+ // Let the microtask recursion + async ticks settle.
324
+ for (let i = 0; i < 100 && strategy.mergeQ.length > 0; i++) {
325
+ await new Promise(r => setTimeout(r, 10));
326
+ }
327
+ await new Promise(r => setTimeout(r, 50));
328
+ assert.ok(strategy.mergeRuns >= 5, `drain must process all merges incl. follow-ons (ran ${strategy.mergeRuns}); pre-fix it halts at 1`);
329
+ assert.equal(strategy.mergeQ.length, 0, 'merge queue fully drained from a single trigger');
330
+ manager.close();
331
+ });
332
+ });
157
333
  //# sourceMappingURL=speculation-cap.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"speculation-cap.test.js","sourceRoot":"","sources":["../../test/speculation-cap.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"speculation-cap.test.js","sourceRoot":"","sources":["../../test/speculation-cap.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Regression tests for the setMergedInto index-desync clobber
3
+ * (Lena chronicle, 2026-07: 4 summaries silently overwritten).
4
+ *
5
+ * `loadPersistedState` filters empty-content summaries out of the
6
+ * in-memory array while they remain in the persisted log. The old
7
+ * `setMergedInto` edited the log by IN-MEMORY index, so with any empty
8
+ * entry in the log every merge-update landed one slot early —
9
+ * overwriting a neighboring summary and leaving the intended entry as
10
+ * a duplicate-id pair with diverging mergedInto.
11
+ *
12
+ * Invariants pinned here:
13
+ * 1. setMergedInto edits the log slot with the matching ID, never a
14
+ * neighbor, even when in-memory indices are shifted.
15
+ * 2. Load dedupes duplicate-id copies, preferring the merged one.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=summary-log-consistency.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary-log-consistency.test.d.ts","sourceRoot":"","sources":["../../test/summary-log-consistency.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Regression tests for the setMergedInto index-desync clobber
3
+ * (Lena chronicle, 2026-07: 4 summaries silently overwritten).
4
+ *
5
+ * `loadPersistedState` filters empty-content summaries out of the
6
+ * in-memory array while they remain in the persisted log. The old
7
+ * `setMergedInto` edited the log by IN-MEMORY index, so with any empty
8
+ * entry in the log every merge-update landed one slot early —
9
+ * overwriting a neighboring summary and leaving the intended entry as
10
+ * a duplicate-id pair with diverging mergedInto.
11
+ *
12
+ * Invariants pinned here:
13
+ * 1. setMergedInto edits the log slot with the matching ID, never a
14
+ * neighbor, even when in-memory indices are shifted.
15
+ * 2. Load dedupes duplicate-id copies, preferring the merged one.
16
+ */
17
+ import { describe, it, before, after } from 'node:test';
18
+ import assert from 'node:assert';
19
+ import { rmSync, existsSync } from 'node:fs';
20
+ import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
21
+ const TEST_STORE_PATH = './test-summary-log-consistency';
22
+ const SLOT = 'default/autobio:summaries';
23
+ function cleanup() {
24
+ if (existsSync(TEST_STORE_PATH)) {
25
+ rmSync(TEST_STORE_PATH, { recursive: true, force: true });
26
+ }
27
+ }
28
+ function entry(id, content, extra = {}) {
29
+ return {
30
+ id,
31
+ level: 1,
32
+ content,
33
+ tokens: Math.max(1, Math.ceil(content.length / 4)),
34
+ sourceLevel: 0,
35
+ sourceIds: ['1', '2'],
36
+ sourceRange: { first: '1', last: '2' },
37
+ created: 1750000000000,
38
+ ...extra,
39
+ };
40
+ }
41
+ describe('Summary log consistency (clobber regression)', () => {
42
+ before(() => cleanup());
43
+ after(() => cleanup());
44
+ it('setMergedInto edits the matching-ID slot, not a shifted neighbor', async () => {
45
+ cleanup();
46
+ // Seed a log whose in-memory view will be SHIFTED: an empty-content
47
+ // entry (filtered on load) sits before two valid entries.
48
+ {
49
+ const strategy = new AutobiographicalStrategy({ recentWindowTokens: 1000 });
50
+ const manager = await ContextManager.open({ path: TEST_STORE_PATH, strategy });
51
+ const store = manager.getStore();
52
+ store.appendToStateJson(SLOT, entry('L1-0', '')); // empty → filtered on load
53
+ store.appendToStateJson(SLOT, entry('L1-1', 'first real memory'));
54
+ store.appendToStateJson(SLOT, entry('L1-2', 'second real memory'));
55
+ manager.sync();
56
+ await manager.close();
57
+ }
58
+ // Reopen: in-memory summaries = [L1-1, L1-2]; log = [L1-0, L1-1, L1-2].
59
+ const strategy = new AutobiographicalStrategy({ recentWindowTokens: 1000 });
60
+ const manager = await ContextManager.open({ path: TEST_STORE_PATH, strategy });
61
+ const s = strategy;
62
+ assert.strictEqual(s.summaries.length, 2, 'empty entry filtered on load');
63
+ const target = s.summaries.find((x) => x.id === 'L1-1');
64
+ s.setMergedInto(target, 'L2-99');
65
+ const stored = manager.getStore().getStateJson(SLOT);
66
+ const byId = new Map(stored.map((x) => [x.id, x]));
67
+ assert.strictEqual(stored.length, 3, 'no entries added or lost');
68
+ assert.strictEqual(byId.get('L1-0')?.content, '', 'empty neighbor NOT clobbered');
69
+ assert.strictEqual(byId.get('L1-1')?.mergedInto, 'L2-99', 'merge update landed on the right entry');
70
+ assert.strictEqual(byId.get('L1-2')?.mergedInto, undefined, 'other neighbor untouched');
71
+ await manager.close();
72
+ });
73
+ it('load dedupes duplicate-id copies, preferring the merged one', async () => {
74
+ cleanup();
75
+ {
76
+ const strategy = new AutobiographicalStrategy({ recentWindowTokens: 1000 });
77
+ const manager = await ContextManager.open({ path: TEST_STORE_PATH, strategy });
78
+ const store = manager.getStore();
79
+ // Simulate a clobber-era store: same id twice, merged copy first
80
+ // (the observed shape from the incident), plain copy second.
81
+ store.appendToStateJson(SLOT, entry('L1-5', 'the memory', { mergedInto: 'L2-7' }));
82
+ store.appendToStateJson(SLOT, entry('L1-5', 'the memory'));
83
+ store.appendToStateJson(SLOT, entry('L1-6', 'another memory'));
84
+ manager.sync();
85
+ await manager.close();
86
+ }
87
+ const strategy = new AutobiographicalStrategy({ recentWindowTokens: 1000 });
88
+ const manager = await ContextManager.open({ path: TEST_STORE_PATH, strategy });
89
+ const s = strategy;
90
+ const copies = s.summaries.filter((x) => x.id === 'L1-5');
91
+ assert.strictEqual(copies.length, 1, 'duplicate id deduped on load');
92
+ assert.strictEqual(copies[0].mergedInto, 'L2-7', 'merged copy preferred — plain dupe must not resurrect on the unmerged frontier');
93
+ assert.strictEqual(s.summaries.length, 2);
94
+ await manager.close();
95
+ });
96
+ });
97
+ //# sourceMappingURL=summary-log-consistency.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary-log-consistency.test.js","sourceRoot":"","sources":["../../test/summary-log-consistency.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Bug 6.7: context-manager had no full post-selection tool-pair validation —
3
+ * only trailing (`trimOrphanedToolUse`) and leading orphan trims. Mid-list
4
+ * orphans (a budget break cutting a raw pin pair; the uncompressed-chunk
5
+ * fallback emitting a raw tool_result whose tool_use chunk already
6
+ * compressed) sailed through to the wire layer.
7
+ *
8
+ * `enforceToolPairing` is the final structural pass in selectHierarchical:
9
+ * every tool_use must be answered by a matching tool_result in the
10
+ * immediately-following entry, and every tool_result must answer a tool_use
11
+ * in the immediately-preceding entry. Repair prefers preserving pairs
12
+ * (stub tool_result) over dropping content.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=tool-pairing-invariant.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-pairing-invariant.test.d.ts","sourceRoot":"","sources":["../../test/tool-pairing-invariant.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Bug 6.7: context-manager had no full post-selection tool-pair validation —
3
+ * only trailing (`trimOrphanedToolUse`) and leading orphan trims. Mid-list
4
+ * orphans (a budget break cutting a raw pin pair; the uncompressed-chunk
5
+ * fallback emitting a raw tool_result whose tool_use chunk already
6
+ * compressed) sailed through to the wire layer.
7
+ *
8
+ * `enforceToolPairing` is the final structural pass in selectHierarchical:
9
+ * every tool_use must be answered by a matching tool_result in the
10
+ * immediately-following entry, and every tool_result must answer a tool_use
11
+ * in the immediately-preceding entry. Repair prefers preserving pairs
12
+ * (stub tool_result) over dropping content.
13
+ */
14
+ import { describe, it, before, after } from 'node:test';
15
+ import assert from 'node:assert';
16
+ import { rmSync, existsSync } from 'node:fs';
17
+ import { AutobiographicalStrategy, ContextManager } from '../src/index.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Invariant checker (mirrors the Anthropic API's structural rules)
20
+ // ---------------------------------------------------------------------------
21
+ function assertPaired(entries) {
22
+ for (let i = 0; i < entries.length; i++) {
23
+ for (const block of entries[i].content) {
24
+ if (block.type === 'tool_use') {
25
+ const id = block.id;
26
+ const next = entries[i + 1];
27
+ assert.ok(next?.content.some((b) => b.type === 'tool_result' && b.toolUseId === id), `entries[${i}]: tool_use ${id} not answered in entries[${i + 1}]`);
28
+ }
29
+ if (block.type === 'tool_result') {
30
+ const tid = block.toolUseId;
31
+ const prev = entries[i - 1];
32
+ assert.ok(prev?.content.some((b) => b.type === 'tool_use' && b.id === tid), `entries[${i}]: tool_result ${tid} has no tool_use in entries[${i - 1}]`);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ function entry(participant, content) {
38
+ return { index: 0, participant, content };
39
+ }
40
+ function text(t) {
41
+ return { type: 'text', text: t };
42
+ }
43
+ function use(id) {
44
+ return { type: 'tool_use', id, name: 'fn', input: {} };
45
+ }
46
+ function result(id, body = 'ok') {
47
+ return { type: 'tool_result', toolUseId: id, content: body };
48
+ }
49
+ function runEnforce(entries) {
50
+ const strategy = new AutobiographicalStrategy({});
51
+ strategy
52
+ .enforceToolPairing(entries);
53
+ return entries;
54
+ }
55
+ describe('enforceToolPairing — post-selection pairing validator', () => {
56
+ it('leaves already-valid output untouched', () => {
57
+ const entries = [
58
+ entry('User', [text('hello')]),
59
+ entry('Claude', [text('working'), use('A')]),
60
+ entry('User', [result('A')]),
61
+ entry('Claude', [text('done')]),
62
+ ];
63
+ entries.forEach((e, i) => { e.index = i; });
64
+ const snapshot = JSON.stringify(entries);
65
+ runEnforce(entries);
66
+ assert.strictEqual(JSON.stringify(entries), snapshot, 'valid input must pass through unchanged');
67
+ assertPaired(entries);
68
+ });
69
+ it('drops a mid-list orphan tool_result (its tool_use was compressed away)', () => {
70
+ const entries = [
71
+ entry('Claude', [text('summary of earlier work')]),
72
+ // Raw tool_result whose tool_use chunk already compressed — the
73
+ // uncompressed-chunk fallback shape.
74
+ entry('User', [result('GONE', 'orphaned payload')]),
75
+ entry('Claude', [text('later message')]),
76
+ ];
77
+ runEnforce(entries);
78
+ assertPaired(entries);
79
+ // Entry preserved as a placeholder, not silently deleted.
80
+ assert.strictEqual(entries.length, 3);
81
+ assert.ok(entries[1].content.every((b) => b.type !== 'tool_result'), 'orphan tool_result block must be removed');
82
+ });
83
+ it('stubs a mid-list tool_use whose result entry was dropped by a budget break', () => {
84
+ const entries = [
85
+ entry('Claude', [text('let me check'), use('A')]),
86
+ // The tool_result entry got cut by a budget break; next entry is
87
+ // ordinary conversation.
88
+ entry('User', [text('unrelated next message')]),
89
+ entry('Claude', [text('reply')]),
90
+ ];
91
+ runEnforce(entries);
92
+ assertPaired(entries);
93
+ // The tool_use is preserved (not dropped) and answered by a stub.
94
+ assert.ok(entries[0].content.some((b) => b.type === 'tool_use'), 'tool_use must be preserved');
95
+ const stub = entries[1];
96
+ assert.ok(stub.content.some((b) => b.type === 'tool_result' && b.toolUseId === 'A'), 'a stub tool_result entry must be inserted immediately after the tool_use');
97
+ });
98
+ it('adds stubs for partially-answered parallel tool_use blocks', () => {
99
+ const entries = [
100
+ entry('Claude', [use('A'), use('B'), use('C')]),
101
+ entry('User', [result('B')]),
102
+ entry('Claude', [text('continues')]),
103
+ ];
104
+ runEnforce(entries);
105
+ assertPaired(entries);
106
+ const ids = entries[1].content
107
+ .filter((b) => b.type === 'tool_result')
108
+ .map((b) => b.toolUseId)
109
+ .sort();
110
+ assert.deepStrictEqual(ids, ['A', 'B', 'C'], 'missing results must be stubbed alongside the real one');
111
+ });
112
+ it('repairs a recall pair interleaved between a tool_use and its (now non-adjacent) result', () => {
113
+ const entries = [
114
+ entry('Claude', [use('A')]),
115
+ // A recall pair landed between the pair members.
116
+ entry('Context Manager', [text('[CM] Recall memory L1-3.')]),
117
+ entry('Claude', [text('I remember researching X.')]),
118
+ entry('User', [result('A', 'the real result, now orphaned')]),
119
+ ];
120
+ runEnforce(entries);
121
+ assertPaired(entries);
122
+ // Look-ahead relocation: the REAL result payload must be preserved, not
123
+ // dropped in favour of a stub (roast finding #2).
124
+ const preserved = entries.some((e) => e.content.some((b) => b.type === 'tool_result' &&
125
+ b.toolUseId === 'A' &&
126
+ b.content === 'the real result, now orphaned'));
127
+ assert.ok(preserved, 'the genuine tool result must be relocated, not replaced by a stub');
128
+ });
129
+ it('handles consecutive tool_use entries (double budget cut)', () => {
130
+ const entries = [
131
+ entry('Claude', [use('A')]),
132
+ entry('Claude', [use('B')]),
133
+ entry('User', [result('B')]),
134
+ ];
135
+ runEnforce(entries);
136
+ assertPaired(entries);
137
+ });
138
+ it('appends a stub entry for a trailing unanswered tool_use', () => {
139
+ const entries = [
140
+ entry('User', [text('hi')]),
141
+ entry('Claude', [text('checking'), use('C')]),
142
+ ];
143
+ runEnforce(entries);
144
+ assertPaired(entries);
145
+ assert.strictEqual(entries.length, 3, 'a stub results entry must be appended');
146
+ assert.ok(entries[2].content.some((b) => b.type === 'tool_result' && b.toolUseId === 'C'));
147
+ });
148
+ it('dedupes duplicate tool_results for the same id', () => {
149
+ const entries = [
150
+ entry('Claude', [use('A')]),
151
+ entry('User', [result('A', 'first'), result('A', 'second')]),
152
+ ];
153
+ runEnforce(entries);
154
+ assertPaired(entries);
155
+ const count = entries[1].content.filter((b) => b.type === 'tool_result').length;
156
+ assert.strictEqual(count, 1, 'duplicate tool_result for the same id must be dropped');
157
+ });
158
+ it('reindexes entries after inserting stub entries', () => {
159
+ const entries = [
160
+ entry('Claude', [use('A')]),
161
+ entry('User', [text('not a result')]),
162
+ ];
163
+ runEnforce(entries);
164
+ assertPaired(entries);
165
+ for (let i = 0; i < entries.length; i++) {
166
+ assert.strictEqual(entries[i].index, i, `entry ${i} has stale index ${entries[i].index}`);
167
+ }
168
+ });
169
+ });
170
+ // ---------------------------------------------------------------------------
171
+ // Regression: the guard must run on the ADAPTIVE render path too. FKM defaults
172
+ // autobiographical strategies onto `selectAdaptive` (adaptiveResolution=true),
173
+ // which has the same mid-list orphan producers as the hierarchical path but
174
+ // only received `trimOrphanedToolUse` before this fix. See roast finding #1.
175
+ // ---------------------------------------------------------------------------
176
+ describe('enforceToolPairing — wired into the adaptive render path (FKM default)', () => {
177
+ const STORE = './test-tool-pairing-adaptive';
178
+ const clean = () => { if (existsSync(STORE))
179
+ rmSync(STORE, { recursive: true, force: true }); };
180
+ before(clean);
181
+ after(clean);
182
+ it('repairs a mid-list orphan produced on the adaptive path', async () => {
183
+ clean();
184
+ // Inject a mid-list orphan tool_use into the merged entries at the exact
185
+ // point `selectAdaptive` hands off to the final structural passes — the
186
+ // shape a budget break or recall-pair interleave produces. It is spliced
187
+ // NOT at the tail, so `trimOrphanedToolUse` (trailing-only) cannot remove
188
+ // it; only `enforceToolPairing` can. If the guard is not wired into
189
+ // `selectAdaptive`, the orphan survives and `assertPaired` fails.
190
+ class InjectingStrategy extends AutobiographicalStrategy {
191
+ mergeAdjacentBodyGroupRaw(entries, store) {
192
+ const merged = super.mergeAdjacentBodyGroupRaw(entries, store);
193
+ if (merged.length >= 2) {
194
+ merged.splice(1, 0, {
195
+ index: 1,
196
+ participant: 'Claude',
197
+ content: [use('MIDLIST_ORPHAN')],
198
+ });
199
+ merged.forEach((e, i) => { e.index = i; });
200
+ }
201
+ return merged;
202
+ }
203
+ }
204
+ const strategy = new InjectingStrategy({ adaptiveResolution: true });
205
+ const manager = await ContextManager.open({
206
+ path: STORE,
207
+ strategy,
208
+ membrane: { complete: async () => ({ content: [{ type: 'text', text: 's' }] }) },
209
+ });
210
+ for (let i = 0; i < 5; i++) {
211
+ manager.addMessage(i % 2 === 0 ? 'User' : 'Claude', [
212
+ { type: 'text', text: `message number ${i}` },
213
+ ]);
214
+ }
215
+ const internals = manager;
216
+ const entries = strategy.select(internals.messageStore.createView(), internals.contextLog.createView(), { maxTokens: 100000, reserveForResponse: 4000 });
217
+ // Sanity: the override ran and the injected use was not trimmed as a tail.
218
+ const orphanPreserved = entries.some(e => e.content.some(b => b.type === 'tool_use' && b.id === 'MIDLIST_ORPHAN'));
219
+ assert.ok(orphanPreserved, 'setup: injected mid-list tool_use should survive to the pairing pass');
220
+ // The guard must have stubbed the orphan — output is API-valid.
221
+ assertPaired(entries);
222
+ await manager.close();
223
+ });
224
+ });
225
+ //# sourceMappingURL=tool-pairing-invariant.test.js.map