Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/48758.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_bedrockagentcore_memory_strategy: Add `reflection_configuration` configuration block for `EPISODIC` strategy type
```
170 changes: 154 additions & 16 deletions internal/service/bedrockagentcore/memory_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ func (r *resourceMemoryStrategy) Schema(ctx context.Context, request resource.Sc
},
},
Blocks: map[string]schema.Block{
"reflection_configuration": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[reflectionConfigurationModel](ctx),
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"namespace_templates": schema.ListAttribute{
CustomType: fwtypes.ListOfStringType,
Optional: true,
},
},
},
},
names.AttrConfiguration: schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[customConfigurationModel](ctx),
Validators: []validator.List{
Expand Down Expand Up @@ -149,6 +163,29 @@ func (r *resourceMemoryStrategy) Schema(ctx context.Context, request resource.Sc
},
},
},
"reflection": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[reflectionOverrideModel](ctx),
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
PlanModifiers: []planmodifier.List{
errorIfSingleBlockRemoved("reflection"),
},
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"append_to_prompt": schema.StringAttribute{
Required: true,
},
"model_id": schema.StringAttribute{
Required: true,
},
"namespace_templates": schema.ListAttribute{
CustomType: fwtypes.ListOfStringType,
Optional: true,
},
},
},
},
},
},
},
Expand Down Expand Up @@ -246,11 +283,19 @@ func (r *resourceMemoryStrategy) ValidateConfig(ctx context.Context, request res
if c.Type.ValueEnum() == awstypes.OverrideTypeSummaryOverride && !(c.Extraction.IsNull() || c.Extraction.IsUnknown()) {
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When configuration type is `SUMMARY_OVERRIDE`, the extraction block cannot be defined."))
}
if !(data.ReflectionConfiguration.IsNull() || data.ReflectionConfiguration.IsUnknown()) {
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When type is `CUSTOM`, the reflection_configuration block must be omitted."))
}
}
} else {
if !(data.Configuration.IsNull() || data.Configuration.IsUnknown()) {
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When type is not `CUSTOM`, the configuration block must be omitted."))
}
if data.Type.ValueEnum() != awstypes.MemoryStrategyTypeEpisodic {
if !(data.ReflectionConfiguration.IsNull() || data.ReflectionConfiguration.IsUnknown()) {
smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("When type is not `EPISODIC`, the reflection_configuration block must be omitted."))
}
}
}
}

Expand Down Expand Up @@ -311,6 +356,17 @@ func (r *resourceMemoryStrategy) Create(ctx context.Context, request resource.Cr
found.Configuration = nil
}
smerr.AddEnrich(ctx, &response.Diagnostics, fwflex.Flatten(ctx, found, &plan, fwflex.WithFieldNamePrefix("Memory")))

// Flatten reflection configuration for built-in EPISODIC type
if plan.Type.ValueEnum() == awstypes.MemoryStrategyTypeEpisodic && found.Configuration != nil && found.Configuration.Reflection != nil {
if episodicReflection, ok := found.Configuration.Reflection.(*awstypes.ReflectionConfigurationMemberEpisodicReflectionConfiguration); ok {
var rc reflectionConfigurationModel
if episodicReflection.Value.NamespaceTemplates != nil {
rc.NamespaceTemplates = fwflex.FlattenFrameworkStringValueListOfString(ctx, episodicReflection.Value.NamespaceTemplates)
}
plan.ReflectionConfiguration, _ = fwtypes.NewListNestedObjectValueOfPtr(ctx, &rc)
}
}
if response.Diagnostics.HasError() {
return
}
Expand Down Expand Up @@ -360,6 +416,19 @@ func (r *resourceMemoryStrategy) Read(ctx context.Context, request resource.Read
}

smerr.AddEnrich(ctx, &response.Diagnostics, fwflex.Flatten(ctx, out, &state, fwflex.WithFieldNamePrefix("Memory")))

// Flatten reflection configuration for built-in EPISODIC type.
// The API returns reflection config inside StrategyConfiguration, which is
// cleared for non-CUSTOM types before flattening. We handle it separately.
if state.Type.ValueEnum() == awstypes.MemoryStrategyTypeEpisodic && out.Configuration != nil && out.Configuration.Reflection != nil {
if episodicReflection, ok := out.Configuration.Reflection.(*awstypes.ReflectionConfigurationMemberEpisodicReflectionConfiguration); ok {
var rc reflectionConfigurationModel
if episodicReflection.Value.NamespaceTemplates != nil {
rc.NamespaceTemplates = fwflex.FlattenFrameworkStringValueListOfString(ctx, episodicReflection.Value.NamespaceTemplates)
}
state.ReflectionConfiguration, _ = fwtypes.NewListNestedObjectValueOfPtr(ctx, &rc)
}
}
if response.Diagnostics.HasError() {
return
}
Expand Down Expand Up @@ -611,15 +680,16 @@ func findMemoryStrategyByTwoPartKey(ctx context.Context, conn *bedrockagentcorec

type memoryStrategyResourceModel struct {
framework.WithRegionModel
Configuration fwtypes.ListNestedObjectValueOf[customConfigurationModel] `tfsdk:"configuration"`
Description types.String `tfsdk:"description"`
MemoryExecutionRoleARN fwtypes.ARN `tfsdk:"memory_execution_role_arn"`
MemoryStrategyID types.String `tfsdk:"memory_strategy_id"`
MemoryID types.String `tfsdk:"memory_id"`
Name types.String `tfsdk:"name"`
Namespaces fwtypes.SetOfString `tfsdk:"namespaces"`
Type fwtypes.StringEnum[awstypes.MemoryStrategyType] `tfsdk:"type"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
Configuration fwtypes.ListNestedObjectValueOf[customConfigurationModel] `tfsdk:"configuration"`
Description types.String `tfsdk:"description"`
MemoryExecutionRoleARN fwtypes.ARN `tfsdk:"memory_execution_role_arn"`
MemoryStrategyID types.String `tfsdk:"memory_strategy_id"`
MemoryID types.String `tfsdk:"memory_id"`
Name types.String `tfsdk:"name"`
Namespaces fwtypes.SetOfString `tfsdk:"namespaces"`
ReflectionConfiguration fwtypes.ListNestedObjectValueOf[reflectionConfigurationModel] `tfsdk:"reflection_configuration"`
Type fwtypes.StringEnum[awstypes.MemoryStrategyType] `tfsdk:"type"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
}

func (m *memoryStrategyResourceModel) GetIdentifier() string {
Expand Down Expand Up @@ -695,11 +765,27 @@ func (m memoryStrategyResourceModel) expandToMemoryStrategyInput(ctx context.Con
if diags.HasError() {
return nil, diags
}
// The API requires the reflection namespace to be the same as or a prefix
// of the episodic namespace. Set it to match the episodic namespaces.
r.Value.ReflectionConfiguration = &awstypes.EpisodicReflectionConfigurationInput{
Namespaces: r.Value.Namespaces,
// Build reflection configuration from user-provided values or defaults.
// If no reflection_configuration block is provided, set namespaceTemplates
// to match the episodic namespaces (matching the legacy default behavior).
reflectionConfig := &awstypes.EpisodicReflectionConfigurationInput{}
if !m.ReflectionConfiguration.IsNull() && !m.ReflectionConfiguration.IsUnknown() {
rc, rcDiags := m.ReflectionConfiguration.ToPtr(ctx)
smerr.AddEnrich(ctx, &diags, rcDiags)
if diags.HasError() {
return nil, diags
}
if !rc.NamespaceTemplates.IsNull() && !rc.NamespaceTemplates.IsUnknown() {
smerr.AddEnrich(ctx, &diags, rc.NamespaceTemplates.ElementsAs(ctx, &reflectionConfig.NamespaceTemplates, false))
if diags.HasError() {
return nil, diags
}
}
} else {
// Default: use episodic namespaces as reflection namespaces
reflectionConfig.Namespaces = r.Value.Namespaces
}
r.Value.ReflectionConfiguration = reflectionConfig
return &r, diags
default:
diags.AddError(
Expand Down Expand Up @@ -728,9 +814,10 @@ func (m memoryStrategyResourceModel) expandToModifyMemoryStrategyInput(ctx conte
}

type customConfigurationModel struct {
Type fwtypes.StringEnum[awstypes.OverrideType] `tfsdk:"type"`
Consolidation fwtypes.ListNestedObjectValueOf[overrideDetailsModel] `tfsdk:"consolidation"`
Extraction fwtypes.ListNestedObjectValueOf[overrideDetailsModel] `tfsdk:"extraction"`
Type fwtypes.StringEnum[awstypes.OverrideType] `tfsdk:"type"`
Consolidation fwtypes.ListNestedObjectValueOf[overrideDetailsModel] `tfsdk:"consolidation"`
Extraction fwtypes.ListNestedObjectValueOf[overrideDetailsModel] `tfsdk:"extraction"`
Reflection fwtypes.ListNestedObjectValueOf[reflectionOverrideModel] `tfsdk:"reflection"`
}

var (
Expand Down Expand Up @@ -773,6 +860,26 @@ func (m *customConfigurationModel) Flatten(ctx context.Context, v any) (diags di
}
}
}

if t.Reflection != nil {
switch rt := t.Reflection.(type) {
case *awstypes.ReflectionConfigurationMemberCustomReflectionConfiguration:
if customReflection, ok := rt.Value.(*awstypes.CustomReflectionConfigurationMemberEpisodicReflectionOverride); ok {
var reflection reflectionOverrideModel
smerr.AddEnrich(ctx, &diags, fwflex.Flatten(ctx, customReflection.Value, &reflection))
if diags.HasError() {
return diags
}
if !reflection.AppendToPrompt.IsNull() && !reflection.ModelID.IsNull() {
m.Reflection, d = fwtypes.NewListNestedObjectValueOfPtr(ctx, &reflection)
smerr.AddEnrich(ctx, &diags, d)
if diags.HasError() {
return diags
}
}
}
}
}
default:
diags.AddError(
"Unsupported Type",
Expand Down Expand Up @@ -864,6 +971,14 @@ func (m customConfigurationModel) expandToModifyStrategyConfiguration(ctx contex
}
}

var reflection *reflectionOverrideModel
if !m.Reflection.IsNull() {
reflection, d = m.Reflection.ToPtr(ctx)
smerr.AddEnrich(ctx, &diags, d)
if diags.HasError() {
return nil, diags
}
}
switch m.Type.ValueEnum() {
case awstypes.OverrideTypeSemanticOverride:
if consolidation != nil {
Expand Down Expand Up @@ -949,6 +1064,17 @@ func (m customConfigurationModel) expandToModifyStrategyConfiguration(ctx contex
Value: &extractionInput,
}
}

if reflection != nil {
var reflectionInput awstypes.CustomReflectionConfigurationInputMemberEpisodicReflectionOverride
smerr.AddEnrich(ctx, &diags, fwflex.Expand(ctx, reflection, &reflectionInput.Value))
if diags.HasError() {
return nil, diags
}
result.Reflection = &awstypes.ModifyReflectionConfigurationMemberCustomReflectionConfiguration{
Value: &reflectionInput,
}
}
default:
diags.AddError(
"Unsupported Type",
Expand All @@ -964,6 +1090,18 @@ type overrideDetailsModel struct {
ModelID types.String `tfsdk:"model_id"`
}

// reflectionConfigurationModel is the model for the built-in EPISODIC reflection_configuration block.
type reflectionConfigurationModel struct {
NamespaceTemplates fwtypes.ListOfString `tfsdk:"namespace_templates"`
}

// reflectionOverrideModel is the model for the reflection block inside configuration (CUSTOM EPISODIC_OVERRIDE).
type reflectionOverrideModel struct {
AppendToPrompt types.String `tfsdk:"append_to_prompt"`
ModelID types.String `tfsdk:"model_id"`
NamespaceTemplates fwtypes.ListOfString `tfsdk:"namespace_templates"`
}

var (
_ fwflex.Flattener = &overrideDetailsModel{}
)
Expand Down
68 changes: 68 additions & 0 deletions website/docs/r/bedrockagentcore_memory_strategy.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ resource "aws_bedrockagentcore_memory_strategy" "episodic" {
}
```


