Skip to content

Commit e60a424

Browse files
committed
enhance(bedrockagentcore_memory_strategy): add reflection_configuration for EPISODIC type
Adds support for configuring reflection behavior in both built-in EPISODIC and CUSTOM EPISODIC_OVERRIDE memory strategies. For built-in EPISODIC strategies, a new optional reflection_configuration block allows users to customize where cross-episode reflection records are stored via namespace_templates. For CUSTOM strategies with EPISODIC_OVERRIDE type, a new optional reflection block inside configuration allows full customization of the reflection step including append_to_prompt, model_id, and namespace_templates. Closes #47938
1 parent 69ad662 commit e60a424

2 files changed

Lines changed: 215 additions & 4 deletions

File tree

internal/service/bedrockagentcore/memory_strategy.go

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ func (r *resourceMemoryStrategy) Schema(ctx context.Context, request resource.Sc
8888
CustomType: fwtypes.SetOfStringType,
8989
Required: true,
9090
},
91+
},
92+
"reflection_configuration": schema.ListNestedBlock{
93+
CustomType: fwtypes.NewListNestedObjectTypeOf[reflectionConfigurationModel](ctx),
94+
Validators: []validator.List{
95+
listvalidator.SizeAtMost(1),
96+
},
97+
NestedObject: schema.NestedBlockObject{
98+
Attributes: map[string]schema.Attribute{
99+
"namespace_templates": schema.ListAttribute{
100+
CustomType: fwtypes.ListOfStringType,
101+
Optional: true,
102+
},
103+
},
104+
},
105+
},
91106
names.AttrType: schema.StringAttribute{
92107
Required: true,
93108
CustomType: fwtypes.StringEnumType[awstypes.MemoryStrategyType](),
@@ -149,6 +164,29 @@ func (r *resourceMemoryStrategy) Schema(ctx context.Context, request resource.Sc
149164
},
150165
},
151166
},
167+
"reflection": schema.ListNestedBlock{
168+
CustomType: fwtypes.NewListNestedObjectTypeOf[reflectionOverrideModel](ctx),
169+
Validators: []validator.List{
170+
listvalidator.SizeAtMost(1),
171+
},
172+
PlanModifiers: []planmodifier.List{
173+
errorIfSingleBlockRemoved("reflection"),
174+
},
175+
NestedObject: schema.NestedBlockObject{
176+
Attributes: map[string]schema.Attribute{
177+
"append_to_prompt": schema.StringAttribute{
178+
Required: true,
179+
},
180+
"model_id": schema.StringAttribute{
181+
Required: true,
182+
},
183+
"namespace_templates": schema.ListAttribute{
184+
CustomType: fwtypes.ListOfStringType,
185+
Optional: true,
186+
},
187+
},
188+
},
189+
},
152190
},
153191
},
154192
},
@@ -246,11 +284,20 @@ func (r *resourceMemoryStrategy) ValidateConfig(ctx context.Context, request res
246284
if c.Type.ValueEnum() == awstypes.OverrideTypeSummaryOverride && !(c.Extraction.IsNull() || c.Extraction.IsUnknown()) {
247285
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When configuration type is `SUMMARY_OVERRIDE`, the extraction block cannot be defined."))
248286
}
287+
if !(data.ReflectionConfiguration.IsNull() || data.ReflectionConfiguration.IsUnknown()) {
288+
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When type is `CUSTOM`, the reflection_configuration block must be omitted."))
289+
}
249290
}
250291
} else {
251292
if !(data.Configuration.IsNull() || data.Configuration.IsUnknown()) {
252293
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When type is not `CUSTOM`, the configuration block must be omitted."))
253294
}
295+
if data.Type.ValueEnum() != awstypes.MemoryStrategyTypeEpisodic {
296+
if !(data.ReflectionConfiguration.IsNull() || data.ReflectionConfiguration.IsUnknown()) {
297+
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When type is not `EPISODIC`, the reflection_configuration block must be omitted."))
298+
}
299+
}
300+
}
254301
}
255302
}
256303

