Skip to content

Commit 5e0f584

Browse files
authored
feat: add semanticOverride support for SEMANTIC memory strategies (#678) (#696)
Add SemanticOverride schema support to memory strategies, allowing users to customize extraction and consolidation behavior with custom prompts and model IDs on SEMANTIC strategy types. Changes: - Add SemanticExtractionOverrideSchema, SemanticConsolidationOverrideSchema, and SemanticOverrideSchema with at-least-one validation - Add cross-field validation rejecting semanticOverride on non-SEMANTIC types - Export new schemas and types through barrel files - Update LLM-compacted types with semanticOverride interface - Add 13 new unit tests for override validation - Add 2 integration tests for agentcore-project validation Constraint: semanticOverride only valid on SEMANTIC strategy type Constraint: At least one of extraction or consolidation must be provided Rejected: Allow override on all strategy types | CFn only supports it on SEMANTIC Confidence: high Scope-risk: narrow
1 parent beac707 commit 5e0f584

File tree

6 files changed

+247
-4
lines changed

6 files changed

+247
-4
lines changed

src/schema/llm-compacted/agentcore.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ interface MemoryStrategy {
8383
description?: string;
8484
namespaces?: string[];
8585
reflectionNamespaces?: string[]; // EPISODIC only: namespaces for cross-episode reflections
86+
semanticOverride?: {
87+
// Only valid when type is 'SEMANTIC'
88+
extraction?: { appendToPrompt: string; modelId: string }; // @min 1 for both, @max 30000 for appendToPrompt
89+
consolidation?: { appendToPrompt: string; modelId: string }; // At least one of extraction/consolidation required
90+
};
8691
}
8792

8893
// ─────────────────────────────────────────────────────────────────────────────

src/schema/schemas/__tests__/agentcore-project.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,4 +478,56 @@ describe('AgentCoreProjectSpecSchema', () => {
478478
});
479479
expect(result.success).toBe(false);
480480
});
481+
482+
it('accepts memory with semanticOverride on SEMANTIC strategy', () => {
483+
const result = AgentCoreProjectSpecSchema.safeParse({
484+
...minimalProject,
485+
memories: [
486+
{
487+
type: 'AgentCoreMemory',
488+
name: 'TestMemory',
489+
eventExpiryDuration: 30,
490+
strategies: [
491+
{
492+
type: 'SEMANTIC',
493+
namespaces: ['/users/{actorId}/facts'],
494+
semanticOverride: {
495+
extraction: {
496+
appendToPrompt: 'Extract key facts',
497+
modelId: 'anthropic.claude-3-sonnet-20240229-v1:0',
498+
},
499+
},
500+
},
501+
],
502+
},
503+
],
504+
});
505+
expect(result.success).toBe(true);
506+
});
507+
508+
it('rejects memory with semanticOverride on SUMMARIZATION strategy', () => {
509+
const result = AgentCoreProjectSpecSchema.safeParse({
510+
...minimalProject,
511+
memories: [
512+
{
513+
type: 'AgentCoreMemory',
514+
name: 'TestMemory',
515+
eventExpiryDuration: 30,
516+
strategies: [
517+
{
518+
type: 'SUMMARIZATION',
519+
namespaces: ['/summaries/{actorId}/{sessionId}'],
520+
semanticOverride: {
521+
extraction: {
522+
appendToPrompt: 'test',
523+
modelId: 'model-1',
524+
},
525+
},
526+
},
527+
],
528+
},
529+
],
530+
});
531+
expect(result.success).toBe(false);
532+
});
481533
});

src/schema/schemas/agentcore-project.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ export {
3030
MemoryStrategyTypeSchema,
3131
};
3232
export { EvaluationLevelSchema };
33-
export type { MemoryStrategy, MemoryStrategyType } from './primitives/memory';
33+
export type {
34+
MemoryStrategy,
35+
MemoryStrategyType,
36+
SemanticOverride,
37+
SemanticExtractionOverride,
38+
SemanticConsolidationOverride,
39+
} from './primitives/memory';
3440
export type { OnlineEvalConfig } from './primitives/online-eval-config';
3541
export { OnlineEvalConfigSchema, OnlineEvalConfigNameSchema } from './primitives/online-eval-config';
3642
export type { EvaluationLevel, EvaluatorConfig, LlmAsAJudgeConfig, RatingScale } from './primitives/evaluator';

src/schema/schemas/primitives/__tests__/memory.test.ts

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { DEFAULT_STRATEGY_NAMESPACES, MemoryStrategySchema, MemoryStrategyTypeSchema } from '../memory';
1+
import {
2+
DEFAULT_STRATEGY_NAMESPACES,
3+
MemoryStrategySchema,
4+
MemoryStrategyTypeSchema,
5+
SemanticOverrideSchema,
6+
} from '../memory';
27
import { describe, expect, it } from 'vitest';
38