### Episodic Strategy with Custom Reflection Configuration

```terraform
resource "aws_bedrockagentcore_memory_strategy" "episodic_reflection" {
name = "episodic-reflection-strategy"
memory_id = aws_bedrockagentcore_memory.example.id
type = "EPISODIC"
description = "Episodic strategy with custom reflection namespaces"
namespaces = ["/strategies/{memoryStrategyId}/actors/{actorId}/sessions/{sessionId}"]

reflection_configuration {
namespace_templates = ["/strategies/{memoryStrategyId}/actors/{actorId}/reflections"]
}
}
```

### Custom Strategy with Semantic Override

```terraform
Expand Down Expand Up @@ -167,6 +184,40 @@ resource "aws_bedrockagentcore_memory_strategy" "custom_episodic" {
}
```


### Custom Strategy with Episodic Override and Reflection

```terraform
resource "aws_bedrockagentcore_memory_strategy" "custom_episodic_reflection" {
name = "custom-episodic-reflection-strategy"
memory_id = aws_bedrockagentcore_memory.example.id
memory_execution_role_arn = aws_bedrockagentcore_memory.example.memory_execution_role_arn
type = "CUSTOM"
description = "Custom episodic strategy with reflection override"
namespaces = ["/strategies/{memoryStrategyId}/actors/{actorId}/sessions/{sessionId}"]

configuration {
type = "EPISODIC_OVERRIDE"

consolidation {
append_to_prompt = "Consolidate episodic memories into coherent narratives"
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
}

extraction {
append_to_prompt = "Extract key events and episodes from interactions"
model_id = "anthropic.claude-3-haiku-20240307-v1:0"
}

reflection {
append_to_prompt = "Identify successful patterns and recurring failure modes across episodes"
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
namespace_templates = ["/strategies/{memoryStrategyId}/actors/{actorId}/reflections"]
}
}
}
```

