@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
package/src/types/index.ts
CHANGED
|
@@ -37,6 +37,19 @@ export type {
|
|
|
37
37
|
PhaseType,
|
|
38
38
|
KnowledgeConfig,
|
|
39
39
|
ResettableStrategy,
|
|
40
|
+
ProtectedRange,
|
|
41
|
+
SearchQuery,
|
|
42
|
+
SearchResult,
|
|
43
|
+
SearchableStrategy,
|
|
44
|
+
PinnableStrategy,
|
|
45
|
+
RenderStats,
|
|
46
|
+
RenderStatsCapableStrategy,
|
|
40
47
|
} from './strategy.js';
|
|
41
48
|
|
|
42
|
-
export {
|
|
49
|
+
export {
|
|
50
|
+
DEFAULT_AUTOBIOGRAPHICAL_CONFIG,
|
|
51
|
+
isResettableStrategy,
|
|
52
|
+
isPinnableStrategy,
|
|
53
|
+
isSearchableStrategy,
|
|
54
|
+
isRenderStatsCapable,
|
|
55
|
+
} from './strategy.js';
|
package/src/types/strategy.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { JsStore } from '@animalabs/chronicle';
|
|
1
2
|
import type { Membrane } from '@animalabs/membrane';
|
|
2
3
|
import type { StoredMessage, MessageId, Sequence } from './message.js';
|
|
3
4
|
import type { ContextEntry, TokenBudget, PendingWork } from './context.js';
|
|
@@ -48,6 +49,22 @@ export interface StrategyContext {
|
|
|
48
49
|
membrane?: Membrane;
|
|
49
50
|
/** Current sequence number */
|
|
50
51
|
currentSequence: Sequence;
|
|
52
|
+
/**
|
|
53
|
+
* Underlying Chronicle store. Strategies may register their own state slots
|
|
54
|
+
* (via `store.registerState`) for durable strategy state that needs to
|
|
55
|
+
* survive process restart and follow Chronicle branches.
|
|
56
|
+
*
|
|
57
|
+
* State IDs should be scoped under `namespace` to avoid collisions with
|
|
58
|
+
* other strategies or with the message/context-log states.
|
|
59
|
+
*/
|
|
60
|
+
store: JsStore;
|
|
61
|
+
/**
|
|
62
|
+
* Namespace under which this strategy should scope its state IDs.
|
|
63
|
+
* Use as a prefix: e.g. `${namespace}/autobio:summaries`. Always defined;
|
|
64
|
+
* defaults to a stable per-manager value when no caller-supplied namespace
|
|
65
|
+
* exists, so strategies never need to handle the unscoped case.
|
|
66
|
+
*/
|
|
67
|
+
namespace: string;
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
/**
|
|
@@ -128,6 +145,109 @@ export function isResettableStrategy(s: ContextStrategy): s is ResettableStrateg
|
|
|
128
145
|
return 'resetHeadWindow' in s && typeof (s as ResettableStrategy).resetHeadWindow === 'function';
|
|
129
146
|
}
|
|
130
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Strategy that supports protected ranges (pins + documents).
|
|
150
|
+
* Pinned ranges are excluded from compression and render raw at their
|
|
151
|
+
* original chronological position. Implemented by AutobiographicalStrategy.
|
|
152
|
+
*/
|
|
153
|
+
export interface PinnableStrategy extends ContextStrategy {
|
|
154
|
+
pinRange(firstMessageId: string, lastMessageId: string, opts?: { name?: string }): string;
|
|
155
|
+
markDocument(messageId: string, opts?: { name?: string }): string;
|
|
156
|
+
unpin(pinId: string): boolean;
|
|
157
|
+
listPins(): ReadonlyArray<ProtectedRange>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Type guard for strategies that support pins / documents. */
|
|
161
|
+
export function isPinnableStrategy(s: ContextStrategy): s is PinnableStrategy {
|
|
162
|
+
return (
|
|
163
|
+
'pinRange' in s &&
|
|
164
|
+
typeof (s as PinnableStrategy).pinRange === 'function' &&
|
|
165
|
+
'unpin' in s &&
|
|
166
|
+
typeof (s as PinnableStrategy).unpin === 'function'
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Query for `searchSummaries`. At least one of `text` or `regex` should be
|
|
172
|
+
* provided to constrain results; otherwise all summaries pass.
|
|
173
|
+
*/
|
|
174
|
+
export interface SearchQuery {
|
|
175
|
+
/** Case-insensitive substring match against summary content. */
|
|
176
|
+
text?: string;
|
|
177
|
+
/** Regex match against summary content (overrides `text` if both set). */
|
|
178
|
+
regex?: RegExp;
|
|
179
|
+
/** Filter by summary level(s). Default: all levels. */
|
|
180
|
+
levels?: SummaryLevel[];
|
|
181
|
+
/** Maximum number of results to return. Default: 50. */
|
|
182
|
+
limit?: number;
|
|
183
|
+
/**
|
|
184
|
+
* Include summaries that have been merged into a higher-level summary.
|
|
185
|
+
* Default: false (only "live" unmerged summaries are returned).
|
|
186
|
+
*/
|
|
187
|
+
includeMerged?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Result of a single search match. */
|
|
191
|
+
export interface SearchResult {
|
|
192
|
+
summary: SummaryEntry;
|
|
193
|
+
/** Number of times the query pattern matched in the summary content. */
|
|
194
|
+
matches: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Strategy that supports searching its summary archive. Implemented by
|
|
199
|
+
* AutobiographicalStrategy.
|
|
200
|
+
*/
|
|
201
|
+
export interface SearchableStrategy extends ContextStrategy {
|
|
202
|
+
searchSummaries(query: SearchQuery): SearchResult[];
|
|
203
|
+
getSummary(id: string): SummaryEntry | null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Type guard for strategies that support search. */
|
|
207
|
+
export function isSearchableStrategy(s: ContextStrategy): s is SearchableStrategy {
|
|
208
|
+
return (
|
|
209
|
+
'searchSummaries' in s &&
|
|
210
|
+
typeof (s as SearchableStrategy).searchSummaries === 'function' &&
|
|
211
|
+
'getSummary' in s &&
|
|
212
|
+
typeof (s as SearchableStrategy).getSummary === 'function'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Per-render observability stats from a strategy. Counts and token sums for
|
|
218
|
+
* head / tail / summaries / pending work, suitable for TUIs and dashboards
|
|
219
|
+
* that want to display "how much of the context is folded vs raw" at a
|
|
220
|
+
* glance. Token sums use the strategy's own estimates so they line up with
|
|
221
|
+
* the numbers `select()` uses for budget math.
|
|
222
|
+
*/
|
|
223
|
+
export interface RenderStats {
|
|
224
|
+
head: { messages: number; tokens: number };
|
|
225
|
+
tail: { messages: number; tokens: number };
|
|
226
|
+
summaries: {
|
|
227
|
+
l1: { count: number; tokens: number };
|
|
228
|
+
l2: { count: number; tokens: number };
|
|
229
|
+
l3: { count: number; tokens: number };
|
|
230
|
+
};
|
|
231
|
+
pending: { chunks: number; merges: number };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Strategy that can produce render-time observability stats. Implemented by
|
|
236
|
+
* AutobiographicalStrategy. Optional capability — strategies that don't
|
|
237
|
+
* implement it simply have `ContextManager.getRenderStats()` return `null`.
|
|
238
|
+
*/
|
|
239
|
+
export interface RenderStatsCapableStrategy extends ContextStrategy {
|
|
240
|
+
getRenderStats(store: MessageStoreView): RenderStats;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Type guard for strategies that produce render stats. */
|
|
244
|
+
export function isRenderStatsCapable(s: ContextStrategy): s is RenderStatsCapableStrategy {
|
|
245
|
+
return (
|
|
246
|
+
'getRenderStats' in s &&
|
|
247
|
+
typeof (s as RenderStatsCapableStrategy).getRenderStats === 'function'
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
131
251
|
/**
|
|
132
252
|
* Configuration for the Autobiographical strategy.
|
|
133
253
|
*/
|
|
@@ -181,6 +301,84 @@ export interface AutobiographicalConfig {
|
|
|
181
301
|
l2BudgetTokens?: number;
|
|
182
302
|
/** Token budget for L1 summaries in select() (default: 30000) */
|
|
183
303
|
l1BudgetTokens?: number;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* When true (default), each selected summary emits as its own positioned
|
|
307
|
+
* Q/A recall pair, sorted chronologically by source range. When false,
|
|
308
|
+
* all selected summaries are concatenated into one Q/A pair between head
|
|
309
|
+
* and tail (legacy behavior pre-2026-05-10).
|
|
310
|
+
*
|
|
311
|
+
* Per-region positioning is the spec-faithful behavior: it lets the agent
|
|
312
|
+
* see each memory in its temporal place rather than as a wall of unrelated
|
|
313
|
+
* recollections from another speaker. Without it, hierarchical compression
|
|
314
|
+
* is structurally similar to the dual-recall corruption pattern that
|
|
315
|
+
* caused Lena's context degradation on Hermes.
|
|
316
|
+
*/
|
|
317
|
+
positionedRecallPairs?: boolean;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Template for the per-pair recall question header. Substitutions:
|
|
321
|
+
* {id} — summary id (e.g. "L1-3")
|
|
322
|
+
* {level} — summary level (1, 2, or 3)
|
|
323
|
+
* {first} — first source message id
|
|
324
|
+
* {last} — last source message id
|
|
325
|
+
* Default: '[Recall {id}]'.
|
|
326
|
+
*
|
|
327
|
+
* Only used when `positionedRecallPairs` is true.
|
|
328
|
+
*/
|
|
329
|
+
recallHeaderTemplate?: string;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Per-tool retention limit: keep at most the last N `tool_result` blocks
|
|
333
|
+
* for each tool name. Older results get their content replaced with a
|
|
334
|
+
* brief marker referencing the tool name and how many newer results exist.
|
|
335
|
+
*
|
|
336
|
+
* Two shapes accepted:
|
|
337
|
+
* - `number`: applies as a global default across all tools.
|
|
338
|
+
* - `Record<toolName, number>`: per-tool limit; tools not listed are
|
|
339
|
+
* unlimited.
|
|
340
|
+
*
|
|
341
|
+
* Default: undefined (no pruning). Use a small number (1–5) for tools
|
|
342
|
+
* that produce verbose, mostly-stale output (e.g. file listings, http
|
|
343
|
+
* fetches, log queries).
|
|
344
|
+
*/
|
|
345
|
+
toolResultMaxLastN?: number | Record<string, number>;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Truncate `tool_use` block inputs whose serialized JSON exceeds this
|
|
349
|
+
* many tokens. The truncated input becomes `{ "_truncated": true,
|
|
350
|
+
* "_originalTokens": N }` plus a head slice of the original input
|
|
351
|
+
* keys for context. Default: 0 (no truncation).
|
|
352
|
+
*/
|
|
353
|
+
toolUseInputMaxTokens?: number;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Cap on the number of speculative L1 summaries the strategy will hold
|
|
357
|
+
* (queued + unmerged). When `count(unmerged L1s) + count(queued chunks)`
|
|
358
|
+
* exceeds this cap, `onNewMessage`'s auto-tick is held back. Chunks
|
|
359
|
+
* still form and queue, but compression is deferred until a manual
|
|
360
|
+
* `tick()` or `compile()` triggers it.
|
|
361
|
+
*
|
|
362
|
+
* Default: undefined (no cap; compression fires eagerly on every
|
|
363
|
+
* new message when `autoTickOnNewMessage` is true).
|
|
364
|
+
*/
|
|
365
|
+
maxSpeculativeL1s?: number;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* When false, the rendering pipeline emits the full ideal context (head
|
|
369
|
+
* window + all selected summaries + all recent messages) without
|
|
370
|
+
* truncating to fit `budget.maxTokens`. The caller's API will reject if
|
|
371
|
+
* the result exceeds the model's context window — the philosophy is
|
|
372
|
+
* "surface the overage rather than silently lose content."
|
|
373
|
+
*
|
|
374
|
+
* Default: true (legacy budget-aware truncation: stops emitting recall
|
|
375
|
+
* pairs and recent messages when the running total exceeds maxTokens).
|
|
376
|
+
*
|
|
377
|
+
* Recommended setting for long-lived agents on large-context models
|
|
378
|
+
* (e.g. opus-4-7 with 1M context): false. The window is generous enough
|
|
379
|
+
* that overflow is rare, and when it does happen you want to know.
|
|
380
|
+
*/
|
|
381
|
+
enforceBudget?: boolean;
|
|
184
382
|
}
|
|
185
383
|
|
|
186
384
|
/**
|
|
@@ -217,6 +415,29 @@ export interface SummaryEntry {
|
|
|
217
415
|
phaseType?: string;
|
|
218
416
|
}
|
|
219
417
|
|
|
418
|
+
/**
|
|
419
|
+
* A range of messages protected from compression. Pins keep a span of raw
|
|
420
|
+
* messages visible at their original position in the rendered context.
|
|
421
|
+
*
|
|
422
|
+
* - `kind: 'pin'` — generic protected range (any first/last span).
|
|
423
|
+
* - `kind: 'document'` — typically a single message containing a body of
|
|
424
|
+
* information the agent wants to retain in full; semantically identical
|
|
425
|
+
* to a single-message pin, distinguished by metadata for tooling.
|
|
426
|
+
*/
|
|
427
|
+
export interface ProtectedRange {
|
|
428
|
+
/** Stable id assigned at pin time. */
|
|
429
|
+
id: string;
|
|
430
|
+
/** First message id of the protected range (inclusive). */
|
|
431
|
+
firstMessageId: string;
|
|
432
|
+
/** Last message id of the protected range (inclusive). */
|
|
433
|
+
lastMessageId: string;
|
|
434
|
+
kind: 'pin' | 'document';
|
|
435
|
+
/** Optional human-readable label. */
|
|
436
|
+
name?: string;
|
|
437
|
+
/** Creation timestamp (ms since epoch). */
|
|
438
|
+
created: number;
|
|
439
|
+
}
|
|
440
|
+
|
|
220
441
|
/**
|
|
221
442
|
* Phase type for knowledge extraction workflows.
|
|
222
443
|
*/
|
|
@@ -279,4 +500,6 @@ Write naturally, as recollection of what you experienced.`,
|
|
|
279
500
|
summaryContextLabel: 'What do you remember from earlier?',
|
|
280
501
|
summaryParticipant: 'Claude',
|
|
281
502
|
maxMessageTokens: 0,
|
|
503
|
+
positionedRecallPairs: true,
|
|
504
|
+
recallHeaderTemplate: '[Recall {id}]',
|
|
282
505
|
};
|