From b7387f7122dfa5f97b5cd2088e2ef170c4ac0688 Mon Sep 17 00:00:00 2001 From: Adam Weber Date: Thu, 22 Jan 2026 12:55:38 -0800 Subject: [PATCH 1/4] refactor: use L2 constructs and convert NestedStacks to Constructs --- infra-cdk/lib/amplify-hosting-stack.ts | 9 +- infra-cdk/lib/backend-stack.ts | 186 ++++++------------------- infra-cdk/lib/cognito-stack.ts | 8 +- infra-cdk/lib/fast-main-stack.ts | 54 +++---- 4 files changed, 75 insertions(+), 182 deletions(-) diff --git a/infra-cdk/lib/amplify-hosting-stack.ts b/infra-cdk/lib/amplify-hosting-stack.ts index 4a48645..8a164e5 100644 --- a/infra-cdk/lib/amplify-hosting-stack.ts +++ b/infra-cdk/lib/amplify-hosting-stack.ts @@ -5,18 +5,17 @@ import * as iam from "aws-cdk-lib/aws-iam" import { Construct } from "constructs" import { AppConfig } from "./utils/config-manager" -export interface AmplifyStackProps extends cdk.NestedStackProps { +export interface AmplifyConstructProps { config: AppConfig } -export class AmplifyHostingStack extends cdk.NestedStack { +export class AmplifyHostingConstruct extends Construct { public readonly amplifyApp: amplify.App public readonly amplifyUrl: string public readonly stagingBucket: s3.Bucket - constructor(scope: Construct, id: string, props: AmplifyStackProps) { - const description = "Fullstack AgentCore Solution Template - Amplify Hosting Stack" - super(scope, id, { ...props, description }) + constructor(scope: Construct, id: string, props: AmplifyConstructProps) { + super(scope, id) // Create access logs bucket for staging bucket const accessLogsBucket = new s3.Bucket(this, "StagingBucketAccessLogs", { diff --git a/infra-cdk/lib/backend-stack.ts b/infra-cdk/lib/backend-stack.ts index 4b726a5..23da4d9 100644 --- a/infra-cdk/lib/backend-stack.ts +++ b/infra-cdk/lib/backend-stack.ts @@ -8,7 +8,6 @@ import * as apigateway from "aws-cdk-lib/aws-apigateway" import * as logs from "aws-cdk-lib/aws-logs" import * as s3 from "aws-cdk-lib/aws-s3" import * as agentcore from "@aws-cdk/aws-bedrock-agentcore-alpha" -import * as bedrockagentcore from "aws-cdk-lib/aws-bedrockagentcore" import { PythonFunction } from "@aws-cdk/aws-lambda-python-alpha" import * as lambda from "aws-cdk-lib/aws-lambda" import * as ecr_assets from "aws-cdk-lib/aws-ecr-assets" @@ -19,7 +18,7 @@ import { AgentCoreRole } from "./utils/agentcore-role" import * as path from "path" import * as fs from "fs" -export interface BackendStackProps extends cdk.NestedStackProps { +export interface BackendConstructProps { config: AppConfig userPoolId: string userPoolClientId: string @@ -27,21 +26,19 @@ export interface BackendStackProps extends cdk.NestedStackProps { frontendUrl: string } -export class BackendStack extends cdk.NestedStack { +export class BackendConstruct extends Construct { public readonly userPoolId: string public readonly userPoolClientId: string public readonly userPoolDomain: cognito.UserPoolDomain public feedbackApiUrl: string public runtimeArn: string public memoryArn: string - private agentName: cdk.CfnParameter - private networkMode: cdk.CfnParameter private userPool: cognito.IUserPool private machineClient: cognito.UserPoolClient private agentRuntime: agentcore.Runtime - constructor(scope: Construct, id: string, props: BackendStackProps) { - super(scope, id, props) + constructor(scope: Construct, id: string, props: BackendConstructProps) { + super(scope, id) // Store the Cognito values this.userPoolId = props.userPoolId @@ -96,20 +93,8 @@ export class BackendStack extends cdk.NestedStack { private createAgentCoreRuntime(config: AppConfig): void { const pattern = config.backend?.pattern || "strands-single-agent" - - // Parameters - this.agentName = new cdk.CfnParameter(this, "AgentName", { - type: "String", - default: "StrandsAgent", - description: "Name for the agent runtime", - }) - - this.networkMode = new cdk.CfnParameter(this, "NetworkMode", { - type: "String", - default: "PUBLIC", - description: "Network mode for AgentCore resources", - allowedValues: ["PUBLIC", "PRIVATE"], - }) + const agentName = "StrandsAgent" + const networkMode: string = "PUBLIC" const stack = cdk.Stack.of(this) const deploymentType = config.backend.deployment_type @@ -217,7 +202,7 @@ export class BackendStack extends cdk.NestedStack { // Configure network mode const networkConfiguration = - this.networkMode.valueAsString === "PRIVATE" + networkMode === "PRIVATE" ? undefined // For private mode, you would need to configure VPC settings : agentcore.RuntimeNetworkConfiguration.usingPublicNetwork() @@ -232,22 +217,14 @@ export class BackendStack extends cdk.NestedStack { // Create memory resource with short-term memory (conversation history) as default // To enable long-term strategies (summaries, preferences, facts), see docs/MEMORY_INTEGRATION.md - const memory = new cdk.CfnResource(this, "AgentMemory", { - type: "AWS::BedrockAgentCore::Memory", - properties: { - Name: cdk.Names.uniqueResourceName(this, { maxLength: 48 }), - EventExpiryDuration: 30, - Description: `Short-term memory for ${config.stack_name_base} agent`, - MemoryStrategies: [], // Empty array = short-term only (conversation history) - MemoryExecutionRoleArn: agentRole.roleArn, - Tags: { - Name: `${config.stack_name_base}_Memory`, - ManagedBy: "CDK", - }, - }, + const memory = new agentcore.Memory(this, "AgentMemory", { + memoryName: cdk.Names.uniqueResourceName(this, { maxLength: 48 }), + expirationDuration: cdk.Duration.days(30), + description: `Short-term memory for ${config.stack_name_base} agent`, + executionRole: agentRole, }) - const memoryId = memory.getAtt("MemoryId").toString() - const memoryArn = memory.getAtt("MemoryArn").toString() + const memoryId = memory.memoryId + const memoryArn = memory.memoryArn // Store the memory ARN for access from main stack this.memoryArn = memoryArn @@ -274,7 +251,7 @@ export class BackendStack extends cdk.NestedStack { effect: iam.Effect.ALLOW, actions: ["ssm:GetParameter", "ssm:GetParameters"], resources: [ - `arn:aws:ssm:${this.region}:${this.account}:parameter/${config.stack_name_base}/*`, + `arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/${config.stack_name_base}/*`, ], }) ) @@ -289,7 +266,7 @@ export class BackendStack extends cdk.NestedStack { "bedrock-agentcore:StopCodeInterpreterSession", "bedrock-agentcore:InvokeCodeInterpreter", ], - resources: [`arn:aws:bedrock-agentcore:${this.region}:aws:code-interpreter/*`], + resources: [`arn:aws:bedrock-agentcore:${cdk.Stack.of(this).region}:aws:code-interpreter/*`], }) ) @@ -303,7 +280,7 @@ export class BackendStack extends cdk.NestedStack { // Create the runtime using L2 construct this.agentRuntime = new agentcore.Runtime(this, "Runtime", { - runtimeName: `${config.stack_name_base.replace(/-/g, "_")}_${this.agentName.valueAsString}`, + runtimeName: `${config.stack_name_base.replace(/-/g, "_")}_${agentName}`, agentRuntimeArtifact: agentRuntimeArtifact, executionRole: agentRole, networkConfiguration: networkConfiguration, @@ -563,142 +540,59 @@ export class BackendStack extends cdk.NestedStack { }), }) - // Create comprehensive IAM role for gateway - const gatewayRole = new iam.Role(this, "GatewayRole", { - assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"), - description: "Role for AgentCore Gateway with comprehensive permissions", - }) - - // Lambda invoke permission - toolLambda.grantInvoke(gatewayRole) - - // Bedrock permissions (region-agnostic) - gatewayRole.addToPolicy( - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"], - resources: [ - "arn:aws:bedrock:*::foundation-model/*", - `arn:aws:bedrock:*:${this.account}:inference-profile/*`, - ], - }) - ) - - // SSM parameter access - gatewayRole.addToPolicy( - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ["ssm:GetParameter", "ssm:GetParameters"], - resources: [ - `arn:aws:ssm:${this.region}:${this.account}:parameter/${config.stack_name_base}/*`, - ], - }) - ) - - // Cognito permissions - gatewayRole.addToPolicy( - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ["cognito-idp:DescribeUserPoolClient", "cognito-idp:InitiateAuth"], - resources: [this.userPool.userPoolArn], - }) - ) - - // CloudWatch Logs - gatewayRole.addToPolicy( - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], - resources: [ - `arn:aws:logs:${this.region}:${this.account}:log-group:/aws/bedrock-agentcore/*`, - ], - }) - ) - // Load tool specification from JSON file const toolSpecPath = path.join(__dirname, "../../gateway/tools/sample_tool/tool_spec.json") - const apiSpec = JSON.parse(require("fs").readFileSync(toolSpecPath, "utf8")) + const toolSpec = JSON.parse(fs.readFileSync(toolSpecPath, "utf8")) // Cognito OAuth2 configuration for gateway - const cognitoIssuer = `https://cognito-idp.${this.region}.amazonaws.com/${this.userPool.userPoolId}` - const cognitoDiscoveryUrl = `${cognitoIssuer}/.well-known/openid-configuration` - - // Create Gateway using L1 construct (CfnGateway) - // This replaces the Custom Resource approach with native CloudFormation support - const gateway = new bedrockagentcore.CfnGateway(this, "AgentCoreGateway", { - name: `${config.stack_name_base}-gateway`, - roleArn: gatewayRole.roleArn, - protocolType: "MCP", - protocolConfiguration: { - mcp: { - supportedVersions: ["2025-03-26"], - // Optional: Enable semantic search for tools - // searchType: "SEMANTIC", - }, - }, - authorizerType: "CUSTOM_JWT", - authorizerConfiguration: { - customJwtAuthorizer: { - allowedClients: [this.machineClient.userPoolClientId], - discoveryUrl: cognitoDiscoveryUrl, - }, - }, + const cognitoDiscoveryUrl = `https://cognito-idp.${cdk.Stack.of(this).region}.amazonaws.com/${this.userPool.userPoolId}/.well-known/openid-configuration` + + // Create Gateway using L2 construct + const gateway = new agentcore.Gateway(this, "AgentCoreGateway", { + gatewayName: `${config.stack_name_base}-gateway`, description: "AgentCore Gateway with MCP protocol and JWT authentication", + protocolConfiguration: new agentcore.McpProtocolConfiguration({ + supportedVersions: [agentcore.MCPProtocolVersion.MCP_2025_03_26], + }), + authorizerConfiguration: agentcore.GatewayAuthorizer.usingCustomJwt({ + discoveryUrl: cognitoDiscoveryUrl, + allowedClients: [this.machineClient.userPoolClientId], + }), }) - // Create Gateway Target using L1 construct (CfnGatewayTarget) - const gatewayTarget = new bedrockagentcore.CfnGatewayTarget(this, "GatewayTarget", { - gatewayIdentifier: gateway.attrGatewayIdentifier, - name: "sample-tool-target", + // Add Lambda target using L2 convenience method + const gatewayTarget = gateway.addLambdaTarget("SampleToolTarget", { + gatewayTargetName: "sample-tool-target", description: "Sample tool Lambda target", - targetConfiguration: { - mcp: { - lambda: { - lambdaArn: toolLambda.functionArn, - toolSchema: { - inlinePayload: apiSpec, - }, - }, - }, - }, - credentialProviderConfigurations: [ - { - credentialProviderType: "GATEWAY_IAM_ROLE", - }, - ], + lambdaFunction: toolLambda, + toolSchema: agentcore.ToolSchema.fromInline(toolSpec), }) - // Ensure proper creation order - gatewayTarget.addDependency(gateway) - gateway.node.addDependency(toolLambda) - gateway.node.addDependency(this.machineClient) - gateway.node.addDependency(gatewayRole) - // Store Gateway URL in SSM for runtime access new ssm.StringParameter(this, "GatewayUrlParam", { parameterName: `/${config.stack_name_base}/gateway_url`, - stringValue: gateway.attrGatewayUrl, + stringValue: gateway.gatewayUrl!, description: "AgentCore Gateway URL", }) // Output gateway information new cdk.CfnOutput(this, "GatewayId", { - value: gateway.attrGatewayIdentifier, + value: gateway.gatewayId, description: "AgentCore Gateway ID", }) new cdk.CfnOutput(this, "GatewayUrl", { - value: gateway.attrGatewayUrl, + value: gateway.gatewayUrl!, description: "AgentCore Gateway URL", }) new cdk.CfnOutput(this, "GatewayArn", { - value: gateway.attrGatewayArn, + value: gateway.gatewayArn, description: "AgentCore Gateway ARN", }) new cdk.CfnOutput(this, "GatewayTargetId", { - value: gatewayTarget.ref, + value: gatewayTarget.targetId, description: "AgentCore Gateway Target ID", }) diff --git a/infra-cdk/lib/cognito-stack.ts b/infra-cdk/lib/cognito-stack.ts index 5f2aafa..30551ab 100644 --- a/infra-cdk/lib/cognito-stack.ts +++ b/infra-cdk/lib/cognito-stack.ts @@ -3,18 +3,18 @@ import * as cognito from "aws-cdk-lib/aws-cognito" import { Construct } from "constructs" import { AppConfig } from "./utils/config-manager" -export interface CognitoStackProps extends cdk.NestedStackProps { +export interface CognitoConstructProps { config: AppConfig callbackUrls?: string[] } -export class CognitoStack extends cdk.NestedStack { +export class CognitoConstruct extends Construct { public userPoolId: string public userPoolClientId: string public userPoolDomain: cognito.UserPoolDomain - constructor(scope: Construct, id: string, props: CognitoStackProps) { - super(scope, id, props) + constructor(scope: Construct, id: string, props: CognitoConstructProps) { + super(scope, id) this.createCognitoUserPool(props.config, props.callbackUrls) } diff --git a/infra-cdk/lib/fast-main-stack.ts b/infra-cdk/lib/fast-main-stack.ts index 96205c7..ac70480 100644 --- a/infra-cdk/lib/fast-main-stack.ts +++ b/infra-cdk/lib/fast-main-stack.ts @@ -2,98 +2,98 @@ import * as cdk from "aws-cdk-lib" import { Construct } from "constructs" import { AppConfig } from "./utils/config-manager" -// Import nested stacks -import { BackendStack } from "./backend-stack" -import { AmplifyHostingStack } from "./amplify-hosting-stack" -import { CognitoStack } from "./cognito-stack" +// Import constructs (no longer nested stacks) +import { BackendConstruct } from "./backend-stack" +import { AmplifyHostingConstruct } from "./amplify-hosting-stack" +import { CognitoConstruct } from "./cognito-stack" export interface FastAmplifyStackProps extends cdk.StackProps { config: AppConfig } export class FastMainStack extends cdk.Stack { - public readonly amplifyHostingStack: AmplifyHostingStack - public readonly backendStack: BackendStack - public readonly cognitoStack: CognitoStack + public readonly amplifyHostingConstruct: AmplifyHostingConstruct + public readonly backendConstruct: BackendConstruct + public readonly cognitoConstruct: CognitoConstruct constructor(scope: Construct, id: string, props: FastAmplifyStackProps) { const description = "Fullstack AgentCore Solution Template - Main Stack (v0.3.0) (uksb-v6dos0t5g8)" super(scope, id, { ...props, description }) - // Step 1: Create the Amplify stack to get the predictable domain - this.amplifyHostingStack = new AmplifyHostingStack(this, `${id}-amplify`, { + // Step 1: Create the Amplify construct to get the predictable domain + this.amplifyHostingConstruct = new AmplifyHostingConstruct(this, `${id}-amplify`, { config: props.config, }) - this.cognitoStack = new CognitoStack(this, `${id}-cognito`, { + this.cognitoConstruct = new CognitoConstruct(this, `${id}-cognito`, { config: props.config, - callbackUrls: ["http://localhost:3000", this.amplifyHostingStack.amplifyUrl], + callbackUrls: ["http://localhost:3000", this.amplifyHostingConstruct.amplifyUrl], }) - // Step 2: Create backend stack with the predictable Amplify URL and Cognito details - this.backendStack = new BackendStack(this, `${id}-backend`, { + // Step 2: Create backend construct with the predictable Amplify URL and Cognito details + this.backendConstruct = new BackendConstruct(this, `${id}-backend`, { config: props.config, - userPoolId: this.cognitoStack.userPoolId, - userPoolClientId: this.cognitoStack.userPoolClientId, - userPoolDomain: this.cognitoStack.userPoolDomain, - frontendUrl: this.amplifyHostingStack.amplifyUrl, + userPoolId: this.cognitoConstruct.userPoolId, + userPoolClientId: this.cognitoConstruct.userPoolClientId, + userPoolDomain: this.cognitoConstruct.userPoolDomain, + frontendUrl: this.amplifyHostingConstruct.amplifyUrl, }) // Outputs new cdk.CfnOutput(this, "AmplifyAppId", { - value: this.amplifyHostingStack.amplifyApp.appId, + value: this.amplifyHostingConstruct.amplifyApp.appId, description: "Amplify App ID - use this for manual deployment", exportName: `${props.config.stack_name_base}-AmplifyAppId`, }) new cdk.CfnOutput(this, "CognitoUserPoolId", { - value: this.cognitoStack.userPoolId, + value: this.cognitoConstruct.userPoolId, description: "Cognito User Pool ID", exportName: `${props.config.stack_name_base}-CognitoUserPoolId`, }) new cdk.CfnOutput(this, "CognitoClientId", { - value: this.cognitoStack.userPoolClientId, + value: this.cognitoConstruct.userPoolClientId, description: "Cognito User Pool Client ID", exportName: `${props.config.stack_name_base}-CognitoClientId`, }) new cdk.CfnOutput(this, "CognitoDomain", { - value: `${this.cognitoStack.userPoolDomain.domainName}.auth.${cdk.Aws.REGION}.amazoncognito.com`, + value: `${this.cognitoConstruct.userPoolDomain.domainName}.auth.${cdk.Aws.REGION}.amazoncognito.com`, description: "Cognito Domain for OAuth", exportName: `${props.config.stack_name_base}-CognitoDomain`, }) new cdk.CfnOutput(this, "RuntimeArn", { - value: this.backendStack.runtimeArn, + value: this.backendConstruct.runtimeArn, description: "AgentCore Runtime ARN", exportName: `${props.config.stack_name_base}-RuntimeArn`, }) new cdk.CfnOutput(this, "MemoryArn", { - value: this.backendStack.memoryArn, + value: this.backendConstruct.memoryArn, description: "AgentCore Memory ARN", exportName: `${props.config.stack_name_base}-MemoryArn`, }) new cdk.CfnOutput(this, "FeedbackApiUrl", { - value: this.backendStack.feedbackApiUrl, + value: this.backendConstruct.feedbackApiUrl, description: "Feedback API Gateway URL", exportName: `${props.config.stack_name_base}-FeedbackApiUrl`, }) new cdk.CfnOutput(this, "AmplifyConsoleUrl", { - value: `https://console.aws.amazon.com/amplify/apps/${this.amplifyHostingStack.amplifyApp.appId}`, + value: `https://console.aws.amazon.com/amplify/apps/${this.amplifyHostingConstruct.amplifyApp.appId}`, description: "Amplify Console URL for monitoring deployments", }) new cdk.CfnOutput(this, "AmplifyUrl", { - value: this.amplifyHostingStack.amplifyUrl, + value: this.amplifyHostingConstruct.amplifyUrl, description: "Amplify Frontend URL (available after deployment)", }) new cdk.CfnOutput(this, "StagingBucketName", { - value: this.amplifyHostingStack.stagingBucket.bucketName, + value: this.amplifyHostingConstruct.stagingBucket.bucketName, description: "S3 bucket for Amplify deployment staging", exportName: `${props.config.stack_name_base}-StagingBucket`, }) From 6509dabdec71b948ffa85093fceeae8784855e2b Mon Sep 17 00:00:00 2001 From: Adam Weber Date: Thu, 22 Jan 2026 14:17:06 -0800 Subject: [PATCH 2/4] refactor: replace CfnParameters with config.yaml values --- infra-cdk/config.yaml | 2 ++ infra-cdk/lib/backend-stack.ts | 4 ++-- infra-cdk/lib/utils/config-manager.ts | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/infra-cdk/config.yaml b/infra-cdk/config.yaml index fbb563f..a3168cf 100644 --- a/infra-cdk/config.yaml +++ b/infra-cdk/config.yaml @@ -7,3 +7,5 @@ admin_user_email: null # Example: admin@example.com backend: pattern: strands-single-agent # Available patterns: strands-single-agent, langgraph-single-agent deployment_type: docker # Available deployment types: docker (default), zip + agent_name: StrandsAgent + network_mode: PUBLIC # Available: PUBLIC, PRIVATE (PRIVATE requires VPC configuration) diff --git a/infra-cdk/lib/backend-stack.ts b/infra-cdk/lib/backend-stack.ts index 23da4d9..2536992 100644 --- a/infra-cdk/lib/backend-stack.ts +++ b/infra-cdk/lib/backend-stack.ts @@ -93,8 +93,8 @@ export class BackendConstruct extends Construct { private createAgentCoreRuntime(config: AppConfig): void { const pattern = config.backend?.pattern || "strands-single-agent" - const agentName = "StrandsAgent" - const networkMode: string = "PUBLIC" + const agentName = config.backend.agent_name + const networkMode = config.backend.network_mode const stack = cdk.Stack.of(this) const deploymentType = config.backend.deployment_type diff --git a/infra-cdk/lib/utils/config-manager.ts b/infra-cdk/lib/utils/config-manager.ts index a0868b0..c68f1ba 100644 --- a/infra-cdk/lib/utils/config-manager.ts +++ b/infra-cdk/lib/utils/config-manager.ts @@ -5,6 +5,7 @@ import * as yaml from "yaml" const MAX_STACK_NAME_BASE_LENGTH = 35 export type DeploymentType = "docker" | "zip" +export type NetworkMode = "PUBLIC" | "PRIVATE" export interface AppConfig { stack_name_base: string @@ -12,6 +13,8 @@ export interface AppConfig { backend: { pattern: string deployment_type: DeploymentType + agent_name: string + network_mode: NetworkMode } } @@ -55,6 +58,8 @@ export class ConfigManager { backend: { pattern: parsedConfig.backend?.pattern || "strands-single-agent", deployment_type: deploymentType, + agent_name: parsedConfig.backend?.agent_name || "StrandsAgent", + network_mode: parsedConfig.backend?.network_mode || "PUBLIC", }, } } catch (error) { From 7926ae880f98029fa7bf19db431a5b37c2a60e1a Mon Sep 17 00:00:00 2001 From: Adam Weber Date: Thu, 22 Jan 2026 16:27:50 -0800 Subject: [PATCH 3/4] feat: make AgentCore Memory expiration configurable via config.yaml --- infra-cdk/config.yaml | 1 + infra-cdk/lib/backend-stack.ts | 2 +- infra-cdk/lib/utils/config-manager.ts | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/infra-cdk/config.yaml b/infra-cdk/config.yaml index a3168cf..b7a929f 100644 --- a/infra-cdk/config.yaml +++ b/infra-cdk/config.yaml @@ -9,3 +9,4 @@ backend: deployment_type: docker # Available deployment types: docker (default), zip agent_name: StrandsAgent network_mode: PUBLIC # Available: PUBLIC, PRIVATE (PRIVATE requires VPC configuration) + memory_expiration_days: 30 # How long AgentCore Memory retains conversation history diff --git a/infra-cdk/lib/backend-stack.ts b/infra-cdk/lib/backend-stack.ts index 2536992..b92a927 100644 --- a/infra-cdk/lib/backend-stack.ts +++ b/infra-cdk/lib/backend-stack.ts @@ -219,7 +219,7 @@ export class BackendConstruct extends Construct { // To enable long-term strategies (summaries, preferences, facts), see docs/MEMORY_INTEGRATION.md const memory = new agentcore.Memory(this, "AgentMemory", { memoryName: cdk.Names.uniqueResourceName(this, { maxLength: 48 }), - expirationDuration: cdk.Duration.days(30), + expirationDuration: cdk.Duration.days(config.backend.memory_expiration_days), description: `Short-term memory for ${config.stack_name_base} agent`, executionRole: agentRole, }) diff --git a/infra-cdk/lib/utils/config-manager.ts b/infra-cdk/lib/utils/config-manager.ts index c68f1ba..543d584 100644 --- a/infra-cdk/lib/utils/config-manager.ts +++ b/infra-cdk/lib/utils/config-manager.ts @@ -15,6 +15,7 @@ export interface AppConfig { deployment_type: DeploymentType agent_name: string network_mode: NetworkMode + memory_expiration_days: number } } @@ -60,6 +61,7 @@ export class ConfigManager { deployment_type: deploymentType, agent_name: parsedConfig.backend?.agent_name || "StrandsAgent", network_mode: parsedConfig.backend?.network_mode || "PUBLIC", + memory_expiration_days: parsedConfig.backend?.memory_expiration_days || 30, }, } } catch (error) { From e8ccf48712b93bfeb2aeb3cb068d95c141d3f372 Mon Sep 17 00:00:00 2001 From: Adam Weber Date: Thu, 22 Jan 2026 21:05:52 -0800 Subject: [PATCH 4/4] docs: fix incorrect file paths and update README for Construct architecture --- infra-cdk/README.md | 73 ++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/infra-cdk/README.md b/infra-cdk/README.md index 16ae6a5..fd1a03b 100644 --- a/infra-cdk/README.md +++ b/infra-cdk/README.md @@ -57,7 +57,11 @@ frontend: certificate_arn: null # Optional: Set to your ACM certificate ARN backend: - pattern: "strands-single-agent" # Available patterns: strands-single-agent + pattern: strands-single-agent # Available: strands-single-agent, langgraph-single-agent + deployment_type: docker # Available: docker, zip + agent_name: StrandsAgent + network_mode: PUBLIC # Available: PUBLIC, PRIVATE + memory_expiration_days: 30 # How long AgentCore Memory retains conversation history ``` ## Project Structure @@ -67,9 +71,10 @@ infra-cdk/ ├── bin/ │ └── fast-cdk.ts # CDK app entry point ├── lib/ -│ ├── fast-cdk-stack.ts # Main orchestrator stack -│ ├── backend-stack.ts # Backend/AgentCore stack -│ ├── frontend-stack.ts # Frontend/CloudFront stack +│ ├── fast-main-stack.ts # Main orchestrator stack +│ ├── backend-stack.ts # BackendConstruct +│ ├── cognito-stack.ts # CognitoConstruct +│ ├── amplify-hosting-stack.ts # AmplifyHostingConstruct │ └── utils/ # Utility functions and constructs ├── test/ │ └── fast-cdk.test.ts # Unit tests @@ -93,36 +98,30 @@ npm run watch ## Deployment Details -The CDK deployment creates multiple stacks with a specific deployment order: +The CDK deployment creates a single CloudFormation stack containing all resources, organized into logical Constructs. -### Stack Architecture & Deployment Order +### Architecture -1. **Cognito Stack** (CognitoStack): - - Cognito User Pool for user authentication - - User Pool Client for frontend OAuth flows +The main stack (`FASTStack`) composes three Constructs: + +1. **CognitoConstruct**: User authentication + - Cognito User Pool and Client - User Pool Domain for hosted UI + - Machine Client for service-to-service auth -2. **Backend Stack** (BackendStack): - - **Machine Client & Resource Server**: OAuth2 client credentials for service-to-service auth - - **AgentCore Gateway**: API gateway for tool integration with Lambda targets - - **AgentCore Runtime**: Bedrock AgentCore runtime for agent execution - - **Supporting Resources**: IAM roles, DynamoDB tables, API Gateway for feedback +2. **BackendConstruct**: AgentCore infrastructure + - AgentCore Gateway with Lambda tool targets + - AgentCore Runtime for agent execution + - AgentCore Memory for conversation history + - ECR repository and CodeBuild for container builds + - DynamoDB table for feedback + - API Gateway for feedback endpoints -3. **Amplify Hosting Stack** (AmplifyHostingStack): - - Amplify app for frontend hosting +3. **AmplifyHostingConstruct**: Frontend hosting + - Amplify app for React frontend - Branch configuration for deployments - Custom domain setup (if configured) -### Component Dependencies - -Within the Backend Stack, components are created in this order: -1. **Cognito Integration**: Import user pool from Cognito stack -2. **Machine Client**: Create OAuth2 client for M2M authentication -3. **Gateway**: Create AgentCore Gateway (depends on machine client) -4. **Runtime**: Create AgentCore Runtime (independent of gateway) - -This order ensures authentication components are available before services that depend on them, while keeping the runtime deployment separate since it doesn't directly depend on the gateway. - ### Docker Build Configuration The agent container builds use a specific configuration to handle the repository structure efficiently: @@ -169,21 +168,13 @@ This approach scales to multiple agent patterns without code duplication while m ### Key Resources Created -1. **Backend Stack**: - - Cognito User Pool integration and machine client - - AgentCore Gateway with Lambda tool targets - - AgentCore Runtime for agent execution - - ECR repository for agent container images - - CodeBuild project for container builds - - DynamoDB table for application data - - API Gateway for feedback endpoints - - IAM roles and policies - -2. **Amplify Hosting Stack**: - - Amplify app for frontend deployment - - Automatic builds from Git branches - - Custom domain and SSL certificate integration - - Environment-specific deployments +- **Authentication**: Cognito User Pool, Client, Domain, Machine Client +- **AgentCore**: Gateway, Runtime, Memory +- **Compute**: Lambda functions, ECR repository, CodeBuild project +- **Storage**: DynamoDB tables +- **Frontend**: Amplify app with custom domain support +- **APIs**: API Gateway for feedback endpoints +- **Security**: IAM roles and policies ## Troubleshooting