@@ -311,6 +358,17 @@ func (r *resourceMemoryStrategy) Create(ctx context.Context, request resource.Cr
311358
found.Configuration = nil
312359
}
313360
smerr.AddEnrich(ctx, &response.Diagnostics, fwflex.Flatten(ctx, found, &plan, fwflex.WithFieldNamePrefix("Memory")))
361+
362+
// Flatten reflection configuration for built-in EPISODIC type
363+
if plan.Type.ValueEnum() == awstypes.MemoryStrategyTypeEpisodic && found.Configuration != nil && found.Configuration.Reflection != nil {
364+
if episodicReflection, ok := found.Configuration.Reflection.(*awstypes.ReflectionConfigurationMemberEpisodicReflectionConfiguration); ok {
365+
var rc reflectionConfigurationModel
366+
if episodicReflection.Value.NamespaceTemplates != nil {
367+
rc.NamespaceTemplates, _ = fwtypes.ListOfStringValueFrom(ctx, episodicReflection.Value.NamespaceTemplates)
368+
}
369+
plan.ReflectionConfiguration, _ = fwtypes.NewListNestedObjectValueOfPtr(ctx, &rc)
370+
}
371+
}
314372
if response.Diagnostics.HasError() {
315373
return
316374
}
@@ -360,6 +418,19 @@ func (r *resourceMemoryStrategy) Read(ctx context.Context, request resource.Read
360418
}
361419

