@animalabs/context-manager 0.4.0 → 0.5.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/adaptive/chunker.d.ts +70 -0
- package/dist/src/adaptive/chunker.d.ts.map +1 -0
- package/dist/src/adaptive/chunker.js +233 -0
- package/dist/src/adaptive/chunker.js.map +1 -0
- package/dist/src/adaptive/folding-strategy.d.ts +118 -0
- package/dist/src/adaptive/folding-strategy.d.ts.map +1 -0
- package/dist/src/adaptive/folding-strategy.js +12 -0
- package/dist/src/adaptive/folding-strategy.js.map +1 -0
- package/dist/src/adaptive/index.d.ts +17 -0
- package/dist/src/adaptive/index.d.ts.map +1 -0
- package/dist/src/adaptive/index.js +23 -0
- package/dist/src/adaptive/index.js.map +1 -0
- package/dist/src/adaptive/picker.d.ts +103 -0
- package/dist/src/adaptive/picker.d.ts.map +1 -0
- package/dist/src/adaptive/picker.js +357 -0
- package/dist/src/adaptive/picker.js.map +1 -0
- package/dist/src/adaptive/render.d.ts +39 -0
- package/dist/src/adaptive/render.d.ts.map +1 -0
- package/dist/src/adaptive/render.js +104 -0
- package/dist/src/adaptive/render.js.map +1 -0
- package/dist/src/adaptive/strategies/flat-profile.d.ts +19 -0
- package/dist/src/adaptive/strategies/flat-profile.d.ts.map +1 -0
- package/dist/src/adaptive/strategies/flat-profile.js +86 -0
- package/dist/src/adaptive/strategies/flat-profile.js.map +1 -0
- package/dist/src/adaptive/strategies/oldest-first.d.ts +19 -0
- package/dist/src/adaptive/strategies/oldest-first.d.ts.map +1 -0
- package/dist/src/adaptive/strategies/oldest-first.js +39 -0
- package/dist/src/adaptive/strategies/oldest-first.js.map +1 -0
- package/dist/src/context-manager.d.ts +5 -0
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +22 -0
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/message-store.d.ts +36 -1
- package/dist/src/message-store.d.ts.map +1 -1
- package/dist/src/message-store.js +134 -8
- package/dist/src/message-store.js.map +1 -1
- package/dist/src/strategies/autobiographical.d.ts +217 -2
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +1479 -37
- package/dist/src/strategies/autobiographical.js.map +1 -1
- package/dist/src/strategies/index.d.ts +1 -1
- package/dist/src/strategies/index.d.ts.map +1 -1
- package/dist/src/strategies/index.js.map +1 -1
- package/dist/src/types/message.d.ts +38 -0
- package/dist/src/types/message.d.ts.map +1 -1
- package/dist/src/types/strategy.d.ts +94 -10
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js +7 -0
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/adaptive/branching.test.d.ts +20 -0
- package/dist/test/adaptive/branching.test.d.ts.map +1 -0
- package/dist/test/adaptive/branching.test.js +176 -0
- package/dist/test/adaptive/branching.test.js.map +1 -0
- package/dist/test/adaptive/chunker.test.d.ts +2 -0
- package/dist/test/adaptive/chunker.test.d.ts.map +1 -0
- package/dist/test/adaptive/chunker.test.js +120 -0
- package/dist/test/adaptive/chunker.test.js.map +1 -0
- package/dist/test/adaptive/deep-levels.test.d.ts +10 -0
- package/dist/test/adaptive/deep-levels.test.d.ts.map +1 -0
- package/dist/test/adaptive/deep-levels.test.js +167 -0
- package/dist/test/adaptive/deep-levels.test.js.map +1 -0
- package/dist/test/adaptive/doc-plus-chat.test.d.ts +10 -0
- package/dist/test/adaptive/doc-plus-chat.test.d.ts.map +1 -0
- package/dist/test/adaptive/doc-plus-chat.test.js +194 -0
- package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -0
- package/dist/test/adaptive/hard-fail.test.d.ts +9 -0
- package/dist/test/adaptive/hard-fail.test.d.ts.map +1 -0
- package/dist/test/adaptive/hard-fail.test.js +154 -0
- package/dist/test/adaptive/hard-fail.test.js.map +1 -0
- package/dist/test/adaptive/harness.d.ts +109 -0
- package/dist/test/adaptive/harness.d.ts.map +1 -0
- package/dist/test/adaptive/harness.js +263 -0
- package/dist/test/adaptive/harness.js.map +1 -0
- package/dist/test/adaptive/ingestion.test.d.ts +13 -0
- package/dist/test/adaptive/ingestion.test.d.ts.map +1 -0
- package/dist/test/adaptive/ingestion.test.js +306 -0
- package/dist/test/adaptive/ingestion.test.js.map +1 -0
- package/dist/test/adaptive/integration.test.d.ts +12 -0
- package/dist/test/adaptive/integration.test.d.ts.map +1 -0
- package/dist/test/adaptive/integration.test.js +247 -0
- package/dist/test/adaptive/integration.test.js.map +1 -0
- package/dist/test/adaptive/long-chronicle.test.d.ts +13 -0
- package/dist/test/adaptive/long-chronicle.test.d.ts.map +1 -0
- package/dist/test/adaptive/long-chronicle.test.js +186 -0
- package/dist/test/adaptive/long-chronicle.test.js.map +1 -0
- package/dist/test/adaptive/persistence.test.d.ts +11 -0
- package/dist/test/adaptive/persistence.test.d.ts.map +1 -0
- package/dist/test/adaptive/persistence.test.js +200 -0
- package/dist/test/adaptive/persistence.test.js.map +1 -0
- package/dist/test/adaptive/picker.test.d.ts +2 -0
- package/dist/test/adaptive/picker.test.d.ts.map +1 -0
- package/dist/test/adaptive/picker.test.js +333 -0
- package/dist/test/adaptive/picker.test.js.map +1 -0
- package/dist/test/adaptive/render.test.d.ts +2 -0
- package/dist/test/adaptive/render.test.d.ts.map +1 -0
- package/dist/test/adaptive/render.test.js +109 -0
- package/dist/test/adaptive/render.test.js.map +1 -0
- package/dist/test/adaptive/shard-immutability.test.d.ts +14 -0
- package/dist/test/adaptive/shard-immutability.test.d.ts.map +1 -0
- package/dist/test/adaptive/shard-immutability.test.js +184 -0
- package/dist/test/adaptive/shard-immutability.test.js.map +1 -0
- package/dist/test/adaptive/strategy-integration.test.d.ts +9 -0
- package/dist/test/adaptive/strategy-integration.test.d.ts.map +1 -0
- package/dist/test/adaptive/strategy-integration.test.js +293 -0
- package/dist/test/adaptive/strategy-integration.test.js.map +1 -0
- package/dist/test/message-store-sequence.test.d.ts +25 -0
- package/dist/test/message-store-sequence.test.d.ts.map +1 -0
- package/dist/test/message-store-sequence.test.js +157 -0
- package/dist/test/message-store-sequence.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/adaptive/chunker.ts +323 -0
- package/src/adaptive/folding-strategy.ts +129 -0
- package/src/adaptive/index.ts +51 -0
- package/src/adaptive/picker.ts +442 -0
- package/src/adaptive/render.ts +115 -0
- package/src/adaptive/strategies/flat-profile.ts +96 -0
- package/src/adaptive/strategies/oldest-first.ts +48 -0
- package/src/context-manager.ts +32 -0
- package/src/index.ts +1 -1
- package/src/message-store.ts +147 -8
- package/src/strategies/autobiographical.ts +1586 -38
- package/src/strategies/index.ts +1 -1
- package/src/types/message.ts +43 -0
- package/src/types/strategy.ts +109 -10
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realistic Lena-migration scenario: a chronicle that starts with a large doc,
|
|
3
|
+
* then has many turns of chat about/with that doc.
|
|
4
|
+
*
|
|
5
|
+
* Verifies the doc gets sharded at ingestion, the chat turns coexist with
|
|
6
|
+
* the doc shards, the picker handles both correctly, and the compiled
|
|
7
|
+
* output structure makes sense throughout.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
12
|
+
import { ContextManager, AutobiographicalStrategy } from '../../src/index.js';
|
|
13
|
+
const TEST_STORE_PATH = './test-adaptive-doc-plus-chat-store';
|
|
14
|
+
function cleanup() {
|
|
15
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
16
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function makeMockMembrane() {
|
|
20
|
+
let callCount = 0;
|
|
21
|
+
return {
|
|
22
|
+
complete: async () => {
|
|
23
|
+
callCount++;
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: 'text', text: `[mock summary #${callCount}] short content.` }],
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
get callCount() {
|
|
29
|
+
return callCount;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function compiledTokens(compiled) {
|
|
34
|
+
let total = 0;
|
|
35
|
+
for (const m of compiled.messages) {
|
|
36
|
+
for (const block of m.content) {
|
|
37
|
+
if (block.type === 'text')
|
|
38
|
+
total += Math.ceil(block.text.length / 4);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return total;
|
|
42
|
+
}
|
|
43
|
+
describe('AutobiographicalStrategy — doc + chat workload (Lena-like)', () => {
|
|
44
|
+
before(() => cleanup());
|
|
45
|
+
after(() => cleanup());
|
|
46
|
+
beforeEach(() => cleanup());
|
|
47
|
+
it('500K-equivalent doc + 50 chat turns: budget honored throughout', async () => {
|
|
48
|
+
const mock = makeMockMembrane();
|
|
49
|
+
const BUDGET = 30000;
|
|
50
|
+
const strategy = new AutobiographicalStrategy({
|
|
51
|
+
adaptiveResolution: true,
|
|
52
|
+
targetChunkTokens: 1000, // ~4K chars per shard
|
|
53
|
+
recentWindowTokens: 2000,
|
|
54
|
+
mergeThreshold: 6,
|
|
55
|
+
compressionSlackRatio: 0.1,
|
|
56
|
+
});
|
|
57
|
+
const manager = await ContextManager.open({
|
|
58
|
+
path: TEST_STORE_PATH,
|
|
59
|
+
strategy,
|
|
60
|
+
membrane: mock,
|
|
61
|
+
});
|
|
62
|
+
// Synthetic ~500K-equivalent doc. Use 50K chars ≈ 12.5K tokens for test
|
|
63
|
+
// speed; chunker treats ~1K tokens per shard. ~12 shards.
|
|
64
|
+
const sections = [];
|
|
65
|
+
for (let s = 0; s < 12; s++) {
|
|
66
|
+
sections.push(`# Section ${s}\n\n` + 'paragraph content with some words. '.repeat(40));
|
|
67
|
+
}
|
|
68
|
+
const doc = sections.join('\n\n');
|
|
69
|
+
manager.addMessage('User', [{ type: 'text', text: doc }]);
|
|
70
|
+
// Verify sharding happened at ingestion
|
|
71
|
+
const allMessages = manager.getAllMessages();
|
|
72
|
+
assert.ok(allMessages.length > 1, `expected doc to shard; got ${allMessages.length} messages`);
|
|
73
|
+
const shardCount = allMessages.length;
|
|
74
|
+
const groupId = allMessages[0].bodyGroupId;
|
|
75
|
+
assert.ok(groupId, 'first shard should have bodyGroupId');
|
|
76
|
+
for (const m of allMessages) {
|
|
77
|
+
assert.equal(m.bodyGroupId, groupId, 'all shards share bodyGroupId');
|
|
78
|
+
}
|
|
79
|
+
while (!manager.isReady()) {
|
|
80
|
+
await manager.tick();
|
|
81
|
+
}
|
|
82
|
+
// First compile after doc — verify budget honored
|
|
83
|
+
const compiledAfterDoc = await manager.compile({ maxTokens: BUDGET, reserveForResponse: 1000 });
|
|
84
|
+
assert.ok(compiledTokens(compiledAfterDoc) <= BUDGET - 1000, `after-doc tokens exceed budget: ${compiledTokens(compiledAfterDoc)}`);
|
|
85
|
+
// Now 50 chat turns about the doc
|
|
86
|
+
for (let i = 0; i < 50; i++) {
|
|
87
|
+
manager.addMessage('User', [{ type: 'text', text: `Question ${i} about section ${i % 12}: ` + 'word '.repeat(20) }]);
|
|
88
|
+
manager.addMessage('Claude', [{ type: 'text', text: `Answer ${i}: ` + 'word '.repeat(25) }]);
|
|
89
|
+
while (!manager.isReady()) {
|
|
90
|
+
await manager.tick();
|
|
91
|
+
}
|
|
92
|
+
const compiled = await manager.compile({ maxTokens: BUDGET, reserveForResponse: 1000 });
|
|
93
|
+
const tokens = compiledTokens(compiled);
|
|
94
|
+
assert.ok(tokens <= BUDGET - 1000, `turn ${i}: ${tokens} tokens exceeds budget ${BUDGET - 1000}`);
|
|
95
|
+
}
|
|
96
|
+
// The compiled output should still reflect the doc somewhere
|
|
97
|
+
const finalCompile = await manager.compile({ maxTokens: BUDGET, reserveForResponse: 1000 });
|
|
98
|
+
const finalText = finalCompile.messages
|
|
99
|
+
.flatMap((m) => m.content)
|
|
100
|
+
.filter((b) => b.type === 'text')
|
|
101
|
+
.map((b) => b.text)
|
|
102
|
+
.join('\n');
|
|
103
|
+
// Either the doc is still raw-rendered, OR a summary referring to it appears
|
|
104
|
+
assert.ok(finalText.includes('Section') || finalText.includes('summary'), 'final compile should reference doc content via either raw or summary');
|
|
105
|
+
manager.close();
|
|
106
|
+
});
|
|
107
|
+
it('agent can lock a critical doc section and it survives folding pressure', async () => {
|
|
108
|
+
const mock = makeMockMembrane();
|
|
109
|
+
const strategy = new AutobiographicalStrategy({
|
|
110
|
+
adaptiveResolution: true,
|
|
111
|
+
targetChunkTokens: 500,
|
|
112
|
+
recentWindowTokens: 500,
|
|
113
|
+
});
|
|
114
|
+
const manager = await ContextManager.open({
|
|
115
|
+
path: TEST_STORE_PATH,
|
|
116
|
+
strategy,
|
|
117
|
+
membrane: mock,
|
|
118
|
+
});
|
|
119
|
+
// Doc with distinct sections — big enough to shard at targetChunkTokens=500
|
|
120
|
+
// (chunker threshold = 2x targetChunkTokens = 1000 tokens; we need ~10 sections
|
|
121
|
+
// × 200 tokens = 2000+ tokens to trigger sharding).
|
|
122
|
+
const sections = Array.from({ length: 10 }, (_, i) => `# Section ${i}\n\nCRITICAL-MARKER-${i} ` + 'distinctive word here. '.repeat(40));
|
|
123
|
+
manager.addMessage('User', [{ type: 'text', text: sections.join('\n\n') }]);
|
|
124
|
+
const shards = manager.getAllMessages();
|
|
125
|
+
assert.ok(shards.length > 1, `doc should shard; got ${shards.length} messages`);
|
|
126
|
+
// Lock shard 3 specifically
|
|
127
|
+
const criticalShard = shards[3];
|
|
128
|
+
strategy.lockChunk(criticalShard.id);
|
|
129
|
+
// Add chat to push pressure
|
|
130
|
+
for (let i = 0; i < 30; i++) {
|
|
131
|
+
manager.addMessage('User', [{ type: 'text', text: `T${i}` }]);
|
|
132
|
+
}
|
|
133
|
+
while (!manager.isReady()) {
|
|
134
|
+
await manager.tick();
|
|
135
|
+
}
|
|
136
|
+
// Tight compile
|
|
137
|
+
const compiled = await manager.compile({ maxTokens: 3500, reserveForResponse: 500 });
|
|
138
|
+
// The locked shard's resolution should remain 0 in strategy.resolutions
|
|
139
|
+
const resolutions = strategy.resolutions;
|
|
140
|
+
const lockedRes = resolutions.get(criticalShard.id) ?? 0;
|
|
141
|
+
assert.equal(lockedRes, 0, `locked shard should remain L0, got L${lockedRes}`);
|
|
142
|
+
// The compiled output should contain the locked shard's content verbatim
|
|
143
|
+
// (or at least the CRITICAL-MARKER-3 substring from its raw text).
|
|
144
|
+
const allText = compiled.messages
|
|
145
|
+
.flatMap((m) => m.content)
|
|
146
|
+
.filter((b) => b.type === 'text')
|
|
147
|
+
.map((b) => b.text)
|
|
148
|
+
.join('\n');
|
|
149
|
+
assert.ok(allText.includes('CRITICAL-MARKER-3'), 'compiled output should contain the locked shard\'s content verbatim');
|
|
150
|
+
manager.close();
|
|
151
|
+
});
|
|
152
|
+
it('doc + many turns: shards in different regions render correctly', async () => {
|
|
153
|
+
const mock = makeMockMembrane();
|
|
154
|
+
const strategy = new AutobiographicalStrategy({
|
|
155
|
+
adaptiveResolution: true,
|
|
156
|
+
targetChunkTokens: 400,
|
|
157
|
+
recentWindowTokens: 800,
|
|
158
|
+
mergeThreshold: 3,
|
|
159
|
+
});
|
|
160
|
+
const manager = await ContextManager.open({
|
|
161
|
+
path: TEST_STORE_PATH,
|
|
162
|
+
strategy,
|
|
163
|
+
membrane: mock,
|
|
164
|
+
});
|
|
165
|
+
// Doc → some shards in middle, some in tail
|
|
166
|
+
const doc = Array.from({ length: 15 }, (_, i) => `Section ${i}: ` + 'word '.repeat(60)).join('\n\n');
|
|
167
|
+
manager.addMessage('User', [{ type: 'text', text: doc }]);
|
|
168
|
+
// Trailing chat so the doc isn't all-tail
|
|
169
|
+
for (let i = 0; i < 8; i++) {
|
|
170
|
+
manager.addMessage('User', [{ type: 'text', text: `Followup ${i}` }]);
|
|
171
|
+
}
|
|
172
|
+
while (!manager.isReady()) {
|
|
173
|
+
await manager.tick();
|
|
174
|
+
}
|
|
175
|
+
const compiled = await manager.compile({ maxTokens: 5000, reserveForResponse: 500 });
|
|
176
|
+
// The compile should have produced output containing both doc content
|
|
177
|
+
// (some sections) and chat content (followups). The doc should be a
|
|
178
|
+
// combined entry (or entries) representing its shards.
|
|
179
|
+
const userMessages = compiled.messages.filter((m) => m.participant.toLowerCase() === 'user');
|
|
180
|
+
assert.ok(userMessages.length >= 2, 'should have multiple user messages (doc + chat)');
|
|
181
|
+
// Find a user message containing "Section" (from the doc)
|
|
182
|
+
const docEntry = userMessages.find((m) => m.content
|
|
183
|
+
.filter((b) => b.type === 'text')
|
|
184
|
+
.some((b) => b.text.includes('Section ')));
|
|
185
|
+
assert.ok(docEntry, 'should have a user entry with doc section content');
|
|
186
|
+
// Find a user message containing "Followup" (from chat)
|
|
187
|
+
const chatEntry = userMessages.find((m) => m.content
|
|
188
|
+
.filter((b) => b.type === 'text')
|
|
189
|
+
.some((b) => b.text.includes('Followup')));
|
|
190
|
+
assert.ok(chatEntry, 'should have a user entry with chat followup content');
|
|
191
|
+
manager.close();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
//# sourceMappingURL=doc-plus-chat.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-plus-chat.test.js","sourceRoot":"","sources":["../../../test/adaptive/doc-plus-chat.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that selectAdaptive throws OverBudgetError when the picker has
|
|
3
|
+
* folded everything it can but the result still exceeds the hard budget.
|
|
4
|
+
*
|
|
5
|
+
* The strategy's philosophy: surface the overage rather than silently
|
|
6
|
+
* dropping entries. The host decides how to recover.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=hard-fail.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hard-fail.test.d.ts","sourceRoot":"","sources":["../../../test/adaptive/hard-fail.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that selectAdaptive throws OverBudgetError when the picker has
|
|
3
|
+
* folded everything it can but the result still exceeds the hard budget.
|
|
4
|
+
*
|
|
5
|
+
* The strategy's philosophy: surface the overage rather than silently
|
|
6
|
+
* dropping entries. The host decides how to recover.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
11
|
+
import { ContextManager, AutobiographicalStrategy } from '../../src/index.js';
|
|
12
|
+
import { OverBudgetError } from '../../src/adaptive/index.js';
|
|
13
|
+
const TEST_STORE_PATH = './test-adaptive-hard-fail-store';
|
|
14
|
+
function cleanup() {
|
|
15
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
16
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function makeMockMembrane() {
|
|
20
|
+
let callCount = 0;
|
|
21
|
+
return {
|
|
22
|
+
complete: async () => {
|
|
23
|
+
callCount++;
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: 'text', text: `[mock summary #${callCount}]` }],
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
get callCount() {
|
|
29
|
+
return callCount;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
describe('AutobiographicalStrategy — adaptive OverBudgetError', () => {
|
|
34
|
+
before(() => cleanup());
|
|
35
|
+
after(() => cleanup());
|
|
36
|
+
beforeEach(() => cleanup());
|
|
37
|
+
it('throws OverBudgetError when tail alone exceeds budget', async () => {
|
|
38
|
+
const mock = makeMockMembrane();
|
|
39
|
+
const strategy = new AutobiographicalStrategy({
|
|
40
|
+
adaptiveResolution: true,
|
|
41
|
+
targetChunkTokens: 200,
|
|
42
|
+
// Large tail window: everything we add will be tail-resident, NOT
|
|
43
|
+
// foldable. The picker has nothing to do with it.
|
|
44
|
+
recentWindowTokens: 100_000,
|
|
45
|
+
});
|
|
46
|
+
const manager = await ContextManager.open({
|
|
47
|
+
path: TEST_STORE_PATH,
|
|
48
|
+
strategy,
|
|
49
|
+
membrane: mock,
|
|
50
|
+
});
|
|
51
|
+
// Add enough content that the tail alone is huge.
|
|
52
|
+
for (let i = 0; i < 30; i++) {
|
|
53
|
+
manager.addMessage('User', [{ type: 'text', text: `Msg ${i}. ` + 'word '.repeat(80) }]);
|
|
54
|
+
}
|
|
55
|
+
while (!manager.isReady()) {
|
|
56
|
+
await manager.tick();
|
|
57
|
+
}
|
|
58
|
+
// Budget so tight that even the tail can't fit. Picker has nothing it
|
|
59
|
+
// can fold (tail is treated as pinned).
|
|
60
|
+
await assert.rejects(() => manager.compile({ maxTokens: 500, reserveForResponse: 100 }), (err) => {
|
|
61
|
+
assert.ok(err instanceof OverBudgetError, `expected OverBudgetError, got ${err?.constructor?.name}`);
|
|
62
|
+
const e = err;
|
|
63
|
+
assert.ok(e.actual > e.budget, `actual ${e.actual} should exceed budget ${e.budget}`);
|
|
64
|
+
assert.ok(e.diagnostics.tailTokens > 0, 'diagnostics should include tail tokens');
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
manager.close();
|
|
68
|
+
});
|
|
69
|
+
it('does NOT throw when picker can fold its way under budget', async () => {
|
|
70
|
+
const mock = makeMockMembrane();
|
|
71
|
+
const strategy = new AutobiographicalStrategy({
|
|
72
|
+
adaptiveResolution: true,
|
|
73
|
+
targetChunkTokens: 100,
|
|
74
|
+
recentWindowTokens: 200,
|
|
75
|
+
});
|
|
76
|
+
const manager = await ContextManager.open({
|
|
77
|
+
path: TEST_STORE_PATH,
|
|
78
|
+
strategy,
|
|
79
|
+
membrane: mock,
|
|
80
|
+
});
|
|
81
|
+
for (let i = 0; i < 30; i++) {
|
|
82
|
+
manager.addMessage('User', [{ type: 'text', text: `Msg ${i}. ` + 'word '.repeat(40) }]);
|
|
83
|
+
}
|
|
84
|
+
while (!manager.isReady()) {
|
|
85
|
+
await manager.tick();
|
|
86
|
+
}
|
|
87
|
+
// Tight but reachable budget — picker should fold middle content to fit.
|
|
88
|
+
const compiled = await manager.compile({ maxTokens: 3000, reserveForResponse: 200 });
|
|
89
|
+
assert.ok(compiled.messages.length > 0);
|
|
90
|
+
manager.close();
|
|
91
|
+
});
|
|
92
|
+
it('does NOT throw under generous budget', async () => {
|
|
93
|
+
const mock = makeMockMembrane();
|
|
94
|
+
const strategy = new AutobiographicalStrategy({
|
|
95
|
+
adaptiveResolution: true,
|
|
96
|
+
targetChunkTokens: 100,
|
|
97
|
+
recentWindowTokens: 200,
|
|
98
|
+
});
|
|
99
|
+
const manager = await ContextManager.open({
|
|
100
|
+
path: TEST_STORE_PATH,
|
|
101
|
+
strategy,
|
|
102
|
+
membrane: mock,
|
|
103
|
+
});
|
|
104
|
+
for (let i = 0; i < 10; i++) {
|
|
105
|
+
manager.addMessage('User', [{ type: 'text', text: `Msg ${i}` }]);
|
|
106
|
+
}
|
|
107
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 200 });
|
|
108
|
+
assert.ok(compiled.messages.length > 0);
|
|
109
|
+
manager.close();
|
|
110
|
+
});
|
|
111
|
+
it('error diagnostics include useful state info', async () => {
|
|
112
|
+
const mock = makeMockMembrane();
|
|
113
|
+
const strategy = new AutobiographicalStrategy({
|
|
114
|
+
adaptiveResolution: true,
|
|
115
|
+
targetChunkTokens: 200,
|
|
116
|
+
recentWindowTokens: 100_000,
|
|
117
|
+
});
|
|
118
|
+
const manager = await ContextManager.open({
|
|
119
|
+
path: TEST_STORE_PATH,
|
|
120
|
+
strategy,
|
|
121
|
+
membrane: mock,
|
|
122
|
+
});
|
|
123
|
+
for (let i = 0; i < 20; i++) {
|
|
124
|
+
manager.addMessage('User', [{ type: 'text', text: `Msg ${i}. ` + 'word '.repeat(80) }]);
|
|
125
|
+
}
|
|
126
|
+
while (!manager.isReady()) {
|
|
127
|
+
await manager.tick();
|
|
128
|
+
}
|
|
129
|
+
let caught = null;
|
|
130
|
+
try {
|
|
131
|
+
await manager.compile({ maxTokens: 400, reserveForResponse: 100 });
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err instanceof OverBudgetError)
|
|
135
|
+
caught = err;
|
|
136
|
+
}
|
|
137
|
+
assert.ok(caught, 'should have caught OverBudgetError');
|
|
138
|
+
if (!caught) {
|
|
139
|
+
manager.close();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
assert.equal(typeof caught.budget, 'number');
|
|
143
|
+
assert.equal(typeof caught.actual, 'number');
|
|
144
|
+
assert.equal(typeof caught.diagnostics.headTokens, 'number');
|
|
145
|
+
assert.equal(typeof caught.diagnostics.tailTokens, 'number');
|
|
146
|
+
assert.equal(typeof caught.diagnostics.middleTokens, 'number');
|
|
147
|
+
assert.equal(typeof caught.diagnostics.deepestLevel, 'number');
|
|
148
|
+
// The error message should be informative
|
|
149
|
+
assert.ok(caught.message.includes('exhausted'));
|
|
150
|
+
assert.ok(caught.message.includes('hard budget'));
|
|
151
|
+
manager.close();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=hard-fail.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hard-fail.test.js","sourceRoot":"","sources":["../../../test/adaptive/hard-fail.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthetic test harness for the adaptive-resolution picker.
|
|
3
|
+
*
|
|
4
|
+
* Builds a minimal in-memory chronicle simulator with:
|
|
5
|
+
* - Chunks (PickerChunk shape) with deterministic token counts.
|
|
6
|
+
* - A mocked LLM that produces deterministic "summaries" by hashing input.
|
|
7
|
+
* - A summary tree that grows as the LLM is called.
|
|
8
|
+
* - A function to run the picker against a built state.
|
|
9
|
+
*
|
|
10
|
+
* The mock LLM doesn't actually call any API — it constructs fake summary
|
|
11
|
+
* content so we can validate picker logic, fold semantics, and cache stickiness
|
|
12
|
+
* without external dependencies.
|
|
13
|
+
*/
|
|
14
|
+
import type { SummaryEntry } from '../../src/types/strategy.js';
|
|
15
|
+
import type { PickerChunk } from '../../src/adaptive/picker.js';
|
|
16
|
+
import type { ChunkId, SummaryId } from '../../src/adaptive/folding-strategy.js';
|
|
17
|
+
/**
|
|
18
|
+
* Default tokens per recall pair, regardless of source level.
|
|
19
|
+
* The design specifies that L_k summaries target a constant size (~2K tokens
|
|
20
|
+
* by convention) so each level's recall pair has predictable footprint.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_RECALL_PAIR_TOKENS = 200;
|
|
23
|
+
/** Default group size: how many L_k items combine into one L_{k+1}. */
|
|
24
|
+
export declare const DEFAULT_MERGE_THRESHOLD = 6;
|
|
25
|
+
export interface MockChronicleOptions {
|
|
26
|
+
/** Tokens for each recall pair. */
|
|
27
|
+
recallPairTokens?: number;
|
|
28
|
+
/** L_k items per L_{k+1} parent. */
|
|
29
|
+
mergeThreshold?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Mutable in-memory chronicle. Mirrors what the real strategy would manage
|
|
33
|
+
* but trimmed to just what the picker needs.
|
|
34
|
+
*/
|
|
35
|
+
export declare class MockChronicle {
|
|
36
|
+
readonly chunks: PickerChunk[];
|
|
37
|
+
readonly summaries: Map<string, SummaryEntry>;
|
|
38
|
+
readonly recallPairTokens: Map<string, number>;
|
|
39
|
+
private summaryCounter;
|
|
40
|
+
readonly mergeThreshold: number;
|
|
41
|
+
readonly defaultRecallPairTokens: number;
|
|
42
|
+
/** Mock LLM call count, for telemetry assertions. */
|
|
43
|
+
llmCalls: number;
|
|
44
|
+
constructor(opts?: MockChronicleOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Add a chunk to the chronicle (e.g., a chat message or a doc shard).
|
|
47
|
+
*
|
|
48
|
+
* @returns the created PickerChunk
|
|
49
|
+
*/
|
|
50
|
+
addChunk(opts: {
|
|
51
|
+
id: ChunkId;
|
|
52
|
+
rawTokens: number;
|
|
53
|
+
bodyGroupId?: string;
|
|
54
|
+
pinned?: boolean;
|
|
55
|
+
lockedByAgent?: boolean;
|
|
56
|
+
}): PickerChunk;
|
|
57
|
+
/**
|
|
58
|
+
* Produce an L1 summary for a range of chunks. Mocks the LLM call.
|
|
59
|
+
* Idempotent: the same chunk range produces the same summary id.
|
|
60
|
+
*
|
|
61
|
+
* @returns the produced SummaryEntry (or the existing one if already produced)
|
|
62
|
+
*/
|
|
63
|
+
produceL1(chunkIds: ChunkId[]): SummaryEntry;
|
|
64
|
+
/**
|
|
65
|
+
* Produce an L_{k+1} summary from N L_k summaries.
|
|
66
|
+
*/
|
|
67
|
+
produceUpper(level: number, sourceSummaryIds: SummaryId[]): SummaryEntry;
|
|
68
|
+
/**
|
|
69
|
+
* Speculative pre-producer: when N siblings exist sharing the same potential
|
|
70
|
+
* parent at level k+1, eagerly produce L_{k+1}. Bottom-up, idempotent.
|
|
71
|
+
* Mirrors the design's background pre-producer (§7 #2 settled decisions).
|
|
72
|
+
*/
|
|
73
|
+
preProduceUpperLevels(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Build a head/tail bookkeeping from chunk indices.
|
|
76
|
+
*/
|
|
77
|
+
computeHeadTail(opts: {
|
|
78
|
+
headTokens: number;
|
|
79
|
+
tailTokens: number;
|
|
80
|
+
}): {
|
|
81
|
+
headTokens: number;
|
|
82
|
+
tailTokens: number;
|
|
83
|
+
headChunkIds: Set<ChunkId>;
|
|
84
|
+
tailChunkIds: Set<ChunkId>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Apply a snapshot of resolutions back onto the chunks (used after running
|
|
88
|
+
* the picker to commit the new state for subsequent turns).
|
|
89
|
+
*/
|
|
90
|
+
applyResolutions(resolutions: ReadonlyMap<ChunkId, number>): void;
|
|
91
|
+
private linkChunksToL1;
|
|
92
|
+
private allocSummaryId;
|
|
93
|
+
private firstChunkSeq;
|
|
94
|
+
private sumTokens;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Convenience: build N chunks with uniform token size, then auto-produce
|
|
98
|
+
* the full summary chain (L1, L2, …) bottom-up via the mergeThreshold rule.
|
|
99
|
+
*
|
|
100
|
+
* Returns the populated MockChronicle.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildChronicleWithChain(opts: {
|
|
103
|
+
chunkCount: number;
|
|
104
|
+
tokensPerChunk: number;
|
|
105
|
+
mergeThreshold?: number;
|
|
106
|
+
recallPairTokens?: number;
|
|
107
|
+
produceChain?: boolean;
|
|
108
|
+
}): MockChronicle;
|
|
109
|
+
//# sourceMappingURL=harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../../test/adaptive/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,oBAAoB;IACnC,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,CAAM;IACpC,QAAQ,CAAC,SAAS,4BAAsC;IACxD,QAAQ,CAAC,gBAAgB,sBAAgC;IACzD,OAAO,CAAC,cAAc,CAA6B;IAEnD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IAEzC,qDAAqD;IACrD,QAAQ,SAAK;gBAED,IAAI,GAAE,oBAAyB;IAK3C;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE;QACb,EAAE,EAAE,OAAO,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,GAAG,WAAW;IAcf;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY;IA2B5C;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,GAAG,YAAY;IA2CxE;;;;OAIG;IACH,qBAAqB,IAAI,IAAI;IA8B7B;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG;QACF,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;KAC5B;IA4BD;;;OAGG;IACH,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI;IASjE,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,SAAS;CAKlB;AAMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,aAAa,CAmBhB"}
|