Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ Strategy configuration:
"type": "SEMANTIC",
"name": "custom_semantic",
"description": "Custom semantic memory",
"namespaces": ["/users/facts", "/users/preferences"]
"namespaceTemplates": ["/users/facts", "/users/preferences"]
}
```

Expand Down
18 changes: 10 additions & 8 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,19 @@ Each strategy can have optional configuration:
"type": "SEMANTIC",
"name": "custom_semantic",
"description": "Custom semantic memory",
"namespaces": ["/users/facts", "/users/preferences"]
"namespaceTemplates": ["/users/facts", "/users/preferences"]
}
```

| Field | Required | Description |
| ---------------------- | ------------- | --------------------------------------------------------------------------- |
| `type` | Yes | Strategy type |
| `name` | No | Custom name (defaults to `<memoryName>-<type>`) |
| `description` | No | Strategy description |
| `namespaces` | No | Array of namespace paths for scoping |
| `reflectionNamespaces` | EPISODIC only | Namespaces for cross-episode reflections (must be a prefix of `namespaces`) |
| Field | Required | Description |
| ------------------------------ | ------------- | --------------------------------------------------------------------------------------------- |
| `type` | Yes | Strategy type |
| `name` | No | Custom name (defaults to `<memoryName>-<type>`) |
| `description` | No | Strategy description |
| `namespaceTemplates` | No | Array of namespace templates for scoping |
| `reflectionNamespaceTemplates` | EPISODIC only | Templates for cross-episode reflections (must be a prefix of `namespaceTemplates`) |
| `namespaces` | No | **Deprecated alias for `namespaceTemplates`.** Accepted for backward compatibility. |
| `reflectionNamespaces` | EPISODIC only | **Deprecated alias for `reflectionNamespaceTemplates`.** Accepted for backward compatibility. |

