Skip to content

Commit 59f7804

Browse files
committed
feat(memory): add CUSTOM as wizard MemoryOption and wire existing memories to new agents
Add 'custom' as a 4th MemoryOption in the create/add-agent wizards, completing CUSTOM strategy support across all CLI flows. When adding an agent to a project with existing memories, the template now references all existing memories instead of always creating new ones. - Add 'custom' to MemoryOption type, MEMORY_OPTIONS array, and all validation/help text across create and add-agent commands - Add 'custom' case to mapGenerateInputToMemories (CUSTOM strategy, no default namespaces) and getMemoryLabel in wizard UI - Add mapExistingMemoriesToProviders() to convert project Memory[] to MemoryProviderRenderConfig[] for template rendering - Update mapGenerateConfigToRenderConfig to accept existing memories and merge them with new memory providers (deduped by name) - Update writeAgentToProject to skip adding duplicate memories - Wire existing project.memories through both add-agent paths (AgentPrimitive CLI + useAddAgent TUI hook) Constraint: Strands Agent takes a single session_manager, so session.py references memoryProviders[0] as the primary memory Rejected: Multi-select wizard for memory | template only supports one session_manager, all memory env vars already available at runtime Confidence: high Scope-risk: moderate
1 parent 64578f7 commit 59f7804

11 files changed

Lines changed: 57 additions & 19 deletions

File tree

src/cli/commands/add/validate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface ValidationResult {
3333
}
3434

3535
// Constants
36-
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const;
36+
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm', 'custom'] as const;
3737
const VALID_STRATEGIES: readonly string[] = MemoryStrategyTypeSchema.options;
3838

3939
/**
@@ -134,7 +134,7 @@ export function validateAddAgentOptions(options: AddAgentOptions): ValidationRes
134134
if (!MEMORY_OPTIONS.includes(options.memory as (typeof MEMORY_OPTIONS)[number])) {
135135
return {
136136
valid: false,
137-
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`,
137+
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, longAndShortTerm, or custom`,
138138
};
139139
}
140140
// Force import defaults
@@ -230,7 +230,7 @@ export function validateAddAgentOptions(options: AddAgentOptions): ValidationRes
230230
if (!MEMORY_OPTIONS.includes(options.memory as (typeof MEMORY_OPTIONS)[number])) {
231231
return {
232232
valid: false,
233-
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`,
233+
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, longAndShortTerm, or custom`,
234234
};
235235
}
236236
}

src/cli/commands/create/action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea
120120
}
121121
}
122122

123-
type MemoryOption = 'none' | 'shortTerm' | 'longAndShortTerm';
123+
type MemoryOption = 'none' | 'shortTerm' | 'longAndShortTerm' | 'custom';
124124

125125
export interface CreateWithAgentOptions {
126126
name: string;

src/cli/commands/create/command.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ async function handleCreateCLI(options: CreateOptions): Promise<void> {
128128
framework: options.framework as SDKFramework | undefined,
129129
modelProvider: options.modelProvider as ModelProvider | undefined,
130130
apiKey: options.apiKey,
131-
memory: (options.memory as 'none' | 'shortTerm' | 'longAndShortTerm') ?? 'none',
131+
memory: (options.memory as 'none' | 'shortTerm' | 'longAndShortTerm' | 'custom') ?? 'none',
132132
protocol: options.protocol as ProtocolMode | undefined,
133133
agentId: options.agentId,
134134
agentAliasId: options.agentAliasId,
@@ -167,7 +167,7 @@ export const registerCreate = (program: Command) => {
167167
)
168168
.option('--model-provider <provider>', 'Model provider (Bedrock, Anthropic, OpenAI, Gemini) [non-interactive]')
169169
.option('--api-key <key>', 'API key for non-Bedrock providers [non-interactive]')
170-
.option('--memory <option>', 'Memory option (none, shortTerm, longAndShortTerm) [non-interactive]')
170+
.option('--memory <option>', 'Memory option (none, shortTerm, longAndShortTerm, custom) [non-interactive]')
171171
.option('--protocol <protocol>', 'Protocol: HTTP, MCP, A2A (default: HTTP) [non-interactive]')
172172
.option('--type <type>', 'Agent type: create or import (default: create) [non-interactive]')
173173
.option('--agent-id <id>', 'Bedrock Agent ID (required for --type import) [non-interactive]')

src/cli/commands/create/validate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface ValidationResult {
2020
error?: string;
2121
}
2222

23-
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const;
23+
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm', 'custom'] as const;
2424

2525
/** Check if a folder with the given name already exists in the directory */
2626
export function validateFolderNotExists(name: string, cwd: string): true | string {
@@ -71,7 +71,7 @@ export function validateCreateOptions(options: CreateOptions, cwd?: string): Val
7171
if (!MEMORY_OPTIONS.includes(options.memory as (typeof MEMORY_OPTIONS)[number])) {
7272
return {
7373
valid: false,
74-
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`,
74+
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, longAndShortTerm, or custom`,
7575
};
7676
}
7777
return { valid: true };
@@ -189,7 +189,7 @@ export function validateCreateOptions(options: CreateOptions, cwd?: string): Val
189189
if (!MEMORY_OPTIONS.includes(options.memory as (typeof MEMORY_OPTIONS)[number])) {
190190
return {
191191
valid: false,
192-
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`,
192+
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, longAndShortTerm, or custom`,
193193
};
194194
}
195195
}

src/cli/operations/agent/generate/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export {
33
mapGenerateConfigToResources,
44
mapGenerateConfigToRenderConfig,
55
mapGenerateInputToMemories,
6+
mapExistingMemoriesToProviders,
67
mapModelProviderToCredentials,
78
mapModelProviderToIdentityProviders,
89
type GenerateConfigMappingResult,

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ export function mapGenerateInputToMemories(memory: MemoryOption, projectName: st
7575
...(defaultNamespaces && { namespaces: defaultNamespaces }),
7676
});
7777
}
78+
} else if (memory === 'custom') {
79+
// Custom strategy has no default namespaces - user provides their own extraction logic
80+
strategies.push({ type: 'CUSTOM' });
7881
}
7982

8083
return [
@@ -241,29 +244,54 @@ async function mapGatewaysToGatewayProviders(): Promise<GatewayProviderRenderCon
241244
}
242245
}
243246

247+
/**
248+
* Maps existing Memory resources to memory providers for template rendering.
249+
* Used when adding an agent to a project that already has memories defined.
250+
*/
251+
export function mapExistingMemoriesToProviders(memories: Memory[]): MemoryProviderRenderConfig[] {
252+
return memories.map(m => ({
253+
name: m.name,
254+
envVarName: computeMemoryEnvVarName(m.name),
255+
strategies: m.strategies.map(s => s.type),
256+
}));
257+
}
258+
244259
/**
245260
* Maps GenerateConfig to AgentRenderConfig for template rendering.
246261
* @param config - Generate config (note: config.projectName is actually the agent name)
247262
* @param identityProviders - Identity providers to include (caller controls credential naming)
263+
* @param existingMemories - Existing project memories to wire to the agent template
248264
*/
249265
export async function mapGenerateConfigToRenderConfig(
250266
config: GenerateConfig,
251-
identityProviders: IdentityProviderRenderConfig[]
267+
identityProviders: IdentityProviderRenderConfig[],
268+
existingMemories?: Memory[]
252269
): Promise<AgentRenderConfig> {
253270
const isMcp = config.protocol === 'MCP';
254271
const gatewayProviders = isMcp ? [] : await mapGatewaysToGatewayProviders();
255272

273+
// Build memory providers: existing memories first, then any new memory from the option.
274+
// This ensures agents in existing projects reference all available memories.
275+
const existingProviders = existingMemories?.length ? mapExistingMemoriesToProviders(existingMemories) : [];
276+
const newProviders = mapMemoryOptionToMemoryProviders(config.memory, config.projectName);
277+
const allProviders = [...existingProviders];
278+
for (const np of newProviders) {
279+
if (!allProviders.some(ep => ep.name === np.name)) {
280+
allProviders.push(np);
281+
}
282+
}
283+
256284
return {
257285
name: config.projectName,
258286
sdkFramework: config.sdk,
259287
targetLanguage: config.language,
260288
modelProvider: config.modelProvider,
261-
hasMemory: isMcp ? false : config.memory !== 'none',
289+
hasMemory: isMcp ? false : allProviders.length > 0,
262290
hasIdentity: isMcp ? false : identityProviders.length > 0,
263291
hasGateway: gatewayProviders.length > 0,
264292
isVpc: config.networkMode === 'VPC',
265293
buildType: config.buildType,
266-
memoryProviders: isMcp ? [] : mapMemoryOptionToMemoryProviders(config.memory, config.projectName),
294+
memoryProviders: isMcp ? [] : allProviders,
267295
identityProviders: isMcp ? [] : identityProviders,
268296
gatewayProviders,
269297
gatewayAuthTypes: [...new Set(gatewayProviders.map(g => g.authType))],

src/cli/operations/agent/generate/write-agent-to-project.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export async function writeAgentToProject(config: GenerateConfig, options?: Writ
4040

4141
// Add resources to project
4242
project.agents.push(agent);
43-
project.memories.push(...memories);
43+
44+
// Only add memories that don't already exist in the project (avoid duplicates)
45+
const newMemories = memories.filter(m => !project.memories.some(em => em.name === m.name));
46+
project.memories.push(...newMemories);
4447

4548
// Handle credentials based on strategy
4649
if (strategy) {

src/cli/primitives/AgentPrimitive.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ export class AgentPrimitive extends BasePrimitive<AddAgentOptions, RemovableReso
194194
)
195195
.option('--model-provider <provider>', 'Model provider: Bedrock, Anthropic, OpenAI, Gemini [non-interactive]')
196196
.option('--api-key <key>', 'API key for non-Bedrock providers [non-interactive]')
197-
.option('--memory <mem>', 'Memory: none, shortTerm, longAndShortTerm (create path only) [non-interactive]')
197+
.option(
198+
'--memory <mem>',
199+
'Memory: none, shortTerm, longAndShortTerm, custom (create path only) [non-interactive]'
200+
)
198201
.option('--protocol <protocol>', 'Protocol: HTTP, MCP, A2A (default: HTTP) [non-interactive]')
199202
.option('--code-location <path>', 'Path to existing code (BYO path only) [non-interactive]')
200203
.option('--entrypoint <file>', 'Entry file relative to code-location (BYO, default: main.py) [non-interactive]')
@@ -385,8 +388,8 @@ export class AgentPrimitive extends BasePrimitive<AddAgentOptions, RemovableReso
385388
];
386389
}
387390

388-
// Render templates with correct identity provider
389-
const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders);
391+
// Render templates with correct identity provider and existing project memories
392+
const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders, project.memories);
390393
const renderer = createRenderer(renderConfig);
391394
await renderer.render({ outputDir: projectRoot });
392395

src/cli/tui/screens/agent/useAddAgent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ async function handleCreatePath(
196196
];
197197
}
198198

199-
// Generate agent files with correct identity provider
200-
const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders);
199+
// Generate agent files with correct identity provider and existing project memories
200+
const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders, project.memories);
201201
const renderer = createRenderer(renderConfig);
202202
await renderer.render({ outputDir: projectRoot });
203203

src/cli/tui/screens/generate/GenerateWizardUI.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ function getMemoryLabel(memory: MemoryOption): string {
318318
return 'Short-term';
319319
case 'longAndShortTerm':
320320
return 'Long-term + short-term';
321+
case 'custom':
322+
return 'Custom';
321323
}
322324
}
323325

0 commit comments

Comments
 (0)