Skip to content

Commit 0ed5ee6

Browse files
feat: add FargateAgentStack as alternative compute backend
Introduce an AWS Fargate + Step Functions compute backend that coexists alongside the existing AgentCore runtime stack. The new stack reuses the same agent/ Python code, DynamoDB tables, VPC, and secrets via cross-stack references. - Expose shared resources from AgentStack as public readonly properties - Create FargateAgentCluster construct (ECS Cluster + Task Definition) - Create TaskStepFunction construct (Step Functions state machine) - Create 6 thin Lambda handlers for SFN steps (load, admit, hydrate, transition, finalize, handle-error) - Create FargateAgentStack composing all constructs - Wire FargateAgentStack into main.ts - Add VPC endpoints for ECS and Step Functions - 546 tests passing across 44 test suites
1 parent 03b8c83 commit 0ed5ee6

23 files changed

Lines changed: 2575 additions & 45 deletions

docs/design/FARGATE_STACK.md

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

src/constructs/agent-vpc.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ export class AgentVpc extends Construct {
134134
{ id: 'BedrockRuntimeEndpoint', service: ec2.InterfaceVpcEndpointAwsService.BEDROCK_RUNTIME },
135135
{ id: 'StsEndpoint', service: ec2.InterfaceVpcEndpointAwsService.STS },
136136
{ id: 'XRayEndpoint', service: ec2.InterfaceVpcEndpointAwsService.XRAY },
137+
{ id: 'EcsEndpoint', service: ec2.InterfaceVpcEndpointAwsService.ECS },
138+
{ id: 'EcsAgentEndpoint', service: ec2.InterfaceVpcEndpointAwsService.ECS_AGENT },
139+
{ id: 'EcsTelemetryEndpoint', service: ec2.InterfaceVpcEndpointAwsService.ECS_TELEMETRY },
140+
{ id: 'StepFunctionsEndpoint', service: ec2.InterfaceVpcEndpointAwsService.STEP_FUNCTIONS },
137141
];
138142

