diff --git a/sfn-bedrockagentcore-harness-cdk/.gitignore b/sfn-bedrockagentcore-harness-cdk/.gitignore new file mode 100644 index 0000000000..46052f7ff4 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/.gitignore @@ -0,0 +1,5 @@ +node_modules +build +cdk.out +cdk.context.json +package-lock.json diff --git a/sfn-bedrockagentcore-harness-cdk/README.md b/sfn-bedrockagentcore-harness-cdk/README.md new file mode 100644 index 0000000000..edd92e48a2 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/README.md @@ -0,0 +1,112 @@ +# AWS Step Functions with Amazon Bedrock AgentCore Harness Optimized Integration (CDK) + +This pattern invokes an Amazon Bedrock AgentCore harness directly from AWS Step Functions using the optimized integration — with a 15-minute timeout (vs 60 seconds for the SDK integration), Converse-shaped responses, aggregated token metrics, and CloudWatch reasoning traces. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/sfn-bedrockagentcore-harness-cdk + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node.js 20+](https://nodejs.org/en/download/) installed +* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed and bootstrapped +* An existing Amazon Bedrock AgentCore harness (see [Creating a harness](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-create.html)) + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ``` + cd serverless-patterns/sfn-bedrockagentcore-harness-cdk + ``` +3. Install dependencies: + ``` + npm install + ``` +4. Deploy the stack with your harness ARN: + ``` + cdk deploy --parameters HarnessArn=arn:aws:bedrock-agentcore:us-east-1:ACCOUNT_ID:harness/YOUR-HARNESS-NAME + ``` + +## How it works + +This pattern creates an AWS Step Functions state machine that: + +1. **Invokes the harness** using the optimized integration (`arn:aws:states:::bedrockagentcore:invokeHarness`) — no AWS Lambda in the path +2. **Receives a Converse-shaped response** with the agent's final text, stop reason, token usage, and latency metrics +3. **Formats the output** extracting just the response text, token count, and latency +4. **Handles errors** with specific catches for throttling (exponential backoff) and missing harness + +### Key differences from SDK integration + +| Feature | SDK Integration | Optimized Integration (this pattern) | +|---|---|---| +| Resource URI | `arn:aws:states:::aws-sdk:bedrockagentcore:InvokeHarness` | `arn:aws:states:::bedrockagentcore:invokeHarness` | +| AWS Lambda required | No (but needed for streaming/parsing) | No | +| Response format | Raw API response | Converse-shaped (text only, tool use omitted) | +| Token metrics | Manual calculation | Aggregated across all turns automatically | +| CloudWatch traces | Not available | Turn-by-turn reasoning deep-links | +| Max timeout | 60 seconds (API call limit) | 15 minutes (Task state limit) | + +### Architecture + +``` +User Input → AWS Step Functions → Amazon Bedrock AgentCore Harness + ↓ + Model inference + tool use + ↓ + Converse-shaped response + ↓ + Step Functions → Formatted Output +``` + +## Testing + +1. Start an execution: + ```bash + SM_ARN=$(aws cloudformation describe-stacks \ + --stack-name SfnBedrockagentcoreHarnessStack \ + --query 'Stacks[0].Outputs[?OutputKey==`StateMachineArn`].OutputValue' \ + --output text) + + EXEC_ARN=$(aws stepfunctions start-execution \ + --state-machine-arn $SM_ARN \ + --input '{"prompt": "What is Amazon Bedrock AgentCore and how does a harness work?"}' \ + --query 'executionArn' --output text) + + echo "Execution: $EXEC_ARN" + ``` + +2. Wait 10-30 seconds for the agent to reason, then check output: + ```bash + aws stepfunctions describe-execution \ + --execution-arn $EXEC_ARN \ + --query '{status: status, output: output}' + ``` + +3. Expected output format: + ```json + { + "response": "Amazon Bedrock AgentCore is...", + "stopReason": "end_turn", + "tokensUsed": 1523, + "latencyMs": 8421 + } + ``` + +## Cleanup + +```bash +cdk destroy +``` + +---- +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/sfn-bedrockagentcore-harness-cdk/bin/app.ts b/sfn-bedrockagentcore-harness-cdk/bin/app.ts new file mode 100644 index 0000000000..298c260c51 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/bin/app.ts @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { SfnBedrockagentcoreHarnessStack } from '../lib/sfn-bedrockagentcore-harness-stack'; + +const app = new cdk.App(); +new SfnBedrockagentcoreHarnessStack(app, 'SfnBedrockagentcoreHarnessStack', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION || 'us-east-1', + }, +}); diff --git a/sfn-bedrockagentcore-harness-cdk/cdk.json b/sfn-bedrockagentcore-harness-cdk/cdk.json new file mode 100644 index 0000000000..a6700a2ff4 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts" +} diff --git a/sfn-bedrockagentcore-harness-cdk/example-pattern.json b/sfn-bedrockagentcore-harness-cdk/example-pattern.json new file mode 100644 index 0000000000..b34ecc2f57 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/example-pattern.json @@ -0,0 +1,81 @@ +{ + "title": "AWS Step Functions with Amazon Bedrock AgentCore Harness Optimized Integration (CDK)", + "description": "Invoke Amazon Bedrock AgentCore harness from AWS Step Functions using the optimized integration — zero Lambda, Converse-shaped responses, and token metrics.", + "language": "TypeScript", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern uses the AWS Step Functions optimized integration for Amazon Bedrock AgentCore to invoke a harness directly — no Lambda function required.", + "The harness orchestrates model inference, tool use, and multi-turn conversations. Step Functions receives a Converse-shaped response with aggregated token usage metrics.", + "The optimized integration provides CloudWatch deep-links for turn-by-turn agent reasoning traces, built-in retry/catch for throttling, and a 15-minute execution timeout.", + "The state machine includes error handling with specific catches for ResourceNotFoundException and ThrottlingException with exponential backoff." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sfn-bedrockagentcore-harness-cdk", + "templateURL": "serverless-patterns/sfn-bedrockagentcore-harness-cdk", + "projectFolder": "sfn-bedrockagentcore-harness-cdk", + "templateFile": "lib/sfn-bedrockagentcore-harness-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Invoke Amazon Bedrock AgentCore harness with Step Functions", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-bedrockagentcore.html" + }, + { + "text": "Amazon Bedrock AgentCore Harness", + "link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html" + } + ] + }, + "deploy": { + "text": [ + "cdk bootstrap", + "npm install", + "cdk deploy --parameters HarnessArn=arn:aws:bedrock-agentcore:us-east-1:ACCOUNT:harness/YOUR-HARNESS" + ] + }, + "testing": { + "text": [ + "Start an execution with a prompt:", + "aws stepfunctions start-execution --state-machine-arn $(aws cloudformation describe-stacks --stack-name SfnBedrockagentcoreHarnessStack --query 'Stacks[0].Outputs[?OutputKey==`StateMachineArn`].OutputValue' --output text) --input '{\"prompt\": \"What is Amazon Bedrock AgentCore?\"}'", + "Check execution output (wait 10-30 seconds for agent reasoning):", + "aws stepfunctions describe-execution --execution-arn EXECUTION_ARN --query 'output'" + ] + }, + "cleanup": { + "text": [ + "cdk destroy" + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "sfn", + "label": "AWS Step Functions" + }, + "icon2": { + "x": 80, + "y": 50, + "service": "bedrock", + "label": "Amazon Bedrock AgentCore" + }, + "line1": { + "from": "icon1", + "to": "icon2" + } + } +} diff --git a/sfn-bedrockagentcore-harness-cdk/lib/sfn-bedrockagentcore-harness-stack.ts b/sfn-bedrockagentcore-harness-cdk/lib/sfn-bedrockagentcore-harness-stack.ts new file mode 100644 index 0000000000..5364e44059 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/lib/sfn-bedrockagentcore-harness-stack.ts @@ -0,0 +1,114 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as iam from 'aws-cdk-lib/aws-iam'; + +export class SfnBedrockagentcoreHarnessStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const harnessArn = new cdk.CfnParameter(this, 'HarnessArn', { + type: 'String', + description: 'ARN of the Amazon Bedrock AgentCore harness to invoke', + }); + + // Step Functions execution role with AgentCore permissions + const sfnRole = new iam.Role(this, 'SfnExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + inlinePolicies: { + AgentCoreInvoke: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: [ + 'bedrock-agentcore:InvokeHarness', + 'bedrock-agentcore:InvokeAgentRuntime', + ], + resources: [harnessArn.valueAsString], + }), + ], + }), + }, + }); + + // State machine definition using the optimized integration + // arn:aws:states:::bedrockagentcore:invokeHarness (NOT the SDK integration) + const definition = { + Comment: 'Invoke Amazon Bedrock AgentCore harness using Step Functions optimized integration', + StartAt: 'InvokeHarness', + States: { + InvokeHarness: { + Type: 'Task', + Resource: 'arn:aws:states:::bedrockagentcore:invokeHarness', + Parameters: { + 'HarnessArn': harnessArn.valueAsString, + 'RuntimeSessionId.$': '$$.Execution.Name', + 'Messages': [ + { + 'Content': [{ 'Text.$': '$.prompt' }], + 'Role': 'user', + }, + ], + 'SystemPrompt': [{ 'Text': 'You are a helpful AI assistant. Answer concisely and accurately.' }], + 'MaxIterations': 50, + 'TimeoutSeconds': 600, + }, + Retry: [ + { + ErrorEquals: ['BedrockAgentCore.ThrottlingException'], + IntervalSeconds: 2, + MaxAttempts: 3, + BackoffRate: 2.0, + }, + ], + Catch: [ + { + ErrorEquals: ['BedrockAgentCore.ResourceNotFoundException'], + Next: 'HarnessNotFound', + }, + { + ErrorEquals: ['States.ALL'], + Next: 'HandleError', + }, + ], + Next: 'FormatResponse', + }, + FormatResponse: { + Type: 'Pass', + Parameters: { + 'response.$': '$.Output.Message.Content[0].Text', + 'stopReason.$': '$.StopReason', + 'tokensUsed.$': '$.Usage.TotalTokens', + 'latencyMs.$': '$.Metrics.LatencyMs', + }, + End: true, + }, + HarnessNotFound: { + Type: 'Fail', + Error: 'HarnessNotFound', + Cause: 'The specified Amazon Bedrock AgentCore harness was not found. Verify the HarnessArn parameter.', + }, + HandleError: { + Type: 'Fail', + Error: 'InvocationFailed', + Cause: 'Amazon Bedrock AgentCore harness invocation failed.', + }, + }, + }; + + const stateMachine = new sfn.CfnStateMachine(this, 'HarnessStateMachine', { + definitionString: JSON.stringify(definition), + roleArn: sfnRole.roleArn, + stateMachineType: 'STANDARD', + }); + + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.attrArn, + description: 'AWS Step Functions state machine ARN', + }); + + new cdk.CfnOutput(this, 'HarnessArnOutput', { + value: harnessArn.valueAsString, + description: 'Amazon Bedrock AgentCore harness ARN', + }); + } +} diff --git a/sfn-bedrockagentcore-harness-cdk/package.json b/sfn-bedrockagentcore-harness-cdk/package.json new file mode 100644 index 0000000000..6628198bd4 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/package.json @@ -0,0 +1,17 @@ +{ + "name": "sfn-bedrockagentcore-harness-cdk", + "version": "1.0.0", + "bin": { "app": "bin/app.ts" }, + "scripts": { "build": "tsc", "cdk": "cdk" }, + "dependencies": { + "aws-cdk-lib": "^2.260.0", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "~5.4.0", + "aws-cdk": "^2.260.0", + "ts-node": "^10.9.0" + } +} diff --git a/sfn-bedrockagentcore-harness-cdk/sfn-bedrockagentcore-harness-cdk.json b/sfn-bedrockagentcore-harness-cdk/sfn-bedrockagentcore-harness-cdk.json new file mode 100644 index 0000000000..67a510f8b5 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/sfn-bedrockagentcore-harness-cdk.json @@ -0,0 +1,81 @@ +{ + "title": "AWS Step Functions with Amazon Bedrock AgentCore Harness Optimized Integration (CDK)", + "description": "Invoke Amazon Bedrock AgentCore harness from AWS Step Functions using the optimized integration — zero Lambda, Converse-shaped responses, and token metrics.", + "language": "TypeScript", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern uses the AWS Step Functions optimized integration for Amazon Bedrock AgentCore to invoke a harness directly — no Lambda function required.", + "The harness orchestrates model inference, tool use, and multi-turn conversations. Step Functions receives a Converse-shaped response with aggregated token usage metrics.", + "The optimized integration provides CloudWatch deep-links for turn-by-turn agent reasoning traces, built-in retry/catch for throttling, and a 15-minute execution timeout.", + "The state machine includes error handling with specific catches for ResourceNotFoundException and ThrottlingException with exponential backoff." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sfn-bedrockagentcore-harness-cdk", + "templateURL": "serverless-patterns/sfn-bedrockagentcore-harness-cdk", + "projectFolder": "sfn-bedrockagentcore-harness-cdk", + "templateFile": "lib/sfn-bedrockagentcore-harness-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Invoke Amazon Bedrock AgentCore harness with Step Functions", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-bedrockagentcore.html" + }, + { + "text": "Amazon Bedrock AgentCore Harness", + "link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html" + } + ] + }, + "deploy": { + "text": [ + "cdk bootstrap", + "npm install", + "cdk deploy --parameters HarnessArn=arn:aws:bedrock-agentcore:us-east-1:ACCOUNT:harness/YOUR-HARNESS" + ] + }, + "testing": { + "text": [ + "Start an execution with a prompt:", + "aws stepfunctions start-execution --state-machine-arn $(aws cloudformation describe-stacks --stack-name SfnBedrockagentcoreHarnessStack --query 'Stacks[0].Outputs[?OutputKey==`StateMachineArn`].OutputValue' --output text) --input '{\"prompt\": \"What is Amazon Bedrock AgentCore?\"}'", + "Check execution output (wait 10-30 seconds for agent reasoning):", + "aws stepfunctions describe-execution --execution-arn EXECUTION_ARN --query 'output'" + ] + }, + "cleanup": { + "text": [ + "cdk destroy" + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "sfn", + "label": "AWS Step Functions" + }, + "icon2": { + "x": 80, + "y": 50, + "service": "bedrock", + "label": "Amazon Bedrock AgentCore" + }, + "line1": { + "from": "icon1", + "to": "icon2" + } + } +} diff --git a/sfn-bedrockagentcore-harness-cdk/tsconfig.json b/sfn-bedrockagentcore-harness-cdk/tsconfig.json new file mode 100644 index 0000000000..ae328a5827 --- /dev/null +++ b/sfn-bedrockagentcore-harness-cdk/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "outDir": "build", + "rootDir": ".", + "typeRoots": ["./node_modules/@types"] + }, + "exclude": ["node_modules", "build"] +}