## Argument Reference

The following arguments are required:
Expand All @@ -180,6 +231,7 @@ The following arguments are optional:

* `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).
* `description` - (Optional) Description of the memory strategy.
* `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.
* `configuration` - (Optional) Custom configuration block. Required when `type` is `CUSTOM`, must be omitted for other types. See [`configuration`](#configuration) below.

### `configuration`
Expand All @@ -189,6 +241,7 @@ The `configuration` block supports the following:
* `type` - (Required) Type of custom override. Valid values: `SEMANTIC_OVERRIDE`, `SUMMARY_OVERRIDE`, `USER_PREFERENCE_OVERRIDE`, `EPISODIC_OVERRIDE`. Changing this forces a new resource.
* `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.
* `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.
* `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.

### `consolidation`

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

### `reflection`

The `reflection` block supports the following (only valid when `configuration.type` is `EPISODIC_OVERRIDE`):

* `append_to_prompt` - (Required) Additional text to append to the model prompt for reflection processing.
* `model_id` - (Required) ID of the foundation model to use for reflection processing.
* `namespace_templates` - (Optional) Set of namespace templates where reflection records are stored. Can be less nested than episode namespaces.

### `reflection_configuration`

The `reflection_configuration` block supports the following (only valid when `type` is `EPISODIC`):

* `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.


## Attribute Reference

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