@animalabs/context-manager 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/context-manager.d.ts +69 -2
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +128 -10
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/strategies/autobiographical.d.ts +217 -3
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +813 -77
- package/dist/src/strategies/autobiographical.js.map +1 -1
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/strategy.d.ts +208 -0
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js +21 -0
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/non-blocking-compile.test.d.ts +22 -0
- package/dist/test/non-blocking-compile.test.d.ts.map +1 -0
- package/dist/test/non-blocking-compile.test.js +114 -0
- package/dist/test/non-blocking-compile.test.js.map +1 -0
- package/dist/test/pins.test.d.ts +10 -0
- package/dist/test/pins.test.d.ts.map +1 -0
- package/dist/test/pins.test.js +164 -0
- package/dist/test/pins.test.js.map +1 -0
- package/dist/test/recall-positioning.test.d.ts +16 -0
- package/dist/test/recall-positioning.test.d.ts.map +1 -0
- package/dist/test/recall-positioning.test.js +181 -0
- package/dist/test/recall-positioning.test.js.map +1 -0
- package/dist/test/recent-window-eviction.test.d.ts +19 -0
- package/dist/test/recent-window-eviction.test.d.ts.map +1 -0
- package/dist/test/recent-window-eviction.test.js +206 -0
- package/dist/test/recent-window-eviction.test.js.map +1 -0
- package/dist/test/search.test.d.ts +10 -0
- package/dist/test/search.test.d.ts.map +1 -0
- package/dist/test/search.test.js +141 -0
- package/dist/test/search.test.js.map +1 -0
- package/dist/test/speculation-cap.test.d.ts +17 -0
- package/dist/test/speculation-cap.test.d.ts.map +1 -0
- package/dist/test/speculation-cap.test.js +157 -0
- package/dist/test/speculation-cap.test.js.map +1 -0
- package/dist/test/strategy-persistence.test.d.ts +15 -0
- package/dist/test/strategy-persistence.test.d.ts.map +1 -0
- package/dist/test/strategy-persistence.test.js +244 -0
- package/dist/test/strategy-persistence.test.js.map +1 -0
- package/dist/test/tool-pruning.test.d.ts +10 -0
- package/dist/test/tool-pruning.test.d.ts.map +1 -0
- package/dist/test/tool-pruning.test.js +140 -0
- package/dist/test/tool-pruning.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/context-manager.ts +142 -10
- package/src/strategies/autobiographical.ts +872 -81
- package/src/types/index.ts +14 -1
- package/src/types/strategy.ts +223 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for the postmortem 2026-05-04 (Triumvirate Conhost Silence)
|
|
3
|
+
* findings about AutobiographicalStrategy:
|
|
4
|
+
*
|
|
5
|
+
* (A) Phase-4 recent-window emission iterated forward and broke on budget,
|
|
6
|
+
* dropping the newest messages instead of the oldest. After a bloated
|
|
7
|
+
* compaction ate most of the budget, May-4 messages addressed to the
|
|
8
|
+
* clerk never reached the inference context, and the agent went silent.
|
|
9
|
+
*
|
|
10
|
+
* (B) Synthesised summary turns bypassed `maxMessageTokens`. With L1+L2+L3
|
|
11
|
+
* summary budgets defaulting to 30k each, a single assistant Q&A pair
|
|
12
|
+
* could grow past 90k tokens, eating the inference budget and starving
|
|
13
|
+
* recent messages.
|
|
14
|
+
*
|
|
15
|
+
* These tests construct minimal scenarios that fail under the pre-fix code
|
|
16
|
+
* and pass after the fixes in src/strategies/autobiographical.ts.
|
|
17
|
+
*/
|
|
18
|
+
import { describe, it, before, after } from 'node:test';
|
|
19
|
+
import assert from 'node:assert';
|
|
20
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
21
|
+
import { ContextManager, AutobiographicalStrategy, } from '../src/index.js';
|
|
22
|
+
const TEST_STORE_PATH = './test-recent-window-eviction';
|
|
23
|
+
function cleanup() {
|
|
24
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
25
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function textBlock(text) {
|
|
29
|
+
return [{ type: 'text', text }];
|
|
30
|
+
}
|
|
31
|
+
/** Test subclass that lets the test seed L1 summaries directly without a real LLM. */
|
|
32
|
+
class SeedableStrategy extends AutobiographicalStrategy {
|
|
33
|
+
seedL1Summary(content, sourceIds) {
|
|
34
|
+
const entry = {
|
|
35
|
+
id: `L1-test-${this.summaries.length}`,
|
|
36
|
+
level: 1,
|
|
37
|
+
content,
|
|
38
|
+
tokens: Math.ceil(content.length / 4),
|
|
39
|
+
sourceLevel: 0,
|
|
40
|
+
sourceIds,
|
|
41
|
+
sourceRange: {
|
|
42
|
+
first: sourceIds[0] ?? '',
|
|
43
|
+
last: sourceIds[sourceIds.length - 1] ?? '',
|
|
44
|
+
},
|
|
45
|
+
created: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
this.summaries.push(entry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
describe('Recent window newest-first eviction (postmortem bug A)', () => {
|
|
51
|
+
before(() => cleanup());
|
|
52
|
+
after(() => cleanup());
|
|
53
|
+
it('preserves the LATEST recent-window messages when budget is tight', async () => {
|
|
54
|
+
cleanup();
|
|
55
|
+
const strategy = new AutobiographicalStrategy({
|
|
56
|
+
headWindowTokens: 0,
|
|
57
|
+
recentWindowTokens: 100_000, // big — keep all messages in the recent zone
|
|
58
|
+
maxMessageTokens: 0,
|
|
59
|
+
hierarchical: true,
|
|
60
|
+
});
|
|
61
|
+
const manager = await ContextManager.open({
|
|
62
|
+
path: TEST_STORE_PATH,
|
|
63
|
+
strategy,
|
|
64
|
+
});
|
|
65
|
+
// 10 recent messages each padded to ~25 tokens (~100 chars) so that a
|
|
66
|
+
// tight budget forces eviction of some of them.
|
|
67
|
+
for (let i = 1; i <= 10; i++) {
|
|
68
|
+
const tag = String(i).padStart(2, '0');
|
|
69
|
+
manager.addMessage('user', textBlock(`msg-${tag} ${'.'.repeat(80)} body`));
|
|
70
|
+
}
|
|
71
|
+
// Tight budget: only ~3-4 messages worth of room out of 10.
|
|
72
|
+
const compiled = await manager.compile({ maxTokens: 80, reserveForResponse: 0 });
|
|
73
|
+
// The pre-fix loop iterated forward and emitted msg-01, msg-02, ... breaking
|
|
74
|
+
// when budget ran out. Post-fix should keep the NEWEST messages.
|
|
75
|
+
const lastEntry = compiled.messages[compiled.messages.length - 1];
|
|
76
|
+
assert.ok(lastEntry, 'should have at least one compiled entry');
|
|
77
|
+
const lastText = lastEntry.content
|
|
78
|
+
.filter((b) => b.type === 'text')
|
|
79
|
+
.map(b => b.text)
|
|
80
|
+
.join(' ');
|
|
81
|
+
assert.match(lastText, /msg-10/, `Latest compiled entry must contain msg-10, got: "${lastText}"`);
|
|
82
|
+
// And the compiled tail must be a strict tail: contain msg-10 but not the
|
|
83
|
+
// oldest entries that wouldn't fit.
|
|
84
|
+
const allText = compiled.messages
|
|
85
|
+
.flatMap(m => m.content)
|
|
86
|
+
.filter((b) => b.type === 'text')
|
|
87
|
+
.map(b => b.text)
|
|
88
|
+
.join(' ');
|
|
89
|
+
assert.ok(allText.includes('msg-10'), 'msg-10 (newest) must survive eviction');
|
|
90
|
+
// Expect SOME oldest message to have been evicted.
|
|
91
|
+
assert.ok(!allText.includes('msg-01'), 'msg-01 (oldest) should have been evicted');
|
|
92
|
+
manager.close();
|
|
93
|
+
});
|
|
94
|
+
it('emits surviving messages in chronological order (not reversed)', async () => {
|
|
95
|
+
cleanup();
|
|
96
|
+
const strategy = new AutobiographicalStrategy({
|
|
97
|
+
headWindowTokens: 0,
|
|
98
|
+
recentWindowTokens: 100_000,
|
|
99
|
+
maxMessageTokens: 0,
|
|
100
|
+
hierarchical: true,
|
|
101
|
+
});
|
|
102
|
+
const manager = await ContextManager.open({
|
|
103
|
+
path: TEST_STORE_PATH,
|
|
104
|
+
strategy,
|
|
105
|
+
});
|
|
106
|
+
for (let i = 1; i <= 5; i++) {
|
|
107
|
+
const tag = String(i).padStart(2, '0');
|
|
108
|
+
manager.addMessage('user', textBlock(`msg-${tag} body content`));
|
|
109
|
+
}
|
|
110
|
+
// Budget large enough to keep all 5.
|
|
111
|
+
const compiled = await manager.compile({ maxTokens: 1000, reserveForResponse: 0 });
|
|
112
|
+
const tags = compiled.messages
|
|
113
|
+
.map(m => m.content
|
|
114
|
+
.filter((b) => b.type === 'text')
|
|
115
|
+
.map(b => b.text)
|
|
116
|
+
.join(' '))
|
|
117
|
+
.map(t => {
|
|
118
|
+
const m = t.match(/msg-(\d+)/);
|
|
119
|
+
return m ? m[1] : null;
|
|
120
|
+
})
|
|
121
|
+
.filter((x) => x !== null);
|
|
122
|
+
assert.deepStrictEqual(tags, ['01', '02', '03', '04', '05'], 'compiled order must be chronological');
|
|
123
|
+
manager.close();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('Synthesised summary turn respects maxMessageTokens (postmortem bug B)', () => {
|
|
127
|
+
before(() => cleanup());
|
|
128
|
+
after(() => cleanup());
|
|
129
|
+
it('truncates a bloated combined-summaries answer entry to the configured cap', async () => {
|
|
130
|
+
cleanup();
|
|
131
|
+
const MSG_CAP = 200; // tokens
|
|
132
|
+
const strategy = new SeedableStrategy({
|
|
133
|
+
headWindowTokens: 0,
|
|
134
|
+
recentWindowTokens: 1000,
|
|
135
|
+
maxMessageTokens: MSG_CAP,
|
|
136
|
+
hierarchical: true,
|
|
137
|
+
// Generous summary budgets so the strategy WANTS to emit lots of summary text.
|
|
138
|
+
l1BudgetTokens: 30_000,
|
|
139
|
+
l2BudgetTokens: 30_000,
|
|
140
|
+
l3BudgetTokens: 30_000,
|
|
141
|
+
});
|
|
142
|
+
const manager = await ContextManager.open({
|
|
143
|
+
path: TEST_STORE_PATH,
|
|
144
|
+
strategy,
|
|
145
|
+
});
|
|
146
|
+
// A few recent messages so the compile is non-trivial.
|
|
147
|
+
manager.addMessage('user', textBlock('hello'));
|
|
148
|
+
manager.addMessage('assistant', textBlock('hi back'));
|
|
149
|
+
// Seed an oversized summary that would otherwise blow past msgCap.
|
|
150
|
+
// sourceIds are synthetic so they don't intersect head/recent message IDs
|
|
151
|
+
// and trigger the anti-redundancy filter.
|
|
152
|
+
const bigContent = 'X'.repeat(20_000); // ≈ 5000 tokens of text
|
|
153
|
+
strategy.seedL1Summary(bigContent, ['synthetic-old-1', 'synthetic-old-2']);
|
|
154
|
+
const compiled = await manager.compile({
|
|
155
|
+
maxTokens: 200_000,
|
|
156
|
+
reserveForResponse: 4000,
|
|
157
|
+
});
|
|
158
|
+
// Find the synthesised summary answer turn. It is a Q&A pair: the question
|
|
159
|
+
// is participant 'Context Manager', the answer is the summary participant
|
|
160
|
+
// (default 'Claude').
|
|
161
|
+
const answerEntry = compiled.messages.find(m => m.participant === 'Claude' &&
|
|
162
|
+
m.content.some(b => b.type === 'text' && b.text.includes('XXXX')));
|
|
163
|
+
assert.ok(answerEntry, 'should find synthesised summary answer entry');
|
|
164
|
+
const answerText = answerEntry.content
|
|
165
|
+
.filter((b) => b.type === 'text')
|
|
166
|
+
.map(b => b.text)
|
|
167
|
+
.join('');
|
|
168
|
+
// truncateContent caps at maxTokens * 4 chars and appends a marker.
|
|
169
|
+
// Allow some slack for the marker text.
|
|
170
|
+
const expectedMaxChars = MSG_CAP * 4 + 200;
|
|
171
|
+
assert.ok(answerText.length <= expectedMaxChars, `answer entry must be truncated to ≈${MSG_CAP} tokens, got ${answerText.length} chars`);
|
|
172
|
+
assert.match(answerText, /\[truncated/, 'truncated answer should carry the truncation marker');
|
|
173
|
+
manager.close();
|
|
174
|
+
});
|
|
175
|
+
it('leaves the answer entry intact when content is within the cap', async () => {
|
|
176
|
+
cleanup();
|
|
177
|
+
const strategy = new SeedableStrategy({
|
|
178
|
+
headWindowTokens: 0,
|
|
179
|
+
recentWindowTokens: 1000,
|
|
180
|
+
maxMessageTokens: 5000,
|
|
181
|
+
hierarchical: true,
|
|
182
|
+
});
|
|
183
|
+
const manager = await ContextManager.open({
|
|
184
|
+
path: TEST_STORE_PATH,
|
|
185
|
+
strategy,
|
|
186
|
+
});
|
|
187
|
+
manager.addMessage('user', textBlock('hello'));
|
|
188
|
+
const small = 'a small honest summary of earlier conversation';
|
|
189
|
+
strategy.seedL1Summary(small, ['synthetic-old-1']);
|
|
190
|
+
const compiled = await manager.compile({
|
|
191
|
+
maxTokens: 200_000,
|
|
192
|
+
reserveForResponse: 4000,
|
|
193
|
+
});
|
|
194
|
+
const answerEntry = compiled.messages.find(m => m.participant === 'Claude' &&
|
|
195
|
+
m.content.some(b => b.type === 'text' && b.text.includes('honest summary')));
|
|
196
|
+
assert.ok(answerEntry, 'should find synthesised summary answer entry');
|
|
197
|
+
const answerText = answerEntry.content
|
|
198
|
+
.filter((b) => b.type === 'text')
|
|
199
|
+
.map(b => b.text)
|
|
200
|
+
.join('');
|
|
201
|
+
assert.doesNotMatch(answerText, /\[truncated/, 'small summaries must not be truncated');
|
|
202
|
+
assert.ok(answerText.includes(small), 'small summary content must round-trip verbatim');
|
|
203
|
+
manager.close();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
//# sourceMappingURL=recent-window-eviction.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recent-window-eviction.test.js","sourceRoot":"","sources":["../../test/recent-window-eviction.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the search API (audit gap #7).
|
|
3
|
+
*
|
|
4
|
+
* Substring + regex search over summary content, plus single-summary
|
|
5
|
+
* lookup by id. Exposed through ContextManager passthrough so callers
|
|
6
|
+
* (e.g. agent-framework MCPL host) can build `memory_search` /
|
|
7
|
+
* `memory_read` agent tools on top.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=search.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.test.d.ts","sourceRoot":"","sources":["../../test/search.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the search API (audit gap #7).
|
|
3
|
+
*
|
|
4
|
+
* Substring + regex search over summary content, plus single-summary
|
|
5
|
+
* lookup by id. Exposed through ContextManager passthrough so callers
|
|
6
|
+
* (e.g. agent-framework MCPL host) can build `memory_search` /
|
|
7
|
+
* `memory_read` agent tools on top.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
10
|
+
import assert from 'node:assert';
|
|
11
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
12
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
13
|
+
const TEST_STORE_PATH = './test-search';
|
|
14
|
+
function cleanup() {
|
|
15
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
16
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function textBlock(text) {
|
|
20
|
+
return [{ type: 'text', text }];
|
|
21
|
+
}
|
|
22
|
+
class SeedableStrategy extends AutobiographicalStrategy {
|
|
23
|
+
seedSummary(content, level = 1, mergedInto) {
|
|
24
|
+
const entry = {
|
|
25
|
+
id: `L${level}-${this.nextSummaryIdCounter()}`,
|
|
26
|
+
level,
|
|
27
|
+
content,
|
|
28
|
+
tokens: Math.ceil(content.length / 4),
|
|
29
|
+
sourceLevel: 0,
|
|
30
|
+
sourceIds: ['x'],
|
|
31
|
+
sourceRange: { first: 'x', last: 'x' },
|
|
32
|
+
created: Date.now(),
|
|
33
|
+
...(mergedInto ? { mergedInto } : {}),
|
|
34
|
+
};
|
|
35
|
+
this.pushSummary(entry);
|
|
36
|
+
return entry;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
describe('AutobiographicalStrategy — search (gap #7)', () => {
|
|
40
|
+
before(cleanup);
|
|
41
|
+
after(cleanup);
|
|
42
|
+
beforeEach(cleanup);
|
|
43
|
+
it('returns empty when no summaries match the substring', async () => {
|
|
44
|
+
const strategy = new SeedableStrategy();
|
|
45
|
+
const manager = await ContextManager.open({
|
|
46
|
+
path: TEST_STORE_PATH,
|
|
47
|
+
strategy,
|
|
48
|
+
});
|
|
49
|
+
strategy.seedSummary('first memory about cats');
|
|
50
|
+
strategy.seedSummary('second memory about dogs');
|
|
51
|
+
const results = manager.searchSummaries({ text: 'unicorns' });
|
|
52
|
+
assert.equal(results.length, 0);
|
|
53
|
+
manager.close();
|
|
54
|
+
});
|
|
55
|
+
it('case-insensitive substring search returns matches sorted by hit count', async () => {
|
|
56
|
+
const strategy = new SeedableStrategy();
|
|
57
|
+
const manager = await ContextManager.open({
|
|
58
|
+
path: TEST_STORE_PATH,
|
|
59
|
+
strategy,
|
|
60
|
+
});
|
|
61
|
+
strategy.seedSummary('a meditation on cats and CATS and cats'); // 3 hits
|
|
62
|
+
strategy.seedSummary('one note about Cats'); // 1 hit
|
|
63
|
+
strategy.seedSummary('something about dogs'); // 0 hits
|
|
64
|
+
const results = manager.searchSummaries({ text: 'cats' });
|
|
65
|
+
assert.equal(results.length, 2, 'two summaries should match');
|
|
66
|
+
assert.equal(results[0].matches, 3, 'highest hit count first');
|
|
67
|
+
assert.equal(results[1].matches, 1);
|
|
68
|
+
manager.close();
|
|
69
|
+
});
|
|
70
|
+
it('regex search supports patterns and counts global matches', async () => {
|
|
71
|
+
const strategy = new SeedableStrategy();
|
|
72
|
+
const manager = await ContextManager.open({
|
|
73
|
+
path: TEST_STORE_PATH,
|
|
74
|
+
strategy,
|
|
75
|
+
});
|
|
76
|
+
strategy.seedSummary('found errors at 14:23 and 16:45 and 18:00');
|
|
77
|
+
strategy.seedSummary('no times here');
|
|
78
|
+
const results = manager.searchSummaries({ regex: /\d{2}:\d{2}/ });
|
|
79
|
+
assert.equal(results.length, 1);
|
|
80
|
+
assert.equal(results[0].matches, 3, 'should count all 3 timestamps');
|
|
81
|
+
manager.close();
|
|
82
|
+
});
|
|
83
|
+
it('level filter restricts to chosen levels', async () => {
|
|
84
|
+
const strategy = new SeedableStrategy();
|
|
85
|
+
const manager = await ContextManager.open({
|
|
86
|
+
path: TEST_STORE_PATH,
|
|
87
|
+
strategy,
|
|
88
|
+
});
|
|
89
|
+
strategy.seedSummary('shared word here', 1);
|
|
90
|
+
strategy.seedSummary('shared word also', 2);
|
|
91
|
+
strategy.seedSummary('shared word too', 3);
|
|
92
|
+
const l2only = manager.searchSummaries({ text: 'shared', levels: [2] });
|
|
93
|
+
assert.equal(l2only.length, 1);
|
|
94
|
+
assert.equal(l2only[0].summary.level, 2);
|
|
95
|
+
const l1andL3 = manager.searchSummaries({ text: 'shared', levels: [1, 3] });
|
|
96
|
+
assert.equal(l1andL3.length, 2);
|
|
97
|
+
assert.ok(l1andL3.every(r => r.summary.level === 1 || r.summary.level === 3));
|
|
98
|
+
manager.close();
|
|
99
|
+
});
|
|
100
|
+
it('excludes merged summaries by default; includeMerged opts in', async () => {
|
|
101
|
+
const strategy = new SeedableStrategy();
|
|
102
|
+
const manager = await ContextManager.open({
|
|
103
|
+
path: TEST_STORE_PATH,
|
|
104
|
+
strategy,
|
|
105
|
+
});
|
|
106
|
+
strategy.seedSummary('alive content', 1);
|
|
107
|
+
strategy.seedSummary('folded content', 1, 'L2-99'); // already merged
|
|
108
|
+
const live = manager.searchSummaries({ text: 'content' });
|
|
109
|
+
assert.equal(live.length, 1, 'merged summaries hidden by default');
|
|
110
|
+
assert.equal(live[0].summary.content, 'alive content');
|
|
111
|
+
const all = manager.searchSummaries({ text: 'content', includeMerged: true });
|
|
112
|
+
assert.equal(all.length, 2, 'includeMerged should reveal both');
|
|
113
|
+
manager.close();
|
|
114
|
+
});
|
|
115
|
+
it('limit caps the result count', async () => {
|
|
116
|
+
const strategy = new SeedableStrategy();
|
|
117
|
+
const manager = await ContextManager.open({
|
|
118
|
+
path: TEST_STORE_PATH,
|
|
119
|
+
strategy,
|
|
120
|
+
});
|
|
121
|
+
for (let i = 0; i < 10; i++) {
|
|
122
|
+
strategy.seedSummary(`hit ${i} hit hit`);
|
|
123
|
+
}
|
|
124
|
+
const results = manager.searchSummaries({ text: 'hit', limit: 3 });
|
|
125
|
+
assert.equal(results.length, 3);
|
|
126
|
+
});
|
|
127
|
+
it('getSummary returns an entry by id', async () => {
|
|
128
|
+
const strategy = new SeedableStrategy();
|
|
129
|
+
const manager = await ContextManager.open({
|
|
130
|
+
path: TEST_STORE_PATH,
|
|
131
|
+
strategy,
|
|
132
|
+
});
|
|
133
|
+
const seeded = strategy.seedSummary('lookup me');
|
|
134
|
+
const got = manager.getSummary(seeded.id);
|
|
135
|
+
assert.ok(got, 'should find the seeded summary');
|
|
136
|
+
assert.equal(got?.content, 'lookup me');
|
|
137
|
+
assert.equal(manager.getSummary('nonexistent'), null);
|
|
138
|
+
manager.close();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=search.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.test.js","sourceRoot":"","sources":["../../test/search.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for speculation cap + preflight hook (audit gap #8).
|
|
3
|
+
*
|
|
4
|
+
* Pre-fix: every new message triggered an auto-tick that compressed any
|
|
5
|
+
* pending chunk, regardless of how many speculative L1s the strategy was
|
|
6
|
+
* already holding. Lena's archive on Hermes ended up with dozens of
|
|
7
|
+
* speculatively-built L1s before there was any real budget pressure.
|
|
8
|
+
*
|
|
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().
|
|
13
|
+
* - `shouldCompressPreflight()` is overridable so subclasses can add
|
|
14
|
+
* custom predictive scheduling.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=speculation-cap.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculation-cap.test.d.ts","sourceRoot":"","sources":["../../test/speculation-cap.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for speculation cap + preflight hook (audit gap #8).
|
|
3
|
+
*
|
|
4
|
+
* Pre-fix: every new message triggered an auto-tick that compressed any
|
|
5
|
+
* pending chunk, regardless of how many speculative L1s the strategy was
|
|
6
|
+
* already holding. Lena's archive on Hermes ended up with dozens of
|
|
7
|
+
* speculatively-built L1s before there was any real budget pressure.
|
|
8
|
+
*
|
|
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().
|
|
13
|
+
* - `shouldCompressPreflight()` is overridable so subclasses can add
|
|
14
|
+
* custom predictive scheduling.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
17
|
+
import assert from 'node:assert';
|
|
18
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
19
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
20
|
+
const TEST_STORE_PATH = './test-speculation-cap';
|
|
21
|
+
function cleanup() {
|
|
22
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
23
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function textBlock(text) {
|
|
27
|
+
return [{ type: 'text', text }];
|
|
28
|
+
}
|
|
29
|
+
class TickCountingStrategy extends AutobiographicalStrategy {
|
|
30
|
+
tickCalls = 0;
|
|
31
|
+
async tick(ctx) {
|
|
32
|
+
this.tickCalls++;
|
|
33
|
+
// Do NOT call super.tick — we don't have a real membrane in tests
|
|
34
|
+
// and we just want to count whether onNewMessage decided to fire.
|
|
35
|
+
}
|
|
36
|
+
seedL1(content) {
|
|
37
|
+
const entry = {
|
|
38
|
+
id: `L1-${this.nextSummaryIdCounter()}`,
|
|
39
|
+
level: 1,
|
|
40
|
+
content,
|
|
41
|
+
tokens: Math.ceil(content.length / 4),
|
|
42
|
+
sourceLevel: 0,
|
|
43
|
+
sourceIds: ['x'],
|
|
44
|
+
sourceRange: { first: 'x', last: 'x' },
|
|
45
|
+
created: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
this.pushSummary(entry);
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
/** True if there's an unprocessed chunk eligible for compression. */
|
|
51
|
+
hasQueuedChunk() {
|
|
52
|
+
return this.compressionQueue.length > 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Add enough small messages to force ≥1 chunk to form in the
|
|
57
|
+
* compressible region. With targetChunkTokens=20 and chunkOnMessageBoundary,
|
|
58
|
+
* 5 messages of ~6 tokens each will close one chunk after 4 msgs.
|
|
59
|
+
*/
|
|
60
|
+
async function setupWithQueuedChunk(strategy) {
|
|
61
|
+
const manager = await ContextManager.open({
|
|
62
|
+
path: TEST_STORE_PATH,
|
|
63
|
+
strategy,
|
|
64
|
+
});
|
|
65
|
+
// recentWindowTokens=5 (small) means only the latest message stays in recent;
|
|
66
|
+
// the rest are compressible and will form chunks once we hit targetChunkTokens.
|
|
67
|
+
// 5 padding messages of ~6 tokens, plus one trailing recent message.
|
|
68
|
+
for (let i = 0; i < 5; i++) {
|
|
69
|
+
manager.addMessage('user', textBlock(`m-${i} hello world`));
|
|
70
|
+
}
|
|
71
|
+
manager.addMessage('user', textBlock('latest one ' + 'X'.repeat(20)));
|
|
72
|
+
// Wait for any onNewMessage handlers to settle
|
|
73
|
+
await new Promise(r => setTimeout(r, 30));
|
|
74
|
+
return manager;
|
|
75
|
+
}
|
|
76
|
+
describe('AutobiographicalStrategy — speculation cap (gap #8)', () => {
|
|
77
|
+
before(cleanup);
|
|
78
|
+
after(cleanup);
|
|
79
|
+
beforeEach(cleanup);
|
|
80
|
+
it('default: auto-tick fires whenever a chunk is queued', async () => {
|
|
81
|
+
const strategy = new TickCountingStrategy({
|
|
82
|
+
headWindowTokens: 0,
|
|
83
|
+
recentWindowTokens: 5,
|
|
84
|
+
targetChunkTokens: 20,
|
|
85
|
+
autoTickOnNewMessage: true,
|
|
86
|
+
});
|
|
87
|
+
const manager = await setupWithQueuedChunk(strategy);
|
|
88
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk should have formed in compressible region');
|
|
89
|
+
assert.ok(strategy.tickCalls >= 1, `default behavior must auto-tick (got ${strategy.tickCalls})`);
|
|
90
|
+
manager.close();
|
|
91
|
+
});
|
|
92
|
+
it('with maxSpeculativeL1s, auto-tick is deferred when at cap', async () => {
|
|
93
|
+
const strategy = new TickCountingStrategy({
|
|
94
|
+
headWindowTokens: 0,
|
|
95
|
+
recentWindowTokens: 5,
|
|
96
|
+
targetChunkTokens: 20,
|
|
97
|
+
autoTickOnNewMessage: true,
|
|
98
|
+
maxSpeculativeL1s: 2,
|
|
99
|
+
});
|
|
100
|
+
const manager = await ContextManager.open({
|
|
101
|
+
path: TEST_STORE_PATH,
|
|
102
|
+
strategy,
|
|
103
|
+
});
|
|
104
|
+
// Seed AFTER open so initialize() doesn't wipe the seeds.
|
|
105
|
+
strategy.seedL1('summary 1');
|
|
106
|
+
strategy.seedL1('summary 2');
|
|
107
|
+
strategy.seedL1('summary 3');
|
|
108
|
+
const ticksBefore = strategy.tickCalls;
|
|
109
|
+
for (let i = 0; i < 5; i++) {
|
|
110
|
+
manager.addMessage('user', textBlock(`m-${i} hello world`));
|
|
111
|
+
}
|
|
112
|
+
manager.addMessage('user', textBlock('latest one ' + 'X'.repeat(20)));
|
|
113
|
+
await new Promise(r => setTimeout(r, 30));
|
|
114
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
|
|
115
|
+
assert.equal(strategy.tickCalls, ticksBefore, 'auto-tick must NOT fire when unmerged L1s already exceed maxSpeculativeL1s');
|
|
116
|
+
manager.close();
|
|
117
|
+
});
|
|
118
|
+
it('cap allows auto-tick when count is under the limit', async () => {
|
|
119
|
+
const strategy = new TickCountingStrategy({
|
|
120
|
+
headWindowTokens: 0,
|
|
121
|
+
recentWindowTokens: 5,
|
|
122
|
+
targetChunkTokens: 20,
|
|
123
|
+
autoTickOnNewMessage: true,
|
|
124
|
+
maxSpeculativeL1s: 5,
|
|
125
|
+
});
|
|
126
|
+
const manager = await ContextManager.open({
|
|
127
|
+
path: TEST_STORE_PATH,
|
|
128
|
+
strategy,
|
|
129
|
+
});
|
|
130
|
+
strategy.seedL1('summary 1');
|
|
131
|
+
const ticksBefore = strategy.tickCalls;
|
|
132
|
+
for (let i = 0; i < 5; i++) {
|
|
133
|
+
manager.addMessage('user', textBlock(`m-${i} hello world`));
|
|
134
|
+
}
|
|
135
|
+
manager.addMessage('user', textBlock('latest one ' + 'X'.repeat(20)));
|
|
136
|
+
await new Promise(r => setTimeout(r, 30));
|
|
137
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
|
|
138
|
+
assert.ok(strategy.tickCalls > ticksBefore, 'auto-tick should fire when under cap');
|
|
139
|
+
manager.close();
|
|
140
|
+
});
|
|
141
|
+
it('shouldCompressPreflight=false defers auto-tick', async () => {
|
|
142
|
+
class DeferAlways extends TickCountingStrategy {
|
|
143
|
+
shouldCompressPreflight() { return false; }
|
|
144
|
+
}
|
|
145
|
+
const strategy = new DeferAlways({
|
|
146
|
+
headWindowTokens: 0,
|
|
147
|
+
recentWindowTokens: 5,
|
|
148
|
+
targetChunkTokens: 20,
|
|
149
|
+
autoTickOnNewMessage: true,
|
|
150
|
+
});
|
|
151
|
+
const manager = await setupWithQueuedChunk(strategy);
|
|
152
|
+
assert.ok(strategy.hasQueuedChunk(), 'sanity: chunk formed');
|
|
153
|
+
assert.equal(strategy.tickCalls, 0, 'preflight=false must block auto-tick');
|
|
154
|
+
manager.close();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=speculation-cap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speculation-cap.test.js","sourceRoot":"","sources":["../../test/speculation-cap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AutobiographicalStrategy persistence + branch fork.
|
|
3
|
+
*
|
|
4
|
+
* Strategy state (summaries, summaryIdCounter, mergeQueue) was previously
|
|
5
|
+
* in-memory only — first process restart wiped the entire L1/L2/L3 pyramid.
|
|
6
|
+
* Forking the underlying chronicle branch silently shared state across
|
|
7
|
+
* branches because nothing was actually branch-scoped.
|
|
8
|
+
*
|
|
9
|
+
* These tests cover:
|
|
10
|
+
* 1. restart-and-resume: state survives close/reopen of the same path.
|
|
11
|
+
* 2. branch fork: a forked branch has its own summary timeline.
|
|
12
|
+
* 3. branch switch: switching back picks up the right branch's state.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=strategy-persistence.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-persistence.test.d.ts","sourceRoot":"","sources":["../../test/strategy-persistence.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|