Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit 4a3b024

Browse files
committed
Update interfaces
1 parent 8fbe97d commit 4a3b024

6 files changed

Lines changed: 233 additions & 218 deletions

File tree

strands-ts/src/agent/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ import type { AgentAsToolOptions } from './agent-as-tool.js'
6969

7070
import type { z } from 'zod'
7171
import { MemoryManager } from '../memory/memory-manager.js'
72-
import type { MemoryManagerConfig } from '../memory/types.js'
72+
import type { MemoryManagerConfig } from '../memory/index.js'
7373
import { SessionManager } from '../session/session-manager.js'
7474
import { Tracer } from '../telemetry/tracer.js'
7575
import { Meter } from '../telemetry/meter.js'

strands-ts/src/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,14 +270,12 @@ export type { ElicitationCallback, ElicitationContext } from './types/elicitatio
270270
// Memory management
271271
export { MemoryManager } from './memory/index.js'
272272
export type {
273-
KnowledgeEntry,
274-
KnowledgeStore,
273+
MemoryEntry,
274+
MemoryStore,
275275
SearchOptions,
276276
MemorySearchOptions,
277277
MemoryStoreOptions,
278-
StoreConfig,
279278
MemoryToolConfig,
280-
MemoryToolsConfig,
281279
MemoryManagerConfig,
282280
} from './memory/index.js'
283281

strands-ts/src/memory/__tests__/memory-manager.test.ts

Lines changed: 118 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { describe, it, expect, vi } from 'vitest'
22
import { Agent } from '../../agent/agent.js'
33
import { 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

Comments
 (0)