362420
smerr.AddEnrich(ctx, &response.Diagnostics, fwflex.Flatten(ctx, out, &state, fwflex.WithFieldNamePrefix("Memory")))
421+
422+
// Flatten reflection configuration for built-in EPISODIC type.
423+
// The API returns reflection config inside StrategyConfiguration, which is
424+
// cleared for non-CUSTOM types before flattening. We handle it separately.
425+
if state.Type.ValueEnum() == awstypes.MemoryStrategyTypeEpisodic && out.Configuration != nil && out.Configuration.Reflection != nil {
426+
if episodicReflection, ok := out.Configuration.Reflection.(*awstypes.ReflectionConfigurationMemberEpisodicReflectionConfiguration); ok {
427+
var rc reflectionConfigurationModel
428+
if episodicReflection.Value.NamespaceTemplates != nil {
429+
rc.NamespaceTemplates, _ = fwtypes.ListOfStringValueFrom(ctx, episodicReflection.Value.NamespaceTemplates)
430+
}
431+
state.ReflectionConfiguration, _ = fwtypes.NewListNestedObjectValueOfPtr(ctx, &rc)
432+
}
433+
}
363434
if response.Diagnostics.HasError() {
364435
return
365436
}
@@ -618,6 +689,7 @@ type memoryStrategyResourceModel struct {
618689
MemoryID types.String `tfsdk:"memory_id"`
619690
Name types.String `tfsdk:"name"`
620691
Namespaces fwtypes.SetOfString `tfsdk:"namespaces"`
692+
ReflectionConfiguration fwtypes.ListNestedObjectValueOf[reflectionConfigurationModel] `tfsdk:"reflection_configuration"`
621693
Type fwtypes.StringEnum[awstypes.MemoryStrategyType] `tfsdk:"type"`
622694
Timeouts timeouts.Value `tfsdk:"timeouts"`
623695
}
@@ -695,11 +767,28 @@ func (m memoryStrategyResourceModel) expandToMemoryStrategyInput(ctx context.Con
695767
if diags.HasError() {
696768
return nil, diags
697769
}
698-
// The API requires the reflection namespace to be the same as or a prefix
699-
// of the episodic namespace. Set it to match the episodic namespaces.
700-
r.Value.ReflectionConfiguration = &awstypes.EpisodicReflectionConfigurationInput{
701-
Namespaces: r.Value.Namespaces,
770+
// Build reflection configuration from user-provided values or defaults.
771+
// If no reflection_configuration block is provided, set namespaceTemplates
772+
// to match the episodic namespaces (matching the legacy default behavior).
773+
reflectionConfig := &awstypes.EpisodicReflectionConfigurationInput{}
774+
if !m.ReflectionConfiguration.IsNull() && !m.ReflectionConfiguration.IsUnknown() {
775+
rc, rcDiags := m.ReflectionConfiguration.ToPtr(ctx)
776+
smerr.AddEnrich(ctx, &diags, rcDiags)
777+
if diags.HasError() {
778+
return nil, diags
779+
}
780+
if !rc.NamespaceTemplates.IsNull() && !rc.NamespaceTemplates.IsUnknown() {
781+
smerr.AddEnrich(ctx, &diags, rc.NamespaceTemplates.ElementsAs(ctx, &reflectionConfig.NamespaceTemplates, false))
782+
if diags.HasError() {
783+
return nil, diags
784+
}
785+
}
786+
} else {
787+
// Default: use episodic namespaces as reflection namespaces
788+
reflectionConfig.Namespaces = r.Value.Namespaces
702789
}
790+
r.Value.ReflectionConfiguration = reflectionConfig
791+
return &r, diags
703792
return &r, diags
704793
default:
705794
diags.AddError(
@@ -731,6 +820,7 @@ type customConfigurationModel struct {
731820
Type fwtypes.StringEnum[awstypes.OverrideType] `tfsdk:"type"`
732821
Consolidation fwtypes.ListNestedObjectValueOf[overrideDetailsModel] `tfsdk:"consolidation"`
733822
Extraction fwtypes.ListNestedObjectValueOf[overrideDetailsModel] `tfsdk:"extraction"`
823+
Reflection fwtypes.ListNestedObjectValueOf[reflectionOverrideModel] `tfsdk:"reflection"`
734824
}
735825

736826
var (
@@ -772,6 +862,27 @@ func (m *customConfigurationModel) Flatten(ctx context.Context, v any) (diags di
772862
return diags
773863
}
774864
}
865+
866+
if t.Reflection != nil {
867+
switch rt := t.Reflection.(type) {
868+
case *awstypes.ReflectionConfigurationMemberCustomReflectionConfiguration:
869+
if customReflection, ok := rt.Value.(*awstypes.CustomReflectionConfigurationMemberEpisodicReflectionOverride); ok {
870+
var reflection reflectionOverrideModel
871+
smerr.AddEnrich(ctx, &diags, fwflex.Flatten(ctx, customReflection.Value, &reflection))
872+
if diags.HasError() {
873+
return diags
874+
}
875+
if !reflection.AppendToPrompt.IsNull() && !reflection.ModelID.IsNull() {
876+
m.Reflection, d = fwtypes.NewListNestedObjectValueOfPtr(ctx, &reflection)
877+
smerr.AddEnrich(ctx, &diags, d)
878+
if diags.HasError() {
879+
return diags
880+
}
881+
}
882+
}
883+
}
884+
}
885+
}
775886
}
776887
default:
777888
diags.AddError(
@@ -864,6 +975,15 @@ func (m customConfigurationModel) expandToModifyStrategyConfiguration(ctx contex
864975
}
865976
}
866977

978+
979+
var reflection *reflectionOverrideModel
980+
if !m.Reflection.IsNull() {
981+
reflection, d = m.Reflection.ToPtr(ctx)
982+
smerr.AddEnrich(ctx, &diags, d)
983+
if diags.HasError() {
984+
return nil, diags
985+
}
986+
}
867987
switch m.Type.ValueEnum() {
868988
case awstypes.OverrideTypeSemanticOverride:
869989
if consolidation != nil {
@@ -949,6 +1069,17 @@ func (m customConfigurationModel) expandToModifyStrategyConfiguration(ctx contex
9491069
Value: &extractionInput,
9501070
}
9511071
}
1072+
1073+
if reflection != nil {
1074+
var reflectionInput awstypes.CustomReflectionConfigurationInputMemberEpisodicReflectionOverride
1075+
smerr.AddEnrich(ctx, &diags, fwflex.Expand(ctx, reflection, &reflectionInput.Value))
1076+
if diags.HasError() {
1077+
return nil, diags
1078+
}
1079+
result.Reflection = &awstypes.ModifyReflectionConfigurationMemberCustomReflectionConfiguration{
1080+
Value: &reflectionInput,
1081+
}
1082+
}
9521083
default:
9531084
diags.AddError(
9541085
"Unsupported Type",
@@ -964,6 +1095,18 @@ type overrideDetailsModel struct {
9641095
ModelID types.String `tfsdk:"model_id"`
9651096
}
9661097

1098+
// reflectionConfigurationModel is the model for the built-in EPISODIC reflection_configuration block.
1099+
type reflectionConfigurationModel struct {
1100+
NamespaceTemplates fwtypes.ListOfString `tfsdk:"namespace_templates"`
1101+
}
1102+
1103+
// reflectionOverrideModel is the model for the reflection block inside configuration (CUSTOM EPISODIC_OVERRIDE).
1104+
type reflectionOverrideModel struct {
1105+
AppendToPrompt types.String `tfsdk:"append_to_prompt"`
1106+
ModelID types.String `tfsdk:"model_id"`
1107+
NamespaceTemplates fwtypes.ListOfString `tfsdk:"namespace_templates"`
1108+
}
1109+
9671110
var (
9681111
_ fwflex.Flattener = &overrideDetailsModel{}
9691112
)

website/docs/r/bedrockagentcore_memory_strategy.html.markdown

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ resource "aws_bedrockagentcore_memory_strategy" "episodic" {
6666
}
6767
```
6868

69+
70+
### Episodic Strategy with Custom Reflection Configuration
71+
72+
```terraform
73+
resource "aws_bedrockagentcore_memory_strategy" "episodic_reflection" {
74+
name = "episodic-reflection-strategy"
75+
memory_id = aws_bedrockagentcore_memory.example.id
76+
type = "EPISODIC"
77+
description = "Episodic strategy with custom reflection namespaces"
78+
namespaces = ["/strategies/{memoryStrategyId}/actors/{actorId}/sessions/{sessionId}"]
79+
80+
reflection_configuration {
81+
namespace_templates = ["/strategies/{memoryStrategyId}/actors/{actorId}/reflections"]
82+
}
83+
}
84+
```
85+
6986
### Custom Strategy with Semantic Override
7087

7188
```terraform
@@ -167,6 +184,40 @@ resource "aws_bedrockagentcore_memory_strategy" "custom_episodic" {
167184
}
168185
```
169186

187+
188+
### Custom Strategy with Episodic Override and Reflection
189+
190+
```terraform
191+
resource "aws_bedrockagentcore_memory_strategy" "custom_episodic_reflection" {
192+
name = "custom-episodic-reflection-strategy"
193+
memory_id = aws_bedrockagentcore_memory.example.id
194+
memory_execution_role_arn = aws_bedrockagentcore_memory.example.memory_execution_role_arn
195+
type = "CUSTOM"
196+
description = "Custom episodic strategy with reflection override"
197+
namespaces = ["/strategies/{memoryStrategyId}/actors/{actorId}/sessions/{sessionId}"]
198+
199+
configuration {
200+
type = "EPISODIC_OVERRIDE"
201+
202+
consolidation {
203+
append_to_prompt = "Consolidate episodic memories into coherent narratives"
204+
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
205+
}
206+
207+
extraction {
208+
append_to_prompt = "Extract key events and episodes from interactions"
209+
model_id = "anthropic.claude-3-haiku-20240307-v1:0"
210+
}
211+
212+
reflection {
213+
append_to_prompt = "Identify successful patterns and recurring failure modes across episodes"
214+
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
215+
namespace_templates = ["/strategies/{memoryStrategyId}/actors/{actorId}/reflections"]
216+
}
217+
}
218+
}
219+
```
220+
170221
## Argument Reference
171222

172223
The following arguments are required:
@@ -180,6 +231,7 @@ The following arguments are optional:
180231

181232
* `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference).
182233
* `description` - (Optional) Description of the memory strategy.
234+
* `reflection_configuration` - (Optional) Reflection configuration block for built-in `EPISODIC` strategies. Controls where reflections (cross-episode insights) are stored. Must be omitted when `type` is not `EPISODIC`. See [`reflection_configuration`](#reflection_configuration) below.
183235
* `configuration` - (Optional) Custom configuration block. Required when `type` is `CUSTOM`, must be omitted for other types. See [`configuration`](#configuration) below.
184236

185237
### `configuration`
@@ -189,6 +241,7 @@ The `configuration` block supports the following:
189241
* `type` - (Required) Type of custom override. Valid values: `SEMANTIC_OVERRIDE`, `SUMMARY_OVERRIDE`, `USER_PREFERENCE_OVERRIDE`, `EPISODIC_OVERRIDE`. Changing this forces a new resource.
190242
* `consolidation` - (Optional) Consolidation configuration for processing and organizing memory content. See [`consolidation`](#consolidation) below. Once added, this block cannot be removed without recreating the resource.
191243
* `extraction` - (Optional) Extraction configuration for identifying and extracting relevant information. See [`extraction`](#extraction) below. Cannot be used with `type` set to `SUMMARY_OVERRIDE`. Once added, this block cannot be removed without recreating the resource.
244+
* `reflection` - (Optional) Reflection configuration for customizing the reflection step. Only valid when `type` is `EPISODIC_OVERRIDE`. See [`reflection`](#reflection) below. Once added, this block cannot be removed without recreating the resource.
192245

193246
### `consolidation`
194247

@@ -204,6 +257,21 @@ The `extraction` block supports the following:
204257
* `append_to_prompt` - (Required) Additional text to append to the model prompt for extraction processing.
205258
* `model_id` - (Required) ID of the foundation model to use for extraction processing.
206259

260+
### `reflection`
261+
262+
The `reflection` block supports the following (only valid when `configuration.type` is `EPISODIC_OVERRIDE`):
263+
264+
* `append_to_prompt` - (Required) Additional text to append to the model prompt for reflection processing.
265+
* `model_id` - (Required) ID of the foundation model to use for reflection processing.
266+
* `namespace_templates` - (Optional) Set of namespace templates where reflection records are stored. Can be less nested than episode namespaces.
267+
268+
### `reflection_configuration`
269+
270+
The `reflection_configuration` block supports the following (only valid when `type` is `EPISODIC`):
271+
272+
* `namespace_templates` - (Optional) Set of namespace templates where reflection records are stored. Can be less nested than episode namespaces. If omitted, defaults to the episodic namespaces.
273+
274+
207275
## Attribute Reference
208276

209277
This resource exports the following attributes in addition to the arguments above:

0 commit comments

Comments
 (0)