Skip to content

Commit 505cb43

Browse files
committed
feat: add streamDeliveryResources schema, CLI flags, and validation for memory record streaming
1 parent c9e5cfe commit 505cb43

File tree

7 files changed

+436
-57
lines changed

7 files changed

+436
-57
lines changed

docs/memory.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,66 @@ Memory events expire after a configurable duration (7-365 days, default 30):
222222
}
223223
```
224224

225+
## Memory Record Streaming
226+
227+
Memory record streaming delivers real-time events when memory records are created, updated, or deleted. Events are
228+
pushed to a delivery target in your account, enabling event-driven architectures without polling.
229+
230+
### Enabling Streaming
231+
232+
Via CLI flags:
233+
234+
```bash
235+
agentcore add memory \
236+
--name MyMemory \
237+
--strategies SEMANTIC \
238+
--data-stream-arn arn:aws:kinesis:us-west-2:123456789012:stream/my-stream \
239+
--stream-content-level FULL_CONTENT
240+
```
241+
242+
For advanced configurations (e.g. multiple delivery targets), pass the full JSON:
243+
244+
```bash
245+
agentcore add memory \
246+
--name MyMemory \
247+
--strategies SEMANTIC \
248+
--stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-west-2:123456789012:stream/my-stream","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}'
249+
```
250+
251+
### Configuration
252+
253+
```json
254+
{
255+
"type": "AgentCoreMemory",
256+
"name": "MyMemory",
257+
"eventExpiryDuration": 30,
258+
"strategies": [{ "type": "SEMANTIC" }],
259+
"streamDeliveryResources": {
260+
"resources": [
261+
{
262+
"kinesis": {
263+
"dataStreamArn": "arn:aws:kinesis:us-west-2:123456789012:stream/my-stream",
264+
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
265+
}
266+
}
267+
]
268+
}
269+
}
270+
```
271+
272+
### Content Level
273+
274+
| Level | Description |
275+
| --------------- | ---------------------------------------------------------- |
276+
| `FULL_CONTENT` | Events include memory record text and all metadata |
277+
| `METADATA_ONLY` | Events include only metadata (IDs, timestamps, namespaces) |
278+
279+
The CDK construct automatically grants the memory execution role permission to publish to the configured delivery
280+
target.
281+
282+
For more details, see the
283+
[Memory Record Streaming documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-record-streaming.html).
284+
225285
## Using Memory in Code
226286

227287
The memory ID is available via environment variable:

src/cli/commands/add/__tests__/validate.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,71 @@ describe('validate', () => {
994994
valid: true,
995995
});
996996
});
997+
998+
// Streaming validation
999+
it('accepts valid streaming options', () => {
1000+
expect(
1001+
validateAddMemoryOptions({
1002+
...validMemoryOptions,
1003+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
1004+
contentLevel: 'FULL_CONTENT',
1005+
})
1006+
).toEqual({ valid: true });
1007+
});
1008+
1009+
it('accepts dataStreamArn without contentLevel (defaults to FULL_CONTENT)', () => {
1010+
expect(
1011+
validateAddMemoryOptions({
1012+
...validMemoryOptions,
1013+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
1014+
})
1015+
).toEqual({ valid: true });
1016+
});
1017+
1018+
it('rejects contentLevel without dataStreamArn', () => {
1019+
const result = validateAddMemoryOptions({ ...validMemoryOptions, contentLevel: 'FULL_CONTENT' });
1020+
expect(result.valid).toBe(false);
1021+
expect(result.error).toContain('--data-stream-arn is required');
1022+
});
1023+
1024+
it('rejects invalid contentLevel', () => {
1025+
const result = validateAddMemoryOptions({
1026+
...validMemoryOptions,
1027+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
1028+
contentLevel: 'INVALID',
1029+
});
1030+
expect(result.valid).toBe(false);
1031+
expect(result.error).toContain('Invalid content level');
1032+
});
1033+
1034+
it('rejects invalid deliveryType', () => {
1035+
const result = validateAddMemoryOptions({ ...validMemoryOptions, deliveryType: 'sqs' });
1036+
expect(result.valid).toBe(false);
1037+
expect(result.error).toContain('Invalid delivery type');
1038+
});
1039+
1040+
it('accepts valid deliveryType', () => {
1041+
expect(validateAddMemoryOptions({ ...validMemoryOptions, deliveryType: 'kinesis' })).toEqual({ valid: true });
1042+
});
1043+
1044+
it('rejects dataStreamArn not starting with arn:', () => {
1045+
const result = validateAddMemoryOptions({
1046+
...validMemoryOptions,
1047+
dataStreamArn: 'not-an-arn',
1048+
});
1049+
expect(result.valid).toBe(false);
1050+
expect(result.error).toContain('valid ARN');
1051+
});
1052+
1053+
it('rejects combining streamDeliveryResources with flat flags', () => {
1054+
const result = validateAddMemoryOptions({
1055+
...validMemoryOptions,
1056+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
1057+
streamDeliveryResources: '{"resources":[]}',
1058+
});
1059+
expect(result.valid).toBe(false);
1060+
expect(result.error).toContain('cannot be combined');
1061+
});
9971062
});
9981063

9991064
describe('validateAddCredentialOptions', () => {

src/cli/commands/add/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ export interface AddMemoryOptions {
109109
name?: string;
110110
strategies?: string;
111111
expiry?: number;
112+
deliveryType?: string;
113+
dataStreamArn?: string;
114+
contentLevel?: string;
115+
streamDeliveryResources?: string;
112116
json?: boolean;
113117
}
114118

src/cli/commands/add/validate.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export interface ValidationResult {
3535
// Constants
3636
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const;
3737
const VALID_STRATEGIES = ['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE', 'EPISODIC'];
38+
const VALID_STREAM_CONTENT_LEVELS = ['FULL_CONTENT', 'METADATA_ONLY'];
39+
const VALID_DELIVERY_TYPES = ['kinesis'];
3840

3941
/**
4042
* Validate that a credential name exists in the project spec.
@@ -677,6 +679,35 @@ export function validateAddMemoryOptions(options: AddMemoryOptions): ValidationR
677679
}
678680
}
679681

682+
if (options.streamDeliveryResources && (options.dataStreamArn || options.contentLevel)) {
683+
return {
684+
valid: false,
685+
error: '--stream-delivery-resources cannot be combined with --data-stream-arn or --stream-content-level',
686+
};
687+
}
688+
689+
if (options.contentLevel && !options.dataStreamArn) {
690+
return { valid: false, error: '--data-stream-arn is required when --stream-content-level is set' };
691+
}
692+
693+
if (options.dataStreamArn && !options.dataStreamArn.startsWith('arn:')) {
694+
return { valid: false, error: '--data-stream-arn must be a valid ARN (starts with arn:)' };
695+
}
696+
697+
if (options.deliveryType && !VALID_DELIVERY_TYPES.includes(options.deliveryType)) {
698+
return {
699+
valid: false,
700+
error: `Invalid delivery type. Must be one of: ${VALID_DELIVERY_TYPES.join(', ')}`,
701+
};
702+
}
703+
704+
if (options.contentLevel && !VALID_STREAM_CONTENT_LEVELS.includes(options.contentLevel)) {
705+
return {
706+
valid: false,
707+
error: `Invalid content level. Must be one of: ${VALID_STREAM_CONTENT_LEVELS.join(', ')}`,
708+
};
709+
}
710+
680711
return { valid: true };
681712
}
682713

0 commit comments

Comments
 (0)