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
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 @@ -73,7 +73,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
12 changes: 6 additions & 6 deletions src/cli/primitives/MemoryPrimitive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import type {
StreamDeliveryResources,
} from '../../schema';
import {
DEFAULT_EPISODIC_REFLECTION_NAMESPACES,
DEFAULT_STRATEGY_NAMESPACES,
DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES,
DEFAULT_STRATEGY_NAMESPACE_TEMPLATES,
MemorySchema,
MemoryStrategyTypeSchema,
StreamContentLevelSchema,
Expand Down Expand Up @@ -287,13 +287,13 @@ export class MemoryPrimitive extends BasePrimitive<AddMemoryOptions, RemovableMe

this.checkDuplicate(project.memories, config.name);

// Map strategies with their default namespaces
// Map strategies with their default namespace templates
const strategies: MemoryStrategy[] = config.strategies.map(s => {
const defaultNamespaces = DEFAULT_STRATEGY_NAMESPACES[s.type];
const defaultTemplates = DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[s.type];
return {
type: s.type,
...(defaultNamespaces && { namespaces: defaultNamespaces }),
...(s.type === 'EPISODIC' && { reflectionNamespaces: DEFAULT_EPISODIC_REFLECTION_NAMESPACES }),
...(defaultTemplates && { namespaceTemplates: defaultTemplates }),
...(s.type === 'EPISODIC' && { reflectionNamespaceTemplates: DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES }),
};
});

Expand Down
6 changes: 5 additions & 1 deletion src/schema/llm-compacted/agentcore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,12 @@ interface MemoryStrategy {
type: MemoryStrategyType;
name?: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
description?: string;
namespaceTemplates?: string[];
reflectionNamespaceTemplates?: string[]; // EPISODIC only: templates for cross-episode reflections
/** @deprecated Use namespaceTemplates instead. */
namespaces?: string[];
reflectionNamespaces?: string[]; // EPISODIC only: namespaces for cross-episode reflections
/** @deprecated Use reflectionNamespaceTemplates instead. */
reflectionNamespaces?: string[];
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down
4 changes: 4 additions & 0 deletions src/schema/schemas/agentcore-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { EvaluationLevelSchema, EvaluatorConfigSchema, EvaluatorNameSchema } fro
import { HttpGatewaySchema } from './primitives/http-gateway';
import {
DEFAULT_EPISODIC_REFLECTION_NAMESPACES,
DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES,
DEFAULT_STRATEGY_NAMESPACES,
DEFAULT_STRATEGY_NAMESPACE_TEMPLATES,
MemoryStrategySchema,
MemoryStrategyTypeSchema,
} from './primitives/memory';
Expand All @@ -27,7 +29,9 @@ import { z } from 'zod';

// Re-export for convenience
export {
DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES,
DEFAULT_EPISODIC_REFLECTION_NAMESPACES,
DEFAULT_STRATEGY_NAMESPACE_TEMPLATES,
DEFAULT_STRATEGY_NAMESPACES,
MemoryStrategySchema,
MemoryStrategyTypeSchema,
Expand Down
Loading
Loading