Skip to content

Commit 12741cd

Browse files
committed
feat: add streamDeliveryResources schema, CLI flags, and validation for memory record streaming
1 parent 0d1021d commit 12741cd

File tree

7 files changed

+435
-57
lines changed

7 files changed

+435
-57
lines changed

docs/memory.md

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

201+
## Memory Record Streaming
202+
203+
Memory record streaming delivers real-time events when memory records are created, updated, or deleted. Events are
204+
pushed to a delivery target in your account, enabling event-driven architectures without polling.
205+
206+
### Enabling Streaming
207+
208+
Via CLI flags:
209+
210+
```bash
211+
agentcore add memory \
212+
--name MyMemory \
213+
--strategies SEMANTIC \
214+
--data-stream-arn arn:aws:kinesis:us-west-2:123456789012:stream/my-stream \
215+
--stream-content-level FULL_CONTENT
216+
```
217+
218+
For advanced configurations (e.g. multiple delivery targets), pass the full JSON:
219+
220+
```bash
221+
agentcore add memory \
222+
--name MyMemory \
223+
--strategies SEMANTIC \
224+
--stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-west-2:123456789012:stream/my-stream","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}'
225+
```
226+
227+
### Configuration
228+
229+
```json
230+
{
231+
"type": "AgentCoreMemory",
232+
"name": "MyMemory",
233+
"eventExpiryDuration": 30,
234+
"strategies": [{ "type": "SEMANTIC" }],
235+
"streamDeliveryResources": {
236+
"resources": [
237+
{
238+
"kinesis": {
239+
"dataStreamArn": "arn:aws:kinesis:us-west-2:123456789012:stream/my-stream",
240+
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
241+
}
242+
}
243+
]
244+
}
245+
}
246+
```
247+
248+
### Content Level
249+
250+
| Level | Description |
251+
| --------------- | ---------------------------------------------------------- |
252+
| `FULL_CONTENT` | Events include memory record text and all metadata |
253+
| `METADATA_ONLY` | Events include only metadata (IDs, timestamps, namespaces) |
254+
255+
The CDK construct automatically grants the memory execution role permission to publish to the configured delivery
256+
target.
257+
258+
For more details, see the
259+
[Memory Record Streaming documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-record-streaming.html).
260+
201261
## Using Memory in Code
202262

203263
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
@@ -738,6 +738,71 @@ describe('validate', () => {
738738
valid: true,
739739
});
740740
});
741+
742+
// Streaming validation
743+
it('accepts valid streaming options', () => {
744+
expect(
745+
validateAddMemoryOptions({
746+
...validMemoryOptions,
747+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
748+
contentLevel: 'FULL_CONTENT',
749+
})
750+
).toEqual({ valid: true });
751+
});
752+
753+
it('accepts dataStreamArn without contentLevel (defaults to FULL_CONTENT)', () => {
754+
expect(
755+
validateAddMemoryOptions({
756+
...validMemoryOptions,
757+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
758+
})
759+
).toEqual({ valid: true });
760+
});
761+
762+
it('rejects contentLevel without dataStreamArn', () => {
763+
const result = validateAddMemoryOptions({ ...validMemoryOptions, contentLevel: 'FULL_CONTENT' });
764+
expect(result.valid).toBe(false);
765+
expect(result.error).toContain('--data-stream-arn is required');
766+
});
767+
768+
it('rejects invalid contentLevel', () => {
769+
const result = validateAddMemoryOptions({
770+
...validMemoryOptions,
771+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
772+
contentLevel: 'INVALID',
773+
});
774+
expect(result.valid).toBe(false);
775+
expect(result.error).toContain('Invalid content level');
776+
});
777+
778+
it('rejects invalid deliveryType', () => {
779+
const result = validateAddMemoryOptions({ ...validMemoryOptions, deliveryType: 'sqs' });
780+
expect(result.valid).toBe(false);
781+
expect(result.error).toContain('Invalid delivery type');
782+
});
783+
784+
it('accepts valid deliveryType', () => {
785+
expect(validateAddMemoryOptions({ ...validMemoryOptions, deliveryType: 'kinesis' })).toEqual({ valid: true });
786+
});
787+
788+
it('rejects dataStreamArn not starting with arn:', () => {
789+
const result = validateAddMemoryOptions({
790+
...validMemoryOptions,
791+
dataStreamArn: 'not-an-arn',
792+
});
793+
expect(result.valid).toBe(false);
794+
expect(result.error).toContain('valid ARN');
795+
});
796+
797+
it('rejects combining streamDeliveryResources with flat flags', () => {
798+
const result = validateAddMemoryOptions({
799+
...validMemoryOptions,
800+
dataStreamArn: 'arn:aws:kinesis:us-west-2:123456789012:stream/test',
801+
streamDeliveryResources: '{"resources":[]}',
802+
});
803+
expect(result.valid).toBe(false);
804+
expect(result.error).toContain('cannot be combined');
805+
});
741806
});
742807

743808
describe('validateAddIdentityOptions', () => {

src/cli/commands/add/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ export interface AddMemoryOptions {
8080
name?: string;
8181
strategies?: string;
8282
expiry?: number;
83+
deliveryType?: string;
84+
dataStreamArn?: string;
85+
contentLevel?: string;
86+
streamDeliveryResources?: string;
8387
json?: boolean;
8488
}
8589

src/cli/commands/add/validate.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface ValidationResult {
2828
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const;
2929
const OIDC_WELL_KNOWN_SUFFIX = '/.well-known/openid-configuration';
3030
const VALID_STRATEGIES = ['SEMANTIC', 'SUMMARIZATION', 'USER_PREFERENCE'];
31+
const VALID_STREAM_CONTENT_LEVELS = ['FULL_CONTENT', 'METADATA_ONLY'];
32+
const VALID_DELIVERY_TYPES = ['kinesis'];
3133

3234
/**
3335
* Validate that a credential name exists in the project spec.
@@ -447,6 +449,35 @@ export function validateAddMemoryOptions(options: AddMemoryOptions): ValidationR
447449
}
448450
}
449451

452+
if (options.streamDeliveryResources && (options.dataStreamArn || options.contentLevel)) {
453+
return {
454+
valid: false,
455+
error: '--stream-delivery-resources cannot be combined with --data-stream-arn or --stream-content-level',
456+
};
457+
}
458+
459+
if (options.contentLevel && !options.dataStreamArn) {
460+
return { valid: false, error: '--data-stream-arn is required when --stream-content-level is set' };
461+
}
462+
463+
if (options.dataStreamArn && !options.dataStreamArn.startsWith('arn:')) {
464+
return { valid: false, error: '--data-stream-arn must be a valid ARN (starts with arn:)' };
465+
}
466+
467+
if (options.deliveryType && !VALID_DELIVERY_TYPES.includes(options.deliveryType)) {
468+
return {
469+
valid: false,
470+
error: `Invalid delivery type. Must be one of: ${VALID_DELIVERY_TYPES.join(', ')}`,
471+
};
472+
}
473+
474+
if (options.contentLevel && !VALID_STREAM_CONTENT_LEVELS.includes(options.contentLevel)) {
475+
return {
476+
valid: false,
477+
error: `Invalid content level. Must be one of: ${VALID_STREAM_CONTENT_LEVELS.join(', ')}`,
478+
};
479+
}
480+
450481
return { valid: true };
451482
}
452483

0 commit comments

Comments
 (0)