## Event Expiry

Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/fixtures/import/setup_memory_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def main():
"semanticMemoryStrategy": {
"name": "bugbash_semantic",
"description": "Semantic strategy for bugbash testing",
"namespaces": ["default"],
"namespaceTemplates": ["default"],
}
},
{
Expand Down Expand Up @@ -68,7 +68,7 @@ def main():
print(f" eventExpiryDuration: 30")
print(f" executionRoleArn: {role_arn}")
print(" strategies:")
print(" - type: SEMANTIC, name: bugbash_semantic, namespaces: [default]")
print(" - type: SEMANTIC, name: bugbash_semantic, namespaceTemplates: [default]")
print(" - type: SUMMARIZATION, name: bugbash_summary")
print(" - type: USER_PREFERENCE, name: bugbash_userpref")
print(" tags: {env: bugbash, team: agentcore-cli}")
Expand Down
10 changes: 5 additions & 5 deletions integ-tests/add-remove-resources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('integration: add and remove resources', () => {
telemetry.assertMetricEmitted({ command: 'add.memory', exit_reason: 'success' });
});

it('adds a memory with EPISODIC strategy and verifies reflectionNamespaces', async () => {
it('adds a memory with EPISODIC strategy and verifies reflectionNamespaceTemplates', async () => {
const episodicMemName = `EpiMem${Date.now().toString().slice(-6)}`;
const result = await runCLI(
['add', 'memory', '--name', episodicMemName, '--strategies', 'EPISODIC', '--json'],
Expand All @@ -56,19 +56,19 @@ describe('integration: add and remove resources', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

// Verify EPISODIC in config with reflectionNamespaces
// Verify EPISODIC in config with reflectionNamespaceTemplates
const config = await readProjectConfig(project.projectPath);
const memories = config.memories as {
name: string;
strategies: { type: string; reflectionNamespaces?: string[] }[];
strategies: { type: string; reflectionNamespaceTemplates?: string[] }[];
}[];
const mem = memories.find(m => m.name === episodicMemName);
expect(mem, 'Memory should exist').toBeTruthy();

const episodic = mem!.strategies.find(s => s.type === 'EPISODIC');
expect(episodic, 'EPISODIC strategy should exist').toBeTruthy();
expect(episodic!.reflectionNamespaces, 'Should have reflectionNamespaces').toBeDefined();
expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0);
expect(episodic!.reflectionNamespaceTemplates, 'Should have reflectionNamespaceTemplates').toBeDefined();
expect(episodic!.reflectionNamespaceTemplates!.length).toBeGreaterThan(0);

telemetry.assertMetricEmitted({
command: 'add.memory',
Expand Down
8 changes: 4 additions & 4 deletions integ-tests/create-memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with memory o

// longAndShortTerm should have strategies defined
const memory = memories![0]!;
const strategies = memory.strategies as { type: string; reflectionNamespaces?: string[] }[] | undefined;
const strategies = memory.strategies as { type: string; reflectionNamespaceTemplates?: string[] }[] | undefined;
expect(strategies, 'memory should have strategies').toBeDefined();
expect(strategies!.length).toBe(4);

Expand All @@ -91,10 +91,10 @@ describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with memory o
expect(types).toContain('SUMMARIZATION');
expect(types).toContain('EPISODIC');

// Verify EPISODIC has reflectionNamespaces
// Verify EPISODIC has reflectionNamespaceTemplates
const episodic = strategies!.find(s => s.type === 'EPISODIC');
expect(episodic, 'EPISODIC strategy should exist').toBeTruthy();
expect(episodic!.reflectionNamespaces, 'EPISODIC should have reflectionNamespaces').toBeDefined();
expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0);
expect(episodic!.reflectionNamespaceTemplates, 'EPISODIC should have reflectionNamespaceTemplates').toBeDefined();
expect(episodic!.reflectionNamespaceTemplates!.length).toBeGreaterThan(0);
});
});
18 changes: 9 additions & 9 deletions integ-tests/tui/add-memory-episodic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
*
* Drives the "Add Memory" wizard through the TUI to verify that when a user
* selects the EPISODIC strategy, it is correctly persisted in agentcore.json
* with both namespaces and reflectionNamespaces.
* with both namespaceTemplates and reflectionNamespaceTemplates.
*
* Exercises:
* - Navigation from HelpScreen -> Add Resource -> Memory
* - Memory name input
* - Expiry selection (default 30 days)
* - Strategy multi-select including EPISODIC
* - Confirm review screen
* - Verification that agentcore.json contains EPISODIC with reflectionNamespaces
* - Verification that agentcore.json contains EPISODIC with reflectionNamespaceTemplates
*/
import { TuiSession, WaitForTimeoutError } from '../../src/tui-harness/index.js';
import { createMinimalProjectDir } from './helpers.js';
Expand Down Expand Up @@ -176,14 +176,14 @@ describe('Add Memory with EPISODIC Strategy', () => {
expect(found).toBe(true);
});

it('Step 9: agentcore.json contains EPISODIC with reflectionNamespaces', async () => {
it('Step 9: agentcore.json contains EPISODIC with reflectionNamespaceTemplates', async () => {
const configPath = join(projectDir.dir, 'agentcore', 'agentcore.json');
const raw = await readFileAsync(configPath, 'utf-8');
const config = JSON.parse(raw);

const memories = config.memories as {
name: string;
strategies: { type: string; namespaces?: string[]; reflectionNamespaces?: string[] }[];
strategies: { type: string; namespaceTemplates?: string[]; reflectionNamespaceTemplates?: string[] }[];
}[];
expect(memories.length).toBeGreaterThan(0);

Expand All @@ -197,12 +197,12 @@ describe('Add Memory with EPISODIC Strategy', () => {
expect(types).toContain('USER_PREFERENCE');
expect(types).toContain('EPISODIC');

// Verify EPISODIC has namespaces AND reflectionNamespaces
// Verify EPISODIC has namespaceTemplates AND reflectionNamespaceTemplates
const episodic = memory!.strategies.find(s => s.type === 'EPISODIC');
expect(episodic, 'EPISODIC strategy should exist').toBeTruthy();
expect(episodic!.namespaces, 'EPISODIC should have namespaces').toBeDefined();
expect(episodic!.namespaces!.length).toBeGreaterThan(0);
expect(episodic!.reflectionNamespaces, 'EPISODIC should have reflectionNamespaces').toBeDefined();
expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0);
expect(episodic!.namespaceTemplates, 'EPISODIC should have namespaceTemplates').toBeDefined();
expect(episodic!.namespaceTemplates!.length).toBeGreaterThan(0);
expect(episodic!.reflectionNamespaceTemplates, 'EPISODIC should have reflectionNamespaceTemplates').toBeDefined();
expect(episodic!.reflectionNamespaceTemplates!.length).toBeGreaterThan(0);
});
});
14 changes: 9 additions & 5 deletions src/cli/aws/agentcore-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ export interface MemoryDetail {
type: string;
name?: string;
description?: string;
namespaces?: string[];
reflectionNamespaces?: string[];
namespaceTemplates?: string[];
reflectionNamespaceTemplates?: string[];
}[];
tags?: Record<string, string>;
encryptionKeyArn?: string;
Expand Down Expand Up @@ -422,13 +422,17 @@ export async function getMemoryDetail(options: GetMemoryOptions): Promise<Memory
if (!s.type) {
throw new Error(`Memory ${options.memoryId} has a strategy with missing required field: type`);
}
const episodicNamespaces = s.configuration?.reflection?.episodicReflectionConfiguration?.namespaces;
// Prefer the new `namespaceTemplates` field; fall back to the deprecated `namespaces` field.
const namespaceTemplates = s.namespaceTemplates ?? s.namespaces;
const reflectionConfig = s.configuration?.reflection?.episodicReflectionConfiguration;
const reflectionTemplates = reflectionConfig?.namespaceTemplates ?? reflectionConfig?.namespaces;
return {
type: s.type,
name: s.name,
description: s.description,
namespaces: s.namespaces,
...(episodicNamespaces && episodicNamespaces.length > 0 && { reflectionNamespaces: episodicNamespaces }),
...(namespaceTemplates && namespaceTemplates.length > 0 && { namespaceTemplates }),
...(reflectionTemplates &&
reflectionTemplates.length > 0 && { reflectionNamespaceTemplates: reflectionTemplates }),
};
}),
};
Expand Down
16 changes: 8 additions & 8 deletions src/cli/commands/add/__tests__/add-memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,20 @@ describe('add memory command', () => {
const memory = projectSpec.memories.find((m: { name: string }) => m.name === memoryName);

const semantic = memory?.strategies?.find((s: { type: string }) => s.type === 'SEMANTIC');
expect(semantic?.namespaces).toEqual(['/users/{actorId}/facts']);
expect(semantic?.namespaceTemplates).toEqual(['/users/{actorId}/facts']);

const userPref = memory?.strategies?.find((s: { type: string }) => s.type === 'USER_PREFERENCE');
expect(userPref?.namespaces).toEqual(['/users/{actorId}/preferences']);
expect(userPref?.namespaceTemplates).toEqual(['/users/{actorId}/preferences']);

const summarization = memory?.strategies?.find((s: { type: string }) => s.type === 'SUMMARIZATION');
expect(summarization?.namespaces).toEqual(['/summaries/{actorId}/{sessionId}']);
expect(summarization?.namespaceTemplates).toEqual(['/summaries/{actorId}/{sessionId}']);

const episodic = memory?.strategies?.find((s: { type: string }) => s.type === 'EPISODIC');
expect(episodic?.namespaces).toEqual(['/episodes/{actorId}/{sessionId}']);
expect(episodic?.reflectionNamespaces).toEqual(['/episodes/{actorId}']);
expect(episodic?.namespaceTemplates).toEqual(['/episodes/{actorId}/{sessionId}']);
expect(episodic?.reflectionNamespaceTemplates).toEqual(['/episodes/{actorId}']);
});

it('creates memory with EPISODIC strategy including default namespaces and reflectionNamespaces', async () => {
it('creates memory with EPISODIC strategy including default namespaceTemplates and reflectionNamespaceTemplates', async () => {
const memoryName = `epi${Date.now()}`;
const result = await runCLI(
['add', 'memory', '--name', memoryName, '--strategies', 'EPISODIC', '--json'],
Expand All @@ -162,8 +162,8 @@ describe('add memory command', () => {
const memory = projectSpec.memories.find((m: { name: string }) => m.name === memoryName);
const episodic = memory?.strategies?.find((s: { type: string }) => s.type === 'EPISODIC');
expect(episodic).toBeTruthy();
expect(episodic?.namespaces).toEqual(['/episodes/{actorId}/{sessionId}']);
expect(episodic?.reflectionNamespaces).toEqual(['/episodes/{actorId}']);
expect(episodic?.namespaceTemplates).toEqual(['/episodes/{actorId}/{sessionId}']);
expect(episodic?.reflectionNamespaceTemplates).toEqual(['/episodes/{actorId}']);
});
});
});
10 changes: 5 additions & 5 deletions src/cli/commands/create/__tests__/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);
expect(json.projectPath).toMatch(new RegExp(`/${projectName}$`));

Check warning on line 50 in src/cli/commands/create/__tests__/create.test.ts

View workflow job for this annotation

GitHub Actions / lint

Found non-literal argument to RegExp Constructor
expect(await exists(join(json.projectPath, 'agentcore'))).toBeTruthy();
});
});
Expand Down Expand Up @@ -143,18 +143,18 @@
const memory = projectSpec.memories[0];