49
describe('MemoryStrategyTypeSchema', () => {
@@ -170,3 +175,115 @@ describe('DEFAULT_STRATEGY_NAMESPACES', () => {
170175
expect(DEFAULT_STRATEGY_NAMESPACES).not.toHaveProperty('CUSTOM');
171176
});
172177
});
178+
179+
describe('SemanticOverrideSchema', () => {
180+
it('accepts extraction-only override', () => {
181+
const result = SemanticOverrideSchema.safeParse({
182+
extraction: { appendToPrompt: 'Extract key facts', modelId: 'anthropic.claude-3-sonnet-20240229-v1:0' },
183+
});
184+
expect(result.success).toBe(true);
185+
});
186+
187+
it('accepts consolidation-only override', () => {
188+
const result = SemanticOverrideSchema.safeParse({
189+
consolidation: { appendToPrompt: 'Consolidate memories', modelId: 'anthropic.claude-3-sonnet-20240229-v1:0' },
190+
});
191+
expect(result.success).toBe(true);
192+
});
193+
194+
it('accepts both extraction and consolidation', () => {
195+
const result = SemanticOverrideSchema.safeParse({
196+
extraction: { appendToPrompt: 'Extract', modelId: 'model-1' },
197+
consolidation: { appendToPrompt: 'Consolidate', modelId: 'model-2' },
198+
});
199+
expect(result.success).toBe(true);
200+
});
201+
202+
it('rejects empty override (at least one required)', () => {
203+
const result = SemanticOverrideSchema.safeParse({});
204+
expect(result.success).toBe(false);
205+
});
206+
207+
it('rejects extraction with empty appendToPrompt', () => {
208+
const result = SemanticOverrideSchema.safeParse({
209+
extraction: { appendToPrompt: '', modelId: 'model-1' },
210+
});
211+
expect(result.success).toBe(false);
212+
});
213+
214+
it('rejects extraction with missing modelId', () => {
215+
const result = SemanticOverrideSchema.safeParse({
216+
extraction: { appendToPrompt: 'test' },
217+
});
218+
expect(result.success).toBe(false);
219+
});
220+
});
221+
222+
describe('MemoryStrategySchema with semanticOverride', () => {
223+
it('accepts SEMANTIC strategy with extraction override', () => {
224+
const result = MemoryStrategySchema.safeParse({
225+
type: 'SEMANTIC',
226+
semanticOverride: {
227+
extraction: { appendToPrompt: 'Extract key facts', modelId: 'anthropic.claude-3-sonnet-20240229-v1:0' },
228+
},
229+
});
230+
expect(result.success).toBe(true);
231+
});
232+
233+
it('accepts SEMANTIC strategy with both overrides', () => {
234+
const result = MemoryStrategySchema.safeParse({
235+
type: 'SEMANTIC',
236+
semanticOverride: {
237+
extraction: { appendToPrompt: 'Extract', modelId: 'model-1' },
238+
consolidation: { appendToPrompt: 'Consolidate', modelId: 'model-2' },
239+
},
240+
});
241+
expect(result.success).toBe(true);
242+
});
243+
244+
it('accepts SEMANTIC strategy without override (backward compat)', () => {
245+
const result = MemoryStrategySchema.safeParse({ type: 'SEMANTIC' });
246+
expect(result.success).toBe(true);
247+
});
248+
249+
it('rejects semanticOverride on SUMMARIZATION strategy', () => {
250+
const result = MemoryStrategySchema.safeParse({
251+
type: 'SUMMARIZATION',
252+
semanticOverride: {
253+
extraction: { appendToPrompt: 'test', modelId: 'model-1' },
254+
},
255+
});
256+
expect(result.success).toBe(false);
257+
if (!result.success) {
258+
expect(result.error.issues.some(i => i.message.includes('SEMANTIC'))).toBe(true);
259+
}
260+
});
261+
262+
it('rejects semanticOverride on USER_PREFERENCE strategy', () => {
263+
const result = MemoryStrategySchema.safeParse({
264+
type: 'USER_PREFERENCE',
265+
semanticOverride: {
266+
extraction: { appendToPrompt: 'test', modelId: 'model-1' },
267+
},
268+
});
269+
expect(result.success).toBe(false);
270+
});
271+
272+
it('rejects consolidation-only semanticOverride on USER_PREFERENCE strategy', () => {
273+
const result = MemoryStrategySchema.safeParse({
274+
type: 'USER_PREFERENCE',
275+
semanticOverride: {
276+
consolidation: { appendToPrompt: 'test', modelId: 'model-1' },
277+
},
278+
});
279+
expect(result.success).toBe(false);
280+
});
281+
282+
it('rejects SEMANTIC strategy with empty semanticOverride', () => {
283+
const result = MemoryStrategySchema.safeParse({
284+
type: 'SEMANTIC',
285+
semanticOverride: {},
286+
});
287+
expect(result.success).toBe(false);
288+
});
289+
});