139143
for (const ep of interfaceEndpoints) {
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/**
2+
* MIT No Attribution
3+
*
4+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
7+
* the Software without restriction, including without limitation the rights to
8+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
* the Software, and to permit persons to whom the Software is furnished to do so.
10+
*
11+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17+
* SOFTWARE.
18+
*/
19+
20+
import * as path from 'path';
21+
import { RemovalPolicy } from 'aws-cdk-lib';
22+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
23+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
24+
import * as ecs from 'aws-cdk-lib/aws-ecs';
25+
import * as iam from 'aws-cdk-lib/aws-iam';
26+
import * as logs from 'aws-cdk-lib/aws-logs';
27+
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
28+
import { NagSuppressions } from 'cdk-nag';
29+
import { Construct } from 'constructs';
30+
31+
/**
32+
* Properties for FargateAgentCluster construct.
33+
*/
34+
export interface FargateAgentClusterProps {
35+
/**
36+
* The VPC to place the ECS cluster in.
37+
*/
38+
readonly vpc: ec2.IVpc;
39+
40+
/**
41+
* Security group for the Fargate tasks.
42+
*/
43+
readonly securityGroup: ec2.ISecurityGroup;
44+
45+
/**
46+
* The DynamoDB task table.
47+
*/
48+
readonly taskTable: dynamodb.ITable;
49+
50+
/**
51+
* The DynamoDB task events table.
52+
*/
53+
readonly taskEventsTable: dynamodb.ITable;
54+
55+
/**
56+
* The DynamoDB user concurrency table.
57+
*/
58+
readonly userConcurrencyTable: dynamodb.ITable;
59+
60+
/**
61+
* The Secrets Manager secret containing the GitHub token.
62+
*/
63+
readonly githubTokenSecret: secretsmanager.ISecret;
64+
65+
/**
66+
* AgentCore Memory resource ID for cross-task learning.
67+
* When provided, the task role is granted AgentCore Memory permissions.
68+
*/
69+
readonly memoryId?: string;
70+
}
71+
72+
/**
73+
* CDK construct that creates the Fargate cluster, task definition, and container
74+
* for running autonomous coding agents.
75+
*/
76+
export class FargateAgentCluster extends Construct {
77+
/**
78+
* The ECS cluster.
79+
*/
80+
public readonly cluster: ecs.Cluster;
81+
82+
/**
83+
* The Fargate task definition.
84+
*/
85+
public readonly taskDefinition: ecs.FargateTaskDefinition;
86+
87+
/**
88+
* The agent container definition.
89+
*/
90+
public readonly containerDefinition: ecs.ContainerDefinition;
91+
92+
/**
93+
* Security group for the Fargate tasks.
94+
*/
95+
public readonly securityGroup: ec2.ISecurityGroup;
96+
97+
constructor(scope: Construct, id: string, props: FargateAgentClusterProps) {
98+
super(scope, id);
99+
100+
this.securityGroup = props.securityGroup;
101+
102+
this.cluster = new ecs.Cluster(this, 'Cluster', {
103+
vpc: props.vpc,
104+
containerInsightsV2: ecs.ContainerInsights.ENHANCED,
105+
});
106+
107+
this.taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
108+
cpu: 4096,
109+
memoryLimitMiB: 16384,
110+
ephemeralStorageGiB: 100,
111+
});
112+
113+
const logGroup = new logs.LogGroup(this, 'LogGroup', {
114+
logGroupName: '/ecs/fargate-agent',
115+
retention: logs.RetentionDays.THREE_MONTHS,
116+
removalPolicy: RemovalPolicy.DESTROY,
117+
});
118+
119+
this.containerDefinition = this.taskDefinition.addContainer('AgentContainer', {
120+
image: ecs.ContainerImage.fromAsset(path.join(__dirname, '..', '..', 'agent')),
121+
command: ['python', '/app/entrypoint.py'],
122+
logging: ecs.LogDrivers.awsLogs({
123+
logGroup,
124+
streamPrefix: 'agent',
125+
}),
126+
environment: {
127+
TASK_TABLE_NAME: props.taskTable.tableName,
128+
TASK_EVENTS_TABLE_NAME: props.taskEventsTable.tableName,
129+
USER_CONCURRENCY_TABLE_NAME: props.userConcurrencyTable.tableName,
130+
GITHUB_TOKEN_SECRET_ARN: props.githubTokenSecret.secretArn,
131+
...(props.memoryId && { MEMORY_ID: props.memoryId }),
132+
},
133+
});
134+
135+
// DynamoDB grants
136+
props.taskTable.grantReadWriteData(this.taskDefinition.taskRole);
137+
props.taskEventsTable.grantReadWriteData(this.taskDefinition.taskRole);
138+
props.userConcurrencyTable.grantReadWriteData(this.taskDefinition.taskRole);
139+
140+
// Secrets Manager grant for GitHub token
141+
props.githubTokenSecret.grantRead(this.taskDefinition.taskRole);
142+
143+
// Bedrock model invocation permissions
144+
this.taskDefinition.taskRole.addToPrincipalPolicy(new iam.PolicyStatement({
145+
actions: [
146+
'bedrock:InvokeModel',
147+
'bedrock:InvokeModelWithResponseStream',
148+
],
149+
resources: [
150+
'arn:aws:bedrock:*:*:foundation-model/*',
151+
'arn:aws:bedrock:*:*:inference-profile/*',
152+
],
153+
}));
154+
155+
// AgentCore Memory permissions (only when memoryId is provided)
156+
if (props.memoryId) {
157+
this.taskDefinition.taskRole.addToPrincipalPolicy(new iam.PolicyStatement({
158+
actions: [
159+
'bedrock-agentcore:RetrieveMemoryRecords',
160+
'bedrock-agentcore:CreateEvent',
161+
],
162+
resources: ['*'],
163+
}));
164+
}
165+
166+
NagSuppressions.addResourceSuppressions(this.taskDefinition, [
167+
{
168+
id: 'AwsSolutions-IAM5',
169+
reason: 'DynamoDB index/* wildcards generated by CDK grantReadWriteData; Secrets Manager wildcards generated by CDK grantRead; Bedrock foundation-model/* and inference-profile/* wildcards required for model invocation; AgentCore Memory wildcards required for cross-task learning',
170+
},
171+
{
172+
id: 'AwsSolutions-ECS2',
173+
reason: 'Environment variables contain DynamoDB table names and secret ARNs (not secret values) — safe to pass directly; Secrets Manager handles actual secret retrieval at runtime',
174+
},
175+
], true);
176+
177+
NagSuppressions.addResourceSuppressions(this.cluster, [
178+
{
179+
id: 'AwsSolutions-ECS4',
180+
reason: 'Container insights enabled on the cluster',
181+
},
182+
], true);
183+
}
184+
}

0 commit comments

Comments
 (0)