const semantic = memory?.strategies?.find((s: { type: string }) => s.type === 'SEMANTIC');
expect(semantic?.namespaces).toEqual(['/users/{actorId}/facts']);
expect(semantic?.namespaceTemplates).toEqual(['/users/{actorId}/facts']);

const userPref = memory?.strategies?.find((s: { type: string }) => s.type === 'USER_PREFERENCE');
expect(userPref?.namespaces).toEqual(['/users/{actorId}/preferences']);
expect(userPref?.namespaceTemplates).toEqual(['/users/{actorId}/preferences']);

const summarization = memory?.strategies?.find((s: { type: string }) => s.type === 'SUMMARIZATION');
expect(summarization?.namespaces).toEqual(['/summaries/{actorId}/{sessionId}']);
expect(summarization?.namespaceTemplates).toEqual(['/summaries/{actorId}/{sessionId}']);

const episodic = memory?.strategies?.find((s: { type: string }) => s.type === 'EPISODIC');
expect(episodic, 'EPISODIC strategy should exist in longAndShortTerm').toBeTruthy();
expect(episodic?.namespaces).toEqual(['/episodes/{actorId}/{sessionId}']);
expect(episodic?.reflectionNamespaces).toEqual(['/episodes/{actorId}']);
expect(episodic?.namespaceTemplates).toEqual(['/episodes/{actorId}/{sessionId}']);
expect(episodic?.reflectionNamespaceTemplates).toEqual(['/episodes/{actorId}']);
});

