11import { describe , it , expect , vi } from 'vitest'
22import { Agent } from '../../agent/agent.js'
33import { MemoryManager } from '../memory-manager.js'
4- import type { KnowledgeStore , KnowledgeEntry } from '../types.js'
5-
6- function createMockStore ( entries : KnowledgeEntry [ ] = [ ] , writable = false ) : KnowledgeStore {
7- const store : KnowledgeStore = {
8- search : vi . fn ( ) . mockResolvedValue ( entries ) ,
4+ import type { MemoryStore , MemoryEntry } from '../types.js'
5+
6+ function createMockStore (
7+ name : string ,
8+ options ?: { entries ?: MemoryEntry [ ] ; writable ?: boolean ; description ?: string ; limit ?: number }
9+ ) : MemoryStore {
10+ const store : MemoryStore = {
11+ name,
12+ ...( options ?. description && { description : options . description } ) ,
13+ ...( options ?. limit != null && { limit : options . limit } ) ,
14+ search : vi . fn ( ) . mockResolvedValue ( options ?. entries ?? [ ] ) ,
915 }
10- if ( writable ) {
16+ if ( options ?. writable ) {
1117 store . add = vi . fn ( ) . mockResolvedValue ( undefined )
1218 }
1319 return store
@@ -20,77 +26,102 @@ describe('MemoryManager', () => {
2026 } )
2127
2228 it ( 'creates instance with valid config' , ( ) => {
23- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( ) } ] } )
29+ const mm = new MemoryManager ( { stores : [ createMockStore ( 'test' ) ] } )
2430 expect ( mm . name ) . toBe ( 'strands:memory-manager' )
2531 } )
32+
33+ it ( 'throws when storeToolConfig references non-existent store' , ( ) => {
34+ expect (
35+ ( ) =>
36+ new MemoryManager ( {
37+ stores : [ createMockStore ( 'a' ) ] ,
38+ storeToolConfig : { stores : [ 'nonexistent' ] } ,
39+ } )
40+ ) . toThrow ( "store 'nonexistent' not found" )
41+ } )
42+
43+ it ( 'throws when storeToolConfig targets no writable stores' , ( ) => {
44+ expect (
45+ ( ) =>
46+ new MemoryManager ( {
47+ stores : [ createMockStore ( 'a' ) ] ,
48+ storeToolConfig : true ,
49+ } )
50+ ) . toThrow ( 'storeToolConfig targets no writable stores' )
51+ } )
52+
53+ it ( 'throws when storeToolConfig is true with multiple writable stores and no explicit stores' , ( ) => {
54+ expect (
55+ ( ) =>
56+ new MemoryManager ( {
57+ stores : [ createMockStore ( 'a' , { writable : true } ) , createMockStore ( 'b' , { writable : true } ) ] ,
58+ storeToolConfig : true ,
59+ } )
60+ ) . toThrow ( 'must specify `stores` when multiple writable stores are configured' )
61+ } )
62+
63+ it ( 'allows storeToolConfig true with single writable store' , ( ) => {
64+ const mm = new MemoryManager ( {
65+ stores : [ createMockStore ( 'a' , { writable : true } ) ] ,
66+ storeToolConfig : true ,
67+ } )
68+ expect ( mm . getTools ( ) . map ( ( t ) => t . name ) ) . toContain ( 'store_memory' )
69+ } )
2670 } )
2771
2872 describe ( 'getTools' , ( ) => {
2973 it ( 'registers search tool by default' , ( ) => {
30- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( ) } ] } )
74+ const mm = new MemoryManager ( { stores : [ createMockStore ( 'test' ) ] } )
3175 const tools = mm . getTools ( )
3276 expect ( tools ) . toHaveLength ( 1 )
3377 expect ( tools [ 0 ] ! . name ) . toBe ( 'search_memory' )
3478 } )
3579
36- it ( 'registers both search and store tools when a writable store exists' , ( ) => {
37- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( [ ] , true ) } ] } )
80+ it ( 'registers store tool when storeToolConfig is enabled' , ( ) => {
81+ const mm = new MemoryManager ( {
82+ stores : [ createMockStore ( 'test' , { writable : true } ) ] ,
83+ storeToolConfig : true ,
84+ } )
3885 const tools = mm . getTools ( )
39- expect ( tools ) . toHaveLength ( 2 )
4086 expect ( tools . map ( ( t ) => t . name ) ) . toStrictEqual ( [ 'search_memory' , 'store_memory' ] )
4187 } )
4288
43- it ( 'does not register store tool when no writable stores exist ' , ( ) => {
44- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( ) } ] } )
89+ it ( 'does not register store tool by default ' , ( ) => {
90+ const mm = new MemoryManager ( { stores : [ createMockStore ( 'test' , { writable : true } ) ] } )
4591 const tools = mm . getTools ( )
4692 expect ( tools . map ( ( t ) => t . name ) ) . toStrictEqual ( [ 'search_memory' ] )
4793 } )
4894
49- it ( 'returns empty array when includeTools is false' , ( ) => {
50- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( [ ] , true ) } ] , includeTools : false } )
51- expect ( mm . getTools ( ) ) . toStrictEqual ( [ ] )
52- } )
53-
54- it ( 'respects ToolsConfig to disable search' , ( ) => {
55- const mm = new MemoryManager ( {
56- stores : [ { store : createMockStore ( [ ] , true ) } ] ,
57- includeTools : { search : false } ,
58- } )
59- const tools = mm . getTools ( )
60- expect ( tools . map ( ( t ) => t . name ) ) . toStrictEqual ( [ 'store_memory' ] )
61- } )
62-
63- it ( 'respects ToolsConfig to disable store' , ( ) => {
95+ it ( 'returns empty array when searchToolConfig is false and storeToolConfig is false' , ( ) => {
6496 const mm = new MemoryManager ( {
65- stores : [ { store : createMockStore ( [ ] , true ) } ] ,
66- includeTools : { store : false } ,
97+ stores : [ createMockStore ( 'test' , { writable : true } ) ] ,
98+ searchToolConfig : false ,
99+ storeToolConfig : false ,
67100 } )
68- const tools = mm . getTools ( )
69- expect ( tools . map ( ( t ) => t . name ) ) . toStrictEqual ( [ 'search_memory' ] )
101+ expect ( mm . getTools ( ) ) . toStrictEqual ( [ ] )
70102 } )
71103
72- it ( 'uses custom tool names from MemoryToolsConfig ' , ( ) => {
104+ it ( 'uses custom tool names from MemoryToolConfig ' , ( ) => {
73105 const mm = new MemoryManager ( {
74- stores : [ { store : createMockStore ( [ ] , true ) } ] ,
75- includeTools : { search : { name : 'recall' } , store : { name : 'remember' } } ,
106+ stores : [ createMockStore ( 'test' , { writable : true } ) ] ,
107+ searchToolConfig : { name : 'recall' } ,
108+ storeToolConfig : { name : 'remember' , stores : [ 'test' ] } ,
76109 } )
77110 const tools = mm . getTools ( )
78111 expect ( tools . map ( ( t ) => t . name ) ) . toStrictEqual ( [ 'recall' , 'remember' ] )
79112 } )
80113
81- it ( 'includes store descriptions in search tool description when stores are named' , ( ) => {
82- const mm = new MemoryManager ( {
83- stores : [ { store : createMockStore ( ) , name : 'personal' , description : 'User preferences' } ] ,
84- } )
114+ it ( 'includes store descriptions in search tool description' , ( ) => {
115+ const store = createMockStore ( 'personal' , { description : 'User preferences' } )
116+ const mm = new MemoryManager ( { stores : [ store ] } )
85117 const tools = mm . getTools ( )
86118 expect ( tools [ 0 ] ! . description ) . toContain ( 'personal: User preferences' )
87119 expect ( tools [ 0 ] ! . description ) . toContain ( 'target one or more memory stores by name' )
88120 } )
89121
90- it ( 'includes store descriptions in store tool description when writable stores are named' , ( ) => {
91- const mm = new MemoryManager ( {
92- stores : [ { store : createMockStore ( [ ] , true ) , name : 'notes' , description : 'Personal notes' } ] ,
93- } )
122+ it ( 'includes store descriptions in store tool description' , ( ) => {
123+ const store = createMockStore ( 'notes' , { writable : true , description : 'Personal notes' } )
124+ const mm = new MemoryManager ( { stores : [ store ] , storeToolConfig : true } )
94125 const tools = mm . getTools ( )
95126 const storeTool = tools . find ( ( t ) => t . name === 'store_memory' ) !
96127 expect ( storeTool . description ) . toContain ( 'notes: Personal notes' )
@@ -100,150 +131,133 @@ describe('MemoryManager', () => {
100131
101132 describe ( 'search' , ( ) => {
102133 it ( 'queries all stores and concatenates results' , async ( ) => {
103- const store1 = createMockStore ( [ { id : '1' , content : 'fact one' } ] )
104- const store2 = createMockStore ( [ { id : '2' , content : 'fact two' } ] )
105- const mm = new MemoryManager ( { stores : [ { store : store1 } , { store : store2 } ] } )
134+ const store1 = createMockStore ( 'a' , { entries : [ { content : 'fact one' } ] } )
135+ const store2 = createMockStore ( 'b' , { entries : [ { content : 'fact two' } ] } )
136+ const mm = new MemoryManager ( { stores : [ store1 , store2 ] } )
106137
107138 const results = await mm . search ( 'query' )
108- expect ( results ) . toStrictEqual ( [
109- { id : '1' , content : 'fact one' } ,
110- { id : '2' , content : 'fact two' } ,
111- ] )
139+ expect ( results ) . toStrictEqual ( [ { content : 'fact one' } , { content : 'fact two' } ] )
112140 } )
113141
114142 it ( 'passes limit to each store' , async ( ) => {
115- const store = createMockStore ( )
116- const mm = new MemoryManager ( { stores : [ { store, limit : 5 } ] } )
143+ const store = createMockStore ( 'a' , { limit : 5 } )
144+ const mm = new MemoryManager ( { stores : [ store ] } )
117145
118146 await mm . search ( 'query' )
119147 expect ( store . search ) . toHaveBeenCalledWith ( 'query' , { limit : 5 } )
120148 } )
121149
122150 it ( 'overrides per-store limit with options.limit' , async ( ) => {
123- const store = createMockStore ( )
124- const mm = new MemoryManager ( { stores : [ { store, limit : 5 } ] } )
151+ const store = createMockStore ( 'a' , { limit : 5 } )
152+ const mm = new MemoryManager ( { stores : [ store ] } )
125153
126- await mm . search ( 'query' , { limit : 3 } )
127- expect ( store . search ) . toHaveBeenCalledWith ( 'query' , { limit : 3 } )
154+ await mm . search ( 'query' , { limit : 2 } )
155+ expect ( store . search ) . toHaveBeenCalledWith ( 'query' , { limit : 2 } )
128156 } )
129157
130- it ( 'defaults to limit of 10 when no limit configured' , async ( ) => {
131- const store = createMockStore ( )
132- const mm = new MemoryManager ( { stores : [ { store } ] } )
158+ it ( 'defaults to limit of 3 when no limit configured' , async ( ) => {
159+ const store = createMockStore ( 'a' )
160+ const mm = new MemoryManager ( { stores : [ store ] } )
133161
134162 await mm . search ( 'query' )
135- expect ( store . search ) . toHaveBeenCalledWith ( 'query' , { limit : 10 } )
163+ expect ( store . search ) . toHaveBeenCalledWith ( 'query' , { limit : 3 } )
136164 } )
137165
138166 it ( 'filters to named stores when options.stores is provided' , async ( ) => {
139- const store1 = createMockStore ( [ { id : '1' , content : 'personal fact' } ] )
140- const store2 = createMockStore ( [ { id : '2' , content : 'team fact' } ] )
141- const mm = new MemoryManager ( {
142- stores : [
143- { store : store1 , name : 'personal' } ,
144- { store : store2 , name : 'team' } ,
145- ] ,
146- } )
167+ const store1 = createMockStore ( 'personal' , { entries : [ { content : 'personal fact' } ] } )
168+ const store2 = createMockStore ( 'team' , { entries : [ { content : 'team fact' } ] } )
169+ const mm = new MemoryManager ( { stores : [ store1 , store2 ] } )
147170
148171 const results = await mm . search ( 'query' , { stores : [ 'personal' ] } )
149- expect ( results ) . toStrictEqual ( [ { id : '1' , content : 'personal fact' } ] )
172+ expect ( results ) . toStrictEqual ( [ { content : 'personal fact' } ] )
150173 expect ( store2 . search ) . not . toHaveBeenCalled ( )
151174 } )
152175
153176 it ( 'gracefully handles store failures' , async ( ) => {
154- const store1 : KnowledgeStore = { search : vi . fn ( ) . mockRejectedValue ( new Error ( 'network error' ) ) }
155- const store2 = createMockStore ( [ { id : '2' , content : 'fact' } ] )
156- const mm = new MemoryManager ( { stores : [ { store : store1 } , { store : store2 } ] } )
177+ const store1 : MemoryStore = { name : 'failing' , search : vi . fn ( ) . mockRejectedValue ( new Error ( 'network error' ) ) }
178+ const store2 = createMockStore ( 'ok' , { entries : [ { content : 'fact' } ] } )
179+ const mm = new MemoryManager ( { stores : [ store1 , store2 ] } )
157180
158181 const results = await mm . search ( 'query' )
159- expect ( results ) . toStrictEqual ( [ { id : '2' , content : 'fact' } ] )
182+ expect ( results ) . toStrictEqual ( [ { content : 'fact' } ] )
160183 } )
161184 } )
162185
163186 describe ( 'store' , ( ) => {
164187 it ( 'writes to all writable stores' , async ( ) => {
165- const store1 = createMockStore ( [ ] , true )
166- const store2 = createMockStore ( [ ] , true )
167- const mm = new MemoryManager ( { stores : [ { store : store1 } , { store : store2 } ] } )
188+ const store1 = createMockStore ( 'a' , { writable : true } )
189+ const store2 = createMockStore ( 'b' , { writable : true } )
190+ const mm = new MemoryManager ( { stores : [ store1 , store2 ] } )
168191
169192 await mm . store ( 'user likes coffee' )
170193 expect ( store1 . add ) . toHaveBeenCalledWith ( 'user likes coffee' , undefined )
171194 expect ( store2 . add ) . toHaveBeenCalledWith ( 'user likes coffee' , undefined )
172195 } )
173196
174197 it ( 'passes metadata to stores' , async ( ) => {
175- const store = createMockStore ( [ ] , true )
176- const mm = new MemoryManager ( { stores : [ { store } ] } )
198+ const store = createMockStore ( 'a' , { writable : true } )
199+ const mm = new MemoryManager ( { stores : [ store ] } )
177200
178201 await mm . store ( 'fact' , { metadata : { source : 'user' } } )
179202 expect ( store . add ) . toHaveBeenCalledWith ( 'fact' , { source : 'user' } )
180203 } )
181204
182205 it ( 'filters to named stores when options.stores is provided' , async ( ) => {
183- const store1 = createMockStore ( [ ] , true )
184- const store2 = createMockStore ( [ ] , true )
185- const mm = new MemoryManager ( {
186- stores : [
187- { store : store1 , name : 'personal' } ,
188- { store : store2 , name : 'team' } ,
189- ] ,
190- } )
206+ const store1 = createMockStore ( 'personal' , { writable : true } )
207+ const store2 = createMockStore ( 'team' , { writable : true } )
208+ const mm = new MemoryManager ( { stores : [ store1 , store2 ] } )
191209
192210 await mm . store ( 'my preference' , { stores : [ 'personal' ] } )
193211 expect ( store1 . add ) . toHaveBeenCalledWith ( 'my preference' , undefined )
194212 expect ( store2 . add ) . not . toHaveBeenCalled ( )
195213 } )
196214
197- it ( 'throws when no writable stores are configured' , async ( ) => {
198- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( ) } ] } )
199- await expect ( mm . store ( 'fact' ) ) . rejects . toThrow ( 'no writable store configured' )
200- } )
201-
202- it ( 'throws when named stores filter matches no writable stores' , async ( ) => {
203- const store = createMockStore ( [ ] , true )
204- const mm = new MemoryManager ( { stores : [ { store, name : 'personal' } ] } )
205- await expect ( mm . store ( 'fact' , { stores : [ 'nonexistent' ] } ) ) . rejects . toThrow ( 'no writable store configured' )
215+ it ( 'throws when no writable stores match' , async ( ) => {
216+ const mm = new MemoryManager ( { stores : [ createMockStore ( 'a' ) ] } )
217+ await expect ( mm . store ( 'fact' ) ) . rejects . toThrow ( 'no writable store matched' )
206218 } )
207219
208220 it ( 'succeeds with partial write failures (some stores fail, some succeed)' , async ( ) => {
209- const store1 : KnowledgeStore = {
221+ const store1 : MemoryStore = {
222+ name : 'failing' ,
210223 search : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
211224 add : vi . fn ( ) . mockRejectedValue ( new Error ( 'write error' ) ) ,
212225 }
213- const store2 = createMockStore ( [ ] , true )
214- const mm = new MemoryManager ( { stores : [ { store : store1 } , { store : store2 } ] } )
226+ const store2 = createMockStore ( 'ok' , { writable : true } )
227+ const mm = new MemoryManager ( { stores : [ store1 , store2 ] } )
215228
216229 await mm . store ( 'fact' )
217230 expect ( store2 . add ) . toHaveBeenCalledWith ( 'fact' , undefined )
218231 } )
219232
220233 it ( 'throws AggregateError when all writes fail' , async ( ) => {
221- const store : KnowledgeStore = {
234+ const store : MemoryStore = {
235+ name : 'failing' ,
222236 search : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
223237 add : vi . fn ( ) . mockRejectedValue ( new Error ( 'write error' ) ) ,
224238 }
225- const mm = new MemoryManager ( { stores : [ { store } ] } )
239+ const mm = new MemoryManager ( { stores : [ store ] } )
226240
227241 await expect ( mm . store ( 'fact' ) ) . rejects . toThrow ( 'all store writes failed' )
228242 } )
229243 } )
230244
231245 describe ( 'initAgent' , ( ) => {
232246 it ( 'does not throw' , ( ) => {
233- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( ) } ] } )
247+ const mm = new MemoryManager ( { stores : [ createMockStore ( 'test' ) ] } )
234248 expect ( ( ) => mm . initAgent ( { } as any ) ) . not . toThrow ( )
235249 } )
236250 } )
237251
238252 describe ( 'AgentConfig integration' , ( ) => {
239253 it ( 'auto-wraps MemoryManagerConfig into MemoryManager instance' , ( ) => {
240- const store = createMockStore ( )
241- const agent = new Agent ( { memoryManager : { stores : [ { store } ] } } )
254+ const store = createMockStore ( 'test' )
255+ const agent = new Agent ( { memoryManager : { stores : [ store ] } } )
242256 expect ( agent . memoryManager ) . toBeInstanceOf ( MemoryManager )
243257 } )
244258
245259 it ( 'passes through MemoryManager instance unchanged' , ( ) => {
246- const mm = new MemoryManager ( { stores : [ { store : createMockStore ( ) } ] } )
260+ const mm = new MemoryManager ( { stores : [ createMockStore ( 'test' ) ] } )
247261 const agent = new Agent ( { memoryManager : mm } )
248262 expect ( agent . memoryManager ) . toBe ( mm )
249263 } )
0 commit comments