Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(project): add `project add memory` by notgitika · Pull Request #2025 · aws/agentcore-cli · GitHub
Skip to content

feat(project): add project add memory - #2025

Merged
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory
Aug 24, 2026
Merged

feat(project): add project add memory#2025
jariy17 merged 15 commits into
aws:refactorfrom
notgitika:feat/project-add-memory

Conversation

@notgitika

@notgitikanotgitika commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR adds agentcore project add memory, which validates and appends a memory resource to spec.memories in agentcore.json. Memory resources do not scaffold any application files and are deployed through the generated CDK application.

Example:

 agentcore project add memory \
--name UserFacts \
--strategies SEMANTIC,EPISODIC

To maintain parity with the xisting CLI functionality, while also adding more configurability and customization, --strategies supports 2 input types:

  • Comma-separated managed strategy types using default namespaces
  • The memory's strategies array as JSON, exactly as it is stored in agentcore.json, for explicit names, descriptions, and namespaces (there is a parameter help in --help for ease of understanding for the user). It is parsed with the project schema's own MemoryStrategySchema, so the flag cannot drift from the schema.

The command also supports expiry duration, indexed keys, stream delivery, encryption and execution roles, descriptions, and tags (description is a new optional field. Here is the CDK PR for it https://github.com/aws/agentcore-l3-cdk-constructs/pull/325 )

Callouts:

  • CUSTOM strategies are not supported because their extraction configuration cannot yet be represented by the project schema.

Validation

I tested it myself e2e with different combinations, flags, strategy conversions and validation failures. It all works well. I also tested it with agetncore project build

TODO: check compatibility with harness resource

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.27%. Comparing base (b75e1c6) to head (da37fed).

Additional details and impacted files
@@ Coverage Diff @@## refactor #2025 +/- ##
============================================
+ Coverage 97.25% 97.27% +0.01% 
============================================
Files 397 398 +1 Lines 24127 24284 +157 ============================================
+ Hits 23465 23622 +157 
Misses 662 662 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/core/project/manager.tsx Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
Comment threadsrc/handlers/project/add/memory/index.ts Outdated
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from d114ff9 to a332f9bCompareAugust 19, 2026 01:27
@notgitika

notgitika commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

My agent ran focused testing for project add memory after the latest validation changes:

  • 122 focused project tests pass, plus build/typecheck/lint/format
  • Rebuilt CLI verified valid JSON persistence and atomic rejection for the new edge cases
  • Covered strategy unions, unsupported fields, indexed keys, stream delivery, tags, malformed JSON, and boundary inputs
  • Live AWS lifecycles passed for minimal memory, all four managed strategies, and Kinesis stream delivery (CREATE_COMPLETE followed by cleanup)
  • Confirmed no test stacks, memories, or streams remain

The testing surfaced and we fixed empty shorthand entries, recursive unsupported-field stripping (including __proto__), missing JSON strategy names, and the JSON-object diagnostic. The remaining long default strategy-name boundary is in the existing project schema/CDK naming layer rather than this handler.

Comment threadsrc/core/project/manager.tsx
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 6d965bf to 4a06960CompareAugust 20, 2026 04:48
Comment threadsrc/handlers/project/add/memory/index.ts
@notgitika
notgitikaforce-pushed the feat/project-add-memory branch from 4a06960 to 3d10b6aCompareAugust 21, 2026 16:29
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for updates!

Registers a `memory` leaf under `project add`, following the same
SDK-union -> flat project-schema conversion pattern as `project add
harness`. A memory scaffolds no files, so the command only appends an
entry to `spec.memories` in agentcore.json; the L3 CDK turns that into
an `AWS::BedrockAgentCore::Memory` at deploy time.
Flags: --name, --event-expiry-duration, --strategies, --indexed-keys,
--stream-delivery-resources, --encryption-key-arn, --execution-role-arn,
--tags.
--strategies accepts two forms: a comma-separated list of strategy types
expanded with the CLI's default namespace templates, or a JSON
MemoryStrategyInput[] mirroring the CreateMemory API for strategies that
need explicit names, descriptions, or namespaces.
clientToken is excluded (it is CreateMemory idempotency and this command
makes no API call), and description is excluded until the L3 CDK schema
supports it.
Stores an optional memory description in agentcore.json, matching the
CreateMemory API's description field (max 4096 characters).
The generated CDK app pins @aws/agentcore-cdk 0.1.0-alpha.45, whose
MemorySchema is a non-strict z.object with no description field, so the key
is stripped at synth rather than rejected until
aws/agentcore-l3-cdk-constructs#325 ships and that pin is bumped. The flag
help text says so.
The CDK's memory schema already models CUSTOM (@aws/agentcore-cdk
0.1.0-alpha.45 maps it to CFN customMemoryStrategy), so the CLI's four-type
enum was the outlier. A customMemoryStrategy in the --strategies JSON now
converts to { type: 'CUSTOM', name, description, namespaceTemplates }.
The shorthand form still takes managed types only: CUSTOM has no default
namespaces to expand. An extraction configuration or memoryRecordSchema is
rejected rather than dropped, since the CDK schema carries neither.
Also names the offending field in the memory validation error.
Reverts 87be86e. I added CUSTOM because the CDK schema already had it in
MemoryStrategyTypeSchema, which turns out to be the argument PR aws#694 made --
and aws#713 reverted a day later.
The CLI has removed CUSTOM twice on purpose. Offering the type without
somewhere to put its extraction configuration is aws#241 ("select custom memory
strategy, note there is no option to add prompts"); aws#266 removed it as a P0 to
stop users picking an unsupported option, aws#694/aws#696 added it back with
semanticOverride, and aws#713 reverted both as premature. aws#676 tracks doing it
properly. The CDK keeping CUSTOM in its enum without a configuration field is
the same hole, not a licence.
So both forms are rejected again, now with an error that says why and points
at aws#676. The one thing kept from the reverted commit: memory validation errors
name the offending field, since issue.path was being dropped.
The one-line flag description is enough; the deploy-time caveat lives in the
PR discussion rather than in help output.
Upstream moved the per-resource `project add` tests out of the monolithic
project.test.ts into colocated add/<resource>/index.test.ts suites (harness
in aws#2034, online-eval in aws#2048). Move the memory tests to match, with the
same locally-duplicated run/inProject helpers those suites use.
project.test.ts is now identical to upstream/refactor again, so this PR no
longer touches it. Also drops the DeserializationError, FsReadWriteJson and
ReadWriteJson imports, left dead there once the harness tests that used them
moved to add/harness/index.test.ts.
No test content changed: 187 project tests still pass, now across 10 files
instead of 9.
nborges-aws
nborges-aws previously approved these changes Aug 21, 2026
namespaceTemplates: z.array(z.string()).optional(),
};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use zod.strictObject

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion!

};