it('uses --project-name for project and --name for agent resource', async () => {
Expand Down Expand Up @@ -186,7 +186,7 @@

const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);
expect(json.projectPath).toMatch(new RegExp(`/${projectName}$`));

Check warning on line 189 in src/cli/commands/create/__tests__/create.test.ts

View workflow job for this annotation

GitHub Actions / lint

Found non-literal argument to RegExp Constructor
expect(json.agentName).toBe(agentName);
expect(await exists(join(json.projectPath, 'app', agentName))).toBeTruthy();

Expand Down Expand Up @@ -228,7 +228,7 @@

expect(result.exitCode).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.projectPath).toMatch(new RegExp(`/${projectName}$`));

Check warning on line 231 in src/cli/commands/create/__tests__/create.test.ts

View workflow job for this annotation

GitHub Actions / lint

Found non-literal argument to RegExp Constructor
expect(json.wouldCreate).toContain(`${json.projectPath}/app/${agentName}/`);
expect(await exists(join(testDir, projectName)), 'Should not create directory').toBe(false);
});
Expand Down
10 changes: 6 additions & 4 deletions src/cli/commands/import/import-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ function filterInternalNamespaces(namespaces: string[]): string[] {
function toMemorySpec(memory: MemoryDetail, localName: string): Memory {
const strategies: Memory['strategies'] = memory.strategies.map(s => {
const mappedType = mapStrategyType(s.type);
const filteredNamespaces = s.namespaces ? filterInternalNamespaces(s.namespaces) : [];
const filteredTemplates = s.namespaceTemplates ? filterInternalNamespaces(s.namespaceTemplates) : [];
return {
type: mappedType as Memory['strategies'][number]['type'],
...(s.name && { name: s.name }),
...(s.description && { description: s.description }),
...(filteredNamespaces.length > 0 && { namespaces: filteredNamespaces }),
...(s.reflectionNamespaces &&
s.reflectionNamespaces.length > 0 && { reflectionNamespaces: s.reflectionNamespaces }),
...(filteredTemplates.length > 0 && { namespaceTemplates: filteredTemplates }),
...(s.reflectionNamespaceTemplates &&
s.reflectionNamespaceTemplates.length > 0 && {
reflectionNamespaceTemplates: s.reflectionNamespaceTemplates,
}),
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ describe('mapGenerateInputToMemories', () => {
expect(types).toContain('EPISODIC');
});

it('includes default namespaces for strategies', () => {
it('includes default namespace templates for strategies', () => {
const result = mapGenerateInputToMemories('longAndShortTerm', 'Proj');
const semantic = result[0]!.strategies.find(s => s.type === 'SEMANTIC');
expect(semantic?.namespaces).toEqual(['/users/{actorId}/facts']);
expect(semantic?.namespaceTemplates).toEqual(['/users/{actorId}/facts']);
});

it('uses project name in memory name', () => {
Expand Down
11 changes: 7 additions & 4 deletions src/cli/operations/agent/generate/schema-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import type {
MemoryStrategyType,
ModelProvider,
} from '../../../../schema';
import { DEFAULT_EPISODIC_REFLECTION_NAMESPACES, DEFAULT_STRATEGY_NAMESPACES } from '../../../../schema';
import {
DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES,
DEFAULT_STRATEGY_NAMESPACE_TEMPLATES,
} from '../../../../schema';
import { GatewayPrimitive } from '../../../primitives/GatewayPrimitive';
import { buildAuthorizerConfigFromJwtConfig } from '../../../primitives/auth-utils';
import {
Expand Down Expand Up @@ -69,11 +72,11 @@ export function mapGenerateInputToMemories(memory: MemoryOption, projectName: st
if (memory === 'longAndShortTerm') {
const strategyTypes: MemoryStrategyType[] = ['SEMANTIC', 'USER_PREFERENCE', 'SUMMARIZATION', 'EPISODIC'];
for (const type of strategyTypes) {
const defaultNamespaces = DEFAULT_STRATEGY_NAMESPACES[type];
const defaultTemplates = DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[type];
strategies.push({
type,
...(defaultNamespaces && { namespaces: defaultNamespaces }),
...(type === 'EPISODIC' && { reflectionNamespaces: DEFAULT_EPISODIC_REFLECTION_NAMESPACES }),
...(defaultTemplates && { namespaceTemplates: defaultTemplates }),
...(type === 'EPISODIC' && { reflectionNamespaceTemplates: DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES }),
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/cli/operations/dev/web-ui/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ export interface ResourceMemory {
/** Memory strategy with namespace patterns */
export interface ResourceMemoryStrategy {
type: string;
/** Namespace patterns, e.g. "/users/{actorId}/facts", "/summaries/{actorId}/{sessionId}" */
namespaces: string[];
/** Namespace templates, e.g. "/users/{actorId}/facts", "/summaries/{actorId}/{sessionId}" */
namespaceTemplates: string[];
}

/** Credential details in the resources response */
Expand Down
2 changes: 1 addition & 1 deletion src/cli/operations/dev/web-ui/handlers/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export async function handleResources(ctx: RouteContext, res: ServerResponse, or
name: m.name,
strategies: m.strategies.map(s => ({
type: s.type,
namespaces: s.namespaces ?? [],
namespaceTemplates: s.namespaceTemplates ?? s.namespaces ?? [],
})),
expiryDays: m.eventExpiryDuration,
deploymentStatus: statusByTypeAndName.get(`memory:${m.name}`),
Expand Down
2 changes: 1 addition & 1 deletion src/cli/operations/memory/__tests__/create-memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('add', () => {
expect(addedMemory).toBeDefined();
expect(addedMemory.eventExpiryDuration).toBe(60);
expect(addedMemory.strategies[0]!.type).toBe('SEMANTIC');
expect(addedMemory.strategies[0]!.namespaces).toEqual(['/users/{actorId}/facts']);
expect(addedMemory.strategies[0]!.namespaceTemplates).toEqual(['/users/{actorId}/facts']);
});

it('rejects invalid strategy type', async () => {
Expand Down
Loading
Loading