Skip to content

Commit 7dc39dc

Browse files
committed
test(memory): add CUSTOM strategy and existingMemories tests
Add 18 new tests to schema-mapper.test.ts covering: - mapGenerateInputToMemories with 'custom' MemoryOption - mapGenerateConfigToResources with custom memory - mapGenerateConfigToRenderConfig with existingMemories parameter (dedup logic, hasMemory from existing, combining existing + new) - mapExistingMemoriesToProviders (CUSTOM, empty strategies, multiple) Also fix docs/configuration.md missing EPISODIC strategy row. Constraint: CUSTOM strategy has no default namespaces or reflectionNamespaces Constraint: Dedup operates on provider name, not strategy type Confidence: high Scope-risk: narrow
1 parent dba02e3 commit 7dc39dc

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ on the next deployment.
244244
| `SEMANTIC` | Vector-based similarity search for relevant context |
245245
| `SUMMARIZATION` | Compressed conversation history |
246246
| `USER_PREFERENCE` | Store user-specific preferences and settings |
247+
| `EPISODIC` | Capture and reflect on meaningful interaction episodes |
247248
| `CUSTOM` | Self-managed strategy with user-controlled extraction logic |
248249

249250
Strategy configuration:

src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { computeManagedOAuthCredentialName } from '../../../../primitives/creden
22
import { mapByoConfigToAgent } from '../../../../tui/screens/agent/useAddAgent.js';
33
import type { GenerateConfig } from '../../../../tui/screens/generate/types.js';
44
import {
5+
mapExistingMemoriesToProviders,
56
mapGenerateConfigToAgent,
67
mapGenerateConfigToRenderConfig,
78
mapGenerateConfigToResources,
@@ -53,6 +54,25 @@ describe('mapGenerateInputToMemories', () => {
5354
expect(semantic?.namespaces).toEqual(['/users/{actorId}/facts']);
5455
});
5556

57+
it('returns memory with single CUSTOM strategy for "custom"', () => {
58+
const result = mapGenerateInputToMemories('custom', 'Proj');
59+
expect(result).toHaveLength(1);
60+
expect(result[0]!.type).toBe('AgentCoreMemory');
61+
expect(result[0]!.name).toBe('ProjMemory');
62+
expect(result[0]!.eventExpiryDuration).toBe(30);
63+
const strategies = result[0]!.strategies;
64+
expect(strategies).toHaveLength(1);
65+
expect(strategies[0]!.type).toBe('CUSTOM');
66+
});
67+
68+
it('does not include namespaces for CUSTOM strategy', () => {
69+
const result = mapGenerateInputToMemories('custom', 'Proj');
70+
const custom = result[0]!.strategies[0]!;
71+
expect(custom.type).toBe('CUSTOM');
72+
expect(custom).not.toHaveProperty('namespaces');
73+
expect(custom).not.toHaveProperty('reflectionNamespaces');
74+
});
75+
5676
it('uses project name in memory name', () => {
5777
const result = mapGenerateInputToMemories('shortTerm', 'MyCustomProject');
5878
expect(result[0]!.name).toBe('MyCustomProjectMemory');
@@ -131,6 +151,14 @@ describe('mapGenerateConfigToResources', () => {
131151
expect(result.credentials).toHaveLength(1);
132152
expect(result.memories[0]!.strategies).toHaveLength(4);
133153
});
154+
155+
it('includes memory with CUSTOM strategy when memory is "custom"', () => {
156+
const config: GenerateConfig = { ...baseConfig, memory: 'custom' };
157+
const result = mapGenerateConfigToResources(config);
158+
expect(result.memories).toHaveLength(1);
159+
expect(result.memories[0]!.strategies).toHaveLength(1);
160+
expect(result.memories[0]!.strategies[0]!.type).toBe('CUSTOM');
161+
});
134162
});
135163

136164
describe('mapModelProviderToIdentityProviders', () => {
@@ -194,6 +222,158 @@ describe('mapGenerateConfigToRenderConfig', () => {
194222
const result = await mapGenerateConfigToRenderConfig(config, []);
195223
expect(result.memoryProviders[0]!.strategies).toEqual(['SEMANTIC', 'USER_PREFERENCE', 'SUMMARIZATION', 'EPISODIC']);
196224
});
225+
226+
it('populates memoryProviders with CUSTOM strategy for custom memory', async () => {
227+
const config: GenerateConfig = { ...baseConfig, memory: 'custom' };
228+
const result = await mapGenerateConfigToRenderConfig(config, []);
229+
expect(result.hasMemory).toBe(true);
230+
expect(result.memoryProviders).toHaveLength(1);
231+
expect(result.memoryProviders[0]!.name).toBe('TestProjectMemory');
232+
expect(result.memoryProviders[0]!.strategies).toEqual(['CUSTOM']);
233+
});
234+
235+
it('includes existing memories in memoryProviders', async () => {
236+
const existingMemories = [
237+
{
238+
type: 'AgentCoreMemory' as const,
239+
name: 'SharedMemory',
240+
eventExpiryDuration: 30,
241+
strategies: [{ type: 'SEMANTIC' as const, namespaces: ['/users/{actorId}/facts'] }],
242+
},
243+
];
244+
const result = await mapGenerateConfigToRenderConfig(baseConfig, [], existingMemories);
245+
expect(result.hasMemory).toBe(true);
246+
expect(result.memoryProviders).toHaveLength(1);
247+
expect(result.memoryProviders[0]!.name).toBe('SharedMemory');
248+
expect(result.memoryProviders[0]!.envVarName).toBe('MEMORY_SHAREDMEMORY_ID');
249+
expect(result.memoryProviders[0]!.strategies).toEqual(['SEMANTIC']);
250+
});
251+
252+
it('deduplicates when existing memory and new memory have the same name', async () => {
253+
const config: GenerateConfig = { ...baseConfig, memory: 'shortTerm' };
254+
const existingMemories = [
255+
{
256+
type: 'AgentCoreMemory' as const,
257+
name: 'TestProjectMemory',
258+
eventExpiryDuration: 30,
259+
strategies: [{ type: 'SEMANTIC' as const, namespaces: ['/users/{actorId}/facts'] }],
260+
},
261+
];
262+
const result = await mapGenerateConfigToRenderConfig(config, [], existingMemories);
263+
expect(result.memoryProviders).toHaveLength(1);
264+
expect(result.memoryProviders[0]!.name).toBe('TestProjectMemory');
265+
// Existing provider wins - strategies come from the existing memory, not the new shortTerm (empty)
266+
expect(result.memoryProviders[0]!.strategies).toEqual(['SEMANTIC']);
267+
});
268+
269+
it('sets hasMemory true from existing memories even when memory option is "none"', async () => {
270+
const existingMemories = [
271+
{
272+
type: 'AgentCoreMemory' as const,
273+
name: 'ProjectMemory',
274+
eventExpiryDuration: 30,
275+
strategies: [],
276+
},
277+
];
278+
const result = await mapGenerateConfigToRenderConfig(baseConfig, [], existingMemories);
279+
expect(result.hasMemory).toBe(true);
280+
expect(result.memoryProviders).toHaveLength(1);
281+
expect(result.memoryProviders[0]!.name).toBe('ProjectMemory');
282+
});
283+
284+
it('combines existing memories with new custom memory', async () => {
285+
const config: GenerateConfig = { ...baseConfig, memory: 'custom', projectName: 'NewAgent' };
286+
const existingMemories = [
287+
{
288+
type: 'AgentCoreMemory' as const,
289+
name: 'SharedMemory',
290+
eventExpiryDuration: 30,
291+
strategies: [{ type: 'SEMANTIC' as const, namespaces: ['/users/{actorId}/facts'] }],
292+
},
293+
];
294+
const result = await mapGenerateConfigToRenderConfig(config, [], existingMemories);
295+
expect(result.memoryProviders).toHaveLength(2);
296+
expect(result.memoryProviders[0]!.name).toBe('SharedMemory');
297+
expect(result.memoryProviders[1]!.name).toBe('NewAgentMemory');
298+
expect(result.memoryProviders[1]!.strategies).toEqual(['CUSTOM']);
299+
});
300+
});
301+
302+
describe('mapExistingMemoriesToProviders', () => {
303+
it('maps memories to providers with correct envVarName and strategies', () => {
304+
const memories = [
305+
{
306+
type: 'AgentCoreMemory' as const,
307+
name: 'MyMemory',
308+
eventExpiryDuration: 30,
309+
strategies: [
310+
{ type: 'SEMANTIC' as const, namespaces: ['/users/{actorId}/facts'] },
311+
{ type: 'SUMMARIZATION' as const, namespaces: ['/conversations/{sessionId}/summary'] },
312+
],
313+
},
314+
];
315+
const result = mapExistingMemoriesToProviders(memories);
316+
expect(result).toHaveLength(1);
317+
expect(result[0]!.name).toBe('MyMemory');
318+
expect(result[0]!.envVarName).toBe('MEMORY_MYMEMORY_ID');
319+
expect(result[0]!.strategies).toEqual(['SEMANTIC', 'SUMMARIZATION']);
320+
});
321+
322+
it('handles memory with CUSTOM strategy (no namespaces)', () => {
323+
const memories = [
324+
{
325+
type: 'AgentCoreMemory' as const,
326+
name: 'CustomMem',
327+
eventExpiryDuration: 30,
328+
strategies: [{ type: 'CUSTOM' as const }],
329+
},
330+
];
331+
const result = mapExistingMemoriesToProviders(memories);
332+
expect(result).toHaveLength(1);
333+
expect(result[0]!.strategies).toEqual(['CUSTOM']);
334+
});
335+
336+
it('handles memory with empty strategies (short-term memory)', () => {
337+
const memories = [
338+
{
339+
type: 'AgentCoreMemory' as const,
340+
name: 'ShortTermMem',
341+
eventExpiryDuration: 30,
342+
strategies: [],
343+
},
344+
];
345+
const result = mapExistingMemoriesToProviders(memories);
346+
expect(result).toHaveLength(1);
347+
expect(result[0]!.name).toBe('ShortTermMem');
348+
expect(result[0]!.strategies).toEqual([]);
349+
});
350+
351+
it('maps multiple memories', () => {
352+
const memories = [
353+
{
354+
type: 'AgentCoreMemory' as const,
355+
name: 'MemOne',
356+
eventExpiryDuration: 30,
357+
strategies: [{ type: 'SEMANTIC' as const, namespaces: ['/users/{actorId}/facts'] }],
358+
},
359+
{
360+
type: 'AgentCoreMemory' as const,
361+
name: 'MemTwo',
362+
eventExpiryDuration: 60,
363+
strategies: [{ type: 'CUSTOM' as const }],
364+
},
365+
];
366+
const result = mapExistingMemoriesToProviders(memories);
367+
expect(result).toHaveLength(2);
368+
expect(result[0]!.name).toBe('MemOne');
369+
expect(result[0]!.envVarName).toBe('MEMORY_MEMONE_ID');
370+
expect(result[1]!.name).toBe('MemTwo');
371+
expect(result[1]!.envVarName).toBe('MEMORY_MEMTWO_ID');
372+
});
373+
374+
it('returns empty array for empty input', () => {
375+
expect(mapExistingMemoriesToProviders([])).toEqual([]);
376+
});
197377
});
198378

199379
describe('mapGenerateConfigToAgent protocol mode', () => {

0 commit comments

Comments
 (0)