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
37 changes: 0 additions & 37 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,43 +210,6 @@ Each strategy can have optional configuration:
| `namespaces` | No | **Deprecated alias for `namespaceTemplates`.** Accepted for backward compatibility. |
| `reflectionNamespaces` | EPISODIC only | **Deprecated alias for `reflectionNamespaceTemplates`.** Accepted for backward compatibility. |

## Indexed Metadata Keys

Indexed keys declare metadata fields on a memory that can be used to filter long-term memory records on retrieval. Up to
10 keys per memory.

```bash
agentcore add memory \
--name SupportMemory \
--strategies SEMANTIC \
--indexed-key priority:NUMBER \
--indexed-key agent_type:STRING \
--indexed-key tags:STRINGLIST
```

In `agentcore.json`:

```json
{
"name": "SupportMemory",
"strategies": [{ "type": "SEMANTIC" }],
"indexedKeys": [
{ "key": "priority", "type": "NUMBER" },
{ "key": "agent_type", "type": "STRING" },
{ "key": "tags", "type": "STRINGLIST" }
]
}
```

| Type | Description |
| ------------ | --------------------- |
| `STRING` | Single string value |
| `STRINGLIST` | List of string values |
| `NUMBER` | Numeric value |

Indexed keys require at least one long-term memory strategy. They can only be added to an existing memory — once
declared, an indexed key cannot be removed.

## Event Expiry

Memory events expire after a configurable duration (7-365 days, default 30):
Expand Down
396 changes: 215 additions & 181 deletions npm-shrinkwrap.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"@aws-sdk/client-bedrock": "^3.1012.0",
"@aws-sdk/client-bedrock-agent": "^3.1012.0",
"@aws-sdk/client-bedrock-agentcore": "^3.1020.0",
"@aws-sdk/client-bedrock-agentcore-control": "^3.1048.0",
"@aws-sdk/client-bedrock-agentcore-control": "^3.1039.0",
"@aws-sdk/client-bedrock-runtime": "^3.893.0",
"@aws-sdk/client-cloudformation": "^3.893.0",
"@aws-sdk/client-cloudwatch-logs": "^3.893.0",
Expand Down
10 changes: 0 additions & 10 deletions src/cli/aws/agentcore-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,6 @@ export interface MemoryDetail {
namespaceTemplates?: string[];
reflectionNamespaceTemplates?: string[];
}[];
indexedKeys?: { key: string; type: string }[];
tags?: Record<string, string>;
encryptionKeyArn?: string;
executionRoleArn?: string;
Expand Down Expand Up @@ -409,14 +408,6 @@ export async function getMemoryDetail(options: GetMemoryOptions): Promise<Memory

const tags = await fetchTags(client, memory.arn, 'memory');

const indexedKeys = memory.indexedKeys?.flatMap(k => {
if (!k.key || !k.type) {
console.warn(`Warning: Skipping malformed indexed key from API response: ${JSON.stringify(k)}`);
return [];
}
return [{ key: k.key, type: k.type }];
});

return {
memoryId: memory.id,
memoryArn: memory.arn,
Expand All @@ -427,7 +418,6 @@ export async function getMemoryDetail(options: GetMemoryOptions): Promise<Memory
tags,
encryptionKeyArn: memory.encryptionKeyArn,
executionRoleArn: memory.memoryExecutionRoleArn,
...(indexedKeys && indexedKeys.length > 0 && { indexedKeys }),
strategies: (memory.strategies ?? []).map(s => {
if (!s.type) {
throw new Error(`Memory ${options.memoryId} has a strategy with missing required field: type`);
Expand Down
2 changes: 1 addition & 1 deletion src/cli/aws/policy-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
StartPolicyGenerationCommand,
waitUntilPolicyGenerationCompleted,
} from '@aws-sdk/client-bedrock-agentcore-control';
import { WaiterState } from '@smithy/core/client';
import { WaiterState } from '@smithy/util-waiter';

export interface StartPolicyGenerationOptions {
policyEngineId: string;
Expand Down
112 changes: 0 additions & 112 deletions src/cli/commands/add/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1126,118 +1126,6 @@ describe('validate', () => {
expect(result.valid).toBe(false);
expect(result.error).toContain('does not match the expected schema');
});

// Indexed keys: requires LTM strategy
it('rejects --indexed-key without any LTM strategy', () => {
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: undefined,
indexedKey: ['priority:NUMBER'],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('requires at least one long-term memory strategy');
});

it('accepts --indexed-key with an LTM strategy', () => {
expect(
validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: ['priority:NUMBER'],
})
).toEqual({ valid: true });
});

it('rejects more than 10 indexed keys', () => {
const eleven = Array.from({ length: 11 }, (_, i) => `k${i}:STRING`);
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: eleven,
});
expect(result.valid).toBe(false);
expect(result.error).toContain('Maximum 10 indexed keys');
});

it('accepts exactly 10 indexed keys (boundary)', () => {
const ten = Array.from({ length: 10 }, (_, i) => `k${i}:STRING`);
expect(validateAddMemoryOptions({ ...validMemoryOptions, strategies: 'SEMANTIC', indexedKey: ten })).toEqual({
valid: true,
});
});

