@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.
- package/dist/src/adaptive/kv-control.d.ts +84 -33
- package/dist/src/adaptive/kv-control.d.ts.map +1 -1
- package/dist/src/adaptive/kv-control.js +510 -145
- package/dist/src/adaptive/kv-control.js.map +1 -1
- package/dist/src/adaptive/picker.d.ts +21 -0
- package/dist/src/adaptive/picker.d.ts.map +1 -1
- package/dist/src/adaptive/picker.js.map +1 -1
- package/dist/src/adaptive/strategies/kv-stable.d.ts +12 -4
- package/dist/src/adaptive/strategies/kv-stable.d.ts.map +1 -1
- package/dist/src/adaptive/strategies/kv-stable.js +67 -7
- package/dist/src/adaptive/strategies/kv-stable.js.map +1 -1
- package/dist/src/context-log.d.ts.map +1 -1
- package/dist/src/context-log.js +11 -0
- package/dist/src/context-log.js.map +1 -1
- package/dist/src/context-manager.d.ts +38 -6
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +51 -0
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/message-store.d.ts +60 -0
- package/dist/src/message-store.d.ts.map +1 -1
- package/dist/src/message-store.js +165 -10
- package/dist/src/message-store.js.map +1 -1
- package/dist/src/phase-channel.d.ts +13 -0
- package/dist/src/phase-channel.d.ts.map +1 -0
- package/dist/src/phase-channel.js +55 -0
- package/dist/src/phase-channel.js.map +1 -0
- package/dist/src/strategies/autobiographical.d.ts +247 -8
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +1419 -141
- 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/strategies/knowledge.d.ts +8 -0
- package/dist/src/strategies/knowledge.d.ts.map +1 -1
- package/dist/src/strategies/knowledge.js +23 -1
- package/dist/src/strategies/knowledge.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/strategy.d.ts +112 -11
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/adaptive/doc-plus-chat.test.js +7 -3
- package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -1
- package/dist/test/adaptive/hard-fail.test.js +22 -0
- package/dist/test/adaptive/hard-fail.test.js.map +1 -1
- package/dist/test/adaptive/ingestion.test.js +3 -1
- package/dist/test/adaptive/ingestion.test.js.map +1 -1
- package/dist/test/adaptive/kv-control.test.js +17 -11
- package/dist/test/adaptive/kv-control.test.js.map +1 -1
- package/dist/test/adaptive/pin-at-level.test.d.ts +16 -0
- package/dist/test/adaptive/pin-at-level.test.d.ts.map +1 -0
- package/dist/test/adaptive/pin-at-level.test.js +133 -0
- package/dist/test/adaptive/pin-at-level.test.js.map +1 -0
- package/dist/test/adaptive/single-path-solve.test.d.ts +16 -0
- package/dist/test/adaptive/single-path-solve.test.d.ts.map +1 -0
- package/dist/test/adaptive/single-path-solve.test.js +204 -0
- package/dist/test/adaptive/single-path-solve.test.js.map +1 -0
- package/dist/test/anti-redundancy-budget.test.d.ts +17 -0
- package/dist/test/anti-redundancy-budget.test.d.ts.map +1 -0
- package/dist/test/anti-redundancy-budget.test.js +197 -0
- package/dist/test/anti-redundancy-budget.test.js.map +1 -0
- package/dist/test/chunk-persistence.test.d.ts +29 -0
- package/dist/test/chunk-persistence.test.d.ts.map +1 -0
- package/dist/test/chunk-persistence.test.js +290 -0
- package/dist/test/chunk-persistence.test.js.map +1 -0
- package/dist/test/compression-dedup.test.d.ts +20 -0
- package/dist/test/compression-dedup.test.d.ts.map +1 -0
- package/dist/test/compression-dedup.test.js +92 -0
- package/dist/test/compression-dedup.test.js.map +1 -0
- package/dist/test/compression-shape-invariants.test.js +40 -3
- package/dist/test/compression-shape-invariants.test.js.map +1 -1
- package/dist/test/head-window-order.test.d.ts +19 -0
- package/dist/test/head-window-order.test.d.ts.map +1 -0
- package/dist/test/head-window-order.test.js +159 -0
- package/dist/test/head-window-order.test.js.map +1 -0
- package/dist/test/image-strip-render-stats.test.js +25 -3
- package/dist/test/image-strip-render-stats.test.js.map +1 -1
- package/dist/test/knowledge-tool-boundary.test.d.ts +14 -0
- package/dist/test/knowledge-tool-boundary.test.d.ts.map +1 -0
- package/dist/test/knowledge-tool-boundary.test.js +93 -0
- package/dist/test/knowledge-tool-boundary.test.js.map +1 -0
- package/dist/test/l1-holdback.test.d.ts +15 -0
- package/dist/test/l1-holdback.test.d.ts.map +1 -0
- package/dist/test/l1-holdback.test.js +119 -0
- package/dist/test/l1-holdback.test.js.map +1 -0
- package/dist/test/merge-contiguity.test.d.ts +11 -0
- package/dist/test/merge-contiguity.test.d.ts.map +1 -0
- package/dist/test/merge-contiguity.test.js +74 -0
- package/dist/test/merge-contiguity.test.js.map +1 -0
- package/dist/test/message-store-window.test.d.ts +13 -0
- package/dist/test/message-store-window.test.d.ts.map +1 -0
- package/dist/test/message-store-window.test.js +177 -0
- package/dist/test/message-store-window.test.js.map +1 -0
- package/dist/test/pins.test.js +40 -0
- package/dist/test/pins.test.js.map +1 -1
- package/dist/test/speculation-cap.test.d.ts +9 -3
- package/dist/test/speculation-cap.test.d.ts.map +1 -1
- package/dist/test/speculation-cap.test.js +179 -3
- package/dist/test/speculation-cap.test.js.map +1 -1
- package/dist/test/summary-log-consistency.test.d.ts +18 -0
- package/dist/test/summary-log-consistency.test.d.ts.map +1 -0
- package/dist/test/summary-log-consistency.test.js +97 -0
- package/dist/test/summary-log-consistency.test.js.map +1 -0
- package/dist/test/tool-pairing-invariant.test.d.ts +15 -0
- package/dist/test/tool-pairing-invariant.test.d.ts.map +1 -0
- package/dist/test/tool-pairing-invariant.test.js +225 -0
- package/dist/test/tool-pairing-invariant.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -3
- package/src/adaptive/kv-control.ts +554 -152
- package/src/adaptive/picker.ts +21 -0
- package/src/adaptive/strategies/kv-stable.ts +62 -12
- package/src/context-log.ts +11 -0
- package/src/context-manager.ts +61 -4
- package/src/index.ts +6 -2
- package/src/message-store.ts +202 -9
- package/src/phase-channel.ts +57 -0
- package/src/strategies/autobiographical.ts +1482 -167
- package/src/strategies/index.ts +1 -1
- package/src/strategies/knowledge.ts +25 -1
- package/src/types/index.ts +1 -0
- package/src/types/strategy.ts +120 -7
package/src/strategies/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { PassthroughStrategy } from './passthrough.js';
|
|
2
|
-
export { AutobiographicalStrategy, type AutobiographicalProgressSnapshot } from './autobiographical.js';
|
|
2
|
+
export { AutobiographicalStrategy, type AutobiographicalProgressSnapshot, type Chunk } from './autobiographical.js';
|
|
@@ -41,6 +41,17 @@ export class KnowledgeStrategy extends AutobiographicalStrategy {
|
|
|
41
41
|
// Override: Semantic chunking
|
|
42
42
|
// ============================================================================
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* KnowledgeStrategy keeps the legacy recompute-every-rebuild chunker:
|
|
46
|
+
* its chunk boundaries are semantic (phase transitions), recomputed from
|
|
47
|
+
* message content, and its stores are offline mining artifacts — the
|
|
48
|
+
* restart-duplication failure mode that chunk persistence fixes does not
|
|
49
|
+
* apply, and persisted token-sum records would fight the phase chunker.
|
|
50
|
+
*/
|
|
51
|
+
protected get chunkPersistenceEnabled(): boolean {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
protected rebuildChunks(store: MessageStoreView): void {
|
|
45
56
|
const messagesToChunk = this.getCompressibleMessages(store);
|
|
46
57
|
|
|
@@ -81,7 +92,20 @@ export class KnowledgeStrategy extends AutobiographicalStrategy {
|
|
|
81
92
|
const phaseChanged = currentPhase !== null && phase !== currentPhase;
|
|
82
93
|
const maxTokens = this.getMaxChunkTokens(currentPhase ?? phase);
|
|
83
94
|
const sizeExceeded = currentTokens >= maxTokens;
|
|
84
|
-
|
|
95
|
+
// Same tool-pair boundary guard as the base chunker (rebuildChunks in
|
|
96
|
+
// AutobiographicalStrategy): never close a chunk whose LAST message
|
|
97
|
+
// contains a tool_use block — its matching tool_result lives in the
|
|
98
|
+
// immediately-following message (the one about to be pushed). Closing
|
|
99
|
+
// here would split the pair across chunks; when the earlier chunk
|
|
100
|
+
// compresses first, the raw orphan tool_result renders unpaired and
|
|
101
|
+
// the Anthropic API rejects the request. Deferring the close by one
|
|
102
|
+
// iteration pulls the tool_result into the same chunk. (Phase never
|
|
103
|
+
// changes between a tool_use and its tool_result — the result inherits
|
|
104
|
+
// lastToolPhase — so this defers sizeExceeded closes in practice.)
|
|
105
|
+
const shouldClose =
|
|
106
|
+
currentChunk.length >= 2 &&
|
|
107
|
+
(phaseChanged || sizeExceeded) &&
|
|
108
|
+
!this.lastMessageContainsToolUse(currentChunk);
|
|
85
109
|
|
|
86
110
|
if (shouldClose) {
|
|
87
111
|
const chunk = this.createChunk(
|
package/src/types/index.ts
CHANGED
package/src/types/strategy.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsStore } from '@animalabs/chronicle';
|
|
2
|
-
import type { Membrane, ContentBlock } from '@animalabs/membrane';
|
|
2
|
+
import type { Membrane, ContentBlock, ToolDefinition } from '@animalabs/membrane';
|
|
3
3
|
import type { StoredMessage, MessageId, Sequence } from './message.js';
|
|
4
4
|
import type { ContextEntry, TokenBudget, PendingWork } from './context.js';
|
|
5
5
|
|
|
@@ -19,6 +19,9 @@ export interface MessageStoreView {
|
|
|
19
19
|
length(): number;
|
|
20
20
|
/** Estimate tokens for a message */
|
|
21
21
|
estimateTokens(message: StoredMessage): number;
|
|
22
|
+
/** Closed-loop estimator calibration (optional — MessageStore provides it). */
|
|
23
|
+
setTokenCalibration?(factor: number): void;
|
|
24
|
+
getTokenCalibration?(): number;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -43,6 +46,16 @@ export interface ContextLogView {
|
|
|
43
46
|
export interface StrategyContext {
|
|
44
47
|
/** Read-only view of message store */
|
|
45
48
|
messageStore: MessageStoreView;
|
|
49
|
+
/**
|
|
50
|
+
* The agent's live tool definitions, as last supplied by the host (set on
|
|
51
|
+
* every activation via ContextManager.setToolDefinitions). Summarizer /
|
|
52
|
+
* compression requests MUST declare these: replaying a transcript full of
|
|
53
|
+
* tool_use/tool_result blocks with no `tools` param trips Anthropic's
|
|
54
|
+
* reasoning_extraction classifier (looks like a foreign agent trace being
|
|
55
|
+
* duplicated) and the whole compression pass gets deterministically
|
|
56
|
+
* refused. Optional because ticks can fire before the first activation.
|
|
57
|
+
*/
|
|
58
|
+
tools?: ToolDefinition[];
|
|
46
59
|
/** Read-only view of context log */
|
|
47
60
|
contextLog: ContextLogView;
|
|
48
61
|
/** Membrane instance for LLM calls (compression) */
|
|
@@ -186,8 +199,8 @@ export function isResettableStrategy(s: ContextStrategy): s is ResettableStrateg
|
|
|
186
199
|
* original chronological position. Implemented by AutobiographicalStrategy.
|
|
187
200
|
*/
|
|
188
201
|
export interface PinnableStrategy extends ContextStrategy {
|
|
189
|
-
pinRange(firstMessageId: string, lastMessageId: string, opts?:
|
|
190
|
-
markDocument(messageId: string, opts?:
|
|
202
|
+
pinRange(firstMessageId: string, lastMessageId: string, opts?: PinLevelOptions): string;
|
|
203
|
+
markDocument(messageId: string, opts?: PinLevelOptions): string;
|
|
191
204
|
unpin(pinId: string): boolean;
|
|
192
205
|
listPins(): ReadonlyArray<ProtectedRange>;
|
|
193
206
|
}
|
|
@@ -329,6 +342,14 @@ export interface AutobiographicalConfig {
|
|
|
329
342
|
* this limit have their text/tool_result content truncated. 0 = no limit. */
|
|
330
343
|
maxMessageTokens: number;
|
|
331
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Fractional grace above the configured hard context budget before an
|
|
347
|
+
* exhausted adaptive picker rejects the compile. The solver still targets
|
|
348
|
+
* the original budget; this only absorbs coarse fold quanta and indivisible
|
|
349
|
+
* head/tail boundary overshoot. Default 0 (strict budget enforcement).
|
|
350
|
+
*/
|
|
351
|
+
overBudgetGraceRatio?: number;
|
|
352
|
+
|
|
332
353
|
// --- Live image policy ---
|
|
333
354
|
|
|
334
355
|
/** Maximum number of images kept "live" (sent as real image blocks) in the
|
|
@@ -342,6 +363,42 @@ export interface AutobiographicalConfig {
|
|
|
342
363
|
* Typically much shallower than recentWindowTokens. 0 = never strip by depth. */
|
|
343
364
|
imageStripDepthTokens?: number;
|
|
344
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Merge grouping (2026-07-12 contiguity fix): break a merge run when the
|
|
368
|
+
* positional gap between consecutive unmerged candidates exceeds this many
|
|
369
|
+
* messages. Small holes (wiped/pruned nodes) bridge fine; cross-era gaps
|
|
370
|
+
* must not — a merge group spanning already-merged history can straddle
|
|
371
|
+
* the recent window and block its whole lineage from folding. Default 300.
|
|
372
|
+
*/
|
|
373
|
+
mergeContiguityGapLimit?: number;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Byte wall for live images (2026-07-12): keep inline images newest-first
|
|
377
|
+
* only while their cumulative base64 size stays under this budget — on top
|
|
378
|
+
* of `maxLiveImages` / `imageStripDepthTokens`. Guarantees the compiled
|
|
379
|
+
* window fits the API's total request size cap (413 request_too_large), so
|
|
380
|
+
* membrane's oversize check is a true invariant, never a silent editor.
|
|
381
|
+
* Default 20MB (of base64; the API cap is 32MB total).
|
|
382
|
+
*/
|
|
383
|
+
maxLiveImageBytes?: number;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Merge grouping: exclude candidates whose OWN source span exceeds this
|
|
387
|
+
* many messages (replay-era summaries can span the entire chronicle and
|
|
388
|
+
* would bridge any run they join). They stay on the frontier. Default 1500.
|
|
389
|
+
*/
|
|
390
|
+
mergeMaxSourceSpanMessages?: number;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* L1 production holdback: keep the newest N closed chunks out of the
|
|
394
|
+
* speculative compression queue (default 1). The chunk at the live edge is
|
|
395
|
+
* the one most likely to still be in motion — summarize it only once a
|
|
396
|
+
* newer chunk has closed behind it. Speculative production of everything
|
|
397
|
+
* older proceeds ahead of need as usual. A picker `produce` op (real fold
|
|
398
|
+
* demand) bypasses the holdback. 0 = compress at close (old behavior).
|
|
399
|
+
*/
|
|
400
|
+
l1HoldbackChunks?: number;
|
|
401
|
+
|
|
345
402
|
// Legacy aliases (deprecated, use summary* instead)
|
|
346
403
|
/** @deprecated Use summarySystemPrompt */
|
|
347
404
|
diarySystemPrompt?: string;
|
|
@@ -387,6 +444,20 @@ export interface AutobiographicalConfig {
|
|
|
387
444
|
*/
|
|
388
445
|
compressionRecallBudgetTokens?: number;
|
|
389
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Minimum substantive text (non-whitespace chars across a chunk's
|
|
449
|
+
* messages) below which L1 compression skips the LLM call and stores a
|
|
450
|
+
* mechanical stub summary instead (default: 200).
|
|
451
|
+
*
|
|
452
|
+
* A chunk of silent/skip turns and bare system traffic gives the
|
|
453
|
+
* summarizer nothing to remember; asked anyway, it confabulates —
|
|
454
|
+
* it reaches for the nearest salient content (head window, prior
|
|
455
|
+
* recall pairs) and narrates it as if it just happened (the
|
|
456
|
+
* "68 initiations" incident). Chunks containing any non-text blocks
|
|
457
|
+
* (tool cycles, images) are never stubbed. Set to 0 to disable.
|
|
458
|
+
*/
|
|
459
|
+
minChunkCharsForLLM?: number;
|
|
460
|
+
|
|
390
461
|
/**
|
|
391
462
|
* When true (default), each selected summary emits as its own positioned
|
|
392
463
|
* Q/A recall pair, sorted chronologically by source range. When false,
|
|
@@ -487,13 +558,26 @@ export interface AutobiographicalConfig {
|
|
|
487
558
|
foldingStrategy?: 'flat-profile' | 'oldest-first' | 'kv-stable';
|
|
488
559
|
|
|
489
560
|
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
561
|
+
* Trust region P (tokens) for `foldingStrategy: 'kv-stable'` — bounds how
|
|
562
|
+
* much prefix re-read (exact kvCost) an ordinary turn may take; the solver
|
|
563
|
+
* adopts the relevance-ideal cut within it, amortizes bigger repairs across
|
|
564
|
+
* turns (suffix adoption), and exceeds it only with a recorded override
|
|
565
|
+
* (bootstrap / infeasible / quality-gap — logged as `[kv-escalation]`).
|
|
566
|
+
* See docs/adaptive-resolution-design.md §13. Default: the hard budget
|
|
567
|
+
* (a rendered layout never exceeds it, so the default never binds).
|
|
494
568
|
*/
|
|
495
569
|
kvStableReachTokens?: number;
|
|
496
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Quality-gap override threshold for `foldingStrategy: 'kv-stable'`
|
|
573
|
+
* (design §13.4): a plan within the trust region is rejected — and the
|
|
574
|
+
* relevance-ideal cut adopted, paying the perturbation — when its
|
|
575
|
+
* salience-weighted misallocation loss exceeds the ideal's by more than
|
|
576
|
+
* this fraction of the ideal's. Also bounds how misallocated a dead-band
|
|
577
|
+
* hold may be before the solver self-heals. Default 0.35.
|
|
578
|
+
*/
|
|
579
|
+
kvStableQualityGapRatio?: number;
|
|
580
|
+
|
|
497
581
|
/**
|
|
498
582
|
* Slack ratio (hysteresis) for the picker. The picker folds until total
|
|
499
583
|
* tokens ≤ budget * (1 - slack), and stays quiet while between slack
|
|
@@ -596,6 +680,35 @@ export interface ProtectedRange {
|
|
|
596
680
|
name?: string;
|
|
597
681
|
/** Creation timestamp (ms since epoch). */
|
|
598
682
|
created: number;
|
|
683
|
+
/**
|
|
684
|
+
* Pin-AT-level-k (V2 dynamic pins, `docs/best-fit-frontier-resolution.md` §7).
|
|
685
|
+
* Fix the covered chunks to render at EXACTLY this fold level (0 = raw): the
|
|
686
|
+
* frontier cut passes through the L_k node — neither folded deeper nor
|
|
687
|
+
* un-folded shallower. When set, this range is NOT a classic force-raw pin.
|
|
688
|
+
* Takes precedence over `maxLevel`. Omitted → not a fixed-level pin.
|
|
689
|
+
*
|
|
690
|
+
* Honored only by the KV-stable controller (`foldingStrategy: 'kv-stable'`);
|
|
691
|
+
* other strategies fall back to treating the range as raw (a safe superset).
|
|
692
|
+
*/
|
|
693
|
+
level?: number;
|
|
694
|
+
/**
|
|
695
|
+
* Pin-max-level (V2 dynamic pins): the covered chunks may fold no DEEPER than
|
|
696
|
+
* this level — raw..L_k allowed, deeper forbidden — a hard cap honored even
|
|
697
|
+
* under the window-pressure emergency. `maxLevel: 0` ≡ classic pin-raw.
|
|
698
|
+
* Ignored when `level` is set. Omitted → this pin imposes no depth cap.
|
|
699
|
+
*
|
|
700
|
+
* Honored only by the KV-stable controller; see `level`.
|
|
701
|
+
*/
|
|
702
|
+
maxLevel?: number;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Optional fold-depth bounds for a V2 dynamic pin (see `ProtectedRange`). */
|
|
706
|
+
export interface PinLevelOptions {
|
|
707
|
+
name?: string;
|
|
708
|
+
/** Pin AT exactly this level (0 = raw). Fixes the cut through the L_k node. */
|
|
709
|
+
level?: number;
|
|
710
|
+
/** Fold no deeper than this level (hard cap; `0` ≡ classic pin-raw). */
|
|
711
|
+
maxLevel?: number;
|
|
599
712
|
}
|
|
600
713
|
|
|
601
714
|
/**
|