@animalabs/context-manager 0.5.5 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/src/adaptive/kv-control.d.ts +69 -32
  2. package/dist/src/adaptive/kv-control.d.ts.map +1 -1
  3. package/dist/src/adaptive/kv-control.js +471 -172
  4. package/dist/src/adaptive/kv-control.js.map +1 -1
  5. package/dist/src/adaptive/picker.d.ts +8 -0
  6. package/dist/src/adaptive/picker.d.ts.map +1 -1
  7. package/dist/src/adaptive/picker.js.map +1 -1
  8. package/dist/src/adaptive/strategies/kv-stable.d.ts +12 -4
  9. package/dist/src/adaptive/strategies/kv-stable.d.ts.map +1 -1
  10. package/dist/src/adaptive/strategies/kv-stable.js +14 -5
  11. package/dist/src/adaptive/strategies/kv-stable.js.map +1 -1
  12. package/dist/src/context-log.d.ts.map +1 -1
  13. package/dist/src/context-log.js +11 -0
  14. package/dist/src/context-log.js.map +1 -1
  15. package/dist/src/context-manager.d.ts +28 -1
  16. package/dist/src/context-manager.d.ts.map +1 -1
  17. package/dist/src/context-manager.js +42 -0
  18. package/dist/src/context-manager.js.map +1 -1
  19. package/dist/src/index.d.ts +3 -2
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +1 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/message-store.d.ts +60 -0
  24. package/dist/src/message-store.d.ts.map +1 -1
  25. package/dist/src/message-store.js +165 -10
  26. package/dist/src/message-store.js.map +1 -1
  27. package/dist/src/strategies/autobiographical.d.ts +207 -2
  28. package/dist/src/strategies/autobiographical.d.ts.map +1 -1
  29. package/dist/src/strategies/autobiographical.js +1283 -124
  30. package/dist/src/strategies/autobiographical.js.map +1 -1
  31. package/dist/src/strategies/index.d.ts +1 -1
  32. package/dist/src/strategies/index.d.ts.map +1 -1
  33. package/dist/src/strategies/index.js.map +1 -1
  34. package/dist/src/strategies/knowledge.d.ts +8 -0
  35. package/dist/src/strategies/knowledge.d.ts.map +1 -1
  36. package/dist/src/strategies/knowledge.js +23 -1
  37. package/dist/src/strategies/knowledge.js.map +1 -1
  38. package/dist/src/types/strategy.d.ts +82 -5
  39. package/dist/src/types/strategy.d.ts.map +1 -1
  40. package/dist/src/types/strategy.js.map +1 -1
  41. package/dist/test/adaptive/doc-plus-chat.test.js +7 -3
  42. package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -1
  43. package/dist/test/adaptive/hard-fail.test.js +22 -0
  44. package/dist/test/adaptive/hard-fail.test.js.map +1 -1
  45. package/dist/test/adaptive/ingestion.test.js +3 -1
  46. package/dist/test/adaptive/ingestion.test.js.map +1 -1
  47. package/dist/test/adaptive/kv-control.test.js +17 -11
  48. package/dist/test/adaptive/kv-control.test.js.map +1 -1
  49. package/dist/test/adaptive/single-path-solve.test.d.ts +16 -0
  50. package/dist/test/adaptive/single-path-solve.test.d.ts.map +1 -0
  51. package/dist/test/adaptive/single-path-solve.test.js +204 -0
  52. package/dist/test/adaptive/single-path-solve.test.js.map +1 -0
  53. package/dist/test/anti-redundancy-budget.test.d.ts +17 -0
  54. package/dist/test/anti-redundancy-budget.test.d.ts.map +1 -0
  55. package/dist/test/anti-redundancy-budget.test.js +197 -0
  56. package/dist/test/anti-redundancy-budget.test.js.map +1 -0
  57. package/dist/test/chunk-persistence.test.d.ts +29 -0
  58. package/dist/test/chunk-persistence.test.d.ts.map +1 -0
  59. package/dist/test/chunk-persistence.test.js +290 -0
  60. package/dist/test/chunk-persistence.test.js.map +1 -0
  61. package/dist/test/compression-dedup.test.d.ts +20 -0
  62. package/dist/test/compression-dedup.test.d.ts.map +1 -0
  63. package/dist/test/compression-dedup.test.js +92 -0
  64. package/dist/test/compression-dedup.test.js.map +1 -0
  65. package/dist/test/compression-shape-invariants.test.js +40 -3
  66. package/dist/test/compression-shape-invariants.test.js.map +1 -1
  67. package/dist/test/head-window-order.test.d.ts +19 -0
  68. package/dist/test/head-window-order.test.d.ts.map +1 -0
  69. package/dist/test/head-window-order.test.js +159 -0
  70. package/dist/test/head-window-order.test.js.map +1 -0
  71. package/dist/test/image-strip-render-stats.test.js +25 -3
  72. package/dist/test/image-strip-render-stats.test.js.map +1 -1
  73. package/dist/test/knowledge-tool-boundary.test.d.ts +14 -0
  74. package/dist/test/knowledge-tool-boundary.test.d.ts.map +1 -0
  75. package/dist/test/knowledge-tool-boundary.test.js +93 -0
  76. package/dist/test/knowledge-tool-boundary.test.js.map +1 -0
  77. package/dist/test/l1-holdback.test.d.ts +15 -0
  78. package/dist/test/l1-holdback.test.d.ts.map +1 -0
  79. package/dist/test/l1-holdback.test.js +119 -0
  80. package/dist/test/l1-holdback.test.js.map +1 -0
  81. package/dist/test/merge-contiguity.test.d.ts +11 -0
  82. package/dist/test/merge-contiguity.test.d.ts.map +1 -0
  83. package/dist/test/merge-contiguity.test.js +74 -0
  84. package/dist/test/merge-contiguity.test.js.map +1 -0
  85. package/dist/test/message-store-window.test.d.ts +13 -0
  86. package/dist/test/message-store-window.test.d.ts.map +1 -0
  87. package/dist/test/message-store-window.test.js +177 -0
  88. package/dist/test/message-store-window.test.js.map +1 -0
  89. package/dist/test/speculation-cap.test.js +4 -0
  90. package/dist/test/speculation-cap.test.js.map +1 -1
  91. package/dist/test/summary-log-consistency.test.d.ts +18 -0
  92. package/dist/test/summary-log-consistency.test.d.ts.map +1 -0
  93. package/dist/test/summary-log-consistency.test.js +97 -0
  94. package/dist/test/summary-log-consistency.test.js.map +1 -0
  95. package/dist/test/tool-pairing-invariant.test.d.ts +15 -0
  96. package/dist/test/tool-pairing-invariant.test.d.ts.map +1 -0
  97. package/dist/test/tool-pairing-invariant.test.js +225 -0
  98. package/dist/test/tool-pairing-invariant.test.js.map +1 -0
  99. package/dist/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +11 -3
  101. package/src/adaptive/kv-control.ts +509 -184
  102. package/src/adaptive/picker.ts +8 -0
  103. package/src/adaptive/strategies/kv-stable.ts +22 -10
  104. package/src/context-log.ts +11 -0
  105. package/src/context-manager.ts +48 -2
  106. package/src/index.ts +3 -2
  107. package/src/message-store.ts +202 -9
  108. package/src/strategies/autobiographical.ts +1343 -148
  109. package/src/strategies/index.ts +1 -1
  110. package/src/strategies/knowledge.ts +25 -1
  111. package/src/types/strategy.ts +89 -5
@@ -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
- const shouldClose = currentChunk.length >= 2 && (phaseChanged || sizeExceeded);
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(
@@ -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) */
@@ -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
- * Per-turn divergence reach (tokens) for `foldingStrategy: 'kv-stable'` — the
491
- * structural KV-perturbation cap P. Smaller = gentler per-turn cache churn but
492
- * less efficient compression; exceeded only to stay under the hard budget.
493
- * Default: unbounded within the budget.
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