adaptive-memory-multi-model-router 2.14.45 → 2.14.46

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.
@@ -1,14 +1,14 @@
1
1
  {
2
- "timestamp": "2026-06-07T21:49:39.717Z",
2
+ "timestamp": "2026-06-07T22:37:49.653Z",
3
3
  "version": "2.14.44",
4
4
  "routing": {
5
5
  "total": 81,
6
- "correct": 30,
7
- "accuracy": 37,
6
+ "correct": 29,
7
+ "accuracy": 35.8,
8
8
  "offByOne": 66,
9
9
  "offByOneAccuracy": 81.5,
10
- "totalCost": 54.5135,
11
- "avgCost": 0.67301,
10
+ "totalCost": 53.7775,
11
+ "avgCost": 0.66392,
12
12
  "perTier": {
13
13
  "free": {
14
14
  "total": 20,
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "mid": {
22
22
  "total": 20,
23
- "correct": 10
23
+ "correct": 9
24
24
  },
25
25
  "premium": {
26
26
  "total": 20,
package/dist/index.d.ts CHANGED
@@ -7,6 +7,10 @@ export { CostTracker } from './cost/costTracker';
7
7
  export { BudgetEnforcer, BudgetExceededError, createBudgetEnforcer } from './cost/budgetEnforcer';
8
8
  export type { BudgetConfig, SpendRecord, BudgetCheckResult } from './cost/budgetEnforcer';
9
9
  export { MemoryTree } from './memory/memoryTree';
10
+ export { ReasoningBank } from './memory/reasoningBank';
11
+ export type { ReasoningMemory, ReasoningBankConfig } from './memory/reasoningBank';
12
+ export { HybridMemory } from './memory/hybridMemory';
13
+ export type { HybridMemoryConfig, HybridResult } from './memory/hybridMemory';
10
14
  export type { MemoryChunk, TreeNode } from './memory/memoryTree';
11
15
  export { countTokens, estimateTokens } from './utils/tokenUtils';
12
16
  export { SemanticCache } from './cache/semanticCache';
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // A3M Router - Main Entry Point
3
3
  // Version: 2.0.0
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.applyCredit = exports.createAccuracyFn = exports.calculateEnhancedShapley = exports.HandicapCalculator = exports.LoyaltyMatrix = exports.EnsembleOrchestrator = exports.budgetAlertMiddleware = exports.observabilityPlugin = exports.observabilityMiddleware = exports.createMetricsCollector = exports.getMetrics = exports.MetricsCollector = exports.createTracer = exports.getTracer = exports.Tracer = exports.createProxyServer = exports.CostAnalytics = exports.GuardrailEngine = exports.SemanticCache = exports.estimateTokens = exports.countTokens = exports.MemoryTree = exports.createBudgetEnforcer = exports.BudgetExceededError = exports.BudgetEnforcer = exports.CostTracker = exports.saveConfig = exports.loadConfig = exports.findFastestAvailableProvider = exports.findCheapestAvailableProvider = exports.checkAllProviders = exports.healthCheck = exports.updateProvider = exports.deregisterProvider = exports.registerProvider = exports.getAvailableProviders = exports.DEFAULT_PROVIDERS = exports.PROVIDER_CONTEXT_LIMITS = exports.DEFAULT_PROVIDER_CONFIG = exports.DEFAULT_RETRY_CONFIG = exports.getDefaultRetryHandler = exports.createRetryHandler = exports.ProviderRetryHandler = exports.getProviderHealth = exports.updateModelProfile = exports.MODEL_PROFILES = exports.extractQueryFeatures = exports.recommendForTask = exports.routeBatch = exports.routeQuery = void 0;
6
- exports.RESEARCH_TEMPLATES = exports.scienceTools = exports.detectScienceDomain = exports.isScienceQuery = exports.routeScienceQuery = exports.executeScienceQuery = exports.dialogOptimizer = exports.MultiRoundDialogOptimizer = exports.summarize = void 0;
5
+ exports.calculateEnhancedShapley = exports.HandicapCalculator = exports.LoyaltyMatrix = exports.EnsembleOrchestrator = exports.budgetAlertMiddleware = exports.observabilityPlugin = exports.observabilityMiddleware = exports.createMetricsCollector = exports.getMetrics = exports.MetricsCollector = exports.createTracer = exports.getTracer = exports.Tracer = exports.createProxyServer = exports.CostAnalytics = exports.GuardrailEngine = exports.SemanticCache = exports.estimateTokens = exports.countTokens = exports.HybridMemory = exports.ReasoningBank = exports.MemoryTree = exports.createBudgetEnforcer = exports.BudgetExceededError = exports.BudgetEnforcer = exports.CostTracker = exports.saveConfig = exports.loadConfig = exports.findFastestAvailableProvider = exports.findCheapestAvailableProvider = exports.checkAllProviders = exports.healthCheck = exports.updateProvider = exports.deregisterProvider = exports.registerProvider = exports.getAvailableProviders = exports.DEFAULT_PROVIDERS = exports.PROVIDER_CONTEXT_LIMITS = exports.DEFAULT_PROVIDER_CONFIG = exports.DEFAULT_RETRY_CONFIG = exports.getDefaultRetryHandler = exports.createRetryHandler = exports.ProviderRetryHandler = exports.getProviderHealth = exports.updateModelProfile = exports.MODEL_PROFILES = exports.extractQueryFeatures = exports.recommendForTask = exports.routeBatch = exports.routeQuery = void 0;
6
+ exports.RESEARCH_TEMPLATES = exports.scienceTools = exports.detectScienceDomain = exports.isScienceQuery = exports.routeScienceQuery = exports.executeScienceQuery = exports.dialogOptimizer = exports.MultiRoundDialogOptimizer = exports.summarize = exports.applyCredit = exports.createAccuracyFn = void 0;
7
7
  exports.createA3MRouter = createA3MRouter;
8
8
  // ============================================================
9
9
  // ROUTING ENGINE
@@ -55,6 +55,12 @@ Object.defineProperty(exports, "createBudgetEnforcer", { enumerable: true, get:
55
55
  // ============================================================
56
56
  var memoryTree_1 = require("./memory/memoryTree");
57
57
  Object.defineProperty(exports, "MemoryTree", { enumerable: true, get: function () { return memoryTree_1.MemoryTree; } });
58
+ // ReasoningBank — experience-based memory (semantic retrieval + learning)
59
+ var reasoningBank_1 = require("./memory/reasoningBank");
60
+ Object.defineProperty(exports, "ReasoningBank", { enumerable: true, get: function () { return reasoningBank_1.ReasoningBank; } });
61
+ // Hybrid Memory — merges MemoryTree (keyword) + ReasoningBank (semantic)
62
+ var hybridMemory_1 = require("./memory/hybridMemory");
63
+ Object.defineProperty(exports, "HybridMemory", { enumerable: true, get: function () { return hybridMemory_1.HybridMemory; } });
58
64
  // ============================================================
59
65
  // UTILITIES
60
66
  // ============================================================
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Hybrid Memory — Merges MemoryTree (keyword) + ReasoningBank (semantic)
3
+ *
4
+ * Provides unified search across both memory systems with configurable
5
+ * weighting. Falls back gracefully when ReasoningBank has no data or
6
+ * no embedding keys configured.
7
+ *
8
+ * Merge formula: final_score = keyword_score * w1 + semantic_score * w2
9
+ * where w1 + w2 = 1.0, configurable via config.
10
+ */
11
+ import { ReasoningBankConfig } from './reasoningBank';
12
+ export interface HybridMemoryConfig {
13
+ /** Weight for MemoryTree keyword score (0-1). ReasoningBank gets (1 - this). */
14
+ keywordWeight: number;
15
+ /** ReasoningBank config */
16
+ reasoningBank: Partial<ReasoningBankConfig>;
17
+ }
18
+ export interface HybridResult {
19
+ id: string;
20
+ content: string;
21
+ score: number;
22
+ source: 'keyword' | 'semantic' | 'merged';
23
+ metadata?: Record<string, unknown>;
24
+ }
25
+ export declare class HybridMemory {
26
+ private memoryTree;
27
+ private reasoningBank;
28
+ private config;
29
+ constructor(config?: Partial<HybridMemoryConfig>);
30
+ /** Initialize both memory systems */
31
+ init(): Promise<void>;
32
+ /** Add data to MemoryTree (fast, always works) */
33
+ add(data: string): Promise<void>;
34
+ /** Induce a memory in ReasoningBank from a routing decision */
35
+ learnFromDecision(params: {
36
+ query: string;
37
+ provider: string;
38
+ cost: number;
39
+ complexity: number;
40
+ success: boolean;
41
+ reasoning?: string;
42
+ }): Promise<void>;
43
+ /**
44
+ * Unified search across both memory systems.
45
+ * Returns merged, deduplicated results sorted by relevance.
46
+ */
47
+ search(query: string, topK?: number): Promise<HybridResult[]>;
48
+ /** Get context string for router injection */
49
+ getContext(query: string, maxTokens?: number): Promise<string>;
50
+ /** Get combined stats */
51
+ getStats(): {
52
+ memoryTree: {
53
+ totalChunks: number;
54
+ maxDepth: number;
55
+ rootChunks: number;
56
+ treeSize: number;
57
+ };
58
+ reasoningBank: {
59
+ totalMemories: number;
60
+ successes: number;
61
+ failures: number;
62
+ withEmbeddings: number;
63
+ providers: string[];
64
+ };
65
+ keywordWeight: number;
66
+ };
67
+ /** Save both systems */
68
+ save(): Promise<void>;
69
+ private normalizeScore;
70
+ }
71
+ export default HybridMemory;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * Hybrid Memory — Merges MemoryTree (keyword) + ReasoningBank (semantic)
4
+ *
5
+ * Provides unified search across both memory systems with configurable
6
+ * weighting. Falls back gracefully when ReasoningBank has no data or
7
+ * no embedding keys configured.
8
+ *
9
+ * Merge formula: final_score = keyword_score * w1 + semantic_score * w2
10
+ * where w1 + w2 = 1.0, configurable via config.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.HybridMemory = void 0;
14
+ const memoryTree_1 = require("./memoryTree");
15
+ const reasoningBank_1 = require("./reasoningBank");
16
+ const DEFAULT_CONFIG = {
17
+ keywordWeight: 0.3, // 30% keyword, 70% semantic
18
+ reasoningBank: {},
19
+ };
20
+ class HybridMemory {
21
+ memoryTree;
22
+ reasoningBank;
23
+ config;
24
+ constructor(config = {}) {
25
+ this.config = { ...DEFAULT_CONFIG, ...config };
26
+ this.memoryTree = new memoryTree_1.MemoryTree();
27
+ this.reasoningBank = new reasoningBank_1.ReasoningBank(this.config.reasoningBank);
28
+ }
29
+ /** Initialize both memory systems */
30
+ async init() {
31
+ await this.reasoningBank.load();
32
+ }
33
+ /** Add data to MemoryTree (fast, always works) */
34
+ async add(data) {
35
+ await this.memoryTree.add(data);
36
+ }
37
+ /** Induce a memory in ReasoningBank from a routing decision */
38
+ async learnFromDecision(params) {
39
+ await this.reasoningBank.induceMemory(params);
40
+ }
41
+ /**
42
+ * Unified search across both memory systems.
43
+ * Returns merged, deduplicated results sorted by relevance.
44
+ */
45
+ async search(query, topK = 10) {
46
+ const results = [];
47
+ const seen = new Set();
48
+ // 1. MemoryTree keyword search (always available)
49
+ const keywordResults = this.memoryTree.search(query, topK * 2);
50
+ for (const chunk of keywordResults) {
51
+ const score = this.normalizeScore(chunk.score, 0, 1);
52
+ results.push({
53
+ id: chunk.id,
54
+ content: chunk.content,
55
+ score: score * this.config.keywordWeight,
56
+ source: 'keyword',
57
+ metadata: { accessCount: chunk.accessCount, depth: chunk.depth },
58
+ });
59
+ seen.add(chunk.id);
60
+ }
61
+ // 2. ReasoningBank semantic search (if available)
62
+ try {
63
+ const semanticResults = await this.reasoningBank.selectMemories(query);
64
+ for (const mem of semanticResults) {
65
+ if (seen.has(mem.id))
66
+ continue;
67
+ results.push({
68
+ id: mem.id,
69
+ content: `[${mem.status.toUpperCase()}] ${mem.title}\n${mem.description}\n${mem.content}`,
70
+ score: 0.7 * (1 - this.config.keywordWeight), // semantic weight
71
+ source: 'semantic',
72
+ metadata: {
73
+ provider: mem.provider,
74
+ cost: mem.cost,
75
+ complexity: mem.complexity,
76
+ status: mem.status,
77
+ },
78
+ });
79
+ seen.add(mem.id);
80
+ }
81
+ }
82
+ catch {
83
+ // ReasoningBank unavailable — keyword results still returned
84
+ }
85
+ // 3. Sort by score and return topK
86
+ results.sort((a, b) => b.score - a.score);
87
+ return results.slice(0, topK);
88
+ }
89
+ /** Get context string for router injection */
90
+ async getContext(query, maxTokens = 3000) {
91
+ const results = await this.search(query, 5);
92
+ if (results.length === 0)
93
+ return '';
94
+ const parts = results.map((r, i) => {
95
+ const prefix = r.source === 'semantic' ? `[Experience] ` : '';
96
+ return `${prefix}${r.content}`;
97
+ });
98
+ let context = parts.join('\n\n');
99
+ if (context.length > maxTokens) {
100
+ context = context.slice(0, maxTokens) + '...';
101
+ }
102
+ return context;
103
+ }
104
+ /** Get combined stats */
105
+ getStats() {
106
+ return {
107
+ memoryTree: this.memoryTree.getStats(),
108
+ reasoningBank: this.reasoningBank.getStats(),
109
+ keywordWeight: this.config.keywordWeight,
110
+ };
111
+ }
112
+ /** Save both systems */
113
+ async save() {
114
+ await this.reasoningBank.save();
115
+ }
116
+ normalizeScore(score, min, max) {
117
+ if (max === min)
118
+ return 0.5;
119
+ return Math.min(1, Math.max(0, (score - min) / (max - min)));
120
+ }
121
+ }
122
+ exports.HybridMemory = HybridMemory;
123
+ exports.default = HybridMemory;
124
+ //# sourceMappingURL=hybridMemory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybridMemory.js","sourceRoot":"","sources":["../../src/memory/hybridMemory.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEH,6CAAuD;AACvD,mDAAsF;AAStF,MAAM,cAAc,GAAuB;IACzC,aAAa,EAAE,GAAG,EAAG,4BAA4B;IACjD,aAAa,EAAE,EAAE;CAClB,CAAC;AAUF,MAAa,YAAY;IACf,UAAU,CAAa;IACvB,aAAa,CAAgB;IAC7B,MAAM,CAAqB;IAEnC,YAAY,SAAsC,EAAE;QAClD,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,uBAAU,EAAE,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,IAAI,6BAAa,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACpE,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAClC,CAAC;IAED,kDAAkD;IAClD,KAAK,CAAC,GAAG,CAAC,IAAY;QACpB,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,iBAAiB,CAAC,MAOvB;QACC,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,IAAI,GAAG,EAAE;QACnC,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAE/B,kDAAkD;QAClD,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;QAC/D,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa;gBACxC,MAAM,EAAE,SAAS;gBACjB,QAAQ,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;aACjE,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QAED,kDAAkD;QAClD,IAAI,CAAC;YACH,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACvE,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC/B,OAAO,CAAC,IAAI,CAAC;oBACX,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,OAAO,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC,OAAO,EAAE;oBACzF,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,kBAAkB;oBAChE,MAAM,EAAE,UAAU;oBAClB,QAAQ,EAAE;wBACR,QAAQ,EAAE,GAAG,CAAC,QAAQ;wBACtB,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,UAAU,EAAE,GAAG,CAAC,UAAU;wBAC1B,MAAM,EAAE,GAAG,CAAC,MAAM;qBACnB;iBACF,CAAC,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;QAED,mCAAmC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,UAAU,CAAC,KAAa,EAAE,SAAS,GAAG,IAAI;QAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAC/B,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;QAChD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,yBAAyB;IACzB,QAAQ;QACN,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE;YACtC,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE;YAC5C,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;SACzC,CAAC;IACJ,CAAC;IAED,wBAAwB;IACxB,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAClC,CAAC;IAEO,cAAc,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;QAC5D,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,GAAG,CAAC;QAC5B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;CACF;AAtHD,oCAsHC;AAED,kBAAe,YAAY,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ReasoningBank — Experience-Based Memory Layer
3
+ *
4
+ * Complements MemoryTree with semantic retrieval and experience-based learning.
5
+ * Learns from both successful and failed routing decisions.
6
+ *
7
+ * Based on: "ReasoningBank: Scaling Agent Self-Evolving with Reasoning Memory"
8
+ * (Google Research, ICLR 2026) — github.com/google-research/reasoning-bank
9
+ *
10
+ * Architecture:
11
+ * MemoryTree (keyword, fast, free) ←→ ReasoningBank (semantic, quality-scored)
12
+ * Merge: keyword_score * 0.3 + semantic_score * 0.7
13
+ *
14
+ * Storage: JSONL files (no external vector DB required)
15
+ * Embedding: Optional — falls back to keyword search when no embedding key configured
16
+ */
17
+ export interface ReasoningMemory {
18
+ id: string;
19
+ taskId: string;
20
+ query: string;
21
+ title: string;
22
+ description: string;
23
+ content: string;
24
+ status: 'success' | 'failure';
25
+ provider: string;
26
+ cost: number;
27
+ complexity: number;
28
+ timestamp: number;
29
+ embedding?: number[];
30
+ }
31
+ export interface ReasoningBankConfig {
32
+ /** Directory to store reasoning bank files */
33
+ dataDir: string;
34
+ /** Max memories to retrieve per query */
35
+ maxResults: number;
36
+ /** Minimum similarity threshold (0-1) */
37
+ minSimilarity: number;
38
+ /** Embedding provider: 'gemini' | 'openai' | 'none' (keyword fallback) */
39
+ embeddingProvider: 'gemini' | 'openai' | 'none';
40
+ /** API key for embedding provider */
41
+ embeddingApiKey?: string;
42
+ /** Embedding model ID */
43
+ embeddingModel?: string;
44
+ }
45
+ export declare class ReasoningBank {
46
+ private config;
47
+ private memories;
48
+ private embeddings;
49
+ private loaded;
50
+ constructor(config?: Partial<ReasoningBankConfig>);
51
+ /** Load memories from disk */
52
+ load(): Promise<void>;
53
+ /** Save memories to disk */
54
+ save(): Promise<void>;
55
+ /**
56
+ * Extract a memory from a routing decision.
57
+ * Called after a query is routed and the outcome is known.
58
+ */
59
+ induceMemory(params: {
60
+ query: string;
61
+ provider: string;
62
+ cost: number;
63
+ complexity: number;
64
+ success: boolean;
65
+ reasoning?: string;
66
+ }): Promise<ReasoningMemory>;
67
+ /**
68
+ * Select relevant memories for a query.
69
+ * Uses embedding similarity if available, falls back to keyword search.
70
+ */
71
+ selectMemories(query: string): Promise<ReasoningMemory[]>;
72
+ private embed;
73
+ private embedGemini;
74
+ private embedOpenAI;
75
+ private cosineSimilarity;
76
+ private tokenize;
77
+ /** Get bank statistics */
78
+ getStats(): {
79
+ totalMemories: number;
80
+ successes: number;
81
+ failures: number;
82
+ withEmbeddings: number;
83
+ providers: string[];
84
+ };
85
+ /** Clear all memories */
86
+ clear(): Promise<void>;
87
+ }
88
+ export default ReasoningBank;
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ /**
3
+ * ReasoningBank — Experience-Based Memory Layer
4
+ *
5
+ * Complements MemoryTree with semantic retrieval and experience-based learning.
6
+ * Learns from both successful and failed routing decisions.
7
+ *
8
+ * Based on: "ReasoningBank: Scaling Agent Self-Evolving with Reasoning Memory"
9
+ * (Google Research, ICLR 2026) — github.com/google-research/reasoning-bank
10
+ *
11
+ * Architecture:
12
+ * MemoryTree (keyword, fast, free) ←→ ReasoningBank (semantic, quality-scored)
13
+ * Merge: keyword_score * 0.3 + semantic_score * 0.7
14
+ *
15
+ * Storage: JSONL files (no external vector DB required)
16
+ * Embedding: Optional — falls back to keyword search when no embedding key configured
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.ReasoningBank = void 0;
53
+ const fs = __importStar(require("fs"));
54
+ const path = __importStar(require("path"));
55
+ const DEFAULT_CONFIG = {
56
+ dataDir: './data/reasoning-bank',
57
+ maxResults: 5,
58
+ minSimilarity: 0.3,
59
+ embeddingProvider: 'none',
60
+ };
61
+ // ============================================================
62
+ // REASONING BANK
63
+ // ============================================================
64
+ class ReasoningBank {
65
+ config;
66
+ memories = [];
67
+ embeddings = new Map();
68
+ loaded = false;
69
+ constructor(config = {}) {
70
+ this.config = { ...DEFAULT_CONFIG, ...config };
71
+ }
72
+ // ----------------------------------------------------------
73
+ // STORAGE
74
+ // ----------------------------------------------------------
75
+ /** Load memories from disk */
76
+ async load() {
77
+ const bankFile = path.join(this.config.dataDir, 'memories.jsonl');
78
+ const embFile = path.join(this.config.dataDir, 'embeddings.jsonl');
79
+ this.memories = [];
80
+ this.embeddings = new Map();
81
+ try {
82
+ if (fs.existsSync(bankFile)) {
83
+ const lines = fs.readFileSync(bankFile, 'utf8').trim().split('\n').filter(Boolean);
84
+ for (const line of lines) {
85
+ try {
86
+ this.memories.push(JSON.parse(line));
87
+ }
88
+ catch { /* skip corrupt */ }
89
+ }
90
+ }
91
+ }
92
+ catch { /* no existing bank */ }
93
+ try {
94
+ if (fs.existsSync(embFile)) {
95
+ const lines = fs.readFileSync(embFile, 'utf8').trim().split('\n').filter(Boolean);
96
+ for (const line of lines) {
97
+ try {
98
+ const { id, embedding } = JSON.parse(line);
99
+ if (id && embedding)
100
+ this.embeddings.set(id, embedding);
101
+ }
102
+ catch { /* skip corrupt */ }
103
+ }
104
+ }
105
+ }
106
+ catch { /* no existing embeddings */ }
107
+ this.loaded = true;
108
+ }
109
+ /** Save memories to disk */
110
+ async save() {
111
+ if (!fs.existsSync(this.config.dataDir)) {
112
+ fs.mkdirSync(this.config.dataDir, { recursive: true });
113
+ }
114
+ const bankFile = path.join(this.config.dataDir, 'memories.jsonl');
115
+ const embFile = path.join(this.config.dataDir, 'embeddings.jsonl');
116
+ // Save memories
117
+ const memLines = this.memories.map(m => JSON.stringify(m)).join('\n');
118
+ fs.writeFileSync(bankFile, memLines + '\n');
119
+ // Save embeddings
120
+ const embLines = Array.from(this.embeddings.entries())
121
+ .map(([id, emb]) => JSON.stringify({ id, embedding: emb }))
122
+ .join('\n');
123
+ fs.writeFileSync(embFile, embLines + '\n');
124
+ }
125
+ // ----------------------------------------------------------
126
+ // MEMORY INDUCTION (Phase 3 of ReasoningBank pipeline)
127
+ // ----------------------------------------------------------
128
+ /**
129
+ * Extract a memory from a routing decision.
130
+ * Called after a query is routed and the outcome is known.
131
+ */
132
+ async induceMemory(params) {
133
+ const { query, provider, cost, complexity, success, reasoning } = params;
134
+ // Generate structured memory content
135
+ const status = success ? 'success' : 'failure';
136
+ const title = success
137
+ ? `Successful routing for: ${query.slice(0, 50)}`
138
+ : `Failed routing for: ${query.slice(0, 50)}`;
139
+ const description = success
140
+ ? `Use ${provider} for queries with complexity ${complexity.toFixed(2)} and similar patterns`
141
+ : `Avoid ${provider} for queries with complexity ${complexity.toFixed(2)} — consider higher-tier model`;
142
+ const content = reasoning || (success
143
+ ? `Routing to ${provider} (cost $${cost.toFixed(4)}) was successful for this ${complexity.toFixed(2)}-complexity query.`
144
+ : `Routing to ${provider} (cost $${cost.toFixed(4)}) underperformed for this ${complexity.toFixed(2)}-complexity query.`);
145
+ const memory = {
146
+ id: `rb_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
147
+ taskId: `task_${Date.now()}`,
148
+ query,
149
+ title,
150
+ description,
151
+ content,
152
+ status,
153
+ provider,
154
+ cost,
155
+ complexity,
156
+ timestamp: Date.now(),
157
+ };
158
+ // Generate embedding if provider is configured
159
+ if (this.config.embeddingProvider !== 'none' && this.config.embeddingApiKey) {
160
+ try {
161
+ memory.embedding = await this.embed(query);
162
+ }
163
+ catch {
164
+ // Embedding failed — memory still usable via keyword search
165
+ }
166
+ }
167
+ this.memories.push(memory);
168
+ if (memory.embedding) {
169
+ this.embeddings.set(memory.id, memory.embedding);
170
+ }
171
+ // Auto-save every 10 memories
172
+ if (this.memories.length % 10 === 0) {
173
+ await this.save();
174
+ }
175
+ return memory;
176
+ }
177
+ // ----------------------------------------------------------
178
+ // RETRIEVAL (Phase 1 of ReasoningBank pipeline)
179
+ // ----------------------------------------------------------
180
+ /**
181
+ * Select relevant memories for a query.
182
+ * Uses embedding similarity if available, falls back to keyword search.
183
+ */
184
+ async selectMemories(query) {
185
+ if (!this.loaded)
186
+ await this.load();
187
+ if (this.memories.length === 0)
188
+ return [];
189
+ // Try embedding-based retrieval first
190
+ if (this.config.embeddingProvider !== 'none' && this.config.embeddingApiKey) {
191
+ try {
192
+ const queryEmbedding = await this.embed(query);
193
+ const scored = this.memories.map(m => ({
194
+ memory: m,
195
+ score: m.embedding ? this.cosineSimilarity(queryEmbedding, m.embedding) : 0,
196
+ }));
197
+ scored.sort((a, b) => b.score - a.score);
198
+ return scored
199
+ .filter(s => s.score >= this.config.minSimilarity)
200
+ .slice(0, this.config.maxResults)
201
+ .map(s => s.memory);
202
+ }
203
+ catch {
204
+ // Fall through to keyword search
205
+ }
206
+ }
207
+ // Keyword fallback: TF-IDF style overlap
208
+ const queryWords = this.tokenize(query);
209
+ if (queryWords.length === 0)
210
+ return [];
211
+ const scored = this.memories.map(m => {
212
+ const contentWords = this.tokenize(m.query + ' ' + m.content);
213
+ const contentSet = new Set(contentWords);
214
+ const matches = queryWords.filter(w => contentSet.has(w)).length;
215
+ const score = matches / queryWords.length;
216
+ return { memory: m, score };
217
+ });
218
+ scored.sort((a, b) => b.score - a.score);
219
+ return scored
220
+ .filter(s => s.score >= this.config.minSimilarity)
221
+ .slice(0, this.config.maxResults)
222
+ .map(s => s.memory);
223
+ }
224
+ // ----------------------------------------------------------
225
+ // EMBEDDING (optional)
226
+ // ----------------------------------------------------------
227
+ async embed(text) {
228
+ if (this.config.embeddingProvider === 'gemini' && this.config.embeddingApiKey) {
229
+ return this.embedGemini(text);
230
+ }
231
+ if (this.config.embeddingProvider === 'openai' && this.config.embeddingApiKey) {
232
+ return this.embedOpenAI(text);
233
+ }
234
+ throw new Error('No embedding provider configured');
235
+ }
236
+ async embedGemini(text) {
237
+ const model = this.config.embeddingModel || 'gemini-embedding-001';
238
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:embedContent?key=${this.config.embeddingApiKey}`;
239
+ const resp = await fetch(url, {
240
+ method: 'POST',
241
+ headers: { 'Content-Type': 'application/json' },
242
+ body: JSON.stringify({
243
+ content: { parts: [{ text }] },
244
+ taskType: 'RETRIEVAL_QUERY',
245
+ }),
246
+ });
247
+ const data = await resp.json();
248
+ return data.embedding?.values || [];
249
+ }
250
+ async embedOpenAI(text) {
251
+ const model = this.config.embeddingModel || 'text-embedding-3-small';
252
+ const resp = await fetch('https://api.openai.com/v1/embeddings', {
253
+ method: 'POST',
254
+ headers: {
255
+ 'Content-Type': 'application/json',
256
+ 'Authorization': `Bearer ${this.config.embeddingApiKey}`,
257
+ },
258
+ body: JSON.stringify({ model, input: text }),
259
+ });
260
+ const data = await resp.json();
261
+ return data.data?.[0]?.embedding || [];
262
+ }
263
+ // ----------------------------------------------------------
264
+ // UTILITIES
265
+ // ----------------------------------------------------------
266
+ cosineSimilarity(a, b) {
267
+ if (a.length !== b.length || a.length === 0)
268
+ return 0;
269
+ let dot = 0, normA = 0, normB = 0;
270
+ for (let i = 0; i < a.length; i++) {
271
+ dot += a[i] * b[i];
272
+ normA += a[i] * a[i];
273
+ normB += b[i] * b[i];
274
+ }
275
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-8);
276
+ }
277
+ tokenize(text) {
278
+ return text
279
+ .toLowerCase()
280
+ .split(/\s+/)
281
+ .map(w => w.replace(/[^a-z0-9\u00C0-\u024F]/g, ''))
282
+ .filter(w => w.length > 2);
283
+ }
284
+ /** Get bank statistics */
285
+ getStats() {
286
+ return {
287
+ totalMemories: this.memories.length,
288
+ successes: this.memories.filter(m => m.status === 'success').length,
289
+ failures: this.memories.filter(m => m.status === 'failure').length,
290
+ withEmbeddings: this.embeddings.size,
291
+ providers: [...new Set(this.memories.map(m => m.provider))],
292
+ };
293
+ }
294
+ /** Clear all memories */
295
+ async clear() {
296
+ this.memories = [];
297
+ this.embeddings = new Map();
298
+ await this.save();
299
+ }
300
+ }
301
+ exports.ReasoningBank = ReasoningBank;
302
+ exports.default = ReasoningBank;
303
+ //# sourceMappingURL=reasoningBank.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasoningBank.js","sourceRoot":"","sources":["../../src/memory/reasoningBank.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adaptive-memory-multi-model-router",
3
- "version": "2.14.45",
3
+ "version": "2.14.46",
4
4
  "shortName": "A3M Router",
5
5
  "displayName": "A3M Router - Adaptive Memory Multi-Model Router",
6
6
  "description": "🥇 Cheapest LLM router on RouterArena ($0.05/1K) · 15K+ downloads in 2 weeks · Open-source AI gateway with parallel multi-LLM execution across 47+ providers, ensemble voting, semantic cache, and budget enforcement",
package/src/index.ts CHANGED
@@ -68,6 +68,14 @@ export type { BudgetConfig, SpendRecord, BudgetCheckResult } from './cost/budget
68
68
  // MEMORY
69
69
  // ============================================================
70
70
  export { MemoryTree } from './memory/memoryTree';
71
+
72
+ // ReasoningBank — experience-based memory (semantic retrieval + learning)
73
+ export { ReasoningBank } from './memory/reasoningBank';
74
+ export type { ReasoningMemory, ReasoningBankConfig } from './memory/reasoningBank';
75
+
76
+ // Hybrid Memory — merges MemoryTree (keyword) + ReasoningBank (semantic)
77
+ export { HybridMemory } from './memory/hybridMemory';
78
+ export type { HybridMemoryConfig, HybridResult } from './memory/hybridMemory';
71
79
  export type { MemoryChunk, TreeNode } from './memory/memoryTree';
72
80
 
73
81
  // ============================================================
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Hybrid Memory — Merges MemoryTree (keyword) + ReasoningBank (semantic)
3
+ *
4
+ * Provides unified search across both memory systems with configurable
5
+ * weighting. Falls back gracefully when ReasoningBank has no data or
6
+ * no embedding keys configured.
7
+ *
8
+ * Merge formula: final_score = keyword_score * w1 + semantic_score * w2
9
+ * where w1 + w2 = 1.0, configurable via config.
10
+ */
11
+
12
+ import { MemoryTree, MemoryChunk } from './memoryTree';
13
+ import { ReasoningBank, ReasoningMemory, ReasoningBankConfig } from './reasoningBank';
14
+
15
+ export interface HybridMemoryConfig {
16
+ /** Weight for MemoryTree keyword score (0-1). ReasoningBank gets (1 - this). */
17
+ keywordWeight: number;
18
+ /** ReasoningBank config */
19
+ reasoningBank: Partial<ReasoningBankConfig>;
20
+ }
21
+
22
+ const DEFAULT_CONFIG: HybridMemoryConfig = {
23
+ keywordWeight: 0.3, // 30% keyword, 70% semantic
24
+ reasoningBank: {},
25
+ };
26
+
27
+ export interface HybridResult {
28
+ id: string;
29
+ content: string;
30
+ score: number;
31
+ source: 'keyword' | 'semantic' | 'merged';
32
+ metadata?: Record<string, unknown>;
33
+ }
34
+
35
+ export class HybridMemory {
36
+ private memoryTree: MemoryTree;
37
+ private reasoningBank: ReasoningBank;
38
+ private config: HybridMemoryConfig;
39
+
40
+ constructor(config: Partial<HybridMemoryConfig> = {}) {
41
+ this.config = { ...DEFAULT_CONFIG, ...config };
42
+ this.memoryTree = new MemoryTree();
43
+ this.reasoningBank = new ReasoningBank(this.config.reasoningBank);
44
+ }
45
+
46
+ /** Initialize both memory systems */
47
+ async init(): Promise<void> {
48
+ await this.reasoningBank.load();
49
+ }
50
+
51
+ /** Add data to MemoryTree (fast, always works) */
52
+ async add(data: string): Promise<void> {
53
+ await this.memoryTree.add(data);
54
+ }
55
+
56
+ /** Induce a memory in ReasoningBank from a routing decision */
57
+ async learnFromDecision(params: {
58
+ query: string;
59
+ provider: string;
60
+ cost: number;
61
+ complexity: number;
62
+ success: boolean;
63
+ reasoning?: string;
64
+ }): Promise<void> {
65
+ await this.reasoningBank.induceMemory(params);
66
+ }
67
+
68
+ /**
69
+ * Unified search across both memory systems.
70
+ * Returns merged, deduplicated results sorted by relevance.
71
+ */
72
+ async search(query: string, topK = 10): Promise<HybridResult[]> {
73
+ const results: HybridResult[] = [];
74
+ const seen = new Set<string>();
75
+
76
+ // 1. MemoryTree keyword search (always available)
77
+ const keywordResults = this.memoryTree.search(query, topK * 2);
78
+ for (const chunk of keywordResults) {
79
+ const score = this.normalizeScore(chunk.score, 0, 1);
80
+ results.push({
81
+ id: chunk.id,
82
+ content: chunk.content,
83
+ score: score * this.config.keywordWeight,
84
+ source: 'keyword',
85
+ metadata: { accessCount: chunk.accessCount, depth: chunk.depth },
86
+ });
87
+ seen.add(chunk.id);
88
+ }
89
+
90
+ // 2. ReasoningBank semantic search (if available)
91
+ try {
92
+ const semanticResults = await this.reasoningBank.selectMemories(query);
93
+ for (const mem of semanticResults) {
94
+ if (seen.has(mem.id)) continue;
95
+ results.push({
96
+ id: mem.id,
97
+ content: `[${mem.status.toUpperCase()}] ${mem.title}\n${mem.description}\n${mem.content}`,
98
+ score: 0.7 * (1 - this.config.keywordWeight), // semantic weight
99
+ source: 'semantic',
100
+ metadata: {
101
+ provider: mem.provider,
102
+ cost: mem.cost,
103
+ complexity: mem.complexity,
104
+ status: mem.status,
105
+ },
106
+ });
107
+ seen.add(mem.id);
108
+ }
109
+ } catch {
110
+ // ReasoningBank unavailable — keyword results still returned
111
+ }
112
+
113
+ // 3. Sort by score and return topK
114
+ results.sort((a, b) => b.score - a.score);
115
+ return results.slice(0, topK);
116
+ }
117
+
118
+ /** Get context string for router injection */
119
+ async getContext(query: string, maxTokens = 3000): Promise<string> {
120
+ const results = await this.search(query, 5);
121
+ if (results.length === 0) return '';
122
+
123
+ const parts = results.map((r, i) => {
124
+ const prefix = r.source === 'semantic' ? `[Experience] ` : '';
125
+ return `${prefix}${r.content}`;
126
+ });
127
+
128
+ let context = parts.join('\n\n');
129
+ if (context.length > maxTokens) {
130
+ context = context.slice(0, maxTokens) + '...';
131
+ }
132
+ return context;
133
+ }
134
+
135
+ /** Get combined stats */
136
+ getStats() {
137
+ return {
138
+ memoryTree: this.memoryTree.getStats(),
139
+ reasoningBank: this.reasoningBank.getStats(),
140
+ keywordWeight: this.config.keywordWeight,
141
+ };
142
+ }
143
+
144
+ /** Save both systems */
145
+ async save(): Promise<void> {
146
+ await this.reasoningBank.save();
147
+ }
148
+
149
+ private normalizeScore(score: number, min: number, max: number): number {
150
+ if (max === min) return 0.5;
151
+ return Math.min(1, Math.max(0, (score - min) / (max - min)));
152
+ }
153
+ }
154
+
155
+ export default HybridMemory;
@@ -0,0 +1,335 @@
1
+ /**
2
+ * ReasoningBank — Experience-Based Memory Layer
3
+ *
4
+ * Complements MemoryTree with semantic retrieval and experience-based learning.
5
+ * Learns from both successful and failed routing decisions.
6
+ *
7
+ * Based on: "ReasoningBank: Scaling Agent Self-Evolving with Reasoning Memory"
8
+ * (Google Research, ICLR 2026) — github.com/google-research/reasoning-bank
9
+ *
10
+ * Architecture:
11
+ * MemoryTree (keyword, fast, free) ←→ ReasoningBank (semantic, quality-scored)
12
+ * Merge: keyword_score * 0.3 + semantic_score * 0.7
13
+ *
14
+ * Storage: JSONL files (no external vector DB required)
15
+ * Embedding: Optional — falls back to keyword search when no embedding key configured
16
+ */
17
+
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+
21
+ // ============================================================
22
+ // TYPES
23
+ // ============================================================
24
+
25
+ export interface ReasoningMemory {
26
+ id: string;
27
+ taskId: string;
28
+ query: string;
29
+ title: string;
30
+ description: string; // when to use this memory
31
+ content: string; // 1-3 sentences of actionable insight
32
+ status: 'success' | 'failure';
33
+ provider: string; // which model was used
34
+ cost: number; // actual cost of the routing decision
35
+ complexity: number; // query complexity at time of routing
36
+ timestamp: number;
37
+ embedding?: number[]; // optional embedding vector
38
+ }
39
+
40
+ export interface ReasoningBankConfig {
41
+ /** Directory to store reasoning bank files */
42
+ dataDir: string;
43
+ /** Max memories to retrieve per query */
44
+ maxResults: number;
45
+ /** Minimum similarity threshold (0-1) */
46
+ minSimilarity: number;
47
+ /** Embedding provider: 'gemini' | 'openai' | 'none' (keyword fallback) */
48
+ embeddingProvider: 'gemini' | 'openai' | 'none';
49
+ /** API key for embedding provider */
50
+ embeddingApiKey?: string;
51
+ /** Embedding model ID */
52
+ embeddingModel?: string;
53
+ }
54
+
55
+ const DEFAULT_CONFIG: ReasoningBankConfig = {
56
+ dataDir: './data/reasoning-bank',
57
+ maxResults: 5,
58
+ minSimilarity: 0.3,
59
+ embeddingProvider: 'none',
60
+ };
61
+
62
+ // ============================================================
63
+ // REASONING BANK
64
+ // ============================================================
65
+
66
+ export class ReasoningBank {
67
+ private config: ReasoningBankConfig;
68
+ private memories: ReasoningMemory[] = [];
69
+ private embeddings: Map<string, number[]> = new Map();
70
+ private loaded = false;
71
+
72
+ constructor(config: Partial<ReasoningBankConfig> = {}) {
73
+ this.config = { ...DEFAULT_CONFIG, ...config };
74
+ }
75
+
76
+ // ----------------------------------------------------------
77
+ // STORAGE
78
+ // ----------------------------------------------------------
79
+
80
+ /** Load memories from disk */
81
+ async load(): Promise<void> {
82
+ const bankFile = path.join(this.config.dataDir, 'memories.jsonl');
83
+ const embFile = path.join(this.config.dataDir, 'embeddings.jsonl');
84
+
85
+ this.memories = [];
86
+ this.embeddings = new Map();
87
+
88
+ try {
89
+ if (fs.existsSync(bankFile)) {
90
+ const lines = fs.readFileSync(bankFile, 'utf8').trim().split('\n').filter(Boolean);
91
+ for (const line of lines) {
92
+ try { this.memories.push(JSON.parse(line)); } catch { /* skip corrupt */ }
93
+ }
94
+ }
95
+ } catch { /* no existing bank */ }
96
+
97
+ try {
98
+ if (fs.existsSync(embFile)) {
99
+ const lines = fs.readFileSync(embFile, 'utf8').trim().split('\n').filter(Boolean);
100
+ for (const line of lines) {
101
+ try {
102
+ const { id, embedding } = JSON.parse(line);
103
+ if (id && embedding) this.embeddings.set(id, embedding);
104
+ } catch { /* skip corrupt */ }
105
+ }
106
+ }
107
+ } catch { /* no existing embeddings */ }
108
+
109
+ this.loaded = true;
110
+ }
111
+
112
+ /** Save memories to disk */
113
+ async save(): Promise<void> {
114
+ if (!fs.existsSync(this.config.dataDir)) {
115
+ fs.mkdirSync(this.config.dataDir, { recursive: true });
116
+ }
117
+
118
+ const bankFile = path.join(this.config.dataDir, 'memories.jsonl');
119
+ const embFile = path.join(this.config.dataDir, 'embeddings.jsonl');
120
+
121
+ // Save memories
122
+ const memLines = this.memories.map(m => JSON.stringify(m)).join('\n');
123
+ fs.writeFileSync(bankFile, memLines + '\n');
124
+
125
+ // Save embeddings
126
+ const embLines = Array.from(this.embeddings.entries())
127
+ .map(([id, emb]) => JSON.stringify({ id, embedding: emb }))
128
+ .join('\n');
129
+ fs.writeFileSync(embFile, embLines + '\n');
130
+ }
131
+
132
+ // ----------------------------------------------------------
133
+ // MEMORY INDUCTION (Phase 3 of ReasoningBank pipeline)
134
+ // ----------------------------------------------------------
135
+
136
+ /**
137
+ * Extract a memory from a routing decision.
138
+ * Called after a query is routed and the outcome is known.
139
+ */
140
+ async induceMemory(params: {
141
+ query: string;
142
+ provider: string;
143
+ cost: number;
144
+ complexity: number;
145
+ success: boolean;
146
+ reasoning?: string;
147
+ }): Promise<ReasoningMemory> {
148
+ const { query, provider, cost, complexity, success, reasoning } = params;
149
+
150
+ // Generate structured memory content
151
+ const status = success ? 'success' : 'failure';
152
+ const title = success
153
+ ? `Successful routing for: ${query.slice(0, 50)}`
154
+ : `Failed routing for: ${query.slice(0, 50)}`;
155
+
156
+ const description = success
157
+ ? `Use ${provider} for queries with complexity ${complexity.toFixed(2)} and similar patterns`
158
+ : `Avoid ${provider} for queries with complexity ${complexity.toFixed(2)} — consider higher-tier model`;
159
+
160
+ const content = reasoning || (success
161
+ ? `Routing to ${provider} (cost $${cost.toFixed(4)}) was successful for this ${complexity.toFixed(2)}-complexity query.`
162
+ : `Routing to ${provider} (cost $${cost.toFixed(4)}) underperformed for this ${complexity.toFixed(2)}-complexity query.`);
163
+
164
+ const memory: ReasoningMemory = {
165
+ id: `rb_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
166
+ taskId: `task_${Date.now()}`,
167
+ query,
168
+ title,
169
+ description,
170
+ content,
171
+ status,
172
+ provider,
173
+ cost,
174
+ complexity,
175
+ timestamp: Date.now(),
176
+ };
177
+
178
+ // Generate embedding if provider is configured
179
+ if (this.config.embeddingProvider !== 'none' && this.config.embeddingApiKey) {
180
+ try {
181
+ memory.embedding = await this.embed(query);
182
+ } catch {
183
+ // Embedding failed — memory still usable via keyword search
184
+ }
185
+ }
186
+
187
+ this.memories.push(memory);
188
+ if (memory.embedding) {
189
+ this.embeddings.set(memory.id, memory.embedding);
190
+ }
191
+
192
+ // Auto-save every 10 memories
193
+ if (this.memories.length % 10 === 0) {
194
+ await this.save();
195
+ }
196
+
197
+ return memory;
198
+ }
199
+
200
+ // ----------------------------------------------------------
201
+ // RETRIEVAL (Phase 1 of ReasoningBank pipeline)
202
+ // ----------------------------------------------------------
203
+
204
+ /**
205
+ * Select relevant memories for a query.
206
+ * Uses embedding similarity if available, falls back to keyword search.
207
+ */
208
+ async selectMemories(query: string): Promise<ReasoningMemory[]> {
209
+ if (!this.loaded) await this.load();
210
+
211
+ if (this.memories.length === 0) return [];
212
+
213
+ // Try embedding-based retrieval first
214
+ if (this.config.embeddingProvider !== 'none' && this.config.embeddingApiKey) {
215
+ try {
216
+ const queryEmbedding = await this.embed(query);
217
+ const scored = this.memories.map(m => ({
218
+ memory: m,
219
+ score: m.embedding ? this.cosineSimilarity(queryEmbedding, m.embedding) : 0,
220
+ }));
221
+ scored.sort((a, b) => b.score - a.score);
222
+ return scored
223
+ .filter(s => s.score >= this.config.minSimilarity)
224
+ .slice(0, this.config.maxResults)
225
+ .map(s => s.memory);
226
+ } catch {
227
+ // Fall through to keyword search
228
+ }
229
+ }
230
+
231
+ // Keyword fallback: TF-IDF style overlap
232
+ const queryWords = this.tokenize(query);
233
+ if (queryWords.length === 0) return [];
234
+
235
+ const scored = this.memories.map(m => {
236
+ const contentWords = this.tokenize(m.query + ' ' + m.content);
237
+ const contentSet = new Set(contentWords);
238
+ const matches = queryWords.filter(w => contentSet.has(w)).length;
239
+ const score = matches / queryWords.length;
240
+ return { memory: m, score };
241
+ });
242
+
243
+ scored.sort((a, b) => b.score - a.score);
244
+ return scored
245
+ .filter(s => s.score >= this.config.minSimilarity)
246
+ .slice(0, this.config.maxResults)
247
+ .map(s => s.memory);
248
+ }
249
+
250
+ // ----------------------------------------------------------
251
+ // EMBEDDING (optional)
252
+ // ----------------------------------------------------------
253
+
254
+ private async embed(text: string): Promise<number[]> {
255
+ if (this.config.embeddingProvider === 'gemini' && this.config.embeddingApiKey) {
256
+ return this.embedGemini(text);
257
+ }
258
+ if (this.config.embeddingProvider === 'openai' && this.config.embeddingApiKey) {
259
+ return this.embedOpenAI(text);
260
+ }
261
+ throw new Error('No embedding provider configured');
262
+ }
263
+
264
+ private async embedGemini(text: string): Promise<number[]> {
265
+ const model = this.config.embeddingModel || 'gemini-embedding-001';
266
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:embedContent?key=${this.config.embeddingApiKey}`;
267
+ const resp = await fetch(url, {
268
+ method: 'POST',
269
+ headers: { 'Content-Type': 'application/json' },
270
+ body: JSON.stringify({
271
+ content: { parts: [{ text }] },
272
+ taskType: 'RETRIEVAL_QUERY',
273
+ }),
274
+ });
275
+ const data = await resp.json() as any;
276
+ return data.embedding?.values || [];
277
+ }
278
+
279
+ private async embedOpenAI(text: string): Promise<number[]> {
280
+ const model = this.config.embeddingModel || 'text-embedding-3-small';
281
+ const resp = await fetch('https://api.openai.com/v1/embeddings', {
282
+ method: 'POST',
283
+ headers: {
284
+ 'Content-Type': 'application/json',
285
+ 'Authorization': `Bearer ${this.config.embeddingApiKey}`,
286
+ },
287
+ body: JSON.stringify({ model, input: text }),
288
+ });
289
+ const data = await resp.json() as any;
290
+ return data.data?.[0]?.embedding || [];
291
+ }
292
+
293
+ // ----------------------------------------------------------
294
+ // UTILITIES
295
+ // ----------------------------------------------------------
296
+
297
+ private cosineSimilarity(a: number[], b: number[]): number {
298
+ if (a.length !== b.length || a.length === 0) return 0;
299
+ let dot = 0, normA = 0, normB = 0;
300
+ for (let i = 0; i < a.length; i++) {
301
+ dot += a[i] * b[i];
302
+ normA += a[i] * a[i];
303
+ normB += b[i] * b[i];
304
+ }
305
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-8);
306
+ }
307
+
308
+ private tokenize(text: string): string[] {
309
+ return text
310
+ .toLowerCase()
311
+ .split(/\s+/)
312
+ .map(w => w.replace(/[^a-z0-9\u00C0-\u024F]/g, ''))
313
+ .filter(w => w.length > 2);
314
+ }
315
+
316
+ /** Get bank statistics */
317
+ getStats() {
318
+ return {
319
+ totalMemories: this.memories.length,
320
+ successes: this.memories.filter(m => m.status === 'success').length,
321
+ failures: this.memories.filter(m => m.status === 'failure').length,
322
+ withEmbeddings: this.embeddings.size,
323
+ providers: [...new Set(this.memories.map(m => m.provider))],
324
+ };
325
+ }
326
+
327
+ /** Clear all memories */
328
+ async clear(): Promise<void> {
329
+ this.memories = [];
330
+ this.embeddings = new Map();
331
+ await this.save();
332
+ }
333
+ }
334
+
335
+ export default ReasoningBank;