it('rejects an empty key (":STRING")', () => {
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: [':STRING'],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('Key name cannot be empty');
});

it('rejects a key longer than 128 characters', () => {
const longKey = 'a'.repeat(129);
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: [`${longKey}:STRING`],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('exceeds maximum length');
});

it('rejects an invalid type token', () => {
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: ['priority:INTEGER'],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('Invalid type');
});

it('rejects duplicate keys', () => {
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: ['priority:NUMBER', 'priority:STRING'],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('Duplicate indexed key');
});

it('rejects whitespace-only key', () => {
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: [' :STRING'],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('whitespace');
});

it('rejects malformed entry without colon', () => {
const result = validateAddMemoryOptions({
...validMemoryOptions,
strategies: 'SEMANTIC',
indexedKey: ['priority'],
});
expect(result.valid).toBe(false);
expect(result.error).toContain('Expected key:TYPE');
});

it.each([
['user.email:STRING'],
['tag/v2:STRINGLIST'],
['kebab-case:STRING'],
['x-custom:STRING'],
['has:colons:in:key:NUMBER'],
])('accepts punctuation-rich key %s', raw => {
expect(validateAddMemoryOptions({ ...validMemoryOptions, strategies: 'SEMANTIC', indexedKey: [raw] })).toEqual({
valid: true,
});
});
});

describe('validateAddCredentialOptions', () => {
Expand Down
1 change: 0 additions & 1 deletion src/cli/commands/add/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export interface AddMemoryOptions {
dataStreamArn?: string;
contentLevel?: string;
streamDeliveryResources?: string;
indexedKey?: string[];
json?: boolean;
}

Expand Down
32 changes: 0 additions & 32 deletions src/cli/commands/add/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
} from '../../../schema';
import { ARN_VALIDATION_MESSAGE, isValidArn } from '../shared/arn-utils';
import { validateHeaderAllowlist } from '../shared/header-utils';
import { MAX_INDEXED_KEYS, parseIndexedKeyArg } from '../shared/indexed-key-parser';
import { parseAndValidateLifecycleOptions } from '../shared/lifecycle-utils';
import { validateVpcOptions } from '../shared/vpc-utils';
import { validateJwtAuthorizerOptions } from './auth-options';
Expand Down Expand Up @@ -727,37 +726,6 @@ export function validateAddMemoryOptions(options: AddMemoryOptions): ValidationR
}
}

if (options.indexedKey && options.indexedKey.length > 0) {
const ltmStrategies = (options.strategies ?? '')
.split(',')
.map(s => s.trim().toUpperCase())
.filter(Boolean);
if (ltmStrategies.length === 0) {
return {
valid: false,
error:
'--indexed-key requires at least one long-term memory strategy (--strategies). Indexed keys filter long-term memory records on retrieval.',
};
}

if (options.indexedKey.length > MAX_INDEXED_KEYS) {
return { valid: false, error: `Maximum ${MAX_INDEXED_KEYS} indexed keys allowed` };
}

const seenKeys = new Set<string>();
for (const raw of options.indexedKey) {
const result = parseIndexedKeyArg(raw);
if (!result.ok) {
return { valid: false, error: result.error };
}
const { key } = result.value;
if (seenKeys.has(key)) {
return { valid: false, error: `Duplicate indexed key: "${key}"` };
}
seenKeys.add(key);
}
}

if (options.streamDeliveryResources && (options.dataStreamArn || options.contentLevel || options.deliveryType)) {
return {
valid: false,
Expand Down
17 changes: 0 additions & 17 deletions src/cli/commands/import/import-memory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Memory } from '../../../schema';
import { IndexedKeyTypeSchema } from '../../../schema';
import type { MemoryDetail, MemorySummary } from '../../aws/agentcore-control';
import { getMemoryDetail, listAllMemories } from '../../aws/agentcore-control';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
Expand Down Expand Up @@ -57,26 +56,10 @@ function toMemorySpec(memory: MemoryDetail, localName: string): Memory {
};
});

// Validate each indexed key's type against our enum. Drop
// entries whose type is not one we recognize with a warning
const indexedKeys: Memory['indexedKeys'] = memory.indexedKeys
?.flatMap(k => {
const parsedType = IndexedKeyTypeSchema.safeParse(k.type);
if (!parsedType.success) {
console.warn(
`${ANSI.yellow}[warn]${ANSI.reset} Skipping indexed key "${k.key}" with unrecognised type "${k.type}".`
);
return [];
}
return [{ key: k.key, type: parsedType.data }];
})
.filter(Boolean);

return {
name: localName,
eventExpiryDuration: Math.max(3, Math.min(365, memory.eventExpiryDuration)),
strategies,
...(indexedKeys && indexedKeys.length > 0 && { indexedKeys }),
...(memory.tags && Object.keys(memory.tags).length > 0 && { tags: memory.tags }),
...(memory.encryptionKeyArn && { encryptionKeyArn: memory.encryptionKeyArn }),
...(memory.executionRoleArn && { executionRoleArn: memory.executionRoleArn }),
Expand Down
86 changes: 0 additions & 86 deletions src/cli/commands/shared/indexed-key-parser.ts

This file was deleted.

Loading
Loading