function projectMemoryObject<T extends z.ZodRawShape>(shape: T, label: string) {
const supportedFields = new Set(Object.keys(shape));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is a lot going on here just for parse and converting strategy inputs. Maybe we should create an adapter like this

xport interface MemoryStrategyAdapter<TInput = unknown> {
readonly type: MemoryStrategyType;
readonly memberKey: string;
readonly inputSchema: z.ZodType<TInput>;
fromInput(input: TInput): MemoryStrategy;
toInput(strategy: MemoryStrategy): TInput;
fromShorthand?(): MemoryStrategy;
canUseShorthand(strategy: MemoryStrategy): boolean;
}
The codec indexes registered adapters:
export class MemoryStrategyFlagCodec {
private readonly byType: Map<string, MemoryStrategyAdapter>;
private readonly byMember: Map<string, MemoryStrategyAdapter>;
constructor(adapters: readonly MemoryStrategyAdapter[]) {
this.byType = new Map(
adapters.map((adapter) => [adapter.type, adapter]),
);
this.byMember = new Map(
adapters.map((adapter) => [adapter.memberKey, adapter]),
);
}
parse(raw: string): MemoryStrategy[] {
return raw.trimStart().startsWith("[")
? this.parseJson(raw)
: this.parseShorthand(raw);
}
toFlag(strategies: readonly MemoryStrategy[]): string {
const adapters = strategies.map((strategy) => {
const adapter = this.byType.get(strategy.type);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy '${strategy.type}'`,
);
}
return adapter;
});
if (
strategies.every((strategy, index) =>
adapters[index].canUseShorthand(strategy),
)
) {
return strategies.map((strategy) => strategy.type).join(",");
}
return JSON.stringify(
strategies.map((strategy, index) => {
const adapter = adapters[index];
return {
[adapter.memberKey]: adapter.toInput(strategy),
};
}),
);
}
private parseShorthand(raw: string): MemoryStrategy[] {
return raw.split(",").map((value) => {
const type = value.trim();
const adapter = this.byType.get(type);
if (!adapter?.fromShorthand) {
throw new InputValidationError(
`Unsupported shorthand strategy '${type}'`,
);
}
return adapter.fromShorthand();
});
}
private parseJson(raw: string): MemoryStrategy[] {
const inputs = JSON.parse(raw) as unknown[];
return inputs.map((input) => this.parseJsonMember(input));
}
private parseJsonMember(input: unknown): MemoryStrategy {
// Validate that input is an object containing exactly one member.
const [memberKey] = Object.keys(input as object);
const adapter = this.byMember.get(memberKey);
if (!adapter) {
throw new InputValidationError(
`Unsupported memory strategy member '${memberKey}'`,
);
}
const value = adapter.inputSchema.parse(
(input as Record<string, unknown>)[memberKey],
);
return adapter.fromInput(value);
}
}
Registration is centralized:
const strategyCodec = new MemoryStrategyFlagCodec([
new StandardStrategyAdapter("SEMANTIC", "semanticMemoryStrategy"),
new StandardStrategyAdapter(
"SUMMARIZATION",
"summaryMemoryStrategy",
),
new StandardStrategyAdapter(
"USER_PREFERENCE",
"userPreferenceMemoryStrategy",
),
new EpisodicStrategyAdapter(),
]);

Conflict in src/handlers/project/add/index.ts: 'project add memory' and
'project add runtime' each registered their handler on the same line;
keep both registrations.
The --strategies flag re-declared its own strategy input schema, modelled
on the CreateMemory API's tagged union (semanticMemoryStrategy et al.) and
requiring a name. agentcore.json stores strategies flat with an optional
name, so the flag accepted a shape the project file never holds and
rejected one it does.
Parse the JSON form with MemoryStrategySchema itself, wrapped only for the
unsupported-field diagnostics, so the flag cannot drift from the schema.
@notgitika

Copy link
Copy Markdown
ContributorAuthor

Per @jariy17's ask for a 1-by-1 comparison — every flag is one agentcore.json memory field, and MemorySchema has exactly these 9 fields, so there is no flag without a field and no field without a flag.

flagmemories[] field
--namename
--descriptiondescription
--event-expiry-durationeventExpiryDuration
--strategiesstrategies[]
--indexed-keysindexedKeys[]
--stream-delivery-resourcesstreamDeliveryResources
--encryption-key-arnencryptionKeyArn
--execution-role-arnexecutionRoleArn
--tagstags

One command using all 9 flags:

agentcore project add memory \ --name UserFacts \ --description "Durable facts and preferences for each end user." \ --event-expiry-duration 45 \ --strategies '[{"type":"SEMANTIC","name":"facts","description":"Durable user facts","namespaceTemplates":["/users/{actorId}/facts"]},{"type":"EPISODIC","name":"episodes","namespaceTemplates":["/episodes/{actorId}/{sessionId}"],"reflectionNamespaceTemplates":["/episodes/{actorId}"]}]' \ --indexed-keys '[{"key":"tenant","type":"STRING"}]' \ --stream-delivery-resources '{"resources":[{"kinesis":{"dataStreamArn":"arn:aws:kinesis:us-east-1:123456789012:stream/memory","contentConfigurations":[{"type":"MEMORY_RECORDS","level":"FULL_CONTENT"}]}}]}' \ --encryption-key-arn arn:aws:kms:us-east-1:123456789012:key/abc \ --execution-role-arn arn:aws:iam::123456789012:role/MyMemoryRole \ --tags '{"team":"ml"}'

The resulting memories[0], verbatim from the written agentcore.json:

{
"name": "UserFacts",
"description": "Durable facts and preferences for each end user.",
"eventExpiryDuration": 45,
"strategies": [
{
"type": "SEMANTIC",
"name": "facts",
"description": "Durable user facts",
"namespaceTemplates": ["/users/{actorId}/facts"]
},
{
"type": "EPISODIC",
"name": "episodes",
"namespaceTemplates": ["/episodes/{actorId}/{sessionId}"],
"reflectionNamespaceTemplates": ["/episodes/{actorId}"]
}
],
"indexedKeys": [{ "key": "tenant", "type": "STRING" }],
"tags": { "team": "ml" },
"encryptionKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abc",
"executionRoleArn": "arn:aws:iam::123456789012:role/MyMemoryRole",
"streamDeliveryResources": {
"resources": [
{
"kinesis": {
"dataStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/memory",
"contentConfigurations": [{ "type": "MEMORY_RECORDS", "level": "FULL_CONTENT" }]
}
}
]
}
}

The --strategies JSON above is the strategies array as stored, character for character — da37fedf drops the flag's own copy of that schema and parses with the project schema's MemoryStrategySchema itself (wrapped only to report unsupported fields), so type/name/description/namespaceTemplates/reflectionNamespaceTemplates and all of the schema's cross-field rules come from one place and can't drift. --strategies SEMANTIC,EPISODIC remains as the shorthand that fills in the default namespaces.

),
flag(
"strategies",
"long-term memory strategies: comma-separated types, or the JSON strategies[] as stored in agentcore.json",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use SourceResolver.


return {
type: parsed.data,
namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[parsed.data],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift maps into handler. could be a follow up.

@nborges-awsnborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Agree with TJs latest comments for a follow up

@jariy17jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow Up PR

z.number().int().min(3).max(365).default(DEFAULT_EVENT_EXPIRY_DURATION),
),
flag(
"strategies",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SourceResolver in follow up PR

);

return {
type: parsed.data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift templates to here because its not part of the schema type.

@jariy17
jariy17 merged commit ba69115 into aws:refactorAug 24, 2026
8 of 13 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@notgitika@codecov-commenter@Hweinstock@jariy17@nborges-aws