@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
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Bug 6.9: anti-redundancy × budget-cap = silent memory holes.
3
+ *
4
+ * `getAntiRedundantSummaries` excludes an L2 when ALL of its source L1s are
5
+ * in the CANDIDATE shown-set — computed BEFORE budget selection. When the
6
+ * budget cap (e.g. KnowledgeStrategy's research L1 cap) then drops some of
7
+ * those L1s, their parent L2 was already excluded, so the covered history
8
+ * appears at NEITHER level — unrecoverable until a later merge changes the
9
+ * shown-set. Long research-heavy runs lose their oldest research memories
10
+ * entirely and the agent re-researches (a token-burning feedback loop).
11
+ *
12
+ * Fix: a post-selection coverage-repair pass in selectHierarchical
13
+ * re-includes an excluded L2/L3 whose children did not all survive budget
14
+ * selection.
15
+ */
16
+ import { describe, it, before, after } from 'node:test';
17
+ import assert from 'node:assert';
18
+ import { rmSync, existsSync } from 'node:fs';
19
+ import { ContextManager, KnowledgeStrategy } from '../src/index.js';
20
+ const TEST_STORE_PATH = './test-anti-redundancy-budget';
21
+ function cleanup() {
22
+ if (existsSync(TEST_STORE_PATH)) {
23
+ rmSync(TEST_STORE_PATH, { recursive: true, force: true });
24
+ }
25
+ }
26
+ describe('Anti-redundancy vs budget cap (memory-hole repair)', () => {
27
+ before(() => cleanup());
28
+ after(() => cleanup());
29
+ it('re-includes an excluded L2 when the budget drops its L1 children', async () => {
30
+ cleanup();
31
+ const strategy = new KnowledgeStrategy({
32
+ targetChunkTokens: 20, // research max = 40 → one tool pair per chunk
33
+ headWindowTokens: 0,
34
+ recentWindowTokens: 0,
35
+ autoTickOnNewMessage: false,
36
+ // Tight L1 budget: research cap = 0.3 * 1000 = 300 tokens → only 3 of
37
+ // the 10 research L1s (100 tokens each) survive selection.
38
+ l1BudgetTokens: 1000,
39
+ l2BudgetTokens: 0,
40
+ l3BudgetTokens: 0,
41
+ researchL1BudgetCap: 0.3,
42
+ });
43
+ const manager = await ContextManager.open({
44
+ path: TEST_STORE_PATH,
45
+ strategy,
46
+ });
47
+ // 10 research tool pairs → 10 chunks of [tool_use, tool_result].
48
+ for (let k = 0; k < 10; k++) {
49
+ manager.addMessage('Claude', [
50
+ { type: 'tool_use', id: `tu-${k}`, name: 'mcpl:search', input: { q: 'x'.repeat(120) } },
51
+ ]);
52
+ manager.addMessage('User', [
53
+ { type: 'tool_result', toolUseId: `tu-${k}`, content: 'r'.repeat(120) },
54
+ ]);
55
+ }
56
+ const s = strategy;
57
+ const internals = manager;
58
+ s.rebuildChunks(internals.messageStore);
59
+ assert.ok(s.chunks.length >= 6, `need several chunks to exercise the cap, got ${s.chunks.length}`);
60
+ // Inject one research L1 per chunk (sourceIds exactly matching the chunk
61
+ // key, so rebuildChunks re-links them as compressed and the raw fallback
62
+ // does NOT re-emit these messages), plus one L2 covering ALL the L1s.
63
+ // The L1s are unmerged — the candidate shown-set contains all of them, so
64
+ // pre-fix anti-redundancy excludes the L2 outright.
65
+ const l1Ids = [];
66
+ const allMsgIds = [];
67
+ s.chunks.forEach((chunk, i) => {
68
+ const msgIds = chunk.messages.map(m => m.id);
69
+ allMsgIds.push(...msgIds);
70
+ const id = `L1-${i}`;
71
+ l1Ids.push(id);
72
+ s.summaries.push({
73
+ id,
74
+ level: 1,
75
+ content: `[[R${i}]] research memory ${i}`,
76
+ tokens: 100,
77
+ sourceLevel: 0,
78
+ sourceIds: msgIds,
79
+ sourceRange: { first: msgIds[0], last: msgIds[msgIds.length - 1] },
80
+ created: Date.now(),
81
+ phaseType: 'research',
82
+ });
83
+ });
84
+ s.summaries.push({
85
+ id: 'L2-0',
86
+ level: 2,
87
+ content: '[[L2COVER]] consolidated research memory',
88
+ tokens: 150,
89
+ sourceLevel: 1,
90
+ sourceIds: l1Ids,
91
+ sourceRange: { first: allMsgIds[0], last: allMsgIds[allMsgIds.length - 1] },
92
+ created: Date.now(),
93
+ });
94
+ // Render with a generous overall budget — the hole is created by the
95
+ // research cap, not by overall exhaustion.
96
+ const entries = strategy.select(internals.messageStore.createView(), internals.contextLog.createView(), { maxTokens: 50000, reserveForResponse: 0 });
97
+ const renderedText = entries
98
+ .map(e => e.content.filter(b => b.type === 'text').map(b => b.text).join('\n'))
99
+ .join('\n');
100
+ const rawMsgIds = new Set(entries.map(e => e.sourceMessageId).filter(Boolean));
101
+ // Which L1s made it?
102
+ const shownL1Indexes = new Set();
103
+ for (let i = 0; i < l1Ids.length; i++) {
104
+ if (renderedText.includes(`[[R${i}]]`))
105
+ shownL1Indexes.add(i);
106
+ }
107
+ assert.ok(shownL1Indexes.size < l1Ids.length, 'test setup: the research cap should drop at least one L1 (else nothing to repair)');
108
+ // The core assertion: every source message is represented at SOME level —
109
+ // raw, via its L1, or via the L2 that covers everything.
110
+ const l2Shown = renderedText.includes('[[L2COVER]]');
111
+ s.chunks.forEach((chunk, i) => {
112
+ for (const m of chunk.messages) {
113
+ const represented = rawMsgIds.has(m.id) || shownL1Indexes.has(i) || l2Shown;
114
+ assert.ok(represented, `message ${m.id} (chunk ${i}) is represented at no level — silent memory hole`);
115
+ }
116
+ });
117
+ // And specifically: since the cap dropped some L1s, the parent L2 must
118
+ // have been re-included.
119
+ assert.ok(l2Shown, 'excluded L2 must be re-included when its children are budget-dropped');
120
+ await manager.close();
121
+ });
122
+ it('bounds coverage repairs by a per-level allowance and warns on a corrupted store', async () => {
123
+ cleanup();
124
+ const strategy = new KnowledgeStrategy({
125
+ targetChunkTokens: 20,
126
+ headWindowTokens: 0,
127
+ recentWindowTokens: 0,
128
+ autoTickOnNewMessage: false,
129
+ l1BudgetTokens: 1000,
130
+ l2BudgetTokens: 0, // zero level budget → repair falls back to the floor
131
+ l3BudgetTokens: 0,
132
+ researchL1BudgetCap: 0.3, // drops most L1s → every covering L2 is a repair candidate
133
+ });
134
+ const manager = await ContextManager.open({
135
+ path: TEST_STORE_PATH,
136
+ strategy,
137
+ });
138
+ for (let k = 0; k < 10; k++) {
139
+ manager.addMessage('Claude', [
140
+ { type: 'tool_use', id: `tu-${k}`, name: 'mcpl:search', input: { q: 'x'.repeat(120) } },
141
+ ]);
142
+ manager.addMessage('User', [
143
+ { type: 'tool_result', toolUseId: `tu-${k}`, content: 'r'.repeat(120) },
144
+ ]);
145
+ }
146
+ const s = strategy;
147
+ const internals = manager;
148
+ s.rebuildChunks(internals.messageStore);
149
+ const l1Ids = [];
150
+ const allMsgIds = [];
151
+ s.chunks.forEach((chunk, i) => {
152
+ const msgIds = chunk.messages.map(m => m.id);
153
+ allMsgIds.push(...msgIds);
154
+ const id = `L1-${i}`;
155
+ l1Ids.push(id);
156
+ s.summaries.push({
157
+ id, level: 1, content: `[[R${i}]] research memory ${i}`, tokens: 100,
158
+ sourceLevel: 0, sourceIds: msgIds,
159
+ sourceRange: { first: msgIds[0], last: msgIds[msgIds.length - 1] },
160
+ created: Date.now(), phaseType: 'research',
161
+ });
162
+ });
163
+ // Simulate a store damaged mid-merge: MANY excluded L2s, each covering all
164
+ // L1s (so each is individually a repair candidate once the cap drops L1s).
165
+ const L2_COUNT = 6;
166
+ for (let j = 0; j < L2_COUNT; j++) {
167
+ s.summaries.push({
168
+ id: `L2-${j}`, level: 2, content: `[[L2-${j}]] consolidated research ${j}`, tokens: 150,
169
+ sourceLevel: 1, sourceIds: l1Ids,
170
+ sourceRange: { first: allMsgIds[0], last: allMsgIds[allMsgIds.length - 1] },
171
+ created: Date.now(),
172
+ });
173
+ }
174
+ const warnings = [];
175
+ const origWarn = console.warn;
176
+ console.warn = (...args) => { warnings.push(args.join(' ')); };
177
+ let entries;
178
+ try {
179
+ // Small overall budget → floor allowance (0.05 * maxTokens) is small, so
180
+ // only a couple of the six covering L2s can be re-included.
181
+ entries = strategy.select(internals.messageStore.createView(), internals.contextLog.createView(), { maxTokens: 8000, reserveForResponse: 0 });
182
+ }
183
+ finally {
184
+ console.warn = origWarn;
185
+ }
186
+ const renderedText = entries
187
+ .map(e => e.content.filter(b => b.type === 'text').map(b => b.text).join('\n'))
188
+ .join('\n');
189
+ const l2ShownCount = Array.from({ length: L2_COUNT }, (_, j) => j)
190
+ .filter(j => renderedText.includes(`[[L2-${j}]]`)).length;
191
+ assert.ok(l2ShownCount >= 1, 'at least one covering L2 should still be repaired');
192
+ assert.ok(l2ShownCount < L2_COUNT, `repair allowance must cap re-inclusion, but all ${L2_COUNT} L2s rendered`);
193
+ assert.ok(warnings.some(w => w.includes('coverage-repair allowance exceeded')), 'a corrupted store exceeding the repair allowance must warn');
194
+ await manager.close();
195
+ });
196
+ });
197
+ //# sourceMappingURL=anti-redundancy-budget.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anti-redundancy-budget.test.js","sourceRoot":"","sources":["../../test/anti-redundancy-budget.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Regression tests for chunk-boundary persistence + close-then-compress.
3
+ *
4
+ * Background (Mythos duplication incident, 2026-07; fleet audit):
5
+ * 1. Chunk boundaries were recomputed from a running token sum on every
6
+ * rebuild; "already compressed" was recovered only by EXACT
7
+ * message-ID-set match against persisted L1 sourceIds. Any boundary
8
+ * shift (config change, restart, message mutation, chain break)
9
+ * re-keyed the tail → mass duplicate L1s over already-lived ground.
10
+ * 2. The trailing PARTIAL chunk (>=4 messages, under targetChunkTokens)
11
+ * was compressed eagerly on every rebuild while it grew → families of
12
+ * prefix-generation L1s (same start, growing span), all retained, all
13
+ * merged upward into the same L2 (content multiplication).
14
+ *
15
+ * New contract pinned here:
16
+ * - A chunk is compressed only after it CLOSES (running sum reaches
17
+ * targetChunkTokens); the trailing partial chunk is never compressed.
18
+ * - Closing a chunk persists a ChunkRecord to the `autobio:chunks`
19
+ * chronicle state slot; persisted records OWN the past — rebuilds and
20
+ * restarts never re-chunk covered ground, regardless of config drift.
21
+ * - L1 production refuses to overlap an existing L1 (strict guard).
22
+ * - If most records resolve to zero live messages (chain-break
23
+ * signature), compression fails CLOSED (no re-chunking of history).
24
+ * - Stores from before this change migrate lazily: records are
25
+ * synthesized from existing L1 sourceIds, longest generation per
26
+ * prefix family.
27
+ */
28
+ export {};
29
+ //# sourceMappingURL=chunk-persistence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-persistence.test.d.ts","sourceRoot":"","sources":["../../test/chunk-persistence.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Regression tests for chunk-boundary persistence + close-then-compress.
3
+ *
4
+ * Background (Mythos duplication incident, 2026-07; fleet audit):
5
+ * 1. Chunk boundaries were recomputed from a running token sum on every
6
+ * rebuild; "already compressed" was recovered only by EXACT
7
+ * message-ID-set match against persisted L1 sourceIds. Any boundary
8
+ * shift (config change, restart, message mutation, chain break)
9
+ * re-keyed the tail → mass duplicate L1s over already-lived ground.
10
+ * 2. The trailing PARTIAL chunk (>=4 messages, under targetChunkTokens)
11
+ * was compressed eagerly on every rebuild while it grew → families of
12
+ * prefix-generation L1s (same start, growing span), all retained, all
13
+ * merged upward into the same L2 (content multiplication).
14
+ *
15
+ * New contract pinned here:
16
+ * - A chunk is compressed only after it CLOSES (running sum reaches
17
+ * targetChunkTokens); the trailing partial chunk is never compressed.
18
+ * - Closing a chunk persists a ChunkRecord to the `autobio:chunks`
19
+ * chronicle state slot; persisted records OWN the past — rebuilds and
20
+ * restarts never re-chunk covered ground, regardless of config drift.
21
+ * - L1 production refuses to overlap an existing L1 (strict guard).
22
+ * - If most records resolve to zero live messages (chain-break
23
+ * signature), compression fails CLOSED (no re-chunking of history).
24
+ * - Stores from before this change migrate lazily: records are
25
+ * synthesized from existing L1 sourceIds, longest generation per
26
+ * prefix family.
27
+ */
28
+ import { describe, it, before, after } from 'node:test';
29
+ import assert from 'node:assert';
30
+ import { rmSync, existsSync } from 'node:fs';
31
+ import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
32
+ const BASE = './test-chunk-persistence';
33
+ let storeSeq = 0;
34
+ function freshPath() {
35
+ return `${BASE}-${storeSeq++}`;
36
+ }
37
+ function cleanup() {
38
+ for (let i = 0; i < storeSeq + 1; i++) {
39
+ const p = `${BASE}-${i}`;
40
+ if (existsSync(p))
41
+ rmSync(p, { recursive: true, force: true });
42
+ }
43
+ }
44
+ const SUMS = 'default/autobio:summaries';
45
+ const CHUNKS = 'default/autobio:chunks';
46
+ function mockMembrane() {
47
+ const calls = [];
48
+ return {
49
+ calls,
50
+ membrane: {
51
+ complete: async (request) => {
52
+ calls.push({ messages: request.messages });
53
+ const inputChars = request.messages
54
+ .flatMap((m) => m.content)
55
+ .map((b) => b.text ?? '')
56
+ .join('').length;
57
+ const text = `[mock summary inChars=${inputChars}] ` + 'x '.repeat(40);
58
+ return {
59
+ content: [{ type: 'text', text }],
60
+ usage: { input_tokens: Math.ceil(inputChars / 4), output_tokens: 25 },
61
+ };
62
+ },
63
+ },
64
+ };
65
+ }
66
+ const t = (s) => ({ type: 'text', text: s });
67
+ async function drain(manager) {
68
+ for (let i = 0; i < 500; i++) {
69
+ if (manager.isReady())
70
+ return;
71
+ await manager.tick();
72
+ }
73
+ throw new Error('drain: queue did not converge within 500 ticks');
74
+ }
75
+ function l1s(manager) {
76
+ const stored = manager.getStore().getStateJson(SUMS);
77
+ return (Array.isArray(stored) ? stored : []).filter((s) => s && s.level === 1);
78
+ }
79
+ function assertDisjointL1s(entries) {
80
+ const seen = new Map();
81
+ for (const s of entries) {
82
+ for (const mid of s.sourceIds ?? []) {
83
+ const prev = seen.get(mid);
84
+ assert.ok(prev === undefined || prev === s.id, `message ${mid} covered by both ${prev} and ${s.id} — duplicate L1s over the same span`);
85
+ seen.set(mid, s.id);
86
+ }
87
+ }
88
+ }
89
+ /** ~10 tokens of text per message at estimateTokens' chars/4 heuristic. */
90
+ const filler = (i) => `message ${i} ` + 'word '.repeat(8);
91
+ describe('Chunk persistence: close-then-compress', () => {
92
+ before(() => cleanup());
93
+ after(() => cleanup());
94
+ it('never compresses an unclosed (trailing partial) chunk', async () => {
95
+ const path = freshPath();
96
+ const { membrane, calls } = mockMembrane();
97
+ const strategy = new AutobiographicalStrategy({
98
+ targetChunkTokens: 1000, // far above what 6 tiny messages reach
99
+ headWindowTokens: 0,
100
+ recentWindowTokens: 0,
101
+ hierarchical: true,
102
+ });
103
+ const manager = await ContextManager.open({ path, strategy, membrane });
104
+ for (let i = 0; i < 6; i++)
105
+ manager.addMessage(i % 2 ? 'agent' : 'user', [t(filler(i))]);
106
+ await drain(manager);
107
+ assert.strictEqual(calls.length, 0, 'partial tail chunk (under targetChunkTokens) must not be compressed');
108
+ assert.strictEqual(l1s(manager).length, 0);
109
+ await manager.close();
110
+ });
111
+ it('a growing tail never produces prefix-generation L1 families', async () => {
112
+ const path = freshPath();
113
+ const { membrane } = mockMembrane();
114
+ const strategy = new AutobiographicalStrategy({
115
+ targetChunkTokens: 120,
116
+ headWindowTokens: 0,
117
+ recentWindowTokens: 0,
118
+ hierarchical: true,
119
+ });
120
+ const manager = await ContextManager.open({ path, strategy, membrane });
121
+ // Grow in small increments with a full drain between each — the exact
122
+ // pattern that used to mint one L1 generation per drain.
123
+ for (let batch = 0; batch < 12; batch++) {
124
+ for (let i = 0; i < 4; i++) {
125
+ manager.addMessage(i % 2 ? 'agent' : 'user', [t(filler(batch * 4 + i))]);
126
+ }
127
+ await drain(manager);
128
+ }
129
+ const entries = l1s(manager);
130
+ assert.ok(entries.length > 0, 'expected some closed chunks to compress');
131
+ assertDisjointL1s(entries);
132
+ // No two L1s may share a starting message (prefix-family signature).
133
+ const starts = entries.map((s) => s.sourceIds[0]);
134
+ assert.strictEqual(new Set(starts).size, starts.length, 'two L1s share a starting message — prefix-generation family detected');
135
+ await manager.close();
136
+ });
137
+ it('persists chunk records when chunks close', async () => {
138
+ const path = freshPath();
139
+ const { membrane } = mockMembrane();
140
+ const strategy = new AutobiographicalStrategy({
141
+ targetChunkTokens: 100,
142
+ headWindowTokens: 0,
143
+ recentWindowTokens: 0,
144
+ hierarchical: true,
145
+ l1HoldbackChunks: 0, // this test asserts EVERY record compresses after drain
146
+ });
147
+ const manager = await ContextManager.open({ path, strategy, membrane });
148
+ for (let i = 0; i < 40; i++)
149
+ manager.addMessage(i % 2 ? 'agent' : 'user', [t(filler(i))]);
150
+ await drain(manager);
151
+ const records = manager.getStore().getStateJson(CHUNKS);
152
+ assert.ok(Array.isArray(records) && records.length > 0, 'chunk records persisted');
153
+ for (const r of records) {
154
+ assert.ok(typeof r.id === 'string' && r.id.length > 0);
155
+ assert.ok(Array.isArray(r.sourceIds) && r.sourceIds.length >= 4);
156
+ assert.strictEqual(r.compressed, true, `record ${r.id} compressed after drain`);
157
+ assert.ok(typeof r.summaryId === 'string', `record ${r.id} linked to its L1`);
158
+ }
159
+ const l1 = l1s(manager);
160
+ assert.strictEqual(records.length, l1.length, 'one record per L1');
161
+ await manager.close();
162
+ });
163
+ it('restart + config drift does not re-compress covered ground', async () => {
164
+ const path = freshPath();
165
+ const { membrane } = mockMembrane();
166
+ {
167
+ const strategy = new AutobiographicalStrategy({
168
+ targetChunkTokens: 100,
169
+ headWindowTokens: 0,
170
+ recentWindowTokens: 0,
171
+ hierarchical: true,
172
+ });
173
+ const manager = await ContextManager.open({ path, strategy, membrane });
174
+ for (let i = 0; i < 60; i++)
175
+ manager.addMessage(i % 2 ? 'agent' : 'user', [t(filler(i))]);
176
+ await drain(manager);
177
+ assert.ok(l1s(manager).length >= 3, 'seed store built several L1s');
178
+ await manager.close();
179
+ }
180
+ // Restart with SHIFTED boundary inputs — the pre-fix code re-keys every
181
+ // chunk (exact sourceIds match fails) and re-compresses all of history.
182
+ const fresh = mockMembrane();
183
+ const strategy = new AutobiographicalStrategy({
184
+ targetChunkTokens: 170, // drifted
185
+ headWindowTokens: 0,
186
+ recentWindowTokens: 0,
187
+ hierarchical: true,
188
+ });
189
+ const manager = await ContextManager.open({
190
+ path,
191
+ strategy,
192
+ membrane: fresh.membrane,
193
+ });
194
+ await drain(manager);
195
+ const entries = l1s(manager);
196
+ assertDisjointL1s(entries);
197
+ assert.strictEqual(fresh.calls.length, 0, `restart with drifted config re-compressed covered ground (${fresh.calls.length} calls)`);
198
+ await manager.close();
199
+ });
200
+ it('fails closed when chunk records mass-orphan (chain-break signature)', async () => {
201
+ const path = freshPath();
202
+ const { membrane, calls } = mockMembrane();
203
+ // Seed: chunk records referencing message ids that do NOT exist (as after
204
+ // a messages chain break / renumbering), while live messages are present.
205
+ {
206
+ const strategy = new AutobiographicalStrategy({ recentWindowTokens: 1000 });
207
+ const manager = await ContextManager.open({ path, strategy, membrane });
208
+ const store = manager.getStore();
209
+ try {
210
+ store.registerState({ id: CHUNKS, strategy: 'append_log', deltaSnapshotEvery: 50, fullSnapshotEvery: 10 });
211
+ }
212
+ catch { /* registered by strategy already */ }
213
+ for (let c = 0; c < 4; c++) {
214
+ store.appendToStateJson(CHUNKS, {
215
+ id: `c-${c}`,
216
+ sourceIds: [`ghost-${c}-a`, `ghost-${c}-b`, `ghost-${c}-c`, `ghost-${c}-d`],
217
+ compressed: true,
218
+ summaryId: `L1-${c}`,
219
+ });
220
+ }
221
+ manager.sync();
222
+ await manager.close();
223
+ }
224
+ const strategy = new AutobiographicalStrategy({
225
+ targetChunkTokens: 100,
226
+ headWindowTokens: 0,
227
+ recentWindowTokens: 0,
228
+ hierarchical: true,
229
+ });
230
+ const manager = await ContextManager.open({ path, strategy, membrane });
231
+ for (let i = 0; i < 60; i++)
232
+ manager.addMessage(i % 2 ? 'agent' : 'user', [t(filler(i))]);
233
+ await drain(manager);
234
+ assert.strictEqual(calls.length, 0, 'mass-orphaned records must fail closed: no compression until an operator looks');
235
+ assert.strictEqual(l1s(manager).length, 0);
236
+ await manager.close();
237
+ });
238
+ it('migrates legacy stores: synthesizes records, collapses prefix families, no recompression', async () => {
239
+ const path = freshPath();
240
+ const { membrane, calls } = mockMembrane();
241
+ // Seed a LEGACY store: messages + L1 summaries, NO chunks slot.
242
+ // Includes a prefix family (L1-1 ⊂ L1-2) from the old partial-tail bug.
243
+ {
244
+ const strategy = new AutobiographicalStrategy({ recentWindowTokens: 100000 });
245
+ const manager = await ContextManager.open({ path, strategy, membrane });
246
+ const store = manager.getStore();
247
+ const ids = [];
248
+ for (let i = 0; i < 16; i++) {
249
+ const m = manager.addMessage(i % 2 ? 'agent' : 'user', [t(filler(i))]);
250
+ ids.push(m.id ?? String(i));
251
+ }
252
+ const mk = (id, src, extra = {}) => ({
253
+ id,
254
+ level: 1,
255
+ content: `memory ${id}`,
256
+ tokens: 10,
257
+ sourceLevel: 0,
258
+ sourceIds: src,
259
+ sourceRange: { first: src[0], last: src[src.length - 1] },
260
+ created: 1750000000000,
261
+ ...extra,
262
+ });
263
+ store.appendToStateJson(SUMS, mk('L1-0', ids.slice(0, 6)));
264
+ store.appendToStateJson(SUMS, mk('L1-1', ids.slice(6, 10))); // stale generation
265
+ store.appendToStateJson(SUMS, mk('L1-2', ids.slice(6, 14))); // superset, same start
266
+ manager.sync();
267
+ await manager.close();
268
+ }
269
+ const strategy = new AutobiographicalStrategy({
270
+ targetChunkTokens: 100,
271
+ headWindowTokens: 0,
272
+ recentWindowTokens: 0,
273
+ hierarchical: true,
274
+ });
275
+ const manager = await ContextManager.open({ path, strategy, membrane });
276
+ await drain(manager);
277
+ const records = manager.getStore().getStateJson(CHUNKS);
278
+ assert.ok(Array.isArray(records), 'migration created the chunks slot');
279
+ const bySummary = new Map(records.map((r) => [r.summaryId, r]));
280
+ assert.ok(bySummary.has('L1-0'), 'record synthesized for L1-0');
281
+ assert.ok(bySummary.has('L1-2'), 'record synthesized for longest generation L1-2');
282
+ assert.ok(!bySummary.has('L1-1'), 'stale prefix generation L1-1 gets NO record');
283
+ // Covered ground must not be re-compressed; only the uncovered tail
284
+ // (ids[14..15], 2 messages — under min chunk length) may ever compress,
285
+ // and it cannot close here, so: zero calls.
286
+ assert.strictEqual(calls.length, 0, 'migration must not trigger recompression');
287
+ await manager.close();
288
+ });
289
+ });
290
+ //# sourceMappingURL=chunk-persistence.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-persistence.test.js","sourceRoot":"","sources":["../../test/chunk-persistence.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Bug 6.10: compression concurrent with new messages — stale chunk identity
3
+ * across rebuildChunks.
4
+ *
5
+ * tick() shifts an index into `this.chunks` and starts an async
6
+ * `compressChunkHierarchical` holding a reference to that chunk OBJECT.
7
+ * A rebuildChunks (fired by select()/onNewMessage while the LLM call is in
8
+ * flight) rebuilds `this.chunks` and resets `compressionQueue`; the rebuilt
9
+ * chunk for the same messages is created BEFORE the in-flight summary
10
+ * exists, so it re-queues as uncompressed. When the in-flight call
11
+ * completes it marks only the STALE object — the next tick compresses the
12
+ * same messages again, producing a duplicate L1 over identical history.
13
+ *
14
+ * Fix: compressChunkHierarchical reconciles by content identity (sourceIds)
15
+ * against the summary archive before (and after) the LLM call — an exact
16
+ * match adopts the existing summary; full coverage by existing L1s drops
17
+ * the chunk instead of duplicating history.
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=compression-dedup.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compression-dedup.test.d.ts","sourceRoot":"","sources":["../../test/compression-dedup.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}