src/schema/schemas/primitives/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
export type { MemoryStrategy, MemoryStrategyType } from './memory';
1+
export type {
2+
MemoryStrategy,
3+
MemoryStrategyType,
4+
SemanticOverride,
5+
SemanticExtractionOverride,
6+
SemanticConsolidationOverride,
7+
} from './memory';
28
export {
39
DEFAULT_EPISODIC_REFLECTION_NAMESPACES,
410
DEFAULT_STRATEGY_NAMESPACES,
511
MemoryStrategyNameSchema,
612
MemoryStrategySchema,
713
MemoryStrategyTypeSchema,
14+
SemanticOverrideSchema,
15+
SemanticExtractionOverrideSchema,
16+
SemanticConsolidationOverrideSchema,
817
} from './memory';
918

1019
export type {

src/schema/schemas/primitives/memory.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,54 @@ export const MemoryStrategyNameSchema = z
4848
'Must begin with a letter and contain only alphanumeric characters and underscores (max 48 chars)'
4949
);
5050

51+
// ============================================================================
52+
// Semantic Override Types (CloudFormation SemanticOverride)
53+
// ============================================================================
54+
55+
/**
56+
* Configuration for overriding semantic memory extraction behavior.
57+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrockagentcore-memory-semanticoverrideextractionconfigurationinput.html
58+
*/
59+
export const SemanticExtractionOverrideSchema = z.object({
60+
/** Custom prompt to append for memory extraction */
61+
appendToPrompt: z.string().min(1).max(30000),
62+
/** Bedrock model ID to use for extraction */
63+
modelId: z.string().min(1),
64+
});
65+
66+
export type SemanticExtractionOverride = z.infer<typeof SemanticExtractionOverrideSchema>;
67+
68+
/**
69+
* Configuration for overriding semantic memory consolidation behavior.
70+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrockagentcore-memory-semanticoverrideconsolidationconfigurationinput.html
71+
*/
72+
export const SemanticConsolidationOverrideSchema = z.object({
73+
/** Custom prompt to append for memory consolidation */
74+
appendToPrompt: z.string().min(1).max(30000),
75+
/** Bedrock model ID to use for consolidation */
76+
modelId: z.string().min(1),
77+
});
78+
79+
export type SemanticConsolidationOverride = z.infer<typeof SemanticConsolidationOverrideSchema>;
80+
81+
/**
82+
* Override configuration for semantic memory strategy.
83+
* At least one of extraction or consolidation must be provided.
84+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrockagentcore-memory-semanticoverride.html
85+
*/
86+
export const SemanticOverrideSchema = z
87+
.object({
88+
/** Override extraction behavior (custom prompt + model) */
89+
extraction: SemanticExtractionOverrideSchema.optional(),
90+
/** Override consolidation behavior (custom prompt + model) */
91+
consolidation: SemanticConsolidationOverrideSchema.optional(),
92+
})
93+
.refine(data => data.extraction !== undefined || data.consolidation !== undefined, {
94+
message: 'At least one of extraction or consolidation must be provided',
95+
});
96+
97+
export type SemanticOverride = z.infer<typeof SemanticOverrideSchema>;
98+
5199
/**
52100
* Memory strategy configuration.
53101
* Each memory can have multiple strategies with optional namespace scoping.
@@ -64,6 +112,8 @@ export const MemoryStrategySchema = z
64112
namespaces: z.array(z.string()).optional(),
65113
/** Reflection namespaces for EPISODIC strategy. Required by the service for episodic strategies. */
66114
reflectionNamespaces: z.array(z.string()).optional(),
115+
/** Only valid when type is 'SEMANTIC'. Override extraction and/or consolidation behavior. */
116+
semanticOverride: SemanticOverrideSchema.optional(),
67117
})
68118
.refine(
69119
strategy =>
@@ -83,6 +133,10 @@ export const MemoryStrategySchema = z
83133
message: 'Each reflectionNamespace must be a prefix of at least one namespace',
84134
path: ['reflectionNamespaces'],
85135
}
86-
);
136+
)
137+
.refine(strategy => strategy.semanticOverride === undefined || strategy.type === 'SEMANTIC', {
138+
message: 'semanticOverride is only valid for SEMANTIC strategy type',
139+
path: ['semanticOverride'],
140+
});
87141

88142
export type MemoryStrategy = z.infer<typeof MemoryStrategySchema>;

0 commit comments

Comments
 (0)