From 7705facc768b80041bcea5799092977d02a61f51 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 26 Mar 2026 22:18:38 -0400 Subject: [PATCH 1/6] feat(memory): add CUSTOM strategy type to agentcore-cli (#677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable self-managed (CUSTOM) memory strategy in the CLI, aligning with the CDK constructs which already support customMemoryStrategy via CloudFormation. Changes: - Add CUSTOM to MemoryStrategyTypeSchema enum and validation - Add CUSTOM description to TUI wizard strategy picker - Update CLI help text, LLM-compacted types, and AGENTS.md - Add documentation for self-managed strategy in docs/ - Update tests: flip rejection tests to acceptance, add new cases - Regenerate asset snapshots Constraint: CDK already maps CUSTOM → customMemoryStrategy in CFN Rejected: New SELF_MANAGED type | cross-layer naming inconsistency with CDK Confidence: high Scope-risk: narrow --- docs/commands.md | 12 +++--- docs/configuration.md | 11 ++--- docs/memory.md | 43 ++++++++++++++++--- .../assets.snapshot.test.ts.snap | 2 +- src/assets/agents/AGENTS.md | 2 +- .../commands/add/__tests__/add-memory.test.ts | 13 +++--- .../commands/add/__tests__/validate.test.ts | 12 +++--- src/cli/commands/add/validate.ts | 2 +- src/cli/primitives/MemoryPrimitive.tsx | 2 +- src/cli/tui/screens/memory/types.ts | 1 + src/schema/llm-compacted/agentcore.ts | 2 +- .../primitives/__tests__/memory.test.ts | 31 ++++++++----- src/schema/schemas/primitives/memory.ts | 3 +- 13 files changed, 90 insertions(+), 46 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 56cff8724..fac8fc9c3 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -225,12 +225,12 @@ agentcore add memory \ --expiry 30 ``` -| Flag | Description | -| ---------------------- | --------------------------------------------------------------------------- | -| `--name ` | Memory name | -| `--strategies ` | Comma-separated: `SEMANTIC`, `SUMMARIZATION`, `USER_PREFERENCE`, `EPISODIC` | -| `--expiry ` | Event expiry duration in days (default: 30, min: 7, max: 365) | -| `--json` | JSON output | +| Flag | Description | +| ---------------------- | ------------------------------------------------------------------------------------ | +| `--name ` | Memory name | +| `--strategies ` | Comma-separated: `SEMANTIC`, `SUMMARIZATION`, `USER_PREFERENCE`, `EPISODIC`, `CUSTOM` | +| `--expiry ` | Event expiry duration in days (default: 30, min: 7, max: 365) | +| `--json` | JSON output | ### add gateway diff --git a/docs/configuration.md b/docs/configuration.md index 50ae79817..deaa70b47 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -239,11 +239,12 @@ on the next deployment. ### Memory Strategies -| Strategy | Description | -| ----------------- | --------------------------------------------------- | -| `SEMANTIC` | Vector-based similarity search for relevant context | -| `SUMMARIZATION` | Compressed conversation history | -| `USER_PREFERENCE` | Store user-specific preferences and settings | +| Strategy | Description | +| ----------------- | ----------------------------------------------------------- | +| `SEMANTIC` | Vector-based similarity search for relevant context | +| `SUMMARIZATION` | Compressed conversation history | +| `USER_PREFERENCE` | Store user-specific preferences and settings | +| `CUSTOM` | Self-managed strategy with user-controlled extraction logic | Strategy configuration: diff --git a/docs/memory.md b/docs/memory.md index 3cc4d1528..587fd9ca3 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -151,12 +151,13 @@ async def invoke(payload, context): ## Memory Strategies -| Strategy | Description | -| ----------------- | ------------------------------------------------------ | -| `SEMANTIC` | Vector-based similarity search for relevant context | -| `SUMMARIZATION` | Compressed conversation history | -| `USER_PREFERENCE` | Store user-specific preferences and settings | -| `EPISODIC` | Capture and reflect on meaningful interaction episodes | +| Strategy | Description | +| ----------------- | ----------------------------------------------------------- | +| `SEMANTIC` | Vector-based similarity search for relevant context | +| `SUMMARIZATION` | Compressed conversation history | +| `USER_PREFERENCE` | Store user-specific preferences and settings | +| `EPISODIC` | Capture and reflect on meaningful interaction episodes | +| `CUSTOM` | Self-managed strategy with user-controlled extraction logic | You can combine multiple strategies: @@ -171,6 +172,36 @@ You can combine multiple strategies: } ``` +### Self-Managed (Custom) Strategy + +The `CUSTOM` strategy lets you control memory extraction logic externally rather than relying on built-in +implementations. This is useful when you need specialized extraction pipelines or want to integrate with your own +processing infrastructure. + +**Prerequisites:** CUSTOM strategies require user-managed extraction logic and are not functional without it. You must +implement your own extraction mechanism (e.g., via AWS Lambda). + +**Key characteristics:** + +- No default namespaces are assigned — you provide your own or omit them +- Each memory supports at most one CUSTOM strategy +- You are responsible for implementing the extraction logic that processes memory events + +```json +{ + "type": "AgentCoreMemory", + "name": "MyMemory", + "eventExpiryDuration": 30, + "strategies": [ + { + "type": "CUSTOM", + "name": "my_custom_strategy", + "description": "Custom extraction logic" + } + ] +} +``` + ### Strategy Options Each strategy can have optional configuration: diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 6e15118d9..c8408ed15 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -4330,7 +4330,7 @@ file maps to a JSON config file and includes validation constraints as comments. - **BuildType**: \`'CodeZip'\` | \`'Container'\` - **NetworkMode**: \`'PUBLIC'\` - **RuntimeVersion**: \`'PYTHON_3_10'\` | \`'PYTHON_3_11'\` | \`'PYTHON_3_12'\` | \`'PYTHON_3_13'\` -- **MemoryStrategyType**: \`'SEMANTIC'\` | \`'SUMMARIZATION'\` | \`'USER_PREFERENCE'\` | \`'EPISODIC'\` +- **MemoryStrategyType**: \`'SEMANTIC'\` | \`'SUMMARIZATION'\` | \`'USER_PREFERENCE'\` | \`'EPISODIC'\` | \`'CUSTOM'\` ### Build Types diff --git a/src/assets/agents/AGENTS.md b/src/assets/agents/AGENTS.md index 6e5d95f74..253b184e7 100644 --- a/src/assets/agents/AGENTS.md +++ b/src/assets/agents/AGENTS.md @@ -63,7 +63,7 @@ file maps to a JSON config file and includes validation constraints as comments. - **BuildType**: `'CodeZip'` | `'Container'` - **NetworkMode**: `'PUBLIC'` - **RuntimeVersion**: `'PYTHON_3_10'` | `'PYTHON_3_11'` | `'PYTHON_3_12'` | `'PYTHON_3_13'` -- **MemoryStrategyType**: `'SEMANTIC'` | `'SUMMARIZATION'` | `'USER_PREFERENCE'` | `'EPISODIC'` +- **MemoryStrategyType**: `'SEMANTIC'` | `'SUMMARIZATION'` | `'USER_PREFERENCE'` | `'EPISODIC'` | `'CUSTOM'` ### Build Types diff --git a/src/cli/commands/add/__tests__/add-memory.test.ts b/src/cli/commands/add/__tests__/add-memory.test.ts index 0367dd2f4..47f00bb7a 100644 --- a/src/cli/commands/add/__tests__/add-memory.test.ts +++ b/src/cli/commands/add/__tests__/add-memory.test.ts @@ -51,16 +51,17 @@ describe('add memory command', () => { expect(json.error.includes('INVALID'), `Error: ${json.error}`).toBeTruthy(); }); - // Issue #235: CUSTOM strategy has been removed - it('rejects CUSTOM strategy', async () => { + // Issue #677: CUSTOM strategy is now supported + it('creates memory with CUSTOM strategy', async () => { + const memoryName = `memCustom${Date.now()}`; const result = await runCLI( - ['add', 'memory', '--name', 'testCustom', '--strategies', 'CUSTOM', '--json'], + ['add', 'memory', '--name', memoryName, '--strategies', 'CUSTOM', '--json'], projectDir ); - expect(result.exitCode).toBe(1); + expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); - expect(json.success).toBe(false); - expect(json.error.includes('CUSTOM'), `Error: ${json.error}`).toBeTruthy(); + expect(json.success).toBe(true); + expect(json.memoryName).toBe(memoryName); }); }); diff --git a/src/cli/commands/add/__tests__/validate.test.ts b/src/cli/commands/add/__tests__/validate.test.ts index 25522e428..73613902a 100644 --- a/src/cli/commands/add/__tests__/validate.test.ts +++ b/src/cli/commands/add/__tests__/validate.test.ts @@ -953,17 +953,15 @@ describe('validate', () => { ).toEqual({ valid: true }); }); - // AC23: CUSTOM strategy is not supported (Issue #235) - it('rejects CUSTOM strategy', () => { + // Issue #677: CUSTOM strategy is now supported + it('accepts CUSTOM strategy', () => { const result = validateAddMemoryOptions({ ...validMemoryOptions, strategies: 'CUSTOM' }); - expect(result.valid).toBe(false); - expect(result.error).toContain('Invalid strategy: CUSTOM'); + expect(result.valid).toBe(true); }); - it('rejects CUSTOM even when mixed with valid strategies', () => { + it('accepts CUSTOM mixed with other strategies', () => { const result = validateAddMemoryOptions({ ...validMemoryOptions, strategies: 'SEMANTIC,CUSTOM' }); - expect(result.valid).toBe(false); - expect(result.error).toContain('Invalid strategy: CUSTOM'); + expect(result.valid).toBe(true); }); // AC24: Each individual valid strategy should pass diff --git a/src/cli/commands/add/validate.ts b/src/cli/commands/add/validate.ts index e87b1513a..263390065 100644 --- a/src/cli/commands/add/validate.ts +++ b/src/cli/commands/add/validate.ts @@ -34,7 +34,7 @@ export interface ValidationResult { // Constants const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const; -const VALID_STRATEGIES = ['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC']; +const VALID_STRATEGIES = ['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC', 'CUSTOM']; /** * Validate that a credential name exists in the project spec. diff --git a/src/cli/primitives/MemoryPrimitive.tsx b/src/cli/primitives/MemoryPrimitive.tsx index aaaac575e..366787943 100644 --- a/src/cli/primitives/MemoryPrimitive.tsx +++ b/src/cli/primitives/MemoryPrimitive.tsx @@ -126,7 +126,7 @@ export class MemoryPrimitive extends BasePrimitive', 'Memory name [non-interactive]') .option( '--strategies ', - 'Comma-separated strategies: SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC [non-interactive]' + 'Comma-separated strategies: SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC, CUSTOM [non-interactive]' ) .option('--expiry ', 'Event expiry duration in days (default: 30) [non-interactive]') .option('--json', 'Output as JSON [non-interactive]') diff --git a/src/cli/tui/screens/memory/types.ts b/src/cli/tui/screens/memory/types.ts index f94854645..317f7d3b7 100644 --- a/src/cli/tui/screens/memory/types.ts +++ b/src/cli/tui/screens/memory/types.ts @@ -33,6 +33,7 @@ const STRATEGY_DESCRIPTIONS: Record = { SUMMARIZATION: 'Compress and summarize conversation context', USER_PREFERENCE: 'Track and recall user preferences', EPISODIC: 'Capture and reflect on meaningful interaction episodes', + CUSTOM: 'Self-managed strategy with user-controlled extraction logic', }; export const MEMORY_STRATEGY_OPTIONS = MemoryStrategyTypeSchema.options.map(type => ({ diff --git a/src/schema/llm-compacted/agentcore.ts b/src/schema/llm-compacted/agentcore.ts index d897d506f..e74a75c84 100644 --- a/src/schema/llm-compacted/agentcore.ts +++ b/src/schema/llm-compacted/agentcore.ts @@ -33,7 +33,7 @@ interface NetworkConfig { securityGroups: string[]; // sg-xxx IDs } -type MemoryStrategyType = 'SEMANTIC' | 'SUMMARIZATION' | 'USER_PREFERENCE' | 'EPISODIC'; +type MemoryStrategyType = 'SEMANTIC' | 'SUMMARIZATION' | 'USER_PREFERENCE' | 'EPISODIC' | 'CUSTOM'; type ModelProvider = 'Bedrock' | 'Gemini' | 'OpenAI' | 'Anthropic'; // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/schema/schemas/primitives/__tests__/memory.test.ts b/src/schema/schemas/primitives/__tests__/memory.test.ts index 4b37eb646..fa0b2ee0e 100644 --- a/src/schema/schemas/primitives/__tests__/memory.test.ts +++ b/src/schema/schemas/primitives/__tests__/memory.test.ts @@ -20,13 +20,14 @@ describe('MemoryStrategyTypeSchema', () => { }); }); - describe('invalid strategy types', () => { - // Issue #235: CUSTOM strategy has been removed - it('rejects CUSTOM strategy', () => { + describe('custom strategy type', () => { + it('accepts CUSTOM strategy', () => { const result = MemoryStrategyTypeSchema.safeParse('CUSTOM'); - expect(result.success).toBe(false); + expect(result.success).toBe(true); }); + }); + describe('invalid strategy types', () => { it('rejects arbitrary invalid strategies', () => { expect(MemoryStrategyTypeSchema.safeParse('INVALID').success).toBe(false); expect(MemoryStrategyTypeSchema.safeParse('').success).toBe(false); @@ -35,9 +36,10 @@ describe('MemoryStrategyTypeSchema', () => { }); describe('schema options', () => { - it('contains four valid strategies including EPISODIC', () => { - expect(MemoryStrategyTypeSchema.options).toEqual(['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC']); - expect(MemoryStrategyTypeSchema.options).not.toContain('CUSTOM'); + it('contains five valid strategies including EPISODIC and CUSTOM', () => { + expect(MemoryStrategyTypeSchema.options).toEqual(['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC', 'CUSTOM']); + expect(MemoryStrategyTypeSchema.options).toContain('CUSTOM'); + expect(MemoryStrategyTypeSchema.options).toContain('EPISODIC'); }); }); }); @@ -58,9 +60,18 @@ describe('MemoryStrategySchema', () => { expect(result.success).toBe(true); }); - it('rejects strategy with CUSTOM type', () => { + it('accepts strategy with CUSTOM type', () => { const result = MemoryStrategySchema.safeParse({ type: 'CUSTOM' }); - expect(result.success).toBe(false); + expect(result.success).toBe(true); + }); + + it('accepts CUSTOM strategy with optional fields', () => { + const result = MemoryStrategySchema.safeParse({ + type: 'CUSTOM', + name: 'my_custom', + description: 'My custom strategy', + }); + expect(result.success).toBe(true); }); it('rejects strategy with invalid type', () => { @@ -149,7 +160,7 @@ describe('DEFAULT_STRATEGY_NAMESPACES', () => { expect(DEFAULT_STRATEGY_NAMESPACES.EPISODIC).toEqual(['/episodes/{actorId}/{sessionId}']); }); - it('does not have default namespaces for CUSTOM (removed)', () => { + it('does not have default namespaces for CUSTOM', () => { expect(DEFAULT_STRATEGY_NAMESPACES).not.toHaveProperty('CUSTOM'); }); }); diff --git a/src/schema/schemas/primitives/memory.ts b/src/schema/schemas/primitives/memory.ts index f63874d5d..ab476cff4 100644 --- a/src/schema/schemas/primitives/memory.ts +++ b/src/schema/schemas/primitives/memory.ts @@ -11,9 +11,10 @@ import { z } from 'zod'; * - SUMMARIZATION → SummaryMemoryStrategy (note: CloudFormation uses "Summary") * - USER_PREFERENCE → UserPreferenceMemoryStrategy * - EPISODIC → EpisodicMemoryStrategy + * - CUSTOM → CustomMemoryStrategy * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrockagentcore-memory-memorystrategy.html */ -export const MemoryStrategyTypeSchema = z.enum(['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC']); +export const MemoryStrategyTypeSchema = z.enum(['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC', 'CUSTOM']); export type MemoryStrategyType = z.infer; /** From a961da011d3cbb4d5a1979a2a32756d32f07cd57 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 26 Mar 2026 22:55:37 -0400 Subject: [PATCH 2/6] refactor(memory): derive VALID_STRATEGIES and help text from schema Eliminates source-of-truth drift by deriving the valid strategies array and CLI help text from MemoryStrategyTypeSchema.options instead of maintaining hand-written duplicates. Constraint: MemoryStrategyTypeSchema is the single source of truth Confidence: high Scope-risk: narrow --- src/cli/commands/add/validate.ts | 3 ++- src/cli/primitives/MemoryPrimitive.tsx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/add/validate.ts b/src/cli/commands/add/validate.ts index 263390065..8ce0723fb 100644 --- a/src/cli/commands/add/validate.ts +++ b/src/cli/commands/add/validate.ts @@ -4,6 +4,7 @@ import { BuildTypeSchema, GatewayExceptionLevelSchema, GatewayNameSchema, + MemoryStrategyTypeSchema, ModelProviderSchema, ProtocolModeSchema, RuntimeAuthorizerTypeSchema, @@ -34,7 +35,7 @@ export interface ValidationResult { // Constants const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const; -const VALID_STRATEGIES = ['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC', 'CUSTOM']; +const VALID_STRATEGIES: readonly string[] = MemoryStrategyTypeSchema.options; /** * Validate that a credential name exists in the project spec. diff --git a/src/cli/primitives/MemoryPrimitive.tsx b/src/cli/primitives/MemoryPrimitive.tsx index 366787943..d7cc7e1f9 100644 --- a/src/cli/primitives/MemoryPrimitive.tsx +++ b/src/cli/primitives/MemoryPrimitive.tsx @@ -1,6 +1,6 @@ import { findConfigRoot } from '../../lib'; import type { Memory, MemoryStrategy, MemoryStrategyType } from '../../schema'; -import { DEFAULT_EPISODIC_REFLECTION_NAMESPACES, DEFAULT_STRATEGY_NAMESPACES, MemorySchema } from '../../schema'; +import { DEFAULT_EPISODIC_REFLECTION_NAMESPACES, DEFAULT_STRATEGY_NAMESPACES, MemorySchema, MemoryStrategyTypeSchema } from '../../schema'; import { validateAddMemoryOptions } from '../commands/add/validate'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; @@ -126,7 +126,7 @@ export class MemoryPrimitive extends BasePrimitive', 'Memory name [non-interactive]') .option( '--strategies ', - 'Comma-separated strategies: SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC, CUSTOM [non-interactive]' + `Comma-separated strategies: ${MemoryStrategyTypeSchema.options.join(', ')} [non-interactive]` ) .option('--expiry ', 'Event expiry duration in days (default: 30) [non-interactive]') .option('--json', 'Output as JSON [non-interactive]') From 3162dcd78bdfa9aeec9640f9b0baab03f98e3258 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 26 Mar 2026 23:54:54 -0400 Subject: [PATCH 3/6] fix(memory): add CUSTOM block to session.py templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this, selecting CUSTOM as the only strategy would render an empty retrieval_config dict with no guidance. Now both HTTP and A2A Strands templates include a CUSTOM Handlebars block with a TODO comment guiding users to add their own namespace and retrieval config. Constraint: CUSTOM is self-managed — no default namespaces to vend Rejected: Auto-generating a placeholder namespace | would mislead users into thinking it works out of the box Confidence: high Scope-risk: narrow --- .../__snapshots__/assets.snapshot.test.ts.snap | 10 ++++++++++ .../python/a2a/strands/capabilities/memory/session.py | 5 +++++ .../python/http/strands/capabilities/memory/session.py | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index c8408ed15..08e477dc9 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -1701,6 +1701,11 @@ def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[Agent {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "CUSTOM")}} + # TODO: Add your custom namespace and retrieval config. + # Custom strategies use user-controlled extraction logic. + # Example: f"/custom/{actor_id}/data": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} @@ -3986,6 +3991,11 @@ def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[Agent {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "CUSTOM")}} + # TODO: Add your custom namespace and retrieval config. + # Custom strategies use user-controlled extraction logic. + # Example: f"/custom/{actor_id}/data": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} diff --git a/src/assets/python/a2a/strands/capabilities/memory/session.py b/src/assets/python/a2a/strands/capabilities/memory/session.py index 9661243b1..c95cf40f7 100644 --- a/src/assets/python/a2a/strands/capabilities/memory/session.py +++ b/src/assets/python/a2a/strands/capabilities/memory/session.py @@ -21,6 +21,11 @@ def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[Agent {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "CUSTOM")}} + # TODO: Add your custom namespace and retrieval config. + # Custom strategies use user-controlled extraction logic. + # Example: f"/custom/{actor_id}/data": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} diff --git a/src/assets/python/http/strands/capabilities/memory/session.py b/src/assets/python/http/strands/capabilities/memory/session.py index dc0cb7bf5..afebc29cf 100644 --- a/src/assets/python/http/strands/capabilities/memory/session.py +++ b/src/assets/python/http/strands/capabilities/memory/session.py @@ -21,6 +21,11 @@ def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[Agent {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), +{{/if}} +{{#if (includes memoryProviders.[0].strategies "CUSTOM")}} + # TODO: Add your custom namespace and retrieval config. + # Custom strategies use user-controlled extraction logic. + # Example: f"/custom/{actor_id}/data": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} From dba02e30a5fc50a51ee6bcf3939b11ebf2b7afd3 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 27 Mar 2026 00:12:25 -0400 Subject: [PATCH 4/6] 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 --- src/cli/commands/add/validate.ts | 6 ++-- src/cli/commands/create/action.ts | 2 +- src/cli/commands/create/command.tsx | 4 +-- src/cli/commands/create/validate.ts | 6 ++-- src/cli/operations/agent/generate/index.ts | 1 + .../agent/generate/schema-mapper.ts | 34 +++++++++++++++++-- .../agent/generate/write-agent-to-project.ts | 5 ++- src/cli/primitives/AgentPrimitive.tsx | 9 +++-- src/cli/tui/screens/agent/useAddAgent.ts | 4 +-- .../tui/screens/generate/GenerateWizardUI.tsx | 2 ++ src/cli/tui/screens/generate/types.ts | 3 +- 11 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/cli/commands/add/validate.ts b/src/cli/commands/add/validate.ts index 8ce0723fb..dafa3b302 100644 --- a/src/cli/commands/add/validate.ts +++ b/src/cli/commands/add/validate.ts @@ -34,7 +34,7 @@ export interface ValidationResult { } // Constants -const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const; +const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm', 'custom'] as const; const VALID_STRATEGIES: readonly string[] = MemoryStrategyTypeSchema.options; /** @@ -135,7 +135,7 @@ export function validateAddAgentOptions(options: AddAgentOptions): ValidationRes if (!MEMORY_OPTIONS.includes(options.memory as (typeof MEMORY_OPTIONS)[number])) { return { valid: false, - error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`, + error: `Invalid memory option: ${options.memory}. Use none, shortTerm, longAndShortTerm, or custom`, }; } // Parse and validate lifecycle configuration for import path @@ -243,7 +243,7 @@ export function validateAddAgentOptions(options: AddAgentOptions): ValidationRes if (!MEMORY_OPTIONS.includes(options.memory as (typeof MEMORY_OPTIONS)[number])) { return { valid: false, - error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`, + error: `Invalid memory option: ${options.memory}. Use none, shortTerm, longAndShortTerm, or custom`, }; } } diff --git a/src/cli/commands/create/action.ts b/src/cli/commands/create/action.ts index f947ccbea..61c57e9ca 100644 --- a/src/cli/commands/create/action.ts +++ b/src/cli/commands/create/action.ts @@ -120,7 +120,7 @@ export async function createProject(options: CreateProjectOptions): Promise { framework: options.framework as SDKFramework | undefined, modelProvider: options.modelProvider as ModelProvider | undefined, apiKey: options.apiKey, - memory: (options.memory as 'none' | 'shortTerm' | 'longAndShortTerm') ?? 'none', + memory: (options.memory as 'none' | 'shortTerm' | 'longAndShortTerm' | 'custom') ?? 'none', protocol: options.protocol as ProtocolMode | undefined, agentId: options.agentId, agentAliasId: options.agentAliasId, @@ -170,7 +170,7 @@ export const registerCreate = (program: Command) => { ) .option('--model-provider ', 'Model provider (Bedrock, Anthropic, OpenAI, Gemini) [non-interactive]') .option('--api-key ', 'API key for non-Bedrock providers [non-interactive]') - .option('--memory