@claude-flow/memory 3.0.0-alpha.1
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/.agentic-flow/intelligence.json +16 -0
- package/README.md +249 -0
- package/__tests__/coverage/base.css +224 -0
- package/__tests__/coverage/block-navigation.js +87 -0
- package/__tests__/coverage/coverage-final.json +19 -0
- package/__tests__/coverage/favicon.png +0 -0
- package/__tests__/coverage/index.html +206 -0
- package/__tests__/coverage/lcov-report/base.css +224 -0
- package/__tests__/coverage/lcov-report/block-navigation.js +87 -0
- package/__tests__/coverage/lcov-report/favicon.png +0 -0
- package/__tests__/coverage/lcov-report/index.html +206 -0
- package/__tests__/coverage/lcov-report/prettify.css +1 -0
- package/__tests__/coverage/lcov-report/prettify.js +2 -0
- package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/lcov-report/sorter.js +210 -0
- package/__tests__/coverage/lcov-report/src/agentdb-adapter.ts.html +2737 -0
- package/__tests__/coverage/lcov-report/src/agentdb-backend.ts.html +3130 -0
- package/__tests__/coverage/lcov-report/src/application/commands/delete-memory.command.ts.html +601 -0
- package/__tests__/coverage/lcov-report/src/application/commands/index.html +131 -0
- package/__tests__/coverage/lcov-report/src/application/commands/store-memory.command.ts.html +394 -0
- package/__tests__/coverage/lcov-report/src/application/queries/index.html +116 -0
- package/__tests__/coverage/lcov-report/src/application/queries/search-memory.query.ts.html +796 -0
- package/__tests__/coverage/lcov-report/src/application/services/index.html +116 -0
- package/__tests__/coverage/lcov-report/src/application/services/memory-application-service.ts.html +793 -0
- package/__tests__/coverage/lcov-report/src/cache-manager.ts.html +1633 -0
- package/__tests__/coverage/lcov-report/src/database-provider.ts.html +1618 -0
- package/__tests__/coverage/lcov-report/src/domain/entities/index.html +116 -0
- package/__tests__/coverage/lcov-report/src/domain/entities/memory-entry.ts.html +952 -0
- package/__tests__/coverage/lcov-report/src/domain/services/index.html +116 -0
- package/__tests__/coverage/lcov-report/src/domain/services/memory-domain-service.ts.html +1294 -0
- package/__tests__/coverage/lcov-report/src/hnsw-index.ts.html +3124 -0
- package/__tests__/coverage/lcov-report/src/hybrid-backend.ts.html +2167 -0
- package/__tests__/coverage/lcov-report/src/index.html +266 -0
- package/__tests__/coverage/lcov-report/src/infrastructure/repositories/hybrid-memory-repository.ts.html +1633 -0
- package/__tests__/coverage/lcov-report/src/infrastructure/repositories/index.html +116 -0
- package/__tests__/coverage/lcov-report/src/migration.ts.html +2092 -0
- package/__tests__/coverage/lcov-report/src/query-builder.ts.html +1711 -0
- package/__tests__/coverage/lcov-report/src/sqlite-backend.ts.html +2281 -0
- package/__tests__/coverage/lcov-report/src/sqljs-backend.ts.html +2374 -0
- package/__tests__/coverage/lcov-report/src/types.ts.html +2266 -0
- package/__tests__/coverage/lcov.info +10238 -0
- package/__tests__/coverage/prettify.css +1 -0
- package/__tests__/coverage/prettify.js +2 -0
- package/__tests__/coverage/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/sorter.js +210 -0
- package/__tests__/coverage/src/agentdb-adapter.ts.html +2737 -0
- package/__tests__/coverage/src/agentdb-backend.ts.html +3130 -0
- package/__tests__/coverage/src/application/commands/delete-memory.command.ts.html +601 -0
- package/__tests__/coverage/src/application/commands/index.html +131 -0
- package/__tests__/coverage/src/application/commands/store-memory.command.ts.html +394 -0
- package/__tests__/coverage/src/application/queries/index.html +116 -0
- package/__tests__/coverage/src/application/queries/search-memory.query.ts.html +796 -0
- package/__tests__/coverage/src/application/services/index.html +116 -0
- package/__tests__/coverage/src/application/services/memory-application-service.ts.html +793 -0
- package/__tests__/coverage/src/cache-manager.ts.html +1633 -0
- package/__tests__/coverage/src/database-provider.ts.html +1618 -0
- package/__tests__/coverage/src/domain/entities/index.html +116 -0
- package/__tests__/coverage/src/domain/entities/memory-entry.ts.html +952 -0
- package/__tests__/coverage/src/domain/services/index.html +116 -0
- package/__tests__/coverage/src/domain/services/memory-domain-service.ts.html +1294 -0
- package/__tests__/coverage/src/hnsw-index.ts.html +3124 -0
- package/__tests__/coverage/src/hybrid-backend.ts.html +2167 -0
- package/__tests__/coverage/src/index.html +266 -0
- package/__tests__/coverage/src/infrastructure/repositories/hybrid-memory-repository.ts.html +1633 -0
- package/__tests__/coverage/src/infrastructure/repositories/index.html +116 -0
- package/__tests__/coverage/src/migration.ts.html +2092 -0
- package/__tests__/coverage/src/query-builder.ts.html +1711 -0
- package/__tests__/coverage/src/sqlite-backend.ts.html +2281 -0
- package/__tests__/coverage/src/sqljs-backend.ts.html +2374 -0
- package/__tests__/coverage/src/types.ts.html +2266 -0
- package/benchmarks/cache-hit-rate.bench.ts +535 -0
- package/benchmarks/hnsw-indexing.bench.ts +552 -0
- package/benchmarks/memory-write.bench.ts +469 -0
- package/benchmarks/vector-search.bench.ts +449 -0
- package/dist/agentdb-adapter.d.ts +146 -0
- package/dist/agentdb-adapter.d.ts.map +1 -0
- package/dist/agentdb-adapter.js +679 -0
- package/dist/agentdb-adapter.js.map +1 -0
- package/dist/agentdb-backend.d.ts +214 -0
- package/dist/agentdb-backend.d.ts.map +1 -0
- package/dist/agentdb-backend.js +827 -0
- package/dist/agentdb-backend.js.map +1 -0
- package/dist/agentdb-backend.test.d.ts +7 -0
- package/dist/agentdb-backend.test.d.ts.map +1 -0
- package/dist/agentdb-backend.test.js +258 -0
- package/dist/agentdb-backend.test.js.map +1 -0
- package/dist/application/commands/delete-memory.command.d.ts +65 -0
- package/dist/application/commands/delete-memory.command.d.ts.map +1 -0
- package/dist/application/commands/delete-memory.command.js +129 -0
- package/dist/application/commands/delete-memory.command.js.map +1 -0
- package/dist/application/commands/store-memory.command.d.ts +48 -0
- package/dist/application/commands/store-memory.command.d.ts.map +1 -0
- package/dist/application/commands/store-memory.command.js +72 -0
- package/dist/application/commands/store-memory.command.js.map +1 -0
- package/dist/application/index.d.ts +12 -0
- package/dist/application/index.d.ts.map +1 -0
- package/dist/application/index.js +15 -0
- package/dist/application/index.js.map +1 -0
- package/dist/application/queries/search-memory.query.d.ts +72 -0
- package/dist/application/queries/search-memory.query.d.ts.map +1 -0
- package/dist/application/queries/search-memory.query.js +143 -0
- package/dist/application/queries/search-memory.query.js.map +1 -0
- package/dist/application/services/memory-application-service.d.ts +121 -0
- package/dist/application/services/memory-application-service.d.ts.map +1 -0
- package/dist/application/services/memory-application-service.js +190 -0
- package/dist/application/services/memory-application-service.js.map +1 -0
- package/dist/cache-manager.d.ts +134 -0
- package/dist/cache-manager.d.ts.map +1 -0
- package/dist/cache-manager.js +407 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/database-provider.d.ts +86 -0
- package/dist/database-provider.d.ts.map +1 -0
- package/dist/database-provider.js +385 -0
- package/dist/database-provider.js.map +1 -0
- package/dist/database-provider.test.d.ts +7 -0
- package/dist/database-provider.test.d.ts.map +1 -0
- package/dist/database-provider.test.js +285 -0
- package/dist/database-provider.test.js.map +1 -0
- package/dist/domain/entities/memory-entry.d.ts +143 -0
- package/dist/domain/entities/memory-entry.d.ts.map +1 -0
- package/dist/domain/entities/memory-entry.js +226 -0
- package/dist/domain/entities/memory-entry.js.map +1 -0
- package/dist/domain/index.d.ts +11 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +12 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/repositories/memory-repository.interface.d.ts +102 -0
- package/dist/domain/repositories/memory-repository.interface.d.ts.map +1 -0
- package/dist/domain/repositories/memory-repository.interface.js +11 -0
- package/dist/domain/repositories/memory-repository.interface.js.map +1 -0
- package/dist/domain/services/memory-domain-service.d.ts +105 -0
- package/dist/domain/services/memory-domain-service.d.ts.map +1 -0
- package/dist/domain/services/memory-domain-service.js +297 -0
- package/dist/domain/services/memory-domain-service.js.map +1 -0
- package/dist/hnsw-index.d.ts +111 -0
- package/dist/hnsw-index.d.ts.map +1 -0
- package/dist/hnsw-index.js +781 -0
- package/dist/hnsw-index.js.map +1 -0
- package/dist/hybrid-backend.d.ts +217 -0
- package/dist/hybrid-backend.d.ts.map +1 -0
- package/dist/hybrid-backend.js +491 -0
- package/dist/hybrid-backend.js.map +1 -0
- package/dist/hybrid-backend.test.d.ts +8 -0
- package/dist/hybrid-backend.test.d.ts.map +1 -0
- package/dist/hybrid-backend.test.js +320 -0
- package/dist/hybrid-backend.test.js.map +1 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +345 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/index.d.ts +17 -0
- package/dist/infrastructure/index.d.ts.map +1 -0
- package/dist/infrastructure/index.js +16 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/infrastructure/repositories/hybrid-memory-repository.d.ts +66 -0
- package/dist/infrastructure/repositories/hybrid-memory-repository.d.ts.map +1 -0
- package/dist/infrastructure/repositories/hybrid-memory-repository.js +409 -0
- package/dist/infrastructure/repositories/hybrid-memory-repository.js.map +1 -0
- package/dist/migration.d.ts +68 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +513 -0
- package/dist/migration.js.map +1 -0
- package/dist/query-builder.d.ts +211 -0
- package/dist/query-builder.d.ts.map +1 -0
- package/dist/query-builder.js +438 -0
- package/dist/query-builder.js.map +1 -0
- package/dist/sqlite-backend.d.ts +121 -0
- package/dist/sqlite-backend.d.ts.map +1 -0
- package/dist/sqlite-backend.js +564 -0
- package/dist/sqlite-backend.js.map +1 -0
- package/dist/sqljs-backend.d.ts +128 -0
- package/dist/sqljs-backend.d.ts.map +1 -0
- package/dist/sqljs-backend.js +598 -0
- package/dist/sqljs-backend.js.map +1 -0
- package/dist/types.d.ts +481 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +58 -0
- package/dist/types.js.map +1 -0
- package/docs/AGENTDB-INTEGRATION.md +388 -0
- package/docs/CROSS_PLATFORM.md +505 -0
- package/docs/WINDOWS_SUPPORT.md +422 -0
- package/examples/agentdb-example.ts +345 -0
- package/examples/cross-platform-usage.ts +326 -0
- package/framework/benchmark.ts +112 -0
- package/package.json +31 -0
- package/src/agentdb-adapter.ts +884 -0
- package/src/agentdb-backend.test.ts +339 -0
- package/src/agentdb-backend.ts +1016 -0
- package/src/application/commands/delete-memory.command.ts +172 -0
- package/src/application/commands/store-memory.command.ts +103 -0
- package/src/application/index.ts +36 -0
- package/src/application/queries/search-memory.query.ts +237 -0
- package/src/application/services/memory-application-service.ts +236 -0
- package/src/cache-manager.ts +516 -0
- package/src/database-provider.test.ts +364 -0
- package/src/database-provider.ts +511 -0
- package/src/domain/entities/memory-entry.ts +289 -0
- package/src/domain/index.ts +35 -0
- package/src/domain/repositories/memory-repository.interface.ts +120 -0
- package/src/domain/services/memory-domain-service.ts +403 -0
- package/src/hnsw-index.ts +1013 -0
- package/src/hybrid-backend.test.ts +399 -0
- package/src/hybrid-backend.ts +694 -0
- package/src/index.ts +515 -0
- package/src/infrastructure/index.ts +23 -0
- package/src/infrastructure/repositories/hybrid-memory-repository.ts +516 -0
- package/src/migration.ts +669 -0
- package/src/query-builder.ts +542 -0
- package/src/sqlite-backend.ts +732 -0
- package/src/sqljs-backend.ts +763 -0
- package/src/types.ts +727 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/verify-cross-platform.ts +170 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HybridBackend - Combines SQLite (structured queries) + AgentDB (vector search)
|
|
3
|
+
*
|
|
4
|
+
* Per ADR-009: "HybridBackend (SQLite + AgentDB) as default"
|
|
5
|
+
* - SQLite for: Structured queries, ACID transactions, exact matches
|
|
6
|
+
* - AgentDB for: Semantic search, vector similarity, RAG
|
|
7
|
+
*
|
|
8
|
+
* @module v3/memory/hybrid-backend
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from 'node:events';
|
|
12
|
+
import {
|
|
13
|
+
IMemoryBackend,
|
|
14
|
+
MemoryEntry,
|
|
15
|
+
MemoryEntryInput,
|
|
16
|
+
MemoryEntryUpdate,
|
|
17
|
+
MemoryQuery,
|
|
18
|
+
SearchOptions,
|
|
19
|
+
SearchResult,
|
|
20
|
+
BackendStats,
|
|
21
|
+
HealthCheckResult,
|
|
22
|
+
ComponentHealth,
|
|
23
|
+
EmbeddingGenerator,
|
|
24
|
+
createDefaultEntry,
|
|
25
|
+
QueryType,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
import { SQLiteBackend, SQLiteBackendConfig } from './sqlite-backend.js';
|
|
28
|
+
import { AgentDBBackend, AgentDBBackendConfig } from './agentdb-backend.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Configuration for HybridBackend
|
|
32
|
+
*/
|
|
33
|
+
export interface HybridBackendConfig {
|
|
34
|
+
/** SQLite configuration */
|
|
35
|
+
sqlite?: Partial<SQLiteBackendConfig>;
|
|
36
|
+
|
|
37
|
+
/** AgentDB configuration */
|
|
38
|
+
agentdb?: Partial<AgentDBBackendConfig>;
|
|
39
|
+
|
|
40
|
+
/** Default namespace */
|
|
41
|
+
defaultNamespace?: string;
|
|
42
|
+
|
|
43
|
+
/** Embedding generator function */
|
|
44
|
+
embeddingGenerator?: EmbeddingGenerator;
|
|
45
|
+
|
|
46
|
+
/** Query routing strategy */
|
|
47
|
+
routingStrategy?: 'auto' | 'sqlite-first' | 'agentdb-first';
|
|
48
|
+
|
|
49
|
+
/** Enable dual-write (write to both backends) */
|
|
50
|
+
dualWrite?: boolean;
|
|
51
|
+
|
|
52
|
+
/** Semantic search threshold for hybrid queries */
|
|
53
|
+
semanticThreshold?: number;
|
|
54
|
+
|
|
55
|
+
/** Maximum results to fetch from each backend in hybrid queries */
|
|
56
|
+
hybridMaxResults?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default configuration
|
|
61
|
+
*/
|
|
62
|
+
const DEFAULT_CONFIG: Required<HybridBackendConfig> = {
|
|
63
|
+
sqlite: {},
|
|
64
|
+
agentdb: {},
|
|
65
|
+
defaultNamespace: 'default',
|
|
66
|
+
embeddingGenerator: undefined as any,
|
|
67
|
+
routingStrategy: 'auto',
|
|
68
|
+
dualWrite: true,
|
|
69
|
+
semanticThreshold: 0.7,
|
|
70
|
+
hybridMaxResults: 100,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Structured Query Interface
|
|
75
|
+
* Optimized for SQLite's strengths
|
|
76
|
+
*/
|
|
77
|
+
export interface StructuredQuery {
|
|
78
|
+
/** Exact key match */
|
|
79
|
+
key?: string;
|
|
80
|
+
|
|
81
|
+
/** Key prefix match */
|
|
82
|
+
keyPrefix?: string;
|
|
83
|
+
|
|
84
|
+
/** Namespace filter */
|
|
85
|
+
namespace?: string;
|
|
86
|
+
|
|
87
|
+
/** Owner filter */
|
|
88
|
+
ownerId?: string;
|
|
89
|
+
|
|
90
|
+
/** Type filter */
|
|
91
|
+
type?: string;
|
|
92
|
+
|
|
93
|
+
/** Time range filters */
|
|
94
|
+
createdAfter?: number;
|
|
95
|
+
createdBefore?: number;
|
|
96
|
+
updatedAfter?: number;
|
|
97
|
+
updatedBefore?: number;
|
|
98
|
+
|
|
99
|
+
/** Pagination */
|
|
100
|
+
limit?: number;
|
|
101
|
+
offset?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Semantic Query Interface
|
|
106
|
+
* Optimized for AgentDB's vector search
|
|
107
|
+
*/
|
|
108
|
+
export interface SemanticQuery {
|
|
109
|
+
/** Content to search for (will be embedded) */
|
|
110
|
+
content?: string;
|
|
111
|
+
|
|
112
|
+
/** Pre-computed embedding */
|
|
113
|
+
embedding?: Float32Array;
|
|
114
|
+
|
|
115
|
+
/** Number of results */
|
|
116
|
+
k?: number;
|
|
117
|
+
|
|
118
|
+
/** Similarity threshold (0-1) */
|
|
119
|
+
threshold?: number;
|
|
120
|
+
|
|
121
|
+
/** Additional filters */
|
|
122
|
+
filters?: Partial<MemoryQuery>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Hybrid Query Interface
|
|
127
|
+
* Combines structured + semantic search
|
|
128
|
+
*/
|
|
129
|
+
export interface HybridQuery {
|
|
130
|
+
/** Semantic component */
|
|
131
|
+
semantic: SemanticQuery;
|
|
132
|
+
|
|
133
|
+
/** Structured component */
|
|
134
|
+
structured?: StructuredQuery;
|
|
135
|
+
|
|
136
|
+
/** How to combine results */
|
|
137
|
+
combineStrategy?: 'union' | 'intersection' | 'semantic-first' | 'structured-first';
|
|
138
|
+
|
|
139
|
+
/** Weights for score combination */
|
|
140
|
+
weights?: {
|
|
141
|
+
semantic: number;
|
|
142
|
+
structured: number;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* HybridBackend Implementation
|
|
148
|
+
*
|
|
149
|
+
* Intelligently routes queries between SQLite and AgentDB:
|
|
150
|
+
* - Exact matches, prefix queries → SQLite
|
|
151
|
+
* - Semantic search, similarity → AgentDB
|
|
152
|
+
* - Complex hybrid queries → Both backends with intelligent merging
|
|
153
|
+
*/
|
|
154
|
+
export class HybridBackend extends EventEmitter implements IMemoryBackend {
|
|
155
|
+
private sqlite: SQLiteBackend;
|
|
156
|
+
private agentdb: AgentDBBackend;
|
|
157
|
+
private config: Required<HybridBackendConfig>;
|
|
158
|
+
private initialized: boolean = false;
|
|
159
|
+
|
|
160
|
+
// Performance tracking
|
|
161
|
+
private stats = {
|
|
162
|
+
sqliteQueries: 0,
|
|
163
|
+
agentdbQueries: 0,
|
|
164
|
+
hybridQueries: 0,
|
|
165
|
+
totalQueryTime: 0,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
constructor(config: HybridBackendConfig = {}) {
|
|
169
|
+
super();
|
|
170
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
171
|
+
|
|
172
|
+
// Initialize SQLite backend
|
|
173
|
+
this.sqlite = new SQLiteBackend({
|
|
174
|
+
...this.config.sqlite,
|
|
175
|
+
defaultNamespace: this.config.defaultNamespace,
|
|
176
|
+
embeddingGenerator: this.config.embeddingGenerator,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Initialize AgentDB backend
|
|
180
|
+
this.agentdb = new AgentDBBackend({
|
|
181
|
+
...this.config.agentdb,
|
|
182
|
+
namespace: this.config.defaultNamespace,
|
|
183
|
+
embeddingGenerator: this.config.embeddingGenerator,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Forward events from both backends
|
|
187
|
+
this.sqlite.on('entry:stored', (data) => this.emit('sqlite:stored', data));
|
|
188
|
+
this.sqlite.on('entry:updated', (data) => this.emit('sqlite:updated', data));
|
|
189
|
+
this.sqlite.on('entry:deleted', (data) => this.emit('sqlite:deleted', data));
|
|
190
|
+
|
|
191
|
+
this.agentdb.on('entry:stored', (data) => this.emit('agentdb:stored', data));
|
|
192
|
+
this.agentdb.on('entry:updated', (data) => this.emit('agentdb:updated', data));
|
|
193
|
+
this.agentdb.on('entry:deleted', (data) => this.emit('agentdb:deleted', data));
|
|
194
|
+
this.agentdb.on('cache:hit', (data) => this.emit('cache:hit', data));
|
|
195
|
+
this.agentdb.on('cache:miss', (data) => this.emit('cache:miss', data));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Initialize both backends
|
|
200
|
+
*/
|
|
201
|
+
async initialize(): Promise<void> {
|
|
202
|
+
if (this.initialized) return;
|
|
203
|
+
|
|
204
|
+
await Promise.all([this.sqlite.initialize(), this.agentdb.initialize()]);
|
|
205
|
+
|
|
206
|
+
this.initialized = true;
|
|
207
|
+
this.emit('initialized');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Shutdown both backends
|
|
212
|
+
*/
|
|
213
|
+
async shutdown(): Promise<void> {
|
|
214
|
+
if (!this.initialized) return;
|
|
215
|
+
|
|
216
|
+
await Promise.all([this.sqlite.shutdown(), this.agentdb.shutdown()]);
|
|
217
|
+
|
|
218
|
+
this.initialized = false;
|
|
219
|
+
this.emit('shutdown');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Store in both backends (dual-write for consistency)
|
|
224
|
+
*/
|
|
225
|
+
async store(entry: MemoryEntry): Promise<void> {
|
|
226
|
+
if (this.config.dualWrite) {
|
|
227
|
+
// Write to both backends in parallel
|
|
228
|
+
await Promise.all([this.sqlite.store(entry), this.agentdb.store(entry)]);
|
|
229
|
+
} else {
|
|
230
|
+
// Write to primary backend only (AgentDB has vector search)
|
|
231
|
+
await this.agentdb.store(entry);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.emit('entry:stored', { id: entry.id });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get from AgentDB (has caching enabled)
|
|
239
|
+
*/
|
|
240
|
+
async get(id: string): Promise<MemoryEntry | null> {
|
|
241
|
+
return this.agentdb.get(id);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get by key (SQLite optimized for exact matches)
|
|
246
|
+
*/
|
|
247
|
+
async getByKey(namespace: string, key: string): Promise<MemoryEntry | null> {
|
|
248
|
+
return this.sqlite.getByKey(namespace, key);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Update in both backends
|
|
253
|
+
*/
|
|
254
|
+
async update(id: string, update: MemoryEntryUpdate): Promise<MemoryEntry | null> {
|
|
255
|
+
if (this.config.dualWrite) {
|
|
256
|
+
// Update both backends
|
|
257
|
+
const [sqliteResult, agentdbResult] = await Promise.all([
|
|
258
|
+
this.sqlite.update(id, update),
|
|
259
|
+
this.agentdb.update(id, update),
|
|
260
|
+
]);
|
|
261
|
+
return agentdbResult || sqliteResult;
|
|
262
|
+
} else {
|
|
263
|
+
return this.agentdb.update(id, update);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Delete from both backends
|
|
269
|
+
*/
|
|
270
|
+
async delete(id: string): Promise<boolean> {
|
|
271
|
+
if (this.config.dualWrite) {
|
|
272
|
+
const [sqliteResult, agentdbResult] = await Promise.all([
|
|
273
|
+
this.sqlite.delete(id),
|
|
274
|
+
this.agentdb.delete(id),
|
|
275
|
+
]);
|
|
276
|
+
return sqliteResult || agentdbResult;
|
|
277
|
+
} else {
|
|
278
|
+
return this.agentdb.delete(id);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Query routing - semantic goes to AgentDB, structured to SQLite
|
|
284
|
+
*/
|
|
285
|
+
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
286
|
+
const startTime = performance.now();
|
|
287
|
+
|
|
288
|
+
let results: MemoryEntry[];
|
|
289
|
+
|
|
290
|
+
// Route based on query type
|
|
291
|
+
switch (query.type) {
|
|
292
|
+
case 'exact':
|
|
293
|
+
// SQLite optimized for exact matches
|
|
294
|
+
this.stats.sqliteQueries++;
|
|
295
|
+
results = await this.sqlite.query(query);
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
case 'prefix':
|
|
299
|
+
// SQLite optimized for prefix queries
|
|
300
|
+
this.stats.sqliteQueries++;
|
|
301
|
+
results = await this.sqlite.query(query);
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
case 'tag':
|
|
305
|
+
// Both can handle tags, use SQLite for structured filtering
|
|
306
|
+
this.stats.sqliteQueries++;
|
|
307
|
+
results = await this.sqlite.query(query);
|
|
308
|
+
break;
|
|
309
|
+
|
|
310
|
+
case 'semantic':
|
|
311
|
+
// AgentDB optimized for semantic search
|
|
312
|
+
this.stats.agentdbQueries++;
|
|
313
|
+
results = await this.agentdb.query(query);
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'hybrid':
|
|
317
|
+
// Use hybrid query combining both backends
|
|
318
|
+
this.stats.hybridQueries++;
|
|
319
|
+
results = await this.queryHybridInternal(query);
|
|
320
|
+
break;
|
|
321
|
+
|
|
322
|
+
default:
|
|
323
|
+
// Auto-routing based on query properties
|
|
324
|
+
results = await this.autoRoute(query);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const duration = performance.now() - startTime;
|
|
328
|
+
this.stats.totalQueryTime += duration;
|
|
329
|
+
|
|
330
|
+
this.emit('query:completed', { type: query.type, duration, count: results.length });
|
|
331
|
+
return results;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Structured queries (SQL)
|
|
336
|
+
* Routes to SQLite for optimal performance
|
|
337
|
+
*/
|
|
338
|
+
async queryStructured(query: StructuredQuery): Promise<MemoryEntry[]> {
|
|
339
|
+
this.stats.sqliteQueries++;
|
|
340
|
+
|
|
341
|
+
const memoryQuery: MemoryQuery = {
|
|
342
|
+
type: query.key ? 'exact' : query.keyPrefix ? 'prefix' : 'hybrid',
|
|
343
|
+
key: query.key,
|
|
344
|
+
keyPrefix: query.keyPrefix,
|
|
345
|
+
namespace: query.namespace,
|
|
346
|
+
ownerId: query.ownerId,
|
|
347
|
+
memoryType: query.type as any,
|
|
348
|
+
createdAfter: query.createdAfter,
|
|
349
|
+
createdBefore: query.createdBefore,
|
|
350
|
+
updatedAfter: query.updatedAfter,
|
|
351
|
+
updatedBefore: query.updatedBefore,
|
|
352
|
+
limit: query.limit || 100,
|
|
353
|
+
offset: query.offset || 0,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
return this.sqlite.query(memoryQuery);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Semantic queries (vector)
|
|
361
|
+
* Routes to AgentDB for HNSW-based vector search
|
|
362
|
+
*/
|
|
363
|
+
async querySemantic(query: SemanticQuery): Promise<MemoryEntry[]> {
|
|
364
|
+
this.stats.agentdbQueries++;
|
|
365
|
+
|
|
366
|
+
let embedding = query.embedding;
|
|
367
|
+
|
|
368
|
+
// Generate embedding if content provided
|
|
369
|
+
if (!embedding && query.content && this.config.embeddingGenerator) {
|
|
370
|
+
embedding = await this.config.embeddingGenerator(query.content);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!embedding) {
|
|
374
|
+
throw new Error('SemanticQuery requires either content or embedding');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const searchResults = await this.agentdb.search(embedding, {
|
|
378
|
+
k: query.k || 10,
|
|
379
|
+
threshold: query.threshold || this.config.semanticThreshold,
|
|
380
|
+
filters: query.filters as MemoryQuery | undefined,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return searchResults.map((r) => r.entry);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Hybrid queries (combine both)
|
|
388
|
+
* Intelligently merges results from both backends
|
|
389
|
+
*/
|
|
390
|
+
async queryHybrid(query: HybridQuery): Promise<MemoryEntry[]> {
|
|
391
|
+
this.stats.hybridQueries++;
|
|
392
|
+
|
|
393
|
+
const strategy = query.combineStrategy || 'semantic-first';
|
|
394
|
+
const weights = query.weights || { semantic: 0.7, structured: 0.3 };
|
|
395
|
+
|
|
396
|
+
// Execute both queries in parallel
|
|
397
|
+
const [semanticResults, structuredResults] = await Promise.all([
|
|
398
|
+
this.querySemantic(query.semantic),
|
|
399
|
+
query.structured ? this.queryStructured(query.structured) : Promise.resolve([]),
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
// Combine results based on strategy
|
|
403
|
+
switch (strategy) {
|
|
404
|
+
case 'union':
|
|
405
|
+
return this.combineUnion(semanticResults, structuredResults);
|
|
406
|
+
|
|
407
|
+
case 'intersection':
|
|
408
|
+
return this.combineIntersection(semanticResults, structuredResults);
|
|
409
|
+
|
|
410
|
+
case 'semantic-first':
|
|
411
|
+
return this.combineSemanticFirst(semanticResults, structuredResults);
|
|
412
|
+
|
|
413
|
+
case 'structured-first':
|
|
414
|
+
return this.combineStructuredFirst(semanticResults, structuredResults);
|
|
415
|
+
|
|
416
|
+
default:
|
|
417
|
+
return this.combineUnion(semanticResults, structuredResults);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Semantic vector search (routes to AgentDB)
|
|
423
|
+
*/
|
|
424
|
+
async search(embedding: Float32Array, options: SearchOptions): Promise<SearchResult[]> {
|
|
425
|
+
this.stats.agentdbQueries++;
|
|
426
|
+
return this.agentdb.search(embedding, options);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Bulk insert to both backends
|
|
431
|
+
*/
|
|
432
|
+
async bulkInsert(entries: MemoryEntry[]): Promise<void> {
|
|
433
|
+
if (this.config.dualWrite) {
|
|
434
|
+
await Promise.all([this.sqlite.bulkInsert(entries), this.agentdb.bulkInsert(entries)]);
|
|
435
|
+
} else {
|
|
436
|
+
await this.agentdb.bulkInsert(entries);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Bulk delete from both backends
|
|
442
|
+
*/
|
|
443
|
+
async bulkDelete(ids: string[]): Promise<number> {
|
|
444
|
+
if (this.config.dualWrite) {
|
|
445
|
+
const [sqliteCount, agentdbCount] = await Promise.all([
|
|
446
|
+
this.sqlite.bulkDelete(ids),
|
|
447
|
+
this.agentdb.bulkDelete(ids),
|
|
448
|
+
]);
|
|
449
|
+
return Math.max(sqliteCount, agentdbCount);
|
|
450
|
+
} else {
|
|
451
|
+
return this.agentdb.bulkDelete(ids);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Count entries (use SQLite for efficiency)
|
|
457
|
+
*/
|
|
458
|
+
async count(namespace?: string): Promise<number> {
|
|
459
|
+
return this.sqlite.count(namespace);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* List namespaces (use SQLite)
|
|
464
|
+
*/
|
|
465
|
+
async listNamespaces(): Promise<string[]> {
|
|
466
|
+
return this.sqlite.listNamespaces();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Clear namespace in both backends
|
|
471
|
+
*/
|
|
472
|
+
async clearNamespace(namespace: string): Promise<number> {
|
|
473
|
+
if (this.config.dualWrite) {
|
|
474
|
+
const [sqliteCount, agentdbCount] = await Promise.all([
|
|
475
|
+
this.sqlite.clearNamespace(namespace),
|
|
476
|
+
this.agentdb.clearNamespace(namespace),
|
|
477
|
+
]);
|
|
478
|
+
return Math.max(sqliteCount, agentdbCount);
|
|
479
|
+
} else {
|
|
480
|
+
return this.agentdb.clearNamespace(namespace);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get combined statistics from both backends
|
|
486
|
+
*/
|
|
487
|
+
async getStats(): Promise<BackendStats> {
|
|
488
|
+
const [sqliteStats, agentdbStats] = await Promise.all([
|
|
489
|
+
this.sqlite.getStats(),
|
|
490
|
+
this.agentdb.getStats(),
|
|
491
|
+
]);
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
totalEntries: Math.max(sqliteStats.totalEntries, agentdbStats.totalEntries),
|
|
495
|
+
entriesByNamespace: agentdbStats.entriesByNamespace,
|
|
496
|
+
entriesByType: agentdbStats.entriesByType,
|
|
497
|
+
memoryUsage: sqliteStats.memoryUsage + agentdbStats.memoryUsage,
|
|
498
|
+
hnswStats: agentdbStats.hnswStats,
|
|
499
|
+
cacheStats: agentdbStats.cacheStats,
|
|
500
|
+
avgQueryTime:
|
|
501
|
+
this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.agentdbQueries > 0
|
|
502
|
+
? this.stats.totalQueryTime /
|
|
503
|
+
(this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.agentdbQueries)
|
|
504
|
+
: 0,
|
|
505
|
+
avgSearchTime: agentdbStats.avgSearchTime,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Health check for both backends
|
|
511
|
+
*/
|
|
512
|
+
async healthCheck(): Promise<HealthCheckResult> {
|
|
513
|
+
const [sqliteHealth, agentdbHealth] = await Promise.all([
|
|
514
|
+
this.sqlite.healthCheck(),
|
|
515
|
+
this.agentdb.healthCheck(),
|
|
516
|
+
]);
|
|
517
|
+
|
|
518
|
+
const allIssues = [...sqliteHealth.issues, ...agentdbHealth.issues];
|
|
519
|
+
const allRecommendations = [
|
|
520
|
+
...sqliteHealth.recommendations,
|
|
521
|
+
...agentdbHealth.recommendations,
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
// Determine overall status
|
|
525
|
+
let status: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
|
|
526
|
+
if (
|
|
527
|
+
sqliteHealth.status === 'unhealthy' ||
|
|
528
|
+
agentdbHealth.status === 'unhealthy'
|
|
529
|
+
) {
|
|
530
|
+
status = 'unhealthy';
|
|
531
|
+
} else if (
|
|
532
|
+
sqliteHealth.status === 'degraded' ||
|
|
533
|
+
agentdbHealth.status === 'degraded'
|
|
534
|
+
) {
|
|
535
|
+
status = 'degraded';
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
status,
|
|
540
|
+
components: {
|
|
541
|
+
storage: sqliteHealth.components.storage,
|
|
542
|
+
index: agentdbHealth.components.index,
|
|
543
|
+
cache: agentdbHealth.components.cache,
|
|
544
|
+
},
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
issues: allIssues,
|
|
547
|
+
recommendations: allRecommendations,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ===== Private Methods =====
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Auto-route queries based on properties
|
|
555
|
+
*/
|
|
556
|
+
private async autoRoute(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
557
|
+
// If has embedding or content, use semantic search (AgentDB)
|
|
558
|
+
const hasEmbeddingGenerator = typeof this.config.embeddingGenerator === 'function';
|
|
559
|
+
if (query.embedding || (query.content && hasEmbeddingGenerator)) {
|
|
560
|
+
this.stats.agentdbQueries++;
|
|
561
|
+
return this.agentdb.query(query);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// If has exact key or prefix, use structured search (SQLite)
|
|
565
|
+
if (query.key || query.keyPrefix) {
|
|
566
|
+
this.stats.sqliteQueries++;
|
|
567
|
+
return this.sqlite.query(query);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// For other filters, use routing strategy
|
|
571
|
+
switch (this.config.routingStrategy) {
|
|
572
|
+
case 'sqlite-first':
|
|
573
|
+
this.stats.sqliteQueries++;
|
|
574
|
+
return this.sqlite.query(query);
|
|
575
|
+
|
|
576
|
+
case 'agentdb-first':
|
|
577
|
+
this.stats.agentdbQueries++;
|
|
578
|
+
return this.agentdb.query(query);
|
|
579
|
+
|
|
580
|
+
case 'auto':
|
|
581
|
+
default:
|
|
582
|
+
// Default to AgentDB (has caching)
|
|
583
|
+
this.stats.agentdbQueries++;
|
|
584
|
+
return this.agentdb.query(query);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Internal hybrid query implementation
|
|
590
|
+
*/
|
|
591
|
+
private async queryHybridInternal(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
592
|
+
// If semantic component exists, use hybrid
|
|
593
|
+
if (query.embedding || query.content) {
|
|
594
|
+
const semanticQuery: SemanticQuery = {
|
|
595
|
+
content: query.content,
|
|
596
|
+
embedding: query.embedding,
|
|
597
|
+
k: query.limit || 10,
|
|
598
|
+
threshold: query.threshold,
|
|
599
|
+
filters: query,
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const structuredQuery: StructuredQuery = {
|
|
603
|
+
namespace: query.namespace,
|
|
604
|
+
key: query.key,
|
|
605
|
+
keyPrefix: query.keyPrefix,
|
|
606
|
+
ownerId: query.ownerId,
|
|
607
|
+
type: query.memoryType,
|
|
608
|
+
createdAfter: query.createdAfter,
|
|
609
|
+
createdBefore: query.createdBefore,
|
|
610
|
+
updatedAfter: query.updatedAfter,
|
|
611
|
+
updatedBefore: query.updatedBefore,
|
|
612
|
+
limit: query.limit,
|
|
613
|
+
offset: query.offset,
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
return this.queryHybrid({
|
|
617
|
+
semantic: semanticQuery,
|
|
618
|
+
structured: structuredQuery,
|
|
619
|
+
combineStrategy: 'semantic-first',
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Otherwise, route to structured
|
|
624
|
+
return this.autoRoute(query);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Combine results using union (all unique results)
|
|
629
|
+
*/
|
|
630
|
+
private combineUnion(
|
|
631
|
+
semanticResults: MemoryEntry[],
|
|
632
|
+
structuredResults: MemoryEntry[]
|
|
633
|
+
): MemoryEntry[] {
|
|
634
|
+
const seen = new Set<string>();
|
|
635
|
+
const combined: MemoryEntry[] = [];
|
|
636
|
+
|
|
637
|
+
for (const entry of [...semanticResults, ...structuredResults]) {
|
|
638
|
+
if (!seen.has(entry.id)) {
|
|
639
|
+
seen.add(entry.id);
|
|
640
|
+
combined.push(entry);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return combined;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Combine results using intersection (only common results)
|
|
649
|
+
*/
|
|
650
|
+
private combineIntersection(
|
|
651
|
+
semanticResults: MemoryEntry[],
|
|
652
|
+
structuredResults: MemoryEntry[]
|
|
653
|
+
): MemoryEntry[] {
|
|
654
|
+
const semanticIds = new Set(semanticResults.map((e) => e.id));
|
|
655
|
+
return structuredResults.filter((e) => semanticIds.has(e.id));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Semantic-first: Prefer semantic results, add structured if not present
|
|
660
|
+
*/
|
|
661
|
+
private combineSemanticFirst(
|
|
662
|
+
semanticResults: MemoryEntry[],
|
|
663
|
+
structuredResults: MemoryEntry[]
|
|
664
|
+
): MemoryEntry[] {
|
|
665
|
+
const semanticIds = new Set(semanticResults.map((e) => e.id));
|
|
666
|
+
const additional = structuredResults.filter((e) => !semanticIds.has(e.id));
|
|
667
|
+
return [...semanticResults, ...additional];
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Structured-first: Prefer structured results, add semantic if not present
|
|
672
|
+
*/
|
|
673
|
+
private combineStructuredFirst(
|
|
674
|
+
semanticResults: MemoryEntry[],
|
|
675
|
+
structuredResults: MemoryEntry[]
|
|
676
|
+
): MemoryEntry[] {
|
|
677
|
+
const structuredIds = new Set(structuredResults.map((e) => e.id));
|
|
678
|
+
const additional = semanticResults.filter((e) => !structuredIds.has(e.id));
|
|
679
|
+
return [...structuredResults, ...additional];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Get underlying backends for advanced operations
|
|
684
|
+
*/
|
|
685
|
+
getSQLiteBackend(): SQLiteBackend {
|
|
686
|
+
return this.sqlite;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
getAgentDBBackend(): AgentDBBackend {
|
|
690
|
+
return this.agentdb;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export default HybridBackend;
|