1
0
mirror of synced 2026-05-22 22:53:35 +00:00

Add Terraform Infrastructure-as-Code (IaC) patterns for AgentCore deployment (#654)

* feat: Add Terraform basic-runtime pattern

* Modified the Terraform basic-runtime with test script, README, default region

* feat: Add Terraform mcp-server-runtime pattern

* feat: Add Terraform multi-agent-runtime pattern

* feat: Add Terraform end-to-end-weather-agent runtime pattern

* Added Terraform main README

* Fixed basic runtime test script

* docs: add Terraform support to IaC README

* Replaced resources to intuitive names, removed hardcoded values, cleaned README

* Enhanced Terraform READMEs

* Removed unused imports

---------

Co-authored-by: Tesfagabir Meharizghi <mehariz@amazon.com>
This commit is contained in:
Tesfagabir Meharizghi
2025-11-24 07:38:56 -06:00
committed by GitHub
parent 62965534d8
commit 83b72e1dda
100 changed files with 11054 additions and 7 deletions
+19 -7
View File
@@ -1,6 +1,6 @@
# Infrastructure as Code Samples for Amazon Bedrock AgentCore
Deploy Amazon Bedrock AgentCore resources using CloudFormation templates or AWS CDK.
Deploy Amazon Bedrock AgentCore resources using CloudFormation templates, AWS CDK, or Terraform.
## Overview
@@ -13,6 +13,7 @@ These Infrastructure as Code samples enable you to:
Choose your preferred approach:
- **[CloudFormation](./cloudformation/)** - YAML/JSON templates for declarative infrastructure
- **[CDK](./cdk/)** - Python code for programmatic infrastructure
- **[Terraform](./terraform/)** - HCL code for declarative infrastructure with state management
## Samples
@@ -28,7 +29,7 @@ Deploy a simple AgentCore Runtime with a basic Strands agent - no additional too
**Deployment time:** ~5-15 minutes
**Estimated cost:** ~$50-100/month
**Implementation:** [CloudFormation](./cloudformation/basic-runtime/) | [CDK](./cdk/basic-runtime/)
**Implementation:** [CloudFormation](./cloudformation/basic-runtime/) | [CDK](./cdk/basic-runtime/) | [Terraform](./terraform/basic-runtime/)
### 2. MCP Server on AgentCore Runtime
Deploy a complete MCP (Model Context Protocol) server with automated Docker building and JWT authentication.
@@ -42,7 +43,7 @@ Deploy a complete MCP (Model Context Protocol) server with automated Docker buil
**Deployment time:** ~10-15 minutes
**Estimated cost:** ~$50-100/month
**Implementation:** [CloudFormation](./cloudformation/mcp-server-agentcore-runtime/) | [CDK](./cdk/mcp-server-agentcore-runtime/)
**Implementation:** [CloudFormation](./cloudformation/mcp-server-agentcore-runtime/) | [CDK](./cdk/mcp-server-agentcore-runtime/) | [Terraform](./terraform/mcp-server-agentcore-runtime/)
### 3. Multi-Agent Runtime
Deploy a multi-agent system where Agent1 (orchestrator) can invoke Agent2 (specialist) for complex tasks.
@@ -56,7 +57,7 @@ Deploy a multi-agent system where Agent1 (orchestrator) can invoke Agent2 (speci
**Deployment time:** ~15-20 minutes
**Estimated cost:** ~$100-200/month
**Implementation:** [CloudFormation](./cloudformation/multi-agent-runtime/) | [CDK](./cdk/multi-agent-runtime/)
**Implementation:** [CloudFormation](./cloudformation/multi-agent-runtime/) | [CDK](./cdk/multi-agent-runtime/) | [Terraform](./terraform/multi-agent-runtime/)
### 4. End-to-End Weather Agent with Tools and Memory
Deploy a complete weather-based activity planning agent with browser automation, code interpreter, and memory.
@@ -72,7 +73,7 @@ Deploy a complete weather-based activity planning agent with browser automation,
**Deployment time:** ~15-20 minutes
**Estimated cost:** ~$100-150/month
**Implementation:** [CloudFormation](./cloudformation/end-to-end-weather-agent/) | [CDK](./cdk/end-to-end-weather-agent/)
**Implementation:** [CloudFormation](./cloudformation/end-to-end-weather-agent/) | [CDK](./cdk/end-to-end-weather-agent/) | [Terraform](./terraform/end-to-end-weather-agent/)
## Prerequisites
@@ -93,6 +94,10 @@ For CDK samples, also install:
- Python 3.8+
- AWS CDK v2.218.0 or later
For Terraform samples, also install:
- Terraform >= 1.6 (recommend [tfenv](https://github.com/tfutils/tfenv) for version management)
- Note: `brew install terraform` provides v1.5.7 which is deprecated
## Repository Structure
```
@@ -104,8 +109,14 @@ For CDK samples, also install:
│ ├── mcp-server-agentcore-runtime/
│ ├── multi-agent-runtime/
│ └── end-to-end-weather-agent/
── cdk/ # CDK samples
├── README.md # CDK-specific guide
── cdk/ # CDK samples
├── README.md # CDK-specific guide
│ ├── basic-runtime/
│ ├── mcp-server-agentcore-runtime/
│ ├── multi-agent-runtime/
│ └── end-to-end-weather-agent/
└── terraform/ # Terraform samples
├── README.md # Terraform-specific guide
├── basic-runtime/
├── mcp-server-agentcore-runtime/
├── multi-agent-runtime/
@@ -117,4 +128,5 @@ For CDK samples, also install:
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)
- [AWS CloudFormation Documentation](https://docs.aws.amazon.com/cloudformation/)
- [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/)
- [Terraform Documentation](https://www.terraform.io/docs)
- [Original Tutorials](../01-tutorials/)
@@ -0,0 +1,328 @@
# Terraform Samples
Deploy Amazon Bedrock AgentCore resources using Terraform.
## Prerequisites
- **Terraform >= 1.6**
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
- **Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6
- **AWS CLI** configured with credentials
- **Python 3.11+** (for testing scripts)
- **Docker** (optional, for local testing)
- Access to Amazon Bedrock AgentCore (preview)
## State Management Options
Terraform tracks deployed resources in a state file. Choose the approach that fits your needs:
### Option A: Local State (Quickstart)
Perfect for testing, learning, and solo development:
```bash
cd <sample-directory>
terraform init
```
**Characteristics:**
- State stored in local `terraform.tfstate` file
- Simple setup, no additional configuration
- Best for individual experimentation
- Not suitable for team collaboration
### Option B: Remote State (Teams/Production)
Recommended for team collaboration and production environments:
```bash
cd <sample-directory>
# 1. Setup (one-time per pattern)
cp backend.tf.example backend.tf
# Edit backend.tf with your S3 bucket and DynamoDB table
# 2. Initialize with backend
terraform init
```
**Characteristics:**
- State stored in S3 with DynamoDB locking
- Enables team collaboration
- Provides state versioning and backup
- Prevents concurrent modifications
**Setup Requirements:**
- S3 bucket for state storage
- DynamoDB table for state locking
- See `backend.tf.example` in each pattern for details
💡 **Note**: You must create the S3 bucket and DynamoDB table before running `terraform init` with remote state. See `backend.tf.example` in each pattern directory for setup instructions.
## General Deployment Pattern
### Option 1: Using Automation Scripts (Recommended)
```bash
cd <sample-directory>
chmod +x deploy.sh
./deploy.sh
```
The deployment script will:
- Validate your environment
- Initialize Terraform
- Create and review the plan
- Deploy all resources
- Display outputs and next steps
### Option 2: Manual Terraform Commands
```bash
cd <sample-directory>
# 1. Configure variables
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your values
# 2. Choose state management (see State Management Options above)
terraform init
# 3. Review the plan
terraform plan
# 4. Deploy
terraform apply
# 5. View outputs
terraform output
```
## Testing
All patterns include Python test scripts to verify your deployment.
### Setup Test Environment
**Option 1: Using uv (Recommended)**
```bash
# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment
uv venv
# Activate virtual environment
source .venv/bin/activate # On macOS/Linux
# .venv\Scripts\activate # On Windows
# Install boto3
uv pip install boto3
```
**Option 2: Using pip**
```bash
# Create virtual environment
python3 -m venv .venv
# Activate virtual environment
source .venv/bin/activate # On macOS/Linux
# .venv\Scripts\activate # On Windows
# Install boto3
pip install boto3
```
### Run Tests
```bash
# Get the agent ARN from Terraform outputs
AGENT_ARN=$(terraform output -raw agent_runtime_arn)
# Run the test script
python test_*.py $AGENT_ARN
```
### Cleanup
```bash
# Using automation script
./destroy.sh
# Or using Terraform directly
terraform destroy
```
## Samples
- **[basic-runtime/](./basic-runtime/)** - Simple agent deployment with container runtime
- **[mcp-server-agentcore-runtime/](./mcp-server-agentcore-runtime/)** - MCP Server with JWT authentication and API Gateway
- **[multi-agent-runtime/](./multi-agent-runtime/)** - Multi-agent system with Agent-to-Agent (A2A) communication
- **[end-to-end-weather-agent/](./end-to-end-weather-agent/)** - Weather agent with Browser, Code Interpreter, and Memory tools
## Terraform Advantages
- **Infrastructure as Code**: Define resources declaratively with HCL
- **State Management**: Track and manage infrastructure state
- **Module Reusability**: Create reusable infrastructure components
- **Plan Before Apply**: Preview changes before deployment
- **Automated Image Building**: Uses CodeBuild for Docker image creation
- **Provider Ecosystem**: Access to thousands of providers and resources
- **Automation Scripts**: Included deploy.sh and destroy.sh for easy deployment
## Pattern Comparison
| Pattern | Agent Runtimes | Tools | A2A | MCP Server | Use Case |
|---------|----------------|-------|-----|------------|----------|
| basic-runtime | 1 | - | ❌ | ❌ | Simple agent deployment |
| mcp-server | 1 | - | ❌ | ✅ | API integration with JWT auth |
| multi-agent | 2 | - | ✅ | ❌ | Orchestrator + Specialist pattern |
| weather-agent | 1 | Browser, Code Interpreter, Memory | ❌ | ❌ | Full-featured agent with tools |
## Troubleshooting
### Terraform Version Issues
If you encounter provider compatibility issues:
```bash
# Install specific Terraform version with tfenv
tfenv install 1.6.0
tfenv use 1.6.0
```
### State Management
```bash
# View current state
terraform show
# List all resources in state
terraform state list
# Remove a resource from state (if needed)
terraform state rm <resource_address>
```
### Provider Errors
If you see provider version conflicts:
```bash
# Upgrade providers to latest compatible versions
terraform init -upgrade
# Lock provider versions
terraform providers lock
```
### CodeBuild Failures
Check build logs:
```bash
# Get project name from outputs
PROJECT_NAME=$(terraform output -raw codebuild_project_name)
# View recent build logs
aws codebuild list-builds-for-project \
--project-name $PROJECT_NAME \
--region <region>
# Get specific build details
aws codebuild batch-get-builds \
--ids <build-id> \
--region <region>
```
### Deployment Stuck
If deployment appears stuck:
```bash
# Check CloudWatch Logs for the agent runtime
aws logs tail /aws/bedrock-agentcore/<runtime-name> --follow
# Check CodeBuild progress
aws codebuild list-builds-for-project \
--project-name <project-name> \
--max-items 5
```
### Resource Already Exists
If you encounter "resource already exists" errors:
```bash
# Import existing resource into state
terraform import <resource_type>.<resource_name> <resource_id>
# Example for S3 bucket
terraform import aws_s3_bucket.example my-bucket-name
```
### Cleanup Issues
If `terraform destroy` fails:
```bash
# Manually empty S3 buckets first
aws s3 rm s3://<bucket-name> --recursive
# Force destroy (use with caution)
terraform destroy -auto-approve
# Check for remaining resources
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=ManagedBy,Values=Terraform
```
## Key Features
### State Management
Terraform tracks all deployed resources in a state file. For team collaboration:
```bash
# Setup remote state (example with S3)
cp backend.tf.example backend.tf
# Edit backend.tf with your S3 bucket details
terraform init -migrate-state
```
### Automated Docker Builds
Each pattern uses AWS CodeBuild to automatically build ARM64 Docker images:
- Triggered on source code changes (MD5 hash detection)
- No local Docker daemon required
- Optimized for AWS Graviton processors
### Testing Scripts
All patterns include infrastructure-agnostic Python test scripts:
```bash
# Get the agent ARN from Terraform outputs
AGENT_ARN=$(terraform output -raw agent_runtime_arn)
# Run tests
python test_*.py $AGENT_ARN
```
## Additional Resources
- [Terraform Documentation](https://www.terraform.io/docs)
- [AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html)
- [Terraform Best Practices](https://www.terraform.io/docs/cloud/guides/recommended-practices/index.html)
## Contributing
Contributions are welcome! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
## License
This project is licensed under the MIT-0 license. See the [LICENSE](../../LICENSE) file for details.
@@ -0,0 +1,25 @@
# Terraform files
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
!terraform.tfvars.example
tfplan
# Generated archives
*.zip
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
@@ -0,0 +1,403 @@
# Basic AgentCore Runtime - Terraform
This pattern demonstrates the simplest deployment of an AgentCore Runtime using Terraform. It creates a basic agent without additional tools like Memory, Code Interpreter, or Browser.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Testing the Agent](#testing-the-agent)
- [Sample Queries](#sample-queries)
- [Customization](#customization)
- [File Structure](#file-structure)
- [Troubleshooting](#troubleshooting)
- [Cleanup](#cleanup)
- [Pricing](#pricing)
- [Next Steps](#next-steps)
- [Resources](#resources)
- [🤝 Contributing](#-contributing)
- [📄 License](#-license)
## Overview
This Terraform configuration creates a minimal AgentCore deployment that includes:
- **AgentCore Runtime**: Hosts a simple Strands agent
- **ECR Repository**: Stores the Docker container image
- **IAM Roles**: Provides necessary permissions
- **CodeBuild Project**: Automatically builds the ARM64 Docker image
This makes it ideal for:
- Learning AgentCore basics with Terraform
- Quick prototyping and experimentation
- Understanding the core deployment pattern
- Building a foundation before adding complexity
## Architecture
![Architecture Diagram](architecture.png)
## What's Included
This Terraform configuration creates:
- **S3 Bucket**: Stores agent source code for version-controlled builds
- **ECR Repository**: Container registry for the agent Docker image
- **CodeBuild Project**: Automated Docker image building and pushing
- **IAM Roles**: Execution roles for the agent and CodeBuild
- **AgentCore Runtime**: Serverless agent runtime with the deployed container
### Agent Code Management
The `agent-code/` directory contains your agent's source files:
- `basic_agent.py` - Agent implementation
- `Dockerfile` - Container configuration
- `requirements.txt` - Python dependencies
**Automatic Change Detection**:
- Terraform archives the `agent-code/` directory
- Uploads to S3 with MD5-based versioning
- CodeBuild pulls from S3 and builds the Docker image
- Any changes to files trigger automatic rebuild (new files, modifications, deletions)
## Prerequisites
### Required Tools
1. **Terraform** (>= 1.6)
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
2. **AWS CLI** (configured with credentials)
```bash
aws configure
```
3. **Python 3.11+** (for testing scripts)
```bash
python --version # Verify Python 3.11 or later
pip install boto3
```
4. **Docker** (for local testing, optional)
### AWS Account Requirements
- AWS Account with appropriate permissions
- Access to Amazon Bedrock models
## Quick Start
### 1. Configure Variables
Copy the example variables file and customize:
```bash
cp terraform.tfvars.example terraform.tfvars
```
Edit `terraform.tfvars` with your preferred values.
### 2. Initialize Terraform
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
**Quick start with local state:**
```bash
terraform init
```
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
### 3. Review the Plan
```bash
terraform plan
```
### 4. Deploy
**Method 1: Using Deploy Script (Recommended)**
Make the script executable (first-time only):
```bash
chmod +x deploy.sh
```
Then deploy:
```bash
./deploy.sh
```
The deploy script:
- Validates Terraform configuration
- Shows deployment plan
- Prompts for confirmation
- Applies changes
**Method 2: Direct Terraform Commands**
```bash
terraform apply
```
When prompted, type `yes` to confirm the deployment.
**Note**: The deployment process includes:
1. Creating ECR repository
2. Building Docker image via CodeBuild
3. Creating AgentCore Runtime
Total deployment time: **~3-5 minutes**
### 5. Get Outputs
After deployment completes:
```bash
terraform output
```
Example output:
```
agent_runtime_id = "AGENT1234567890"
agent_runtime_arn = "arn:aws:bedrock-agentcore:<us-west-2>:123456789012:agent-runtime/AGENT1234567890"
ecr_repository_url = "123456789012.dkr.ecr.us-west-2.amazonaws.com/agentcore-basic-basic-agent"
```
## Testing the Agent
### Prerequisites for Testing
Before testing, ensure you have the required packages installed:
**Option A: Using uv (Recommended)**
```bash
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install boto3 # Required for agent invocation
```
**Option B: System-wide installation**
```bash
pip install boto3 # Required for agent invocation
```
**Note**: `boto3` is required for the test script to invoke the agent runtime via AWS API.
### Option 1: Using Test Script (Recommended)
```bash
# Run the test suite
python test_basic_agent.py $(terraform output -raw agent_runtime_arn)
```
### Option 2: Using AWS CLI
```bash
# Get the runtime ARN from outputs
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn)
# Invoke the agent
aws bedrock-agentcore invoke-agent-runtime \
--agent-runtime-arn $RUNTIME_ARN \
--qualifier DEFAULT \
--payload $(echo '{"prompt": "Hello, introduce yourself"}' | base64) \
response.json
# View the response
cat response.json | jq -r '.response'
```
### Option 3: Using AWS Console
1. Navigate to Amazon Bedrock console
2. Go to AgentCore → Runtimes
3. Select your runtime
4. Use the "Test" feature to send queries
## Sample Queries
Try these queries to test your basic agent:
1. **Simple Math**:
```json
{"prompt": "What is 2+2?"}
```
2. **General Knowledge**:
```json
{"prompt": "What is the capital of France?"}
```
3. **Explanation Request**:
```json
{"prompt": "Explain what Amazon Bedrock is in simple terms"}
```
4. **Creative Task**:
```json
{"prompt": "Write a haiku about cloud computing"}
```
## Customization
### Modify Agent Code
Edit files in `agent-code/` and deploy:
- `basic_agent.py` - Agent logic and system prompt
- `Dockerfile` - Container configuration
- `requirements.txt` - Python dependencies
Changes are automatically detected and trigger rebuild. Run `terraform apply` to deploy.
### Environment Variables
Add to `terraform.tfvars`:
```hcl
environment_variables = {
LOG_LEVEL = "DEBUG"
}
```
### Network Mode
Set `network_mode = "PRIVATE"` for VPC deployment (requires additional VPC configuration).
## File Structure
```
basic-runtime/
├── main.tf # AgentCore runtime resource
├── variables.tf # Input variables
├── outputs.tf # Output values
├── versions.tf # Provider configuration
├── iam.tf # IAM roles and policies
├── s3.tf # S3 bucket for source code
├── ecr.tf # ECR repository
├── codebuild.tf # Docker build automation
├── buildspec.yml # CodeBuild build specification
├── terraform.tfvars.example # Example configuration
├── backend.tf.example # Remote state example
├── test_basic_agent.py # Automated test script
├── agent-code/ # Agent source code
│ ├── basic_agent.py # Agent implementation
│ ├── Dockerfile # Container configuration
│ └── requirements.txt # Python dependencies
├── scripts/ # Build automation scripts
│ └── build-image.sh # CodeBuild trigger & verification
├── deploy.sh # Deployment helper script
├── destroy.sh # Cleanup helper script
├── .gitignore # Git ignore patterns
└── README.md # This file
```
## Troubleshooting
### CodeBuild Fails
If the Docker build fails:
1. Check CodeBuild logs:
```bash
aws codebuild batch-get-builds \
--ids $(terraform output -raw codebuild_project_name) \
--region us-west-2
```
2. Common issues:
- Network connectivity issues
- ECR authentication problems
- Python dependency conflicts
### Runtime Creation Fails
If the runtime creation fails:
1. Verify the Docker image exists:
```bash
aws ecr describe-images \
--repository-name $(terraform output -raw ecr_repository_url | cut -d'/' -f2) \
--region us-west-2
```
2. Check IAM role permissions
3. Verify Bedrock AgentCore service quotas
### Agent Invocation Fails
If invoking the agent fails:
1. Check runtime status in AWS Console
2. Review CloudWatch Logs for the runtime
3. Verify Bedrock model access permissions
## Cleanup
### Destroy All Resources
Make the script executable (first-time only):
```bash
chmod +x destroy.sh
```
Then cleanup:
```bash
./destroy.sh
```
Or use Terraform directly:
```bash
terraform destroy
```
### Verify Cleanup
Confirm all resources are deleted:
```bash
# Check ECR repositories
aws ecr describe-repositories --region us-west-2 | grep agentcore-basic
# Check AgentCore runtimes
aws bedrock-agentcore list-agent-runtimes --region us-west-2
```
## Pricing
For current pricing information, please refer to:
- [Amazon Bedrock Pricing](https://aws.amazon.com/bedrock/pricing/)
- [Amazon ECR Pricing](https://aws.amazon.com/ecr/pricing/)
- [AWS CodeBuild Pricing](https://aws.amazon.com/codebuild/pricing/)
- [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/)
- [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)
**Note**: Actual costs depend on your usage patterns, AWS region, and specific services consumed.
## Next Steps
### Explore Other Patterns
- [MCP Server Runtime](../mcp-server-agentcore-runtime/) - Add MCP protocol support
- [Multi-Agent Runtime](../multi-agent-runtime/) - Deploy multiple coordinating agents
- [End-to-End Weather Agent](../end-to-end-weather-agent/) - Full-featured agent with tools
## Resources
- [Terraform AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)
- [Strands Agents Documentation](https://strands-agents.readthedocs.io/)
- [AgentCore Samples Repository](https://github.com/aws-samples/amazon-bedrock-agentcore-samples)
## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](../../../LICENSE) file for details.
@@ -0,0 +1,21 @@
FROM public.ecr.aws/docker/library/python:3.11-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt && \
pip install --no-cache-dir aws-opentelemetry-distro==0.10.1
# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 8080
EXPOSE 8000
COPY . .
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/ping || exit 1
CMD ["opentelemetry-instrument", "python", "-m", "basic_agent"]
@@ -0,0 +1,38 @@
from strands import Agent
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
def create_basic_agent() -> Agent:
"""Create a basic agent with simple functionality"""
system_prompt = """You are a helpful assistant. Answer questions clearly and concisely."""
return Agent(
system_prompt=system_prompt,
name="BasicAgent"
)
@app.entrypoint
async def invoke(payload=None):
"""Main entrypoint for the agent"""
try:
# Get the query from payload
query = payload.get("prompt", "Hello, how are you?") if payload else "Hello, how are you?"
# Create and use the agent
agent = create_basic_agent()
response = agent(query)
return {
"status": "success",
"response": response.message['content'][0]['text']
}
except Exception as e:
return {
"status": "error",
"error": str(e)
}
if __name__ == "__main__":
app.run()
@@ -0,0 +1,3 @@
strands-agents
boto3
bedrock-agentcore
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,33 @@
# ============================================================================
# Terraform Backend Configuration - Remote State Storage
# ============================================================================
# This is an example backend configuration for storing Terraform state remotely
# Copy this file to backend.tf and customize with your backend settings
# Example: cp backend.tf.example backend.tf
# IMPORTANT: Uncomment and configure ONE of the backend options below
# Option 1: S3 Backend (Recommended for AWS)
# terraform {
# backend "s3" {
# bucket = "your-terraform-state-bucket"
# key = "agentcore/basic-runtime/terraform.tfstate"
# region = "us-west-2"
# encrypt = true
# dynamodb_table = "terraform-state-lock"
# }
# }
# Option 2: Terraform Cloud
# terraform {
# cloud {
# organization = "your-organization"
# workspaces {
# name = "agentcore-basic-runtime"
# }
# }
# }
# Option 3: Local Backend (Default - No configuration needed)
# State will be stored locally in terraform.tfstate
# Not recommended for team environments or production use
@@ -0,0 +1,29 @@
version: 0.2
phases:
install:
commands:
- echo Installing dependencies...
- yum install -y unzip
pre_build:
commands:
- echo Source code already extracted by CodeBuild...
- cd $CODEBUILD_SRC_DIR
- ls -la
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image for basic agent ARM64 from agent-code/...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo ARM64 Docker image pushed successfully
@@ -0,0 +1,90 @@
# ============================================================================
# CodeBuild Project - Build and Push Docker Image
# ============================================================================
resource "aws_codebuild_project" "agent_image" {
name = "${var.stack_name}-basic-agent-build"
description = "Build basic agent Docker image for ${var.stack_name}"
service_role = aws_iam_role.image_build.arn
build_timeout = 60
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_LARGE"
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
type = "ARM_CONTAINER"
privileged_mode = true
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "AWS_DEFAULT_REGION"
value = data.aws_region.current.id
}
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.id
}
environment_variable {
name = "IMAGE_REPO_NAME"
value = aws_ecr_repository.agent_ecr.name
}
environment_variable {
name = "IMAGE_TAG"
value = var.image_tag
}
environment_variable {
name = "STACK_NAME"
value = var.stack_name
}
}
source {
type = "S3"
location = "${aws_s3_bucket.agent_source.id}/${aws_s3_object.agent_source.key}"
buildspec = file("${path.module}/buildspec.yml")
}
logs_config {
cloudwatch_logs {
group_name = "/aws/codebuild/${var.stack_name}-basic-agent-build"
}
}
tags = {
Name = "${var.stack_name}-basic-build"
Module = "CodeBuild"
}
}
# ============================================================================
# Trigger CodeBuild - Build Image Before Creating Runtime
# ============================================================================
resource "null_resource" "trigger_build" {
triggers = {
build_project = aws_codebuild_project.agent_image.id
image_tag = var.image_tag
# Trigger rebuild if ECR repository changes
ecr_repository = aws_ecr_repository.agent_ecr.id
# Trigger rebuild when source code changes (MD5 hash)
source_code_md5 = data.archive_file.agent_source.output_md5
}
provisioner "local-exec" {
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.agent_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.agent_ecr.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.agent_ecr.repository_url}\""
}
depends_on = [
aws_codebuild_project.agent_image,
aws_ecr_repository.agent_ecr,
aws_iam_role_policy.image_build,
aws_s3_object.agent_source
]
}
+254
View File
@@ -0,0 +1,254 @@
#!/bin/bash
# ============================================================================
# Deploy Script for Basic AgentCore Runtime (Terraform)
# ============================================================================
# This script automates the deployment process for the Terraform configuration
# Usage: ./deploy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_info "Starting Basic AgentCore Runtime Deployment..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed. Please install Terraform >= 1.6"
print_info "Visit: https://www.terraform.io/downloads"
exit 1
fi
# Check Terraform version
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
print_success "Terraform version: $TERRAFORM_VERSION"
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
exit 1
fi
print_success "AWS CLI is installed"
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
print_info "Run: aws configure"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_success "AWS Account: $AWS_ACCOUNT"
print_success "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Configuration Check
# ============================================================================
print_info "Checking configuration files..."
# Check if terraform.tfvars exists
if [ ! -f "terraform.tfvars" ]; then
print_warning "terraform.tfvars not found"
print_info "Creating terraform.tfvars from example..."
if [ -f "terraform.tfvars.example" ]; then
cp terraform.tfvars.example terraform.tfvars
print_success "Created terraform.tfvars"
print_warning "Please review and update terraform.tfvars with your settings"
print_info "Then run this script again"
exit 0
else
print_error "terraform.tfvars.example not found"
exit 1
fi
fi
print_success "Configuration file found: terraform.tfvars"
echo ""
# ============================================================================
# Terraform Initialization
# ============================================================================
print_info "Initializing Terraform..."
if terraform init; then
print_success "Terraform initialized successfully"
else
print_error "Terraform initialization failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Validation
# ============================================================================
print_info "Validating Terraform configuration..."
if terraform validate; then
print_success "Terraform configuration is valid"
else
print_error "Terraform validation failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Format Check
# ============================================================================
print_info "Checking Terraform formatting..."
if terraform fmt -check -recursive > /dev/null 2>&1; then
print_success "Terraform files are properly formatted"
else
print_warning "Some files need formatting. Running terraform fmt..."
terraform fmt -recursive
print_success "Files formatted"
fi
echo ""
# ============================================================================
# Terraform Plan
# ============================================================================
print_info "Creating Terraform execution plan..."
print_warning "This may take a few moments..."
echo ""
if terraform plan -out=tfplan; then
print_success "Terraform plan created successfully"
else
print_error "Terraform plan failed"
exit 1
fi
echo ""
# ============================================================================
# Deployment Confirmation
# ============================================================================
print_warning "========================================"
print_warning "DEPLOYMENT CONFIRMATION"
print_warning "========================================"
print_info "This will deploy the following resources:"
print_info " - S3 Bucket (source code storage)"
print_info " - ECR Repository"
print_info " - CodeBuild Project"
print_info " - IAM Roles and Policies"
print_info " - AgentCore Runtime"
echo ""
print_info "The deployment includes:"
print_info " - Building Docker image"
print_info " - Creating AWS resources"
echo ""
read -p "Do you want to proceed with deployment? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Deployment cancelled by user"
rm -f tfplan
exit 0
fi
# ============================================================================
# Terraform Apply
# ============================================================================
print_info "Starting deployment..."
echo ""
if terraform apply tfplan; then
print_success "Deployment completed successfully!"
else
print_error "Deployment failed"
rm -f tfplan
exit 1
fi
# Clean up plan file
rm -f tfplan
echo ""
# ============================================================================
# Deployment Summary
# ============================================================================
print_success "========================================"
print_success "DEPLOYMENT COMPLETED"
print_success "========================================"
echo ""
print_info "Retrieving deployment outputs..."
echo ""
# Get outputs
RUNTIME_ID=$(terraform output -raw agent_runtime_id 2>/dev/null || echo "N/A")
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn 2>/dev/null || echo "N/A")
ECR_URL=$(terraform output -raw ecr_repository_url 2>/dev/null || echo "N/A")
print_success "Agent Runtime ID: $RUNTIME_ID"
print_success "Agent Runtime ARN: $RUNTIME_ARN"
print_success "ECR Repository URL: $ECR_URL"
echo ""
print_info "Next Steps:"
print_info "1. Test your agent:"
print_info " aws bedrock-agentcore invoke-agent-runtime \\"
print_info " --agent-runtime-arn $RUNTIME_ARN \\"
print_info " --qualifier DEFAULT \\"
print_info " --payload \$(echo '{\"prompt\": \"Hello, how are you?\"}' | base64) \\"
print_info " response.json"
echo ""
print_info " Then view the response:"
print_info " cat response.json | jq -r '.response'"
echo ""
print_info "2. View all outputs:"
print_info " terraform output"
echo ""
print_info "3. Monitor in AWS Console:"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_success "Deployment completed successfully!"
@@ -0,0 +1,262 @@
#!/bin/bash
# ============================================================================
# Destroy Script for Basic AgentCore Runtime (Terraform)
# ============================================================================
# This script safely destroys all resources created by this Terraform configuration
# Usage: ./destroy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_warning "Starting Resource Cleanup..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed"
exit 1
fi
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed"
exit 1
fi
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_info "AWS Account: $AWS_ACCOUNT"
print_info "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Check for Terraform State
# ============================================================================
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
print_warning "No Terraform state found"
print_info "Either no resources have been deployed, or state is stored remotely"
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Initializing Terraform to fetch remote state..."
terraform init
else
print_info "Cleanup cancelled"
exit 0
fi
fi
# ============================================================================
# Show Destruction Plan
# ============================================================================
print_info "Creating destruction plan..."
echo ""
if ! terraform plan -destroy; then
print_error "Failed to create destruction plan"
exit 1
fi
echo ""
# ============================================================================
# Destruction Confirmation
# ============================================================================
print_warning "========================================"
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
print_warning "========================================"
print_warning "This will permanently delete the following resources:"
print_warning " - AgentCore Runtime"
print_warning " - S3 Bucket (source code storage)"
print_warning " - ECR Repository (including all images)"
print_warning " - CodeBuild Project"
print_warning " - IAM Roles and Policies"
print_warning " - CloudWatch Log Groups"
echo ""
print_warning "THIS ACTION CANNOT BE UNDONE!"
echo ""
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
echo ""
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Destruction cancelled by user"
exit 0
fi
# Double confirmation for safety
print_warning "Second confirmation required..."
read -p "Type 'DESTROY' to confirm: " -r
echo ""
if [ "$REPLY" != "DESTROY" ]; then
print_info "Destruction cancelled - confirmation text did not match"
exit 0
fi
# ============================================================================
# Execute Destruction
# ============================================================================
print_warning "Starting resource destruction..."
echo ""
if terraform destroy -auto-approve; then
print_success "All resources destroyed successfully"
else
print_error "Destruction failed"
print_warning "Some resources may still exist. Please check AWS Console"
exit 1
fi
echo ""
# ============================================================================
# Cleanup Local Files
# ============================================================================
print_info "Cleaning up local Terraform files..."
# Ask about state file cleanup
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -f terraform.tfstate
rm -f terraform.tfstate.backup
rm -f tfplan
print_success "Local state files removed"
fi
# Ask about .terraform directory
read -p "Do you want to remove .terraform directory? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -rf .terraform
rm -f .terraform.lock.hcl
print_success ".terraform directory removed"
fi
echo ""
# ============================================================================
# Verification
# ============================================================================
print_info "Verifying resource cleanup..."
echo ""
# Check for ECR repositories
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-basic")
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep -c "$STACK_NAME" || echo "0")
if [ "$ECR_REPOS" -eq 0 ]; then
print_success "ECR repositories cleaned up"
else
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for AgentCore runtimes
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep -c "$STACK_NAME" || echo "0")
if [ "$RUNTIME_COUNT" -eq 0 ]; then
print_success "AgentCore runtimes cleaned up"
else
print_warning "Found $RUNTIME_COUNT AgentCore runtimes matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for S3 buckets
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep -c "$STACK_NAME" || echo "0")
if [ "$S3_BUCKETS" -eq 0 ]; then
print_success "S3 buckets cleaned up"
else
print_warning "Found $S3_BUCKETS S3 buckets matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
echo ""
# ============================================================================
# Completion Summary
# ============================================================================
print_success "========================================"
print_success "CLEANUP COMPLETED"
print_success "========================================"
echo ""
print_info "Cleanup Summary:"
print_success " ✓ Terraform resources destroyed"
print_success " ✓ Local state files cleaned (if selected)"
echo ""
print_info "What to verify in AWS Console:"
print_info "1. Bedrock AgentCore - No runtimes remaining"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_info "2. S3 - No buckets remaining"
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
echo ""
print_info "3. ECR - No repositories remaining"
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
echo ""
print_info "4. CodeBuild - No projects remaining"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
echo ""
print_info "5. CloudWatch Logs - Check for orphaned log groups"
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
echo ""
print_success "Cleanup completed successfully!"
print_info "You can safely re-deploy by running: ./deploy.sh"
@@ -0,0 +1,63 @@
# ============================================================================
# ECR Repository - Container Registry for Agent Image
# ============================================================================
resource "aws_ecr_repository" "agent_ecr" {
name = "${var.stack_name}-${var.ecr_repository_name}"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
force_delete = true
tags = {
Name = "${var.stack_name}-ecr-repository"
Module = "ECR"
}
}
# ECR Repository Policy
resource "aws_ecr_repository_policy" "agent_ecr" {
repository = aws_ecr_repository.agent_ecr.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPullFromAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
})
}
# ECR Lifecycle Policy - Keep last 5 images
resource "aws_ecr_lifecycle_policy" "agent_ecr" {
repository = aws_ecr_repository.agent_ecr.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 5 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 5
}
action = {
type = "expire"
}
}
]
})
}
@@ -0,0 +1,217 @@
# Data sources
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# ============================================================================
# Agent Execution Role - For AgentCore Runtime
# ============================================================================
resource "aws_iam_role" "agent_execution" {
name = "${var.stack_name}-agent-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AssumeRolePolicy"
Effect = "Allow"
Principal = {
Service = "bedrock-agentcore.amazonaws.com"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.id
}
ArnLike = {
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
}
}
}]
})
tags = {
Name = "${var.stack_name}-agent-execution-role"
Module = "IAM"
}
}
# Attach AWS managed policy for AgentCore
resource "aws_iam_role_policy_attachment" "agent_execution_managed" {
role = aws_iam_role.agent_execution.name
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
}
# Inline policy for agent execution
resource "aws_iam_role_policy" "agent_execution" {
name = "AgentCoreExecutionPolicy"
role = aws_iam_role.agent_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# ECR Access
{
Sid = "ECRImageAccess"
Effect = "Allow"
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
Resource = aws_ecr_repository.agent_ecr.arn
},
{
Sid = "ECRTokenAccess"
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
},
# X-Ray Tracing
{
Sid = "XRayTracing"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets"
]
Resource = "*"
},
# CloudWatch Metrics
{
Sid = "CloudWatchMetrics"
Effect = "Allow"
Action = ["cloudwatch:PutMetricData"]
Resource = "*"
Condition = {
StringEquals = {
"cloudwatch:namespace" = "bedrock-agentcore"
}
}
},
# Bedrock Model Invocation
{
Sid = "BedrockModelInvocation"
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
]
Resource = "*"
},
# Workload Access Tokens
{
Sid = "GetAgentAccessToken"
Effect = "Allow"
Action = [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
]
Resource = [
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
]
}
]
})
}
# ============================================================================
# CodeBuild Service Role - For Docker Image Building
# ============================================================================
resource "aws_iam_role" "image_build" {
name = "${var.stack_name}-codebuild-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
tags = {
Name = "${var.stack_name}-codebuild-role"
Module = "IAM"
}
}
# Inline policy for CodeBuild
resource "aws_iam_role_policy" "image_build" {
name = "CodeBuildPolicy"
role = aws_iam_role.image_build.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
},
# ECR Access
{
Sid = "ECRAccess"
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
Resource = [
aws_ecr_repository.agent_ecr.arn,
"*"
]
},
# S3 Source Access (for agent-code)
{
Sid = "S3SourceAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion"
]
Resource = "${aws_s3_bucket.agent_source.arn}/*"
},
{
Sid = "S3BucketAccess"
Effect = "Allow"
Action = [
"s3:ListBucket",
"s3:GetBucketLocation"
]
Resource = aws_s3_bucket.agent_source.arn
}
]
})
}
@@ -0,0 +1,33 @@
# ============================================================================
# AgentCore Runtime - Main Agent Runtime Resource
# ============================================================================
resource "aws_bedrockagentcore_agent_runtime" "basic_agent" {
agent_runtime_name = replace("${var.stack_name}_${var.agent_name}", "-", "_")
description = var.description
role_arn = aws_iam_role.agent_execution.arn
agent_runtime_artifact {
container_configuration {
container_uri = "${aws_ecr_repository.agent_ecr.repository_url}:${var.image_tag}"
}
}
network_configuration {
network_mode = var.network_mode
}
environment_variables = merge(
{
AWS_REGION = var.aws_region
AWS_DEFAULT_REGION = var.aws_region
},
var.environment_variables
)
depends_on = [
null_resource.trigger_build,
aws_iam_role_policy.agent_execution,
aws_iam_role_policy_attachment.agent_execution_managed
]
}
@@ -0,0 +1,59 @@
output "agent_runtime_id" {
description = "ID of the created agent runtime"
value = aws_bedrockagentcore_agent_runtime.basic_agent.agent_runtime_id
}
output "agent_runtime_arn" {
description = "ARN of the created agent runtime"
value = aws_bedrockagentcore_agent_runtime.basic_agent.agent_runtime_arn
}
output "agent_runtime_version" {
description = "Version of the created agent runtime"
value = aws_bedrockagentcore_agent_runtime.basic_agent.agent_runtime_version
}
output "ecr_repository_url" {
description = "URL of the ECR repository"
value = aws_ecr_repository.agent_ecr.repository_url
}
output "ecr_repository_arn" {
description = "ARN of the ECR repository"
value = aws_ecr_repository.agent_ecr.arn
}
output "agent_execution_role_arn" {
description = "ARN of the agent execution role"
value = aws_iam_role.agent_execution.arn
}
output "codebuild_project_name" {
description = "Name of the CodeBuild project"
value = aws_codebuild_project.agent_image.name
}
output "codebuild_project_arn" {
description = "ARN of the CodeBuild project"
value = aws_codebuild_project.agent_image.arn
}
output "source_bucket_name" {
description = "S3 bucket containing agent source code"
value = aws_s3_bucket.agent_source.id
}
output "source_bucket_arn" {
description = "ARN of the S3 bucket containing agent source code"
value = aws_s3_bucket.agent_source.arn
}
output "source_object_key" {
description = "S3 object key for the agent source code archive"
value = aws_s3_object.agent_source.key
}
output "source_code_md5" {
description = "MD5 hash of the agent source code (triggers rebuild when changed)"
value = data.archive_file.agent_source.output_md5
}
@@ -0,0 +1,58 @@
# ============================================================================
# S3 Bucket for Agent Source Code (CDK Asset Equivalent)
# ============================================================================
resource "aws_s3_bucket" "agent_source" {
bucket_prefix = "${var.stack_name}-agent-source-"
force_destroy = true
tags = {
Name = "${var.stack_name}-agent-source"
Purpose = "Store agent source code for CodeBuild"
}
}
# Block public access
resource "aws_s3_bucket_public_access_block" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning for source code tracking
resource "aws_s3_bucket_versioning" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
versioning_configuration {
status = "Enabled"
}
}
# ============================================================================
# Archive and Upload Agent Source Code
# ============================================================================
# Archive agent-code/ directory
# This automatically detects ALL files including new ones
data "archive_file" "agent_source" {
type = "zip"
source_dir = "${path.module}/agent-code"
output_path = "${path.module}/.terraform/agent-code.zip"
}
# Upload to S3 (re-uploads when MD5 changes)
resource "aws_s3_object" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
key = "agent-code-${data.archive_file.agent_source.output_md5}.zip"
source = data.archive_file.agent_source.output_path
etag = data.archive_file.agent_source.output_md5
tags = {
Name = "agent-source-code"
MD5 = data.archive_file.agent_source.output_md5
Timestamp = timestamp()
}
}
@@ -0,0 +1,196 @@
#!/bin/bash
# ============================================================================
# Build and Verify Docker Image for AgentCore Runtime
# ============================================================================
# This script is called by Terraform during deployment to:
# 1. Trigger CodeBuild to build the Docker image
# 2. Wait for the build to complete
# 3. Verify the image was successfully pushed to ECR
#
# Parameters:
# $1 - CodeBuild project name
# $2 - AWS region
# $3 - ECR repository name
# $4 - Image tag
# $5 - ECR repository URL
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Parameters
PROJECT_NAME="$1"
REGION="$2"
REPO_NAME="$3"
IMAGE_TAG="$4"
REPO_URL="$5"
# ============================================================================
# Print functions
# ============================================================================
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[✓]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[⚠]${NC} $1"
}
print_error() {
echo -e "${RED}[✗]${NC} $1"
}
print_header() {
echo ""
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================${NC}"
}
# ============================================================================
# Start Build Process
# ============================================================================
print_header "Building Docker Image for AgentCore Runtime"
print_info "CodeBuild Project: $PROJECT_NAME"
print_info "Region: $REGION"
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
echo ""
# Start CodeBuild
print_info "Starting CodeBuild project..."
BUILD_ID=$(aws codebuild start-build \
--project-name "$PROJECT_NAME" \
--region "$REGION" \
--query 'build.id' \
--output text 2>&1)
if [ $? -ne 0 ]; then
print_error "Failed to start CodeBuild"
echo "$BUILD_ID"
exit 1
fi
print_success "Build started: $BUILD_ID"
print_info "Waiting for build to complete (typically 5-10 minutes)..."
echo ""
# ============================================================================
# Monitor Build Progress
# ============================================================================
ATTEMPT=0
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT + 1))
STATUS=$(aws codebuild batch-get-builds \
--ids "$BUILD_ID" \
--region "$REGION" \
--query 'builds[0].buildStatus' \
--output text 2>/dev/null)
if [ "$STATUS" != "IN_PROGRESS" ]; then
print_info "Build process completed with status: $STATUS"
break
fi
# Progress indicator
if [ $((ATTEMPT % 6)) -eq 0 ]; then
MINUTES=$((ATTEMPT / 6))
print_info "Build in progress... (${MINUTES} minutes elapsed)"
fi
sleep 10
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
print_error "Build timeout after 10 minutes"
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
exit 1
fi
echo ""
# ============================================================================
# Verify Image in ECR
# ============================================================================
print_header "Verifying Docker Image in ECR"
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
print_info "Waiting for ECR propagation..."
echo ""
sleep 5 # Brief wait for ECR to register the push
VERIFY_ATTEMPT=0
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
if aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" >/dev/null 2>&1; then
print_success "Docker image successfully verified in ECR!"
echo ""
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
# Get image details
IMAGE_SIZE=$(aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" \
--query 'imageDetails[0].imageSizeInBytes' \
--output text 2>/dev/null || echo "Unknown")
if [ "$IMAGE_SIZE" != "Unknown" ]; then
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
fi
echo ""
print_success "Build and verification completed successfully!"
exit 0
fi
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
fi
sleep 5
done
# ============================================================================
# Error: Image Not Found
# ============================================================================
print_error "Docker image not found in ECR after build completion"
echo ""
print_warning "This indicates the build or push step failed."
print_info "Troubleshooting steps:"
print_info " 1. Check CodeBuild logs:"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
print_info ""
print_info " 2. Verify ECR repository:"
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
print_info ""
print_info " 3. Check IAM permissions for CodeBuild role"
exit 1
@@ -0,0 +1,27 @@
# ============================================================================
# Basic AgentCore Runtime - Example Configuration
# ============================================================================
# Copy this file to terraform.tfvars and customize the values
# Example: cp terraform.tfvars.example terraform.tfvars
# Agent Configuration
agent_name = "BasicAgent"
stack_name = "agentcore-basic"
description = "Basic agent runtime without memory, code interpreter, or browser"
# Network Configuration
network_mode = "PUBLIC" # Options: PUBLIC, PRIVATE
# Container Configuration
ecr_repository_name = "basic-agent"
image_tag = "latest"
# AWS Configuration
aws_region = "us-west-2"
environment = "dev"
# Optional: Environment Variables for the Agent Runtime
# environment_variables = {
# LOG_LEVEL = "INFO"
# CUSTOM_VAR = "value"
# }
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""
Basic Agent Test Script
This script tests the Basic Agent with simple conversational prompts.
The basic agent has no additional tools - just core Q&A capabilities.
Usage:
python test_basic_agent.py <agent_arn>
agent_arn: ARN of the basic agent runtime (required)
Examples:
# Test basic agent
python test_basic_agent.py arn:aws:bedrock-agentcore:<region>:123456789012:runtime/basic-agent-id
# From Terraform outputs
python test_basic_agent.py $(terraform output -raw agent_runtime_arn)
"""
import boto3
import json
import sys
def extract_region_from_arn(arn):
"""Extract AWS region from agent runtime ARN.
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
Args:
arn: Agent runtime ARN string
Returns:
str: AWS region code
Raises:
ValueError: If ARN format is invalid or region cannot be extracted
"""
try:
parts = arn.split(':')
if len(parts) < 4:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
region = parts[3]
if not region:
raise ValueError(
f"Region not found in ARN: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
return region
except IndexError:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
def test_agent(client, agent_arn, test_name, prompt):
"""Test the agent with a given prompt
Args:
client: boto3 bedrock-agentcore client
agent_arn: ARN of the agent runtime
test_name: Name of the test for display
prompt: The prompt to send to the agent
Returns:
bool: True if test passed, False otherwise
"""
print(f"\n{'=' * 80}")
print(f"TEST: {test_name}")
print(f"{'=' * 80}\n")
print(f"Prompt: '{prompt}'")
print("-" * 80)
try:
response = client.invoke_agent_runtime(
agentRuntimeArn=agent_arn,
qualifier="DEFAULT",
payload=json.dumps({"prompt": prompt}),
)
print(f"Status: {response['ResponseMetadata']['HTTPStatusCode']}")
print(f"Content Type: {response.get('contentType', 'N/A')}")
# Read the streaming response body
response_text = ""
if 'response' in response:
response_body = response['response'].read()
response_text = response_body.decode('utf-8')
if response_text:
try:
result = json.loads(response_text)
response_content = result.get('response', response_text)
print(f"\n✅ Response:\n{response_content}")
except json.JSONDecodeError:
print(f"\n✅ Response:\n{response_text}")
else:
print("\n⚠️ No response content received")
return True
except Exception as e:
print(f"\n❌ Error: {str(e)}")
return False
def main():
"""Main test execution function"""
if len(sys.argv) < 2:
print("Error: Missing required agent ARN argument")
print("\nUsage:")
print(f" {sys.argv[0]} <agent_arn>")
print("\nExample:")
print(f" {sys.argv[0]} arn:aws:bedrock-agentcore:<region>:123456789012:runtime/agent-id")
print("\nOr from Terraform:")
print(f" {sys.argv[0]} $(terraform output -raw agent_runtime_arn)")
sys.exit(1)
agent_arn = sys.argv[1]
# Extract region from ARN
try:
region = extract_region_from_arn(agent_arn)
except ValueError as e:
print(f"\n❌ ERROR: {e}\n")
sys.exit(1)
print("=" * 80)
print("BASIC AGENT TEST SUITE")
print("=" * 80)
print(f"\nAgent ARN: {agent_arn}")
print(f"Region: {region}\n")
# Initialize boto3 client with extracted region
client = boto3.client("bedrock-agentcore", region_name=region)
# Test cases for basic agent (no tools, just Q&A)
tests = [
{
"name": "Simple Greeting",
"prompt": "Hello! Can you introduce yourself?",
},
{
"name": "Reasoning Task",
"prompt": "Explain what cloud computing is in simple terms and list three key benefits.",
},
]
# Run all tests
results = []
for test in tests:
passed = test_agent(client, agent_arn, test["name"], test["prompt"])
results.append({"name": test["name"], "passed": passed})
# Print summary
print(f"\n{'=' * 80}")
print("TEST SUMMARY")
print("=" * 80)
for result in results:
status = "✅ PASSED" if result["passed"] else "❌ FAILED"
print(f"{status} - {result['name']}")
# Overall result
passed_count = sum(1 for r in results if r["passed"])
total_count = len(results)
print(f"\n{'=' * 80}")
if passed_count == total_count:
print("✅ ALL TESTS PASSED")
else:
print(f"⚠️ {passed_count}/{total_count} TESTS PASSED")
print("=" * 80 + "\n")
# Exit with appropriate code
sys.exit(0 if passed_count == total_count else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,68 @@
variable "agent_name" {
description = "Name for the agent runtime"
type = string
default = "BasicAgent"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.agent_name))
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
}
}
variable "network_mode" {
description = "Network mode for AgentCore resources"
type = string
default = "PUBLIC"
validation {
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
error_message = "Network mode must be either PUBLIC or PRIVATE."
}
}
variable "image_tag" {
description = "Docker image tag"
type = string
default = "latest"
}
variable "aws_region" {
description = "AWS region for deployment (REQUIRED)"
type = string
# No default - must be explicitly provided
validation {
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
}
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "stack_name" {
description = "Stack name for resource naming"
type = string
default = "agentcore-basic"
}
variable "description" {
description = "Description of the agent runtime"
type = string
default = "Basic agent runtime without memory, code interpreter, or browser"
}
variable "environment_variables" {
description = "Environment variables for the agent runtime"
type = map(string)
default = {}
}
variable "ecr_repository_name" {
description = "Name of the ECR repository"
type = string
default = "basic-agent"
}
@@ -0,0 +1,32 @@
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.21"
}
null = {
source = "hashicorp/null"
version = "~> 3.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.9"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "AgentCore"
Pattern = "basic-runtime"
Environment = var.environment
StackName = var.stack_name
ManagedBy = "Terraform"
}
}
}
@@ -0,0 +1,25 @@
# Terraform files
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
!terraform.tfvars.example
tfplan
# Generated archives
*.zip
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
@@ -0,0 +1,646 @@
# End-to-End Weather Agent on Amazon Bedrock AgentCore (Terraform)
This Terraform module deploys a comprehensive weather agent using Amazon Bedrock AgentCore Runtime with integrated AgentCore tools (Browser, Code Interpreter, and Memory).
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [What's Included](#whats-included)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Deployment Process](#deployment-process)
- [Authentication Model](#authentication-model)
- [Testing](#testing)
- [Agent Capabilities](#agent-capabilities)
- [Customization](#customization)
- [File Structure](#file-structure)
- [Monitoring and Observability](#monitoring-and-observability)
- [Security](#security)
- [Troubleshooting](#troubleshooting)
- [Cleanup](#cleanup)
- [Advanced Topics](#advanced-topics)
- [Next Steps](#next-steps)
- [Resources](#resources)
- [Contributing](#contributing)
- [License](#license)
## Overview
This pattern demonstrates deploying a full-featured weather agent with Amazon Bedrock AgentCore tools, enabling sophisticated weather intelligence capabilities through web browsing, code execution, and persistent memory.
**Key Features:**
- Integrated AgentCore tools (Browser, Code Interpreter, Memory)
- Automated Docker image building via CodeBuild
- S3-based artifact storage for analysis results
- IAM-based security with tool-specific permissions
- Weather data visualization and analysis capabilities
This makes it ideal for:
- Building intelligent weather information systems
- Creating data analysis pipelines with code execution
- Implementing web scraping for real-time weather data
- Learning AgentCore tool integration patterns
## Architecture
![Weather Agent Architecture](architecture.png)
### System Components
**Weather Agent Runtime**
- Processes natural language weather queries
- Coordinates multiple tools for comprehensive responses
- Generates visualizations and detailed analyses
- Maintains conversation context across sessions
**AgentCore Tools**
1. **Browser Tool**
- Accesses weather websites and advisories
- Scrapes real-time weather data
- Retrieves alerts and warnings
- Checks weather-related news
2. **Code Interpreter Tool**
- Executes Python for data analysis
- Creates weather visualizations (charts, graphs)
- Performs statistical calculations
- Processes and transforms weather data
3. **Memory**
- Stores conversation history
- Remembers user preferences
- Maintains context across sessions
- Event expiry: 30 days
### Tool Integration
The agent seamlessly coordinates all tools:
- **Query Processing**: Understands weather-related requests
- **Tool Selection**: Chooses appropriate tools automatically
- **Data Analysis**: Uses Code Interpreter for complex calculations
- **Web Access**: Employs Browser for real-time information
- **Context Retention**: Leverages Memory for personalized responses
## What's Included
This Terraform configuration creates:
- **2 S3 Buckets**:
- Source code storage with versioning
- Results bucket for analysis artifacts
- **1 ECR Repository**: Container registry for ARM64 Docker image
- **1 CodeBuild Project**: Automated image building and pushing
- **2 IAM Roles**:
- Agent execution role (with tool permissions)
- CodeBuild service role
- **1 Agent Runtime**: Weather agent with tool integration
- **3 AgentCore Tools**:
- Browser for web access
- Code Interpreter for analysis
- Memory for context retention
- **Build Automation**: Automatic rebuild on code changes (MD5-based detection)
- **Supporting Resources**: S3 lifecycle policies, ECR lifecycle policies, IAM policies
**Total:** ~20 AWS resources deployed and managed by Terraform
## Prerequisites
### Required Tools
1. **Terraform** (>= 1.6)
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
2. **AWS CLI** (configured with credentials)
```bash
aws configure
```
3. **Python 3.11+** (for testing scripts and memory initialization)
```bash
python --version # Verify Python 3.11 or later
pip install boto3
```
**Note**: `boto3` is required for:
- Running test scripts (`test_weather_agent.py`)
- Automatic memory initialization during deployment
4. **Docker** (for local testing, optional)
### AWS Account Requirements
- AWS Account with appropriate permissions
- Access to Amazon Bedrock AgentCore service
- Permissions to create:
- S3 buckets
- ECR repositories
- CodeBuild projects
- IAM roles and policies
- AgentCore Runtime resources
## Quick Start
### 1. Configure Variables
Copy the example variables file and customize:
```bash
cp terraform.tfvars.example terraform.tfvars
```
Edit `terraform.tfvars` with your preferred values:
- `agent_name`: Name for the weather agent (default: "WeatherAgent")
- `memory_name`: Name for memory resource (default: "WeatherAgentMemory")
- `stack_name`: Stack identifier (default: "agentcore-weather")
- `aws_region`: AWS region for deployment (default: "us-west-2")
- `network_mode`: PUBLIC or PRIVATE networking
### 2. Initialize Terraform
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
**Quick start with local state:**
```bash
terraform init
```
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
### 3. Deploy
**Method 1: Using Deploy Script (Recommended)**
```bash
chmod +x deploy.sh
./deploy.sh
```
The script validates configuration, shows the plan, and deploys all resources.
**Method 2: Direct Terraform Commands**
```bash
terraform plan
terraform apply
```
**Note**: Deployment includes creating infrastructure, building the Docker image, and provisioning all AgentCore tools. Total deployment time: **~8-12 minutes**
### 4. Verify Deployment
```bash
# View all outputs
terraform output
# Get Agent and Tool IDs
terraform output agent_runtime_arn
terraform output browser_id
terraform output code_interpreter_id
terraform output memory_id
```
## Deployment Process
### Build and Deployment Sequence
The deployment follows this sequence:
```
1. S3 Buckets Creation (source & results)
2. ECR Repository Creation
3. IAM Roles Creation (with tool permissions)
4. AgentCore Tools Creation (Browser, Code Interpreter, Memory)
5. CodeBuild Project Creation
6. Docker Image Build
7. Weather Agent Runtime Creation (with tool IDs as environment variables)
```
**Tool Integration:**
- Agent receives tool IDs as environment variables
- IAM role grants permissions for tool operations
- Tools are created before agent runtime
- Results bucket for code interpreter outputs
### Build Triggers
The infrastructure automatically triggers Docker image builds:
- When source code changes (MD5 hash detection)
- When infrastructure changes require rebuild
- Build typically takes 5-8 minutes
### Memory Initialization
Memory is automatically initialized with activity preferences (good/ok/poor weather activities) via `scripts/init-memory.py` during deployment. The agent uses these preferences for weather-based activity recommendations.
### Observability
Full observability is automatically configured with CloudWatch Logs (14-day retention) and X-Ray traces. Logs are delivered via vended logs delivery to `/aws/vendedlogs/bedrock-agentcore/${runtime_id}`. Access via CloudWatch Console or `aws logs tail` command.
## Authentication Model
This pattern uses **IAM-based authentication with workload identity tokens**:
- **Service Principal**: Agent assumes IAM role via `bedrock-agentcore.amazonaws.com`
- **Workload Identity**: Agent obtains access tokens for secure operations
- **Tool Access**: IAM permissions for Browser, Code Interpreter, and Memory
- **S3 Access**: Agent can write analysis results to S3 bucket
**Note**: This is a backend infrastructure pattern with no user authentication layer. For user-facing applications, you would add Cognito or API Gateway authorizers separately.
## Testing
The included `test_weather_agent.py` script is **infrastructure-agnostic** and works with any deployment method (Terraform, CDK, CloudFormation, or manual).
### Prerequisites for Testing
Before testing, ensure you have the required packages installed:
**Option A: Using uv (Recommended)**
```bash
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install boto3 # Required for agent invocation
```
**Option B: System-wide installation**
```bash
pip install boto3 # Required for agent invocation
```
**Note**: `boto3` is required for the test script to invoke the agent runtime via AWS API.
### Basic Testing
```bash
# Get ARN from Terraform
AGENT_ARN=$(terraform output -raw agent_runtime_arn)
# Test the agent
python test_weather_agent.py $AGENT_ARN
```
### Test Scenarios
The script runs two comprehensive tests:
1. **Simple Weather Query**: Basic weather information request
2. **Complex Query with Tools**: Demonstrates browser, code interpreter, and memory usage together
### Expected Output
```
TEST 1: Simple Weather Query ✅
TEST 2: Complex Query with Tools ✅
✅ ALL TESTS PASSED
```
## Agent Capabilities
### Weather Agent
**Core Capabilities:**
- Natural language weather queries
- Real-time weather data access
- Historical weather analysis
- Weather forecasting insights
- Multi-location comparisons
- Travel weather planning
**Integrated Tools:**
1. **Browser Tool**
- Access weather.gov, weather.com, and other weather sites
- Retrieve current conditions and forecasts
- Check weather alerts and warnings
- Gather radar and satellite imagery links
2. **Code Interpreter Tool**
- Analyze temperature trends
- Create weather visualizations (line charts, bar graphs)
- Calculate statistics (averages, extremes)
- Process historical weather data
- Generate custom weather reports
3. **Memory**
- Remember user location preferences
- Track frequently requested locations
- Maintain conversation context
- Store user preferences for units (F/C)
- Pre-initialized with activity preferences for weather-based recommendations
### Example Interactions
**Simple Query:**
```
User: "What's the weather like in Seattle?"
Agent: [Uses Browser] Provides current conditions, forecast, temperature
```
**Analysis Query:**
```
User: "Compare temperatures between New York and Miami over the past week"
Agent: [Uses Browser + Code Interpreter] Fetches data, creates comparison chart
```
**Planning Query:**
```
User: "I'm planning a road trip from Boston to Miami next week. What should I expect?"
Agent: [Uses Browser + Memory] Provides route-based weather forecast, remembers trip details
```
## Customization
### Modify Agent Code
1. **Edit Agent Files**
```bash
vim agent-code/weather_agent.py
vim agent-code/requirements.txt
vim agent-code/Dockerfile
```
2. **Redeploy**
```bash
terraform apply # Automatically detects changes and rebuilds
```
### Add Additional AgentCore Tools
To add more tools:
1. Create new tool resource file (e.g., `gateway.tf`)
2. Add tool ID to agent environment variables in `main.tf`
3. Update IAM permissions in `iam.tf` if needed
4. Update `outputs.tf` with new tool outputs
### Modify Network Configuration
Change from PUBLIC to PRIVATE networking:
```hcl
# terraform.tfvars
network_mode = "PRIVATE"
```
Requires VPC configuration (not included in this module).
### Customize Memory Settings
Adjust memory retention period:
```hcl
# memory.tf
event_expiry_duration = 60 # Change from 30 to 60 days
```
## File Structure
```
end-to-end-weather-agent/
├── agent-code/ # Weather agent source code
│ ├── weather_agent.py # Agent implementation
│ ├── requirements.txt # Python dependencies
│ └── Dockerfile # Container definition
├── scripts/ # Build automation and initialization
│ ├── build-image.sh # Docker build script
│ └── init-memory.py # Memory initialization script
├── main.tf # Agent runtime configuration
├── browser.tf # Browser tool
├── code_interpreter.tf # Code interpreter tool
├── memory.tf # Memory resource
├── memory-init.tf # Memory initialization automation
├── observability.tf # CloudWatch Logs and X-Ray traces
├── iam.tf # IAM roles and policies
├── s3.tf # S3 buckets
├── ecr.tf # ECR repository
├── codebuild.tf # CodeBuild project
├── outputs.tf # Output values
├── variables.tf # Input variables
├── versions.tf # Provider versions
├── buildspec.yml # CodeBuild specification
├── test_weather_agent.py # Infrastructure-agnostic test script
├── deploy.sh # Deployment automation
├── destroy.sh # Cleanup automation
├── terraform.tfvars.example # Example configuration
├── backend.tf.example # Remote state example
├── .gitignore # Git exclusions
├── architecture.png # Architecture diagram
└── README.md # This file
```
## Monitoring and Observability
### CloudWatch Logs (Automatic)
Terraform automatically creates CloudWatch Log Group with vended logs delivery:
```bash
# Get log group name
LOG_GROUP=$(terraform output -raw log_group_name)
# Tail agent logs
aws logs tail $LOG_GROUP --follow
# CodeBuild logs
aws logs tail /aws/codebuild/agentcore-weather-agent-build --follow
```
### X-Ray Traces (Automatic)
Distributed tracing automatically delivered to X-Ray. View traces in [X-Ray Console](https://console.aws.amazon.com/xray/home).
### Metrics
Access metrics in CloudWatch:
- Agent invocation count
- Tool usage frequency (Browser, Code Interpreter, Memory)
- Agent execution duration
- Error rates
- Code interpreter execution time
### AWS Console
Monitor in AWS Console:
- **CloudWatch Logs**: Vended logs at `/aws/vendedlogs/bedrock-agentcore/${runtime_id}`
- **X-Ray**: Distributed request traces
- **Bedrock AgentCore**: [Console Link](https://console.aws.amazon.com/bedrock/home#/agentcore)
- **ECR Repository**: Docker images
- **CodeBuild**: Build status
- **S3 Results Bucket**: Generated artifacts
## Security
### IAM Permissions
**Agent Execution Role:**
- Standard AgentCore permissions
- ECR image pull access
- S3 read/write for results bucket
- CloudWatch Logs write access
- Tool-specific permissions (Browser, Code Interpreter, Memory access)
**CodeBuild Role:**
- S3 access to source bucket
- ECR push access
- CloudWatch Logs write access
### Network Security
- Agent runs in specified network mode (PUBLIC/PRIVATE)
- ECR repository has account-level access controls
- S3 buckets block public access
- IAM policies follow least-privilege principle
- Tool resources use network isolation
### Secrets Management
For sensitive data:
- Use AWS Secrets Manager
- Pass secret ARNs as environment variables
- Retrieve secrets at runtime in agent code
## Troubleshooting
### Common Issues
**Issue**: Agent cannot access tools
- **Solution**: Verify tool IDs are set as environment variables
- **Check**: IAM permissions include tool access
**Issue**: Code Interpreter fails
- **Solution**: Check CloudWatch logs for Python errors
- **Check**: Verify results bucket permissions
**Issue**: Browser tool times out
- **Solution**: Check network connectivity
- **Check**: Verify target websites are accessible
**Issue**: Build fails
- **Solution**: Check CodeBuild logs in CloudWatch
- **Check**: Verify source code is in correct directory
**Issue**: Runtime not created
- **Solution**: Verify ECR image exists and is tagged correctly
- **Check**: Review Terraform state for errors
### Debug Commands
```bash
# Check Terraform state
terraform show
# Validate configuration
terraform validate
# View specific resource
terraform state show aws_bedrockagentcore_agent_runtime.agent
# Get tool IDs
terraform output browser_id
terraform output code_interpreter_id
terraform output memory_id
# Check results bucket
aws s3 ls s3://$(terraform output -raw results_bucket_name)/
# Get detailed build logs
PROJECT_NAME=$(terraform output -raw agent_codebuild_project_name)
aws codebuild batch-get-builds --ids $(aws codebuild list-builds-for-project --project-name $PROJECT_NAME --query 'ids[0]' --output text)
```
## Cleanup
### Automated Cleanup
```bash
chmod +x destroy.sh
./destroy.sh
```
The script shows the destruction plan, requires confirmation, and destroys all resources.
### Manual Cleanup
```bash
terraform destroy
```
**Important**: Verify in AWS Console that all resources are deleted:
- Bedrock AgentCore runtimes
- ECR repositories
- S3 buckets
- CodeBuild projects
- IAM roles
## Advanced Topics
### Adding Custom Tools
1. Define tool schema in agent code
2. Implement tool handler function
3. Register tool with agent
4. Rebuild and deploy
### Implementing Memory
Add session management in agent code:
```python
session_data = {}
def handle_request(input_text, session_id):
if session_id not in session_data:
session_data[session_id] = {}
# Use session_data for context
```
### Multi-Region Deployment
For multi-region:
1. Configure backend for state locking
2. Deploy to each region separately
3. Use Route53 for failover
4. Consider cross-region replication for S3/ECR
## Next Steps
1. **Test the deployment**
```bash
python test_weather_agent.py $(terraform output -raw agent_runtime_arn)
```
2. **Customize the agent** for your specific use case
- Modify weather data sources
- Add custom weather analysis logic
- Integrate additional external APIs
- Enhance visualization capabilities
3. **Explore related patterns**
- [Multi-Agent Runtime](../multi-agent-runtime/) - Agent-to-Agent communication
- [MCP Server Pattern](../mcp-server-agentcore-runtime/) - MCP protocol with JWT auth
- [AgentCore Samples](https://github.com/aws-samples/amazon-bedrock-agentcore-samples) - More examples
4. **Add production features**
- Monitoring and alerting
- Custom authentication layer
- VPC deployment for private networking
- CI/CD pipeline integration
- Rate limiting and throttling
## Resources
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html)
- [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
- [Agent-to-Agent Communication](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore-a2a.html)
- [Model Context Protocol](https://modelcontextprotocol.io/)
## Contributing
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
## License
This project is licensed under the MIT-0 license. See the [LICENSE](../../../LICENSE) file for details.
@@ -0,0 +1,21 @@
FROM public.ecr.aws/docker/library/python:3.11-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt && \
pip install --no-cache-dir aws-opentelemetry-distro==0.10.1
# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 8080
EXPOSE 8000
COPY . .
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/ping || exit 1
CMD ["opentelemetry-instrument", "python", "-m", "weather_agent"]
@@ -0,0 +1,9 @@
strands-agents
strands-agents-tools
uv
boto3
bedrock-agentcore
bedrock-agentcore-starter-toolkit
browser-use==0.3.2
langchain-aws>=0.1.0
rich
@@ -0,0 +1,328 @@
from strands import Agent, tool
from strands_tools import use_aws
from typing import Dict, Any
import json
import os
import asyncio
from contextlib import suppress
from bedrock_agentcore.tools.browser_client import BrowserClient
from browser_use import Agent as BrowserAgent
from browser_use.browser.session import BrowserSession
from browser_use.browser import BrowserProfile
from langchain_aws import ChatBedrockConverse
from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter
from bedrock_agentcore.memory import MemoryClient
from rich.console import Console
import re
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
console = Console()
# Configuration - All required, no defaults
BROWSER_ID = os.getenv('BROWSER_ID')
CODE_INTERPRETER_ID = os.getenv('CODE_INTERPRETER_ID')
MEMORY_ID = os.getenv('MEMORY_ID')
RESULTS_BUCKET = os.getenv('RESULTS_BUCKET')
AWS_REGION = os.getenv('AWS_REGION')
# Validate required environment variables
required_vars = {
'BROWSER_ID': BROWSER_ID,
'CODE_INTERPRETER_ID': CODE_INTERPRETER_ID,
'MEMORY_ID': MEMORY_ID,
'RESULTS_BUCKET': RESULTS_BUCKET,
'AWS_REGION': AWS_REGION
}
missing = [k for k, v in required_vars.items() if not v]
if missing:
raise EnvironmentError(f"Required environment variables not set: {', '.join(missing)}")
# Async helper functions
async def run_browser_task(browser_session, bedrock_chat, task: str) -> str:
"""Run a browser automation task using browser_use"""
try:
console.print(f"[blue]🤖 Executing browser task:[/blue] {task[:100]}...")
agent = BrowserAgent(
task=task,
llm=bedrock_chat,
browser=browser_session
)
result = await agent.run()
console.print("[green]✅ Browser task completed successfully![/green]")
if 'done' in result.last_action() and 'text' in result.last_action()['done']:
return result.last_action()['done']['text']
else:
raise ValueError("NO Data")
except Exception as e:
console.print(f"[red]❌ Browser task error: {e}[/red]")
raise
async def initialize_browser_session():
"""Initialize Browser-use session with AgentCore WebSocket connection"""
try:
client = BrowserClient(AWS_REGION)
client.start(identifier=BROWSER_ID)
ws_url, headers = client.generate_ws_headers()
console.print(f"[cyan]🔗 Browser WebSocket URL: {ws_url[:50]}...[/cyan]")
browser_profile = BrowserProfile(
headers=headers,
timeout=150000,
)
browser_session = BrowserSession(
cdp_url=ws_url,
browser_profile=browser_profile,
keep_alive=True
)
console.print("[cyan]🔄 Initializing browser session...[/cyan]")
await browser_session.start()
bedrock_chat = ChatBedrockConverse(
model_id="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
region_name=AWS_REGION
)
console.print("[green]✅ Browser session initialized and ready[/green]")
return browser_session, bedrock_chat, client
except Exception as e:
console.print(f"[red]❌ Failed to initialize browser session: {e}[/red]")
raise
# Tools for Strands Agent
@tool
async def get_weather_data(city: str) -> Dict[str, Any]:
"""Get weather data for a city using browser automation"""
browser_session = None
try:
console.print(f"[cyan]🌐 Getting weather data for {city}[/cyan]")
browser_session, bedrock_chat, browser_client = await initialize_browser_session()
task = f"""Instruction: Extract 8-Day Weather Forecast for {city} from weather.gov
Steps:
- Go to https://weather.gov.
- Enter "{city}" into the search box and Click on `GO` to execute the search.
- On the local forecast page, click the "Printable Forecast" link.
- Wait for the printable forecast page to load completely.
- For each day in the forecast, extract these fields:
- date (format YYYY-MM-DD)
- high (highest temperature)
- low (lowest temperature)
- conditions (short weather summary, e.g., "Clear")
- wind (wind speed as an integer; use mph or km/h as consistent)
- precip (precipitation chance or amount, zero if none)
- Format the extracted data as a JSON array of daily forecast objects, e.g.:
```json
[
{{
"date": "2025-09-17",
"high": 78,
"low": 62,
"conditions": "Clear",
"wind": 10,
"precip": 80
}},
{{
"date": "2025-09-18",
"high": 82,
"low": 65,
"conditions": "Partly Cloudy",
"wind": 10,
"precip": 80
}}
// ... Repeat for each day ...
]```
- Return only this JSON array as the final output.
Additional Notes:
Use null or 0 if any numeric value is missing.
Avoid scraping ads, navigation, or unrelated page elements.
If "Printable Forecast" is missing, fallback to the main forecast page.
Include error handling (e.g., return an empty array if forecast data isn't found).
Confirm the city name matches the requested location before returning results.
"""
result = await run_browser_task(browser_session, bedrock_chat, task)
if browser_client :
browser_client.stop()
return {
"status": "success",
"content": [{"text": result}]
}
except Exception as e:
console.print(f"[red]❌ Error getting weather data: {e}[/red]")
return {
"status": "error",
"content": [{"text": f"Error getting weather data: {str(e)}"}]
}
finally:
if browser_session:
console.print("[yellow]🔌 Closing browser session...[/yellow]")
with suppress(Exception):
await browser_session.close()
console.print("[green]✅ Browser session closed[/green]")
@tool
def generate_analysis_code(weather_data: str) -> Dict[str, Any]:
"""Generate Python code for weather classification"""
try:
query = f"""Create Python code to classify weather days as GOOD/OK/POOR:
Rules:
- GOOD: 65-80°F, clear conditions, no rain
- OK: 55-85°F, partly cloudy, slight rain chance
- POOR: <55°F or >85°F, cloudy/rainy
Weather data:
{weather_data}
Store weather data stored in python variable for using it in python code
Return code that outputs list of tuples: [('2025-09-16', 'GOOD'), ('2025-09-17', 'OK'), ...]"""
agent = Agent()
result = agent(query)
pattern = r'```(?:json|python)\n(.*?)\n```'
match = re.search(pattern, result.message['content'][0]['text'], re.DOTALL)
python_code = match.group(1).strip() if match else result.message['content'][0]['text']
return {"status": "success", "content": [{"text": python_code}]}
except Exception as e:
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
@tool
def execute_code(python_code: str) -> Dict[str, Any]:
"""Execute Python code using AgentCore Code Interpreter"""
try:
code_client = CodeInterpreter(AWS_REGION)
code_client.start(identifier=CODE_INTERPRETER_ID)
response = code_client.invoke("executeCode", {
"code": python_code,
"language": "python",
"clearContext": True
})
for event in response["stream"]:
code_execute_result = json.dumps(event["result"])
analysis_results = json.loads(code_execute_result)
console.print("Analysis results:", analysis_results)
return {"status": "success", "content": [{"text": str(analysis_results)}]}
except Exception as e:
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
@tool
def get_activity_preferences() -> Dict[str, Any]:
"""Get activity preferences from memory"""
try:
client = MemoryClient(region_name=AWS_REGION)
response = client.list_events(
memory_id=MEMORY_ID,
actor_id="user123",
session_id="session456",
max_results=50,
include_payload=True
)
preferences = response[0]["payload"][0]['blob'] if response else "No preferences found"
return {"status": "success", "content": [{"text": preferences}]}
except Exception as e:
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
def create_weather_agent() -> Agent:
"""Create the weather agent with all tools"""
system_prompt = f"""You are a Weather-Based Activity Planning Assistant.
When a user asks about activities for a location, follow below stepes Sequentially:
1. Extract city from user query
2. Call get_weather_data(city) to get weather information
3. Call generate_analysis_code(weather_data) to create classification code
4. Call execute_code(python_code) to get Day Type ( GOOD, OK , POOR ) for forecasting dates.
5. Call get_activity_preferences() to get user preferences
6. Generate Activity Recommendations based on weather and preferences that you have recieved from previous steps
7. Generate the comprehensive Markdown file (results.md) and store it in S3 Bucket : {RESULTS_BUCKET} through use_aws tool.
IMPORTANT: Provide complete recommendations and end your response. Do NOT ask follow-up questions or wait for additional input."""
return Agent(
tools=[get_weather_data, generate_analysis_code, execute_code, get_activity_preferences, use_aws],
system_prompt=system_prompt,
name="WeatherActivityPlanner"
)
@app.async_task
async def async_main(query=None):
"""Async main function"""
console.print("🌤️ Weather-Based Activity Planner - Async Version")
console.print("=" * 30)
agent = create_weather_agent()
query = query or "What should I do this weekend in Richmond VA?"
console.print(f"\n[bold blue]🔍 Query:[/bold blue] {query}")
console.print("-" * 50)
try:
os.environ["BYPASS_TOOL_CONSENT"] = "True"
result = agent(query)
return {
"status": "completed",
"result": result.message['content'][0]['text']
}
except Exception as e:
console.print(f"[red]❌ Error: {e}[/red]")
import traceback
traceback.print_exc()
return {
"status": "error",
"error": str(e)
}
@app.entrypoint
async def invoke(payload=None):
try:
# change
query = payload.get("prompt")
asyncio.create_task(async_main(query))
msg = (
"Processing started ... "
f"You can monitor status in CloudWatch logs at /aws/bedrock-agentcore/runtimes/<agent-runtime-id> ....."
f"You can see the result at {RESULTS_BUCKET} ...."
)
return {
"status": "Started",
"message": msg
}
except Exception as e:
return {"error": str(e)}
if __name__ == "__main__":
app.run()
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,33 @@
# ============================================================================
# Terraform Backend Configuration - Remote State Storage
# ============================================================================
# This is an example backend configuration for storing Terraform state remotely
# Copy this file to backend.tf and customize with your backend settings
# Example: cp backend.tf.example backend.tf
# IMPORTANT: Uncomment and configure ONE of the backend options below
# Option 1: S3 Backend (Recommended for AWS)
# terraform {
# backend "s3" {
# bucket = "your-terraform-state-bucket"
# key = "agentcore/weather-agent/terraform.tfstate"
# region = "us-west-2"
# encrypt = true
# dynamodb_table = "terraform-state-lock"
# }
# }
# Option 2: Terraform Cloud
# terraform {
# cloud {
# organization = "your-organization"
# workspaces {
# name = "agentcore-weather-agent"
# }
# }
# }
# Option 3: Local Backend (Default - No configuration needed)
# State will be stored locally in terraform.tfstate
# Not recommended for team environments or production use
@@ -0,0 +1,21 @@
# ============================================================================
# Browser Tool - For Web Browsing Capabilities
# ============================================================================
resource "aws_bedrockagentcore_browser" "browser" {
name = "${replace(var.stack_name, "-", "_")}_browser"
description = "Browser tool for ${var.stack_name} weather agent to access weather websites and advisories"
network_configuration {
network_mode = var.network_mode
}
tags = merge(
var.common_tags,
{
Name = "${var.stack_name}-browser-tool"
Module = "AgentCore-Tools"
Tool = "Browser"
}
)
}
@@ -0,0 +1,29 @@
version: 0.2
phases:
install:
commands:
- echo Installing dependencies...
- yum install -y unzip
pre_build:
commands:
- echo Source code already extracted by CodeBuild...
- cd $CODEBUILD_SRC_DIR
- ls -la
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image for Weather Agent ARM64...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Weather Agent Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo Weather Agent ARM64 Docker image pushed successfully
@@ -0,0 +1,21 @@
# ============================================================================
# Code Interpreter Tool - For Python Code Execution and Data Analysis
# ============================================================================
resource "aws_bedrockagentcore_code_interpreter" "code_interpreter" {
name = "${replace(var.stack_name, "-", "_")}_code_interpreter"
description = "Code interpreter tool for ${var.stack_name} weather agent to analyze weather data and create visualizations"
network_configuration {
network_mode = var.network_mode
}
tags = merge(
var.common_tags,
{
Name = "${var.stack_name}-code-interpreter-tool"
Module = "AgentCore-Tools"
Tool = "CodeInterpreter"
}
)
}
@@ -0,0 +1,74 @@
# ============================================================================
# CodeBuild Project - Build and Push Weather Agent Docker Image
# ============================================================================
resource "aws_codebuild_project" "agent_image" {
name = "${var.stack_name}-agent-build"
description = "Build Weather Agent Docker image for ${var.stack_name}"
service_role = aws_iam_role.codebuild.arn
build_timeout = 60
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_LARGE"
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
type = "ARM_CONTAINER"
privileged_mode = true
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "AWS_DEFAULT_REGION"
value = data.aws_region.current.id
}
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.id
}
environment_variable {
name = "IMAGE_REPO_NAME"
value = aws_ecr_repository.weather_ecr.name
}
environment_variable {
name = "IMAGE_TAG"
value = var.image_tag
}
environment_variable {
name = "STACK_NAME"
value = var.stack_name
}
environment_variable {
name = "AGENT_NAME"
value = "weather-agent"
}
}
source {
type = "S3"
location = "${aws_s3_bucket.agent_source.id}/${aws_s3_object.agent_source.key}"
buildspec = file("${path.module}/buildspec.yml")
}
logs_config {
cloudwatch_logs {
group_name = "/aws/codebuild/${var.stack_name}-agent-build"
}
}
tags = {
Name = "${var.stack_name}-agent-build"
Module = "CodeBuild"
Agent = "WeatherAgent"
}
depends_on = [
aws_iam_role_policy.codebuild
]
}
@@ -0,0 +1,259 @@
#!/bin/bash
# ============================================================================
# Deploy Script for End-to-End Weather Agent (Terraform)
# ============================================================================
# This script automates the deployment process for the Weather Agent Terraform configuration
# Usage: ./deploy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_info "Starting End-to-End Weather Agent Deployment..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed. Please install Terraform >= 1.6"
print_info "Visit: https://www.terraform.io/downloads"
exit 1
fi
# Check Terraform version
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
print_success "Terraform version: $TERRAFORM_VERSION"
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
exit 1
fi
print_success "AWS CLI is installed"
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
print_info "Run: aws configure"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_success "AWS Account: $AWS_ACCOUNT"
print_success "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Configuration Check
# ============================================================================
print_info "Checking configuration files..."
# Check if terraform.tfvars exists
if [ ! -f "terraform.tfvars" ]; then
print_warning "terraform.tfvars not found"
print_info "Creating terraform.tfvars from example..."
if [ -f "terraform.tfvars.example" ]; then
cp terraform.tfvars.example terraform.tfvars
print_success "Created terraform.tfvars"
print_warning "Please review and update terraform.tfvars with your settings"
print_info "Then run this script again"
exit 0
else
print_error "terraform.tfvars.example not found"
exit 1
fi
fi
print_success "Configuration file found: terraform.tfvars"
echo ""
# ============================================================================
# Terraform Initialization
# ============================================================================
print_info "Initializing Terraform..."
if terraform init; then
print_success "Terraform initialized successfully"
else
print_error "Terraform initialization failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Validation
# ============================================================================
print_info "Validating Terraform configuration..."
if terraform validate; then
print_success "Terraform configuration is valid"
else
print_error "Terraform validation failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Format Check
# ============================================================================
print_info "Checking Terraform formatting..."
if terraform fmt -check -recursive > /dev/null 2>&1; then
print_success "Terraform files are properly formatted"
else
print_warning "Some files need formatting. Running terraform fmt..."
terraform fmt -recursive
print_success "Files formatted"
fi
echo ""
# ============================================================================
# Terraform Plan
# ============================================================================
print_info "Creating Terraform execution plan..."
print_warning "This may take a few moments..."
echo ""
if terraform plan -out=tfplan; then
print_success "Terraform plan created successfully"
else
print_error "Terraform plan failed"
exit 1
fi
echo ""
# ============================================================================
# Deployment Confirmation
# ============================================================================
print_warning "========================================"
print_warning "DEPLOYMENT CONFIRMATION"
print_warning "========================================"
print_info "This will deploy the following resources:"
print_info " - 2x S3 Buckets (source code + results storage)"
print_info " - 1x ECR Repository (weather agent)"
print_info " - 1x CodeBuild Project (weather agent)"
print_info " - Browser Tool (web scraping capability)"
print_info " - Code Interpreter Tool (Python code execution)"
print_info " - Memory Resource (conversation persistence)"
print_info " - Memory Initializer Lambda (activity preferences)"
print_info " - IAM Roles and Policies (agent + tools + Lambda)"
print_info " - Weather Agent Runtime (full-featured)"
echo ""
print_info "The deployment includes:"
print_info " - Building ARM64 Docker image"
print_info " - Initializing memory with weather activity preferences"
print_info " - Configuring Browser and Code Interpreter tools"
echo ""
read -p "Do you want to proceed with deployment? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Deployment cancelled by user"
rm -f tfplan
exit 0
fi
# ============================================================================
# Terraform Apply
# ============================================================================
print_info "Starting deployment..."
echo ""
if terraform apply tfplan; then
print_success "Deployment completed successfully!"
else
print_error "Deployment failed"
rm -f tfplan
exit 1
fi
# Clean up plan file
rm -f tfplan
echo ""
# ============================================================================
# Deployment Summary
# ============================================================================
print_success "========================================"
print_success "DEPLOYMENT COMPLETED"
print_success "========================================"
echo ""
print_info "Retrieving deployment outputs..."
echo ""
# Get outputs
AGENT_ID=$(terraform output -raw agent_runtime_id 2>/dev/null || echo "N/A")
AGENT_ARN=$(terraform output -raw agent_runtime_arn 2>/dev/null || echo "N/A")
BROWSER_ID=$(terraform output -raw browser_id 2>/dev/null || echo "N/A")
CODE_INTERP_ID=$(terraform output -raw code_interpreter_id 2>/dev/null || echo "N/A")
MEMORY_ID=$(terraform output -raw memory_id 2>/dev/null || echo "N/A")
RESULTS_BUCKET=$(terraform output -raw results_bucket_name 2>/dev/null || echo "N/A")
print_success "Weather Agent Runtime ID: $AGENT_ID"
print_success "Weather Agent Runtime ARN: $AGENT_ARN"
echo ""
print_success "Browser Tool ID: $BROWSER_ID"
print_success "Code Interpreter ID: $CODE_INTERP_ID"
print_success "Memory ID: $MEMORY_ID"
print_success "Results Bucket: $RESULTS_BUCKET"
echo ""
print_info "Next Steps:"
print_info "1. Test the weather agent:"
print_info " python test_weather_agent.py $AGENT_ARN"
echo ""
print_info "2. View all outputs (includes test commands):"
print_info " terraform output"
echo ""
print_info "3. Monitor in AWS Console:"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_success "Deployment completed successfully!"
@@ -0,0 +1,306 @@
#!/bin/bash
# ============================================================================
# Destroy Script for End-to-End Weather Agent (Terraform)
# ============================================================================
# This script safely destroys all resources created by this Terraform configuration
# Usage: ./destroy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_warning "Starting Resource Cleanup..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed"
exit 1
fi
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed"
exit 1
fi
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_info "AWS Account: $AWS_ACCOUNT"
print_info "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Check for Terraform State
# ============================================================================
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
print_warning "No Terraform state found"
print_info "Either no resources have been deployed, or state is stored remotely"
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Initializing Terraform to fetch remote state..."
terraform init
else
print_info "Cleanup cancelled"
exit 0
fi
fi
# ============================================================================
# Show Destruction Plan
# ============================================================================
print_info "Creating destruction plan..."
echo ""
if ! terraform plan -destroy; then
print_error "Failed to create destruction plan"
exit 1
fi
echo ""
# ============================================================================
# Destruction Confirmation
# ============================================================================
print_warning "========================================"
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
print_warning "========================================"
print_warning "This will permanently delete the following resources:"
print_warning " - Weather Agent Runtime"
print_warning " - Browser Tool"
print_warning " - Code Interpreter Tool"
print_warning " - Memory Resource (with all conversation history)"
print_warning " - Memory Initializer Lambda"
print_warning " - 2x S3 Buckets (source code + results storage)"
print_warning " - 1x ECR Repository (including all images)"
print_warning " - 1x CodeBuild Project"
print_warning " - IAM Roles and Policies (agent + tools + Lambda)"
print_warning " - CloudWatch Log Groups"
echo ""
print_warning "THIS ACTION CANNOT BE UNDONE!"
echo ""
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
echo ""
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Destruction cancelled by user"
exit 0
fi
# Double confirmation for safety
print_warning "Second confirmation required..."
read -p "Type 'DESTROY' to confirm: " -r
echo ""
if [ "$REPLY" != "DESTROY" ]; then
print_info "Destruction cancelled - confirmation text did not match"
exit 0
fi
# ============================================================================
# Execute Destruction
# ============================================================================
print_warning "Starting resource destruction..."
echo ""
if terraform destroy -auto-approve; then
print_success "All resources destroyed successfully"
else
print_error "Destruction failed"
print_warning "Some resources may still exist. Please check AWS Console"
exit 1
fi
echo ""
# ============================================================================
# Cleanup Local Files
# ============================================================================
print_info "Cleaning up local Terraform files..."
# Ask about state file cleanup
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -f terraform.tfstate
rm -f terraform.tfstate.backup
rm -f tfplan
print_success "Local state files removed"
fi
# Ask about .terraform directory
read -p "Do you want to remove .terraform directory? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -rf .terraform
rm -f .terraform.lock.hcl
print_success ".terraform directory removed"
fi
echo ""
# ============================================================================
# Verification
# ============================================================================
print_info "Verifying resource cleanup..."
echo ""
# Check for ECR repositories
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-basic")
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$ECR_REPOS" -eq 0 ]; then
print_success "ECR repositories cleaned up"
else
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for AgentCore runtimes
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$RUNTIME_COUNT" -eq 0 ]; then
print_success "Weather Agent runtime cleaned up"
else
print_warning "Found $RUNTIME_COUNT AgentCore runtime(s) matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for Memory resources
MEMORY_COUNT=$(aws bedrock-agentcore list-memories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$MEMORY_COUNT" -eq 0 ]; then
print_success "Memory resources cleaned up"
else
print_warning "Found $MEMORY_COUNT Memory resource(s) matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for Tools (Browser and Code Interpreter)
TOOL_COUNT=$(aws bedrock-agentcore list-browser-customs --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
TOOL_COUNT=$((TOOL_COUNT + $(aws bedrock-agentcore list-code-interpreter-customs --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')))
if [ "$TOOL_COUNT" -eq 0 ]; then
print_success "Tools (Browser + Code Interpreter) cleaned up"
else
print_warning "Found $TOOL_COUNT Tool(s) matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for Lambda functions
LAMBDA_COUNT=$(aws lambda list-functions --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$LAMBDA_COUNT" -eq 0 ]; then
print_success "Lambda functions cleaned up"
else
print_warning "Found $LAMBDA_COUNT Lambda function(s) matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for S3 buckets (source + results)
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$S3_BUCKETS" -eq 0 ]; then
print_success "S3 buckets cleaned up (source + results)"
else
print_warning "Found $S3_BUCKETS S3 bucket(s) matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
echo ""
# ============================================================================
# Completion Summary
# ============================================================================
print_success "========================================"
print_success "CLEANUP COMPLETED"
print_success "========================================"
echo ""
print_info "Cleanup Summary:"
print_success " ✓ Terraform resources destroyed"
print_success " ✓ Local state files cleaned (if selected)"
echo ""
print_info "What to verify in AWS Console:"
print_info "1. Bedrock AgentCore - No runtimes remaining"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_info "2. Memory - No memory resources remaining"
print_info " Check via: aws bedrock-agentcore list-memories --region $AWS_REGION"
echo ""
print_info "3. Tools - No browser/code interpreter tools remaining"
print_info " Check via: aws bedrock-agentcore list-browser-customs --region $AWS_REGION"
echo ""
print_info "4. Lambda - No memory initializer functions remaining"
print_info " https://console.aws.amazon.com/lambda/home?region=$AWS_REGION#/functions"
echo ""
print_info "5. S3 - No buckets remaining (source + results)"
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
echo ""
print_info "6. ECR - No repositories remaining"
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
echo ""
print_info "7. CodeBuild - No projects remaining"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
echo ""
print_info "8. CloudWatch Logs - Check for orphaned log groups"
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
echo ""
print_success "Cleanup completed successfully!"
print_info "You can safely re-deploy by running: ./deploy.sh"
@@ -0,0 +1,65 @@
# ============================================================================
# ECR Repository - Container Registry for Weather Agent Image
# ============================================================================
# Weather Agent ECR Repository
resource "aws_ecr_repository" "weather_ecr" {
name = "${var.stack_name}-${var.ecr_repository_name}"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
force_delete = true
tags = {
Name = "${var.stack_name}-agent-ecr-repository"
Module = "ECR"
Agent = "WeatherAgent"
}
}
# ECR Repository Policy - Weather Agent
resource "aws_ecr_repository_policy" "weather_ecr" {
repository = aws_ecr_repository.weather_ecr.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPullFromAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
})
}
# ECR Lifecycle Policy - Weather Agent - Keep last 5 images
resource "aws_ecr_lifecycle_policy" "weather_ecr" {
repository = aws_ecr_repository.weather_ecr.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 5 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 5
}
action = {
type = "expire"
}
}
]
})
}
@@ -0,0 +1,233 @@
# Data sources
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# ============================================================================
# Weather Agent Execution Role - For AgentCore Runtime
# ============================================================================
resource "aws_iam_role" "agent_execution" {
name = "${var.stack_name}-agent-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AssumeRolePolicy"
Effect = "Allow"
Principal = {
Service = "bedrock-agentcore.amazonaws.com"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.id
}
ArnLike = {
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
}
}
}]
})
tags = {
Name = "${var.stack_name}-agent-execution-role"
Module = "IAM"
Agent = "WeatherAgent"
}
}
# Attach AWS managed policy for AgentCore
resource "aws_iam_role_policy_attachment" "agent_execution_managed" {
role = aws_iam_role.agent_execution.name
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
}
# Inline policy for agent execution
resource "aws_iam_role_policy" "agent_execution" {
name = "AgentCoreExecutionPolicy"
role = aws_iam_role.agent_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# ECR Access
{
Sid = "ECRImageAccess"
Effect = "Allow"
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
Resource = aws_ecr_repository.weather_ecr.arn
},
{
Sid = "ECRTokenAccess"
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
},
# X-Ray Tracing
{
Sid = "XRayTracing"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets"
]
Resource = "*"
},
# CloudWatch Metrics
{
Sid = "CloudWatchMetrics"
Effect = "Allow"
Action = ["cloudwatch:PutMetricData"]
Resource = "*"
Condition = {
StringEquals = {
"cloudwatch:namespace" = "bedrock-agentcore"
}
}
},
# Bedrock Model Invocation
{
Sid = "BedrockModelInvocation"
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
]
Resource = "*"
},
# Workload Access Tokens
{
Sid = "GetAgentAccessToken"
Effect = "Allow"
Action = [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
]
Resource = [
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
]
},
# S3 Access for Results Bucket
{
Sid = "S3ResultsAccess"
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.results.arn,
"${aws_s3_bucket.results.arn}/*"
]
}
]
})
}
# ============================================================================
# CodeBuild Service Role - For Docker Image Building
# ============================================================================
resource "aws_iam_role" "codebuild" {
name = "${var.stack_name}-codebuild-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
tags = {
Name = "${var.stack_name}-codebuild-role"
Module = "IAM"
}
}
# Inline policy for CodeBuild
resource "aws_iam_role_policy" "codebuild" {
name = "CodeBuildPolicy"
role = aws_iam_role.codebuild.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
},
# ECR Access
{
Sid = "ECRAccess"
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
Resource = [
aws_ecr_repository.weather_ecr.arn,
"*"
]
},
# S3 Source Access
{
Sid = "S3SourceAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion"
]
Resource = "${aws_s3_bucket.agent_source.arn}/*"
},
{
Sid = "S3BucketAccess"
Effect = "Allow"
Action = [
"s3:ListBucket",
"s3:GetBucketLocation"
]
Resource = aws_s3_bucket.agent_source.arn
}
]
})
}
@@ -0,0 +1,82 @@
# ============================================================================
# Wait for IAM propagation before triggering build
# ============================================================================
resource "time_sleep" "wait_for_iam" {
depends_on = [
aws_iam_role_policy.codebuild,
aws_iam_role_policy.agent_execution
]
create_duration = "30s"
}
# ============================================================================
# Trigger CodeBuild - Build Docker Image
# ============================================================================
resource "null_resource" "trigger_build" {
triggers = {
build_project = aws_codebuild_project.agent_image.id
image_tag = var.image_tag
ecr_repository = aws_ecr_repository.weather_ecr.id
source_code_md5 = data.archive_file.agent_source.output_md5
}
provisioner "local-exec" {
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.agent_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.weather_ecr.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.weather_ecr.repository_url}\""
}
depends_on = [
aws_codebuild_project.agent_image,
aws_ecr_repository.weather_ecr,
aws_iam_role_policy.codebuild,
aws_s3_object.agent_source,
time_sleep.wait_for_iam
]
}
# ============================================================================
# Weather Agent Runtime
# ============================================================================
resource "aws_bedrockagentcore_agent_runtime" "weather_agent" {
agent_runtime_name = "${replace(var.stack_name, "-", "_")}_${var.agent_name}"
description = "Weather agent runtime for ${var.stack_name}"
role_arn = aws_iam_role.agent_execution.arn
agent_runtime_artifact {
container_configuration {
container_uri = "${aws_ecr_repository.weather_ecr.repository_url}:${var.image_tag}"
}
}
network_configuration {
network_mode = var.network_mode
}
environment_variables = {
AWS_REGION = data.aws_region.current.id
AWS_DEFAULT_REGION = data.aws_region.current.id
RESULTS_BUCKET = aws_s3_bucket.results.id
BROWSER_ID = aws_bedrockagentcore_browser.browser.browser_id
CODE_INTERPRETER_ID = aws_bedrockagentcore_code_interpreter.code_interpreter.code_interpreter_id
MEMORY_ID = aws_bedrockagentcore_memory.memory.id
}
tags = {
Name = "${var.stack_name}-agent-runtime"
Environment = "production"
Module = "BedrockAgentCore"
Agent = "WeatherAgent"
}
depends_on = [
null_resource.trigger_build,
aws_iam_role_policy.agent_execution,
aws_iam_role_policy_attachment.agent_execution_managed,
aws_bedrockagentcore_browser.browser,
aws_bedrockagentcore_code_interpreter.code_interpreter,
aws_bedrockagentcore_memory.memory
]
}
@@ -0,0 +1,38 @@
# ============================================================================
# Memory Initialization - Populate Memory with Activity Preferences
# ============================================================================
# Initialize memory with activity preferences after memory is created
resource "null_resource" "initialize_memory" {
# Trigger re-initialization if memory ID changes
triggers = {
memory_id = aws_bedrockagentcore_memory.memory.id
region = data.aws_region.current.id
}
# Execute Python script to initialize memory
provisioner "local-exec" {
command = "python3 ${path.module}/scripts/init-memory.py"
working_dir = path.module
environment = {
MEMORY_ID = aws_bedrockagentcore_memory.memory.id
AWS_REGION = data.aws_region.current.id
}
}
# Ensure memory exists before initialization
depends_on = [
aws_bedrockagentcore_memory.memory
]
}
# ============================================================================
# Outputs
# ============================================================================
output "memory_initialization_status" {
description = "Status of memory initialization"
value = "Memory initialized with activity preferences"
depends_on = [null_resource.initialize_memory]
}
@@ -0,0 +1,18 @@
# ============================================================================
# Memory - For Persistent Conversation Context
# ============================================================================
resource "aws_bedrockagentcore_memory" "memory" {
name = "${replace(var.stack_name, "-", "_")}_${var.memory_name}"
description = "Memory for ${var.stack_name} weather agent to maintain conversation context"
event_expiry_duration = 30 # Days
tags = merge(
var.common_tags,
{
Name = "${var.stack_name}-memory"
Module = "AgentCore-Tools"
Tool = "Memory"
}
)
}
@@ -0,0 +1,130 @@
# ============================================================================
# Observability Module - CloudWatch Logs and X-Ray Traces Delivery
# ============================================================================
# ============================================================================
# Application Logs Setup
# ============================================================================
# CloudWatch Log Group for vended log delivery
resource "aws_cloudwatch_log_group" "agent_runtime_logs" {
name = "/aws/vendedlogs/bedrock-agentcore/${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}"
retention_in_days = 14
tags = {
Name = "${var.stack_name}-agent-logs"
Purpose = "Agent runtime application logs"
Module = "Observability"
}
depends_on = [aws_bedrockagentcore_agent_runtime.weather_agent]
}
# Delivery Source for Application Logs
resource "aws_cloudwatch_log_delivery_source" "logs" {
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-logs-source"
log_type = "APPLICATION_LOGS"
resource_arn = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn
depends_on = [aws_bedrockagentcore_agent_runtime.weather_agent]
}
# Delivery Destination for Logs (CloudWatch Logs)
resource "aws_cloudwatch_log_delivery_destination" "logs" {
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-logs-destination"
delivery_destination_configuration {
destination_resource_arn = aws_cloudwatch_log_group.agent_runtime_logs.arn
}
tags = {
Name = "${var.stack_name}-logs-destination"
Purpose = "CloudWatch Logs delivery destination"
Module = "Observability"
}
depends_on = [aws_cloudwatch_log_group.agent_runtime_logs]
}
# Delivery Connection for Logs
resource "aws_cloudwatch_log_delivery" "logs" {
delivery_source_name = aws_cloudwatch_log_delivery_source.logs.name
delivery_destination_arn = aws_cloudwatch_log_delivery_destination.logs.arn
tags = {
Name = "${var.stack_name}-logs-delivery"
Purpose = "Connect logs source to CloudWatch destination"
Module = "Observability"
}
depends_on = [
aws_cloudwatch_log_delivery_source.logs,
aws_cloudwatch_log_delivery_destination.logs
]
}
# ============================================================================
# X-Ray Traces Setup
# ============================================================================
# Delivery Source for Traces
resource "aws_cloudwatch_log_delivery_source" "traces" {
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-traces-source"
log_type = "TRACES"
resource_arn = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn
depends_on = [aws_bedrockagentcore_agent_runtime.weather_agent]
}
# Delivery Destination for Traces (X-Ray)
resource "aws_cloudwatch_log_delivery_destination" "traces" {
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-traces-destination"
delivery_destination_type = "XRAY"
tags = {
Name = "${var.stack_name}-traces-destination"
Purpose = "X-Ray traces delivery destination"
Module = "Observability"
}
}
# Delivery Connection for Traces
resource "aws_cloudwatch_log_delivery" "traces" {
delivery_source_name = aws_cloudwatch_log_delivery_source.traces.name
delivery_destination_arn = aws_cloudwatch_log_delivery_destination.traces.arn
tags = {
Name = "${var.stack_name}-traces-delivery"
Purpose = "Connect traces source to X-Ray destination"
Module = "Observability"
}
depends_on = [
aws_cloudwatch_log_delivery_source.traces,
aws_cloudwatch_log_delivery_destination.traces
]
}
# ============================================================================
# Outputs
# ============================================================================
output "log_group_name" {
description = "CloudWatch Log Group name for agent runtime vended logs"
value = aws_cloudwatch_log_group.agent_runtime_logs.name
}
output "log_group_arn" {
description = "ARN of the CloudWatch Log Group"
value = aws_cloudwatch_log_group.agent_runtime_logs.arn
}
output "logs_delivery_id" {
description = "ID of the logs delivery connection"
value = aws_cloudwatch_log_delivery.logs.id
}
output "traces_delivery_id" {
description = "ID of the traces delivery connection"
value = aws_cloudwatch_log_delivery.traces.id
}
@@ -0,0 +1,101 @@
# ============================================================================
# Weather Agent Runtime Outputs
# ============================================================================
output "agent_runtime_id" {
description = "ID of the weather agent runtime"
value = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id
}
output "agent_runtime_arn" {
description = "ARN of the weather agent runtime"
value = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn
}
output "agent_runtime_version" {
description = "Version of the weather agent runtime"
value = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_version
}
output "agent_ecr_repository_url" {
description = "URL of the ECR repository for weather agent"
value = aws_ecr_repository.weather_ecr.repository_url
}
output "agent_execution_role_arn" {
description = "ARN of the weather agent execution role"
value = aws_iam_role.agent_execution.arn
}
# ============================================================================
# Build & Storage Outputs
# ============================================================================
output "codebuild_project_name" {
description = "Name of the CodeBuild project for weather agent"
value = aws_codebuild_project.agent_image.name
}
output "source_bucket_name" {
description = "S3 bucket containing weather agent source code"
value = aws_s3_bucket.agent_source.id
}
output "results_bucket_name" {
description = "Name of the S3 bucket for agent results"
value = aws_s3_bucket.results.id
}
output "source_code_md5" {
description = "MD5 hash of agent source code (triggers rebuild when changed)"
value = data.archive_file.agent_source.output_md5
}
# ============================================================================
# Testing Information
# ============================================================================
output "test_agent_command" {
description = "AWS CLI command to test weather agent"
value = "aws bedrock-agentcore invoke-agent-runtime --agent-runtime-arn ${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn} --qualifier DEFAULT --payload '{\"prompt\": \"What's the weather like today and suggest activities?\"}' --region ${data.aws_region.current.id} response.json"
}
output "test_script_command" {
description = "Command to run the comprehensive test script"
value = "python test_weather_agent.py ${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn}"
}
# ============================================================================
# Tool Resource Outputs
# ============================================================================
output "browser_id" {
description = "ID of the browser tool"
value = aws_bedrockagentcore_browser.browser.browser_id
}
output "browser_arn" {
description = "ARN of the browser tool"
value = aws_bedrockagentcore_browser.browser.browser_arn
}
output "code_interpreter_id" {
description = "ID of the code interpreter tool"
value = aws_bedrockagentcore_code_interpreter.code_interpreter.code_interpreter_id
}
output "code_interpreter_arn" {
description = "ARN of the code interpreter tool"
value = aws_bedrockagentcore_code_interpreter.code_interpreter.code_interpreter_arn
}
output "memory_id" {
description = "ID of the memory resource"
value = aws_bedrockagentcore_memory.memory.id
}
output "memory_arn" {
description = "ARN of the memory resource"
value = aws_bedrockagentcore_memory.memory.arn
}
@@ -0,0 +1,88 @@
# ============================================================================
# S3 Buckets for Weather Agent
# ============================================================================
# Agent Source Code Bucket
resource "aws_s3_bucket" "agent_source" {
bucket_prefix = "${var.stack_name}-agent-source-"
force_destroy = true
tags = {
Name = "${var.stack_name}-agent-source"
Purpose = "Store Weather Agent source code for CodeBuild"
}
}
# Results Bucket (for agent-generated artifacts)
resource "aws_s3_bucket" "results" {
bucket_prefix = "${var.stack_name}-results-"
force_destroy = true
tags = {
Name = "${var.stack_name}-results"
Purpose = "Store Weather Agent generated artifacts"
}
}
# Block public access - Agent Source
resource "aws_s3_bucket_public_access_block" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Block public access - Results Bucket
resource "aws_s3_bucket_public_access_block" "results" {
bucket = aws_s3_bucket.results.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning - Agent Source
resource "aws_s3_bucket_versioning" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
versioning_configuration {
status = "Enabled"
}
}
# Enable versioning - Results Bucket
resource "aws_s3_bucket_versioning" "results" {
bucket = aws_s3_bucket.results.id
versioning_configuration {
status = "Enabled"
}
}
# ============================================================================
# Archive and Upload Agent Source Code
# ============================================================================
# Archive agent-code/ directory
data "archive_file" "agent_source" {
type = "zip"
source_dir = "${path.module}/agent-code"
output_path = "${path.module}/.terraform/agent-code.zip"
}
# Upload Agent source to S3
resource "aws_s3_object" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
key = "agent-code-${data.archive_file.agent_source.output_md5}.zip"
source = data.archive_file.agent_source.output_path
etag = data.archive_file.agent_source.output_md5
tags = {
Name = "agent-source-code"
Agent = "WeatherAgent"
MD5 = data.archive_file.agent_source.output_md5
}
}
@@ -0,0 +1,196 @@
#!/bin/bash
# ============================================================================
# Build and Verify Docker Image for AgentCore Runtime
# ============================================================================
# This script is called by Terraform during deployment to:
# 1. Trigger CodeBuild to build the Docker image
# 2. Wait for the build to complete
# 3. Verify the image was successfully pushed to ECR
#
# Parameters:
# $1 - CodeBuild project name
# $2 - AWS region
# $3 - ECR repository name
# $4 - Image tag
# $5 - ECR repository URL
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Parameters
PROJECT_NAME="$1"
REGION="$2"
REPO_NAME="$3"
IMAGE_TAG="$4"
REPO_URL="$5"
# ============================================================================
# Print functions
# ============================================================================
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[✓]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[⚠]${NC} $1"
}
print_error() {
echo -e "${RED}[✗]${NC} $1"
}
print_header() {
echo ""
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================${NC}"
}
# ============================================================================
# Start Build Process
# ============================================================================
print_header "Building Docker Image for AgentCore Runtime"
print_info "CodeBuild Project: $PROJECT_NAME"
print_info "Region: $REGION"
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
echo ""
# Start CodeBuild
print_info "Starting CodeBuild project..."
BUILD_ID=$(aws codebuild start-build \
--project-name "$PROJECT_NAME" \
--region "$REGION" \
--query 'build.id' \
--output text 2>&1)
if [ $? -ne 0 ]; then
print_error "Failed to start CodeBuild"
echo "$BUILD_ID"
exit 1
fi
print_success "Build started: $BUILD_ID"
print_info "Waiting for build to complete (typically 5-10 minutes)..."
echo ""
# ============================================================================
# Monitor Build Progress
# ============================================================================
ATTEMPT=0
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT + 1))
STATUS=$(aws codebuild batch-get-builds \
--ids "$BUILD_ID" \
--region "$REGION" \
--query 'builds[0].buildStatus' \
--output text 2>/dev/null)
if [ "$STATUS" != "IN_PROGRESS" ]; then
print_info "Build process completed with status: $STATUS"
break
fi
# Progress indicator
if [ $((ATTEMPT % 6)) -eq 0 ]; then
MINUTES=$((ATTEMPT / 6))
print_info "Build in progress... (${MINUTES} minutes elapsed)"
fi
sleep 10
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
print_error "Build timeout after 10 minutes"
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
exit 1
fi
echo ""
# ============================================================================
# Verify Image in ECR
# ============================================================================
print_header "Verifying Docker Image in ECR"
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
print_info "Waiting for ECR propagation..."
echo ""
sleep 5 # Brief wait for ECR to register the push
VERIFY_ATTEMPT=0
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
if aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" >/dev/null 2>&1; then
print_success "Docker image successfully verified in ECR!"
echo ""
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
# Get image details
IMAGE_SIZE=$(aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" \
--query 'imageDetails[0].imageSizeInBytes' \
--output text 2>/dev/null || echo "Unknown")
if [ "$IMAGE_SIZE" != "Unknown" ]; then
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
fi
echo ""
print_success "Build and verification completed successfully!"
exit 0
fi
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
fi
sleep 5
done
# ============================================================================
# Error: Image Not Found
# ============================================================================
print_error "Docker image not found in ECR after build completion"
echo ""
print_warning "This indicates the build or push step failed."
print_info "Troubleshooting steps:"
print_info " 1. Check CodeBuild logs:"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
print_info ""
print_info " 2. Verify ECR repository:"
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
print_info ""
print_info " 3. Check IAM permissions for CodeBuild role"
exit 1
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
Memory Initialization Script for Weather Agent
This script initializes the AgentCore Memory with activity preferences
that the weather agent uses for recommendations.
Usage:
python init-memory.py
Environment Variables:
MEMORY_ID (required): ID of the AgentCore Memory to initialize
AWS_REGION (required): AWS region where the memory exists
"""
import boto3
import json
import os
import sys
from datetime import datetime
def main():
"""Initialize memory with activity preferences."""
# Get required environment variables
memory_id = os.environ.get('MEMORY_ID')
region = os.environ.get('AWS_REGION')
if not memory_id:
print("❌ ERROR: MEMORY_ID environment variable is required")
sys.exit(1)
if not region:
print("❌ ERROR: AWS_REGION environment variable is required")
sys.exit(1)
print(f"🎯 Initializing memory: {memory_id}")
print(f"📍 Region: {region}")
# Activity preferences data structure
activity_preferences = {
"good_weather": [
"hiking",
"beach volleyball",
"outdoor picnic",
"farmers market",
"gardening",
"photography",
"bird watching"
],
"ok_weather": [
"walking tours",
"outdoor dining",
"park visits",
"museums"
],
"poor_weather": [
"indoor museums",
"shopping",
"restaurants",
"movies"
]
}
# Convert to JSON string for storage
activity_preferences_json = json.dumps(activity_preferences)
try:
# Initialize bedrock-agentcore client
client = boto3.client('bedrock-agentcore', region_name=region)
# Create timestamp
timestamp = datetime.utcnow().isoformat() + 'Z'
print("📝 Creating memory event with activity preferences...")
# Create memory event
response = client.create_event(
memoryId=memory_id,
actorId="user123",
sessionId="session456",
eventTimestamp=timestamp,
payload=[
{
'blob': activity_preferences_json
}
]
)
print("✅ Memory initialized successfully!")
print(f"📊 Event ID: {response.get('eventId', 'N/A')}")
print(f"📦 Preferences stored: {len(activity_preferences)} categories")
print(f" - Good weather: {len(activity_preferences['good_weather'])} activities")
print(f" - OK weather: {len(activity_preferences['ok_weather'])} activities")
print(f" - Poor weather: {len(activity_preferences['poor_weather'])} activities")
return 0
except Exception as e:
print(f"❌ ERROR: Failed to initialize memory: {str(e)}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,36 @@
# ============================================================================
# End-to-End Weather Agent - Example Configuration
# ============================================================================
# Copy this file to terraform.tfvars and customize
# Example: cp terraform.tfvars.example terraform.tfvars
# Agent Configuration
agent_name = "WeatherAgent"
memory_name = "WeatherAgentMemory"
stack_name = "agentcore-weather"
# Network Configuration
network_mode = "PUBLIC" # PUBLIC or PRIVATE
# Container Configuration
ecr_repository_name = "weather-agent"
image_tag = "latest"
# AWS Configuration
aws_region = "us-west-2"
environment = "dev"
# Optional: Environment Variables (if needed for custom configurations)
# environment_variables = {
# LOG_LEVEL = "INFO"
# }
# Notes:
# - Weather Agent: Full-featured agent with Browser, Code Interpreter, and Memory
# - Browser Tool: Web scraping and data collection capability
# - Code Interpreter: Python code execution for data analysis
# - Memory: Persistent conversation memory with weather activity preferences
# - S3 Results Bucket: Storage for agent-generated artifacts (charts, data files)
# - Agent code in agent-code/ directory
# - Memory initialized with activity preferences (good/ok/poor weather)
# - Test with: python test_weather_agent.py
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
Weather Agent Test Script
This script tests the Weather Agent with various prompts including
weather queries, code interpreter usage, and browser tool interactions.
Usage:
python test_weather_agent.py <agent_arn>
agent_arn: ARN of the weather agent runtime (required)
Examples:
# Test weather agent
python test_weather_agent.py arn:aws:bedrock-agentcore:<region>:123456789012:runtime/weather-agent-id
# From Terraform outputs
python test_weather_agent.py $(terraform output -raw agent_runtime_arn)
"""
import boto3
import json
import sys
def extract_region_from_arn(arn):
"""Extract AWS region from agent runtime ARN.
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
Args:
arn: Agent runtime ARN string
Returns:
str: AWS region code
Raises:
ValueError: If ARN format is invalid or region cannot be extracted
"""
try:
parts = arn.split(':')
if len(parts) < 4:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
region = parts[3]
if not region:
raise ValueError(
f"Region not found in ARN: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
return region
except IndexError:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
def test_agent(client, agent_arn, test_name, prompt):
"""Test the agent with a given prompt
Args:
client: boto3 bedrock-agentcore client
agent_arn: ARN of the agent runtime
test_name: Name of the test for display
prompt: Test prompt to send
"""
print(f"\nTest: {test_name}")
print(f"Prompt: '{prompt}'")
print("-" * 80)
try:
response = client.invoke_agent_runtime(
agentRuntimeArn=agent_arn,
qualifier="DEFAULT",
payload=json.dumps({"prompt": prompt})
)
print(f"Status: {response['ResponseMetadata']['HTTPStatusCode']}")
print(f"Content Type: {response.get('contentType', 'N/A')}")
# Read the streaming response body
response_text = ""
if 'response' in response:
response_body = response['response'].read()
response_text = response_body.decode('utf-8')
if response_text:
try:
result = json.loads(response_text)
response_content = result.get('response', response_text)
# Truncate long responses for readability
if len(response_content) > 500:
print(f"\n✅ Response:\n{response_content[:500]}...")
print("\n[Response truncated for display]")
else:
print(f"\n✅ Response:\n{response_content}")
except json.JSONDecodeError:
if len(response_text) > 500:
print(f"\n✅ Response:\n{response_text[:500]}...")
else:
print(f"\n✅ Response:\n{response_text}")
else:
print("\n⚠️ No response content received")
return True
except Exception as e:
print(f"\n❌ Error: {e}")
return False
def test_weather_agent(agent_arn):
"""Test the weather agent with various scenarios
Args:
agent_arn: Weather agent runtime ARN
"""
# Extract region from ARN
try:
region = extract_region_from_arn(agent_arn)
except ValueError as e:
print(f"\n❌ ERROR: {e}\n")
sys.exit(1)
print("\n" + "="*80)
print("WEATHER AGENT TEST SUITE")
print("="*80)
print(f"\nAgent ARN: {agent_arn}")
print(f"Region: {region}")
# Create bedrock-agentcore client with extracted region
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
test_results = []
# Test 1: Simple weather query
print("\n" + "="*80)
print("TEST 1: Simple Weather Query")
print("="*80)
result = test_agent(
agentcore_client,
agent_arn,
"Basic Weather",
"What's the weather like in San Francisco today?"
)
test_results.append(("Simple Weather Query", result))
# Test 2: Complex query with tools (browser + code interpreter + memory)
print("\n" + "="*80)
print("TEST 2: Complex Query with Tools")
print("="*80)
result = test_agent(
agentcore_client,
agent_arn,
"Weather Analysis with Tools",
"Look up current weather conditions for Seattle, create a visualization of the temperature trend, and suggest outdoor activities based on the forecast."
)
test_results.append(("Complex Query with Tools", result))
# Summary
print("\n" + "="*80)
print("TEST SUMMARY")
print("="*80)
all_passed = True
for test_name, passed in test_results:
status = "✅ PASSED" if passed else "❌ FAILED"
print(f"{status} - {test_name}")
if not passed:
all_passed = False
print("\n" + "="*80)
if all_passed:
print("✅ ALL TESTS PASSED")
else:
print("⚠️ SOME TESTS FAILED")
print("="*80 + "\n")
return all_passed
def main():
"""Main entry point"""
if len(sys.argv) < 2:
print(__doc__)
print("\n❌ ERROR: Agent runtime ARN is required")
print("\nTo get your agent ARN:")
print(" - Terraform: terraform output agent_runtime_arn")
print(" - CloudFormation: aws cloudformation describe-stacks --stack-name <stack> --query 'Stacks[0].Outputs'")
print(" - CDK: cdk deploy --outputs-file outputs.json")
print(" - Console: Check Bedrock Agent Core console")
sys.exit(1)
agent_arn = sys.argv[1]
# Validate ARN format
if not agent_arn.startswith("arn:aws:bedrock-agentcore:"):
print(f"\n❌ ERROR: Invalid ARN format: {agent_arn}")
print("Expected format: arn:aws:bedrock-agentcore:region:account:runtime/runtime-id")
sys.exit(1)
# Run tests
success = test_weather_agent(agent_arn)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,79 @@
variable "agent_name" {
description = "Name for the weather agent runtime"
type = string
default = "WeatherAgent"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.agent_name))
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
}
}
variable "memory_name" {
description = "Name for the agent memory resource"
type = string
default = "WeatherAgentMemory"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.memory_name))
error_message = "Memory name must start with a letter, max 48 characters, alphanumeric and underscores only."
}
}
variable "network_mode" {
description = "Network mode for AgentCore resources"
type = string
default = "PUBLIC"
validation {
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
error_message = "Network mode must be either PUBLIC or PRIVATE."
}
}
variable "image_tag" {
description = "Docker image tag"
type = string
default = "latest"
}
variable "aws_region" {
description = "AWS region for deployment (REQUIRED)"
type = string
# No default - must be explicitly provided
validation {
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
}
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
default = "dev"
}
variable "common_tags" {
description = "Common tags to apply to all resources"
type = map(string)
default = {}
}
variable "stack_name" {
description = "Stack name for resource naming"
type = string
default = "agentcore-weather"
}
variable "environment_variables" {
description = "Environment variables for the agent runtime"
type = map(string)
default = {}
}
variable "ecr_repository_name" {
description = "Name of the ECR repository"
type = string
default = "weather-agent"
}
@@ -0,0 +1,32 @@
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.21"
}
null = {
source = "hashicorp/null"
version = "~> 3.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.9"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "AgentCore"
Pattern = "end-to-end-weather-agent"
Environment = var.environment
StackName = var.stack_name
ManagedBy = "Terraform"
}
}
}
@@ -0,0 +1,25 @@
# Terraform files
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
!terraform.tfvars.example
tfplan
# Generated archives
*.zip
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
@@ -0,0 +1,580 @@
# MCP Server on AgentCore Runtime - Terraform
This pattern demonstrates deploying an MCP (Model Context Protocol) server on Amazon Bedrock AgentCore Runtime using Terraform. It creates an MCP server with JWT authentication and three custom tools.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Testing the MCP Server](#testing-the-mcp-server)
- [Sample Tool Invocations](#sample-tool-invocations)
- [Customization](#customization)
- [File Structure](#file-structure)
- [Troubleshooting](#troubleshooting)
- [Cleanup](#cleanup)
- [Pricing](#pricing)
- [Next Steps](#next-steps)
- [Resources](#resources)
- [🤝 Contributing](#-contributing)
- [📄 License](#-license)
## Overview
This Terraform configuration creates an MCP server deployment that includes:
- **MCP Server**: Hosts three custom tools (add_numbers, multiply_numbers, greet_user)
- **JWT Authentication**: Cognito User Pool for secure access
- **AgentCore Runtime**: Serverless hosting with MCP protocol support
- **ECR Repository**: Stores the Docker container image
- **CodeBuild Project**: Automatically builds the ARM64 Docker image
The stack uses the Amazon Bedrock AgentCore Python SDK to wrap agent functions as an MCP server compatible with Amazon Bedrock AgentCore. When hosting tools, the SDK implements the [Stateless Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) transport protocol with the `MCP-Session-Id` header for session isolation.
This makes it ideal for:
- Learning MCP protocol with AgentCore Runtime
- Building secure MCP servers with JWT authentication
- Understanding MCP tool development patterns
- Creating custom tools for AI agents
### Tutorial Details
| Information | Details |
|:--------------------|:----------------------------------------------------------|
| Tutorial type | Hosting Tools |
| Tool type | MCP server |
| Tutorial components | Terraform, AgentCore Runtime, MCP server, Cognito |
| Tutorial vertical | Cross-vertical |
| Example complexity | Intermediate |
| SDK used | Amazon BedrockAgentCore Python SDK and MCP Client |
## Architecture
![Architecture Diagram](architecture.png)
The architecture consists of:
- **User/MCP Client**: Sends requests to the MCP server with JWT authentication
- **Amazon Cognito**: Provides JWT-based authentication
- User Pool with pre-created test user (testuser/MyPassword123!)
- User Pool Client for application access
- **AWS CodeBuild**: Builds the ARM64 Docker container image with the MCP server
- **Amazon ECR Repository**: Stores the container image
- **AgentCore Runtime**: Hosts the MCP Server
- **MCP Server**: Exposes three tools via HTTP transport on port 8000
- `add_numbers`: Adds two numbers
- `multiply_numbers`: Multiplies two numbers
- `greet_user`: Greets a user by name
- Validates JWT tokens from Cognito
- Processes MCP tool invocations
- **IAM Roles**:
- IAM role for CodeBuild (builds and pushes images)
- IAM role for AgentCore Runtime (runtime permissions)
## What's Included
This Terraform configuration creates:
- **S3 Bucket**: Stores MCP server source code for version-controlled builds
- **ECR Repository**: Container registry for the MCP server Docker image
- **CodeBuild Project**: Automated Docker image building and pushing
- **Cognito User Pool**: JWT authentication with pre-configured test user
- **Cognito User Pool Client**: Application client for authentication
- **IAM Roles**: Execution roles for AgentCore, CodeBuild, and Cognito operations
- **AgentCore Runtime**: Serverless MCP server runtime with JWT validation
### MCP Server Code Management
The `mcp-server-code/` directory contains your MCP server's source files:
- `mcp_server.py` - MCP server implementation with three tools
- `Dockerfile` - Container configuration
- `requirements.txt` - Python dependencies (mcp>=1.10.0, boto3, bedrock-agentcore)
**Automatic Change Detection**:
- Terraform archives the `mcp-server-code/` directory
- Uploads to S3 with MD5-based versioning
- CodeBuild pulls from S3 and builds the Docker image
- Any changes to files trigger automatic rebuild (new files, modifications, deletions)
## Prerequisites
### Required Tools
1. **Terraform** (>= 1.6)
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
2. **AWS CLI** (configured with credentials)
```bash
aws configure
```
3. **Python 3.11+** (for testing scripts)
```bash
python --version # Verify Python 3.11 or later
pip install boto3 mcp
```
4. **Docker** (for local testing, optional)
### AWS Account Requirements
- AWS Account with appropriate permissions
- Access to Amazon Bedrock AgentCore service
- Permissions to create:
- ECR repositories
- CodeBuild projects
- Cognito User Pools
- IAM roles and policies
- AgentCore Runtime resources
## Quick Start
### 1. Configure Variables
Copy the example variables file and customize:
```bash
cp terraform.tfvars.example terraform.tfvars
```
Edit `terraform.tfvars` with your preferred values.
### 2. Initialize Terraform
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
**Quick start with local state:**
```bash
terraform init
```
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
### 3. Review the Plan
```bash
terraform plan
```
### 4. Deploy
**Method 1: Using Deploy Script (Recommended)**
Make the script executable (first-time only):
```bash
chmod +x deploy.sh
```
Then deploy:
```bash
./deploy.sh
```
The deploy script:
- Validates Terraform configuration
- Shows deployment plan
- Prompts for confirmation
- Applies changes
**Method 2: Direct Terraform Commands**
```bash
terraform apply
```
When prompted, type `yes` to confirm the deployment.
**Note**: The deployment process includes:
1. Creating ECR repository
2. Building Docker image via CodeBuild
3. Creating Cognito User Pool and test user
4. Creating AgentCore Runtime with MCP protocol
Total deployment time: **~5-10 minutes**
### 5. Get Outputs
After deployment completes:
```bash
terraform output
```
Example output:
```
agent_runtime_id = "AGENT1234567890"
agent_runtime_arn = "arn:aws:bedrock-agentcore:us-west-2:123456789012:agent-runtime/AGENT1234567890"
cognito_user_pool_id = "us-west-2_AbCdEfGhI"
cognito_user_pool_client_id = "1234567890abcdefghijklmno"
cognito_discovery_url = "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_AbCdEfGhI/.well-known/openid-configuration"
test_username = "testuser"
get_token_command = "python get_token.py 1234567890abcdefghijklmno testuser MyPassword123! us-west-2"
```
## Authentication Model
This pattern uses **Cognito JWT-based authentication**:
- **JWT Tokens**: Cognito User Pool issues JWT tokens for authentication
- **Custom JWT Authorizer**: Runtime validates JWT tokens against Cognito discovery URL
- **Test User**: Pre-configured user (testuser/MyPassword123!) for testing
- **Token Expiry**: JWT tokens expire after 1 hour
- **Discovery URL**: OpenID Connect discovery endpoint for token validation
**Authentication Flow:**
1. User authenticates with Cognito User Pool
2. Cognito issues JWT access token
3. Client includes JWT token in MCP request headers
4. Runtime validates token using Cognito's OIDC discovery endpoint
5. Authorized requests processed by MCP server
**Note**: This is a backend authentication pattern for MCP tool access. For user-facing applications, integrate with your identity provider or use Cognito hosted UI for end-user authentication.
## Testing the MCP Server
### Prerequisites for Testing
Before testing, ensure you have the required packages installed:
**Option A: Using uv (Recommended)**
```bash
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install boto3 mcp # Both required for MCP server testing
```
**Option B: System-wide installation**
```bash
pip install boto3 mcp # Both required for MCP server testing
```
**Note**: Both `boto3` (for AWS API calls) and `mcp` (for MCP protocol) are required for testing the MCP server.
### Step 1: Get Authentication Token
First, get a JWT token from Cognito:
```bash
# Use the command from terraform outputs
terraform output -raw get_token_command | bash
```
Or manually:
```bash
# Get the Client ID
CLIENT_ID=$(terraform output -raw cognito_user_pool_client_id)
REGION=$(terraform output -raw aws_region)
# Get authentication token
python get_token.py $CLIENT_ID testuser MyPassword123! $REGION
```
This will output a JWT token. Copy the token for the next step.
### Step 2: Test the MCP Server
```bash
# Get the Runtime ARN
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn)
REGION=$(terraform output -raw aws_region)
# Test the MCP server (replace YOUR_JWT_TOKEN with the token from step 1)
python test_mcp_server.py $RUNTIME_ARN YOUR_JWT_TOKEN $REGION
```
### Expected Output
```
🔄 Initializing MCP session...
✓ MCP session initialized
🔄 Listing available tools...
📋 Available MCP Tools:
==================================================
🔧 add_numbers: Add two numbers together
🔧 multiply_numbers: Multiply two numbers together
🔧 greet_user: Greet a user by name
🧪 Testing MCP Tools:
==================================================
Testing add_numbers(5, 3)...
Result: 8
✖️ Testing multiply_numbers(4, 7)...
Result: 28
👋 Testing greet_user('Alice')...
Result: Hello, Alice! Nice to meet you.
✅ MCP tool testing completed!
```
## Sample Tool Invocations
Try these MCP tool calls:
1. **Add Numbers**:
```python
# Tool: add_numbers
# Parameters: {"a": 10, "b": 25}
# Expected Result: 35
```
2. **Multiply Numbers**:
```python
# Tool: multiply_numbers
# Parameters: {"a": 6, "b": 7}
# Expected Result: 42
```
3. **Greet User**:
```python
# Tool: greet_user
# Parameters: {"name": "John"}
# Expected Result: "Hello, John! Nice to meet you."
```
## Customization
### Modify MCP Server Code
Edit files in `mcp-server-code/` and deploy:
**Adding New Tools**:
Edit `mcp-server-code/mcp_server.py`:
```python
@mcp.tool()
def subtract_numbers(a: int, b: int) -> int:
"""Subtract two numbers"""
return a - b
```
**Update Dependencies**:
Edit `mcp-server-code/requirements.txt`:
```
mcp>=1.10.0
boto3
bedrock-agentcore
your-new-package>=1.0.0
```
Changes are automatically detected and trigger rebuild. Run `terraform apply` to deploy.
### Modify Authentication
To change Cognito password policy, edit `cognito.tf`:
```hcl
password_policy {
minimum_length = 12
require_uppercase = true
require_lowercase = true
require_numbers = true
require_symbols = true
}
```
### Environment Variables
Add to `terraform.tfvars`:
```hcl
environment_variables = {
LOG_LEVEL = "DEBUG"
CUSTOM_VAR = "value"
}
```
### Network Mode
Set `network_mode = "PRIVATE"` for VPC deployment (requires additional VPC configuration).
## File Structure
```
mcp-server-agentcore-runtime/
├── main.tf # AgentCore runtime with MCP protocol
├── variables.tf # Input variables
├── outputs.tf # Output values (includes Cognito)
├── versions.tf # Provider configuration
├── iam.tf # IAM roles and policies
├── s3.tf # S3 bucket for MCP server source
├── ecr.tf # ECR repository
├── codebuild.tf # Docker build automation
├── cognito.tf # Cognito User Pool & Client
├── buildspec.yml # CodeBuild build specification
├── terraform.tfvars.example # Example configuration
├── backend.tf.example # Remote state example
├── mcp-server-code/ # MCP server source code
│ ├── mcp_server.py # MCP server with 3 tools
│ ├── Dockerfile # Container configuration
│ └── requirements.txt # Python dependencies
├── scripts/ # Build automation scripts
│ └── build-image.sh # CodeBuild trigger & verification
├── get_token.py # Cognito JWT token retrieval
├── test_mcp_server.py # MCP server testing script
├── deploy.sh # Deployment helper script
├── destroy.sh # Cleanup helper script
├── architecture.png # Architecture diagram
├── .gitignore # Git ignore patterns
└── README.md # This file
```
## Troubleshooting
### CodeBuild Fails
If the Docker build fails:
1. Check CodeBuild logs:
```bash
PROJECT_NAME=$(terraform output -raw codebuild_project_name)
aws codebuild batch-get-builds \
--ids $PROJECT_NAME
```
2. Common issues:
- Network connectivity issues
- ECR authentication problems
- Python dependency conflicts in requirements.txt
### Runtime Creation Fails
If the runtime creation fails:
1. Verify the Docker image exists:
```bash
REPO_NAME=$(terraform output -raw ecr_repository_url | cut -d'/' -f2)
aws ecr describe-images --repository-name $REPO_NAME
```
2. Check IAM role permissions
3. Verify Bedrock AgentCore service quotas
4. Ensure MCP protocol is properly configured
### Authentication Issues
If JWT authentication fails:
1. Verify Cognito user exists:
```bash
USER_POOL_ID=$(terraform output -raw cognito_user_pool_id)
aws cognito-idp admin-get-user \
--user-pool-id $USER_POOL_ID \
--username testuser
```
2. Check token expiration (tokens expire after 1 hour)
3. Verify the discovery URL is accessible
4. Ensure allowed_clients matches the client ID
### MCP Server Connection Fails
If MCP tool invocations fail:
1. Check runtime status in AWS Console
2. Review CloudWatch Logs for the runtime
3. Verify JWT token is valid and not expired
4. Check that MCP protocol is configured correctly
5. Ensure the runtime is in ACTIVE state
## Cleanup
### Destroy All Resources
Make the script executable (first-time only):
```bash
chmod +x destroy.sh
```
Then cleanup:
```bash
./destroy.sh
```
Or use Terraform directly:
```bash
terraform destroy
```
**Note**: This will delete:
- AgentCore Runtime
- ECR Repository (and all images)
- Cognito User Pool (and all users)
- S3 Bucket (and all source code archives)
- All IAM roles and policies
### Verify Cleanup
Confirm all resources are deleted:
```bash
# Check AgentCore runtimes
aws bedrock-agentcore list-agent-runtimes
# Check ECR repositories
aws ecr describe-repositories | grep mcp-server
# Check Cognito User Pools
aws cognito-idp list-user-pools --max-results 10
```
## Pricing
For current pricing information, please refer to:
- [Amazon Bedrock Pricing](https://aws.amazon.com/bedrock/pricing/)
- [Amazon ECR Pricing](https://aws.amazon.com/ecr/pricing/)
- [AWS CodeBuild Pricing](https://aws.amazon.com/codebuild/pricing/)
- [Amazon Cognito Pricing](https://aws.amazon.com/cognito/pricing/)
- [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/)
- [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)
- [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/)
**Note**: Actual costs depend on your usage patterns, AWS region, and specific services consumed.
## Next Steps
### Explore More Patterns
- [Basic Runtime](../basic-runtime/) - Simpler deployment without MCP protocol
- [Multi-Agent Runtime](../multi-agent-runtime/) - Deploy multiple coordinating agents
- [End-to-End Weather Agent](../end-to-end-weather-agent/) - Full-featured agent with tools
### Extend This Pattern
- Add more MCP tools to `mcp-server-code/mcp_server.py`
- Integrate with external APIs
- Add persistent storage (DynamoDB, S3)
- Implement custom authentication logic
- Add monitoring and alerting
- Deploy to VPC for private networking
### Learn More About MCP
- [Model Context Protocol Specification](https://modelcontextprotocol.io/specification/)
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [Building MCP Servers](https://modelcontextprotocol.io/docs/building-mcp-servers)
## Resources
- [Terraform AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)
- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
- [Amazon Cognito Documentation](https://docs.aws.amazon.com/cognito/)
- [AgentCore Samples Repository](https://github.com/aws-samples/amazon-bedrock-agentcore-samples)
## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](../../../LICENSE) file for details.
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@@ -0,0 +1,33 @@
# ============================================================================
# Terraform Backend Configuration - Remote State Storage
# ============================================================================
# This is an example backend configuration for storing Terraform state remotely
# Copy this file to backend.tf and customize with your backend settings
# Example: cp backend.tf.example backend.tf
# IMPORTANT: Uncomment and configure ONE of the backend options below
# Option 1: S3 Backend (Recommended for AWS)
# terraform {
# backend "s3" {
# bucket = "your-terraform-state-bucket"
# key = "agentcore/mcp-server-agentcore-runtime/terraform.tfstate"
# region = "us-west-2"
# encrypt = true
# dynamodb_table = "terraform-state-lock"
# }
# }
# Option 2: Terraform Cloud
# terraform {
# cloud {
# organization = "your-organization"
# workspaces {
# name = "agentcore-basic-runtime"
# }
# }
# }
# Option 3: Local Backend (Default - No configuration needed)
# State will be stored locally in terraform.tfstate
# Not recommended for team environments or production use
@@ -0,0 +1,29 @@
version: 0.2
phases:
install:
commands:
- echo Installing dependencies...
- yum install -y unzip
pre_build:
commands:
- echo Source code already extracted by CodeBuild...
- cd $CODEBUILD_SRC_DIR
- ls -la
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image for MCP server ARM64 from mcp-server-code/...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo ARM64 Docker image pushed successfully
@@ -0,0 +1,90 @@
# ============================================================================
# CodeBuild Project - Build and Push MCP Server Docker Image
# ============================================================================
resource "aws_codebuild_project" "agent_image" {
name = "${var.stack_name}-mcp-server-build"
description = "Build MCP server Docker image for ${var.stack_name}"
service_role = aws_iam_role.codebuild.arn
build_timeout = 60
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_LARGE"
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
type = "ARM_CONTAINER"
privileged_mode = true
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "AWS_DEFAULT_REGION"
value = data.aws_region.current.id
}
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.id
}
environment_variable {
name = "IMAGE_REPO_NAME"
value = aws_ecr_repository.server_ecr.name
}
environment_variable {
name = "IMAGE_TAG"
value = var.image_tag
}
environment_variable {
name = "STACK_NAME"
value = var.stack_name
}
}
source {
type = "S3"
location = "${aws_s3_bucket.agent_source.id}/${aws_s3_object.agent_source.key}"
buildspec = file("${path.module}/buildspec.yml")
}
logs_config {
cloudwatch_logs {
group_name = "/aws/codebuild/${var.stack_name}-mcp-server-build"
}
}
tags = {
Name = "${var.stack_name}-mcp-server-build"
Module = "CodeBuild"
}
}
# ============================================================================
# Trigger CodeBuild - Build Image Before Creating Runtime
# ============================================================================
resource "null_resource" "trigger_build" {
triggers = {
build_project = aws_codebuild_project.agent_image.id
image_tag = var.image_tag
# Trigger rebuild if ECR repository changes
ecr_repository = aws_ecr_repository.server_ecr.id
# Trigger rebuild when source code changes (MD5 hash)
source_code_md5 = data.archive_file.agent_source.output_md5
}
provisioner "local-exec" {
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.agent_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.server_ecr.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.server_ecr.repository_url}\""
}
depends_on = [
aws_codebuild_project.agent_image,
aws_ecr_repository.server_ecr,
aws_iam_role_policy.codebuild,
aws_s3_object.agent_source
]
}
@@ -0,0 +1,81 @@
# ============================================================================
# Cognito User Pool for JWT Authentication
# ============================================================================
resource "aws_cognito_user_pool" "mcp_user_pool" {
name = "${var.stack_name}-user-pool"
password_policy {
minimum_length = 8
require_uppercase = false
require_lowercase = false
require_numbers = false
require_symbols = false
}
schema {
name = "email"
attribute_data_type = "String"
required = false
mutable = true
}
tags = {
Name = "${var.stack_name}-user-pool"
StackName = var.stack_name
Module = "Cognito"
}
}
# ============================================================================
# Cognito User Pool Client
# ============================================================================
resource "aws_cognito_user_pool_client" "mcp_client" {
name = "${var.stack_name}-client"
user_pool_id = aws_cognito_user_pool.mcp_user_pool.id
explicit_auth_flows = [
"ALLOW_USER_PASSWORD_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH"
]
generate_secret = false
prevent_user_existence_errors = "ENABLED"
}
# ============================================================================
# Test User
# ============================================================================
resource "aws_cognito_user" "test_user" {
user_pool_id = aws_cognito_user_pool.mcp_user_pool.id
username = "testuser"
message_action = "SUPPRESS"
}
# ============================================================================
# Set Permanent Password for Test User
# ============================================================================
resource "null_resource" "set_cognito_password" {
triggers = {
user_id = aws_cognito_user.test_user.id
}
provisioner "local-exec" {
command = <<-EOT
aws cognito-idp admin-set-user-password \
--user-pool-id ${aws_cognito_user_pool.mcp_user_pool.id} \
--username testuser \
--password 'MyPassword123!' \
--permanent \
--region ${data.aws_region.current.id}
EOT
}
depends_on = [
aws_cognito_user.test_user
]
}
@@ -0,0 +1,257 @@
#!/bin/bash
# ============================================================================
# Deploy Script for MCP Server on AgentCore Runtime (Terraform)
# ============================================================================
# This script automates the deployment process for the Terraform configuration
# Usage: ./deploy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_info "Starting MCP Server on AgentCore Runtime Deployment..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed. Please install Terraform >= 1.6"
print_info "Visit: https://www.terraform.io/downloads"
exit 1
fi
# Check Terraform version
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
print_success "Terraform version: $TERRAFORM_VERSION"
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
exit 1
fi
print_success "AWS CLI is installed"
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
print_info "Run: aws configure"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_success "AWS Account: $AWS_ACCOUNT"
print_success "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Configuration Check
# ============================================================================
print_info "Checking configuration files..."
# Check if terraform.tfvars exists
if [ ! -f "terraform.tfvars" ]; then
print_warning "terraform.tfvars not found"
print_info "Creating terraform.tfvars from example..."
if [ -f "terraform.tfvars.example" ]; then
cp terraform.tfvars.example terraform.tfvars
print_success "Created terraform.tfvars"
print_warning "Please review and update terraform.tfvars with your settings"
print_info "Then run this script again"
exit 0
else
print_error "terraform.tfvars.example not found"
exit 1
fi
fi
print_success "Configuration file found: terraform.tfvars"
echo ""
# ============================================================================
# Terraform Initialization
# ============================================================================
print_info "Initializing Terraform..."
if terraform init; then
print_success "Terraform initialized successfully"
else
print_error "Terraform initialization failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Validation
# ============================================================================
print_info "Validating Terraform configuration..."
if terraform validate; then
print_success "Terraform configuration is valid"
else
print_error "Terraform validation failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Format Check
# ============================================================================
print_info "Checking Terraform formatting..."
if terraform fmt -check -recursive > /dev/null 2>&1; then
print_success "Terraform files are properly formatted"
else
print_warning "Some files need formatting. Running terraform fmt..."
terraform fmt -recursive
print_success "Files formatted"
fi
echo ""
# ============================================================================
# Terraform Plan
# ============================================================================
print_info "Creating Terraform execution plan..."
print_warning "This may take a few moments..."
echo ""
if terraform plan -out=tfplan; then
print_success "Terraform plan created successfully"
else
print_error "Terraform plan failed"
exit 1
fi
echo ""
# ============================================================================
# Deployment Confirmation
# ============================================================================
print_warning "========================================"
print_warning "DEPLOYMENT CONFIRMATION"
print_warning "========================================"
print_info "This will deploy the following resources:"
print_info " - S3 Bucket (source code storage)"
print_info " - ECR Repository"
print_info " - CodeBuild Project"
print_info " - Cognito User Pool + Client (JWT authentication)"
print_info " - IAM Roles and Policies"
print_info " - AgentCore Runtime (with MCP protocol)"
echo ""
print_info "The deployment includes:"
print_info " - Building ARM64 Docker image with MCP server"
print_info " - Creating AWS resources"
print_info " - Setting up JWT authentication with test user"
echo ""
read -p "Do you want to proceed with deployment? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Deployment cancelled by user"
rm -f tfplan
exit 0
fi
# ============================================================================
# Terraform Apply
# ============================================================================
print_info "Starting deployment..."
echo ""
if terraform apply tfplan; then
print_success "Deployment completed successfully!"
else
print_error "Deployment failed"
rm -f tfplan
exit 1
fi
# Clean up plan file
rm -f tfplan
echo ""
# ============================================================================
# Deployment Summary
# ============================================================================
print_success "========================================"
print_success "DEPLOYMENT COMPLETED"
print_success "========================================"
echo ""
print_info "Retrieving deployment outputs..."
echo ""
# Get outputs
RUNTIME_ID=$(terraform output -raw agent_runtime_id 2>/dev/null || echo "N/A")
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn 2>/dev/null || echo "N/A")
ECR_URL=$(terraform output -raw ecr_repository_url 2>/dev/null || echo "N/A")
USER_POOL_ID=$(terraform output -raw cognito_user_pool_id 2>/dev/null || echo "N/A")
CLIENT_ID=$(terraform output -raw cognito_user_pool_client_id 2>/dev/null || echo "N/A")
print_success "Agent Runtime ID: $RUNTIME_ID"
print_success "Agent Runtime ARN: $RUNTIME_ARN"
print_success "ECR Repository URL: $ECR_URL"
print_success "Cognito User Pool ID: $USER_POOL_ID"
print_success "Cognito Client ID: $CLIENT_ID"
echo ""
print_info "Next Steps:"
print_info "1. Get JWT token for authentication:"
print_info " terraform output -raw get_token_command | bash"
echo ""
print_info "2. Test the MCP server:"
print_info " # Export the JWT token from step 1, then:"
print_info " python test_mcp_server.py $RUNTIME_ARN \$JWT_TOKEN $AWS_REGION"
echo ""
print_info "3. View all outputs:"
print_info " terraform output"
echo ""
print_info "4. Monitor in AWS Console:"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_success "Deployment completed successfully!"
@@ -0,0 +1,276 @@
#!/bin/bash
# ============================================================================
# Destroy Script for MCP Server on AgentCore Runtime (Terraform)
# ============================================================================
# This script safely destroys all resources created by this Terraform configuration
# Usage: ./destroy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_warning "Starting Resource Cleanup..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed"
exit 1
fi
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed"
exit 1
fi
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_info "AWS Account: $AWS_ACCOUNT"
print_info "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Check for Terraform State
# ============================================================================
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
print_warning "No Terraform state found"
print_info "Either no resources have been deployed, or state is stored remotely"
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Initializing Terraform to fetch remote state..."
terraform init
else
print_info "Cleanup cancelled"
exit 0
fi
fi
# ============================================================================
# Show Destruction Plan
# ============================================================================
print_info "Creating destruction plan..."
echo ""
if ! terraform plan -destroy; then
print_error "Failed to create destruction plan"
exit 1
fi
echo ""
# ============================================================================
# Destruction Confirmation
# ============================================================================
print_warning "========================================"
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
print_warning "========================================"
print_warning "This will permanently delete the following resources:"
print_warning " - AgentCore Runtime (with MCP server)"
print_warning " - Cognito User Pool (including test user)"
print_warning " - S3 Bucket (source code storage)"
print_warning " - ECR Repository (including all images)"
print_warning " - CodeBuild Project"
print_warning " - IAM Roles and Policies"
print_warning " - CloudWatch Log Groups"
echo ""
print_warning "THIS ACTION CANNOT BE UNDONE!"
echo ""
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
echo ""
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Destruction cancelled by user"
exit 0
fi
# Double confirmation for safety
print_warning "Second confirmation required..."
read -p "Type 'DESTROY' to confirm: " -r
echo ""
if [ "$REPLY" != "DESTROY" ]; then
print_info "Destruction cancelled - confirmation text did not match"
exit 0
fi
# ============================================================================
# Execute Destruction
# ============================================================================
print_warning "Starting resource destruction..."
echo ""
if terraform destroy -auto-approve; then
print_success "All resources destroyed successfully"
else
print_error "Destruction failed"
print_warning "Some resources may still exist. Please check AWS Console"
exit 1
fi
echo ""
# ============================================================================
# Cleanup Local Files
# ============================================================================
print_info "Cleaning up local Terraform files..."
# Ask about state file cleanup
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -f terraform.tfstate
rm -f terraform.tfstate.backup
rm -f tfplan
print_success "Local state files removed"
fi
# Ask about .terraform directory
read -p "Do you want to remove .terraform directory? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -rf .terraform
rm -f .terraform.lock.hcl
print_success ".terraform directory removed"
fi
echo ""
# ============================================================================
# Verification
# ============================================================================
print_info "Verifying resource cleanup..."
echo ""
# Check for ECR repositories
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-mcp-server")
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$ECR_REPOS" -eq 0 ]; then
print_success "ECR repositories cleaned up"
else
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for AgentCore runtimes
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$RUNTIME_COUNT" -eq 0 ]; then
print_success "AgentCore runtimes cleaned up"
else
print_warning "Found $RUNTIME_COUNT AgentCore runtimes matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for S3 buckets
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$S3_BUCKETS" -eq 0 ]; then
print_success "S3 buckets cleaned up"
else
print_warning "Found $S3_BUCKETS S3 buckets matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for Cognito User Pools
COGNITO_POOLS=$(aws cognito-idp list-user-pools --max-results 60 --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$COGNITO_POOLS" -eq 0 ]; then
print_success "Cognito User Pools cleaned up"
else
print_warning "Found $COGNITO_POOLS Cognito User Pools matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
echo ""
# ============================================================================
# Completion Summary
# ============================================================================
print_success "========================================"
print_success "CLEANUP COMPLETED"
print_success "========================================"
echo ""
print_info "Cleanup Summary:"
print_success " ✓ Terraform resources destroyed"
print_success " ✓ Local state files cleaned (if selected)"
echo ""
print_info "What to verify in AWS Console:"
print_info "1. Bedrock AgentCore - No runtimes remaining"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_info "2. S3 - No buckets remaining"
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
echo ""
print_info "3. ECR - No repositories remaining"
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
echo ""
print_info "4. Cognito - No user pools remaining"
print_info " https://console.aws.amazon.com/cognito/v2/idp/user-pools?region=$AWS_REGION"
echo ""
print_info "5. CodeBuild - No projects remaining"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
echo ""
print_info "6. CloudWatch Logs - Check for orphaned log groups"
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
echo ""
print_success "Cleanup completed successfully!"
print_info "You can safely re-deploy by running: ./deploy.sh"
@@ -0,0 +1,63 @@
# ============================================================================
# ECR Repository - Container Registry for Agent Image
# ============================================================================
resource "aws_ecr_repository" "server_ecr" {
name = "${var.stack_name}-${var.ecr_repository_name}"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
force_delete = true
tags = {
Name = "${var.stack_name}-ecr-repository"
Module = "ECR"
}
}
# ECR Repository Policy
resource "aws_ecr_repository_policy" "server_ecr" {
repository = aws_ecr_repository.server_ecr.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPullFromAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
})
}
# ECR Lifecycle Policy - Keep last 5 images
resource "aws_ecr_lifecycle_policy" "server_ecr" {
repository = aws_ecr_repository.server_ecr.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 5 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 5
}
action = {
type = "expire"
}
}
]
})
}
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
Simple Cognito Authentication Script
Matches the approach from the original tutorial
"""
import boto3
import sys
def get_token(client_id, username, password, region=None):
"""Get authentication token from Cognito."""
# Use provided region or default from environment/config
if region:
cognito_client = boto3.client("cognito-idp", region_name=region)
else:
cognito_client = boto3.client("cognito-idp")
try:
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": username, "PASSWORD": password},
)
return auth_response["AuthenticationResult"]["AccessToken"]
except Exception as e:
print(f"Error: {e}")
print("Troubleshooting:")
print(" - Verify the Client ID is correct")
print(" - Ensure you're using the correct region")
print(" - Check that the user exists and password is correct")
print(" - Verify USER_PASSWORD_AUTH is enabled for this client")
sys.exit(1)
def main():
if len(sys.argv) < 4 or len(sys.argv) > 5:
print("Usage: python get_token.py <client_id> <username> <password> [region]")
sys.exit(1)
client_id = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
region = sys.argv[4] if len(sys.argv) == 5 else None
if region:
print(f"Authenticating with Cognito in region {region}...")
else:
print("Authenticating with Cognito...")
token = get_token(client_id, username, password, region)
print("\n" + "=" * 70)
print("Authentication Successful!")
print("=" * 70)
print("\nAccess Token:")
print(token)
print("\n" + "=" * 70)
print("Export Command:")
print("=" * 70)
print(f'\nexport JWT_TOKEN="{token}"')
print("\nThen use in curl:")
print('curl -H "Authorization: Bearer $JWT_TOKEN" <your-api-url>')
print()
if __name__ == "__main__":
main()
@@ -0,0 +1,217 @@
# Data sources
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# ============================================================================
# Agent Execution Role - For AgentCore Runtime
# ============================================================================
resource "aws_iam_role" "agent_execution" {
name = "${var.stack_name}-agent-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AssumeRolePolicy"
Effect = "Allow"
Principal = {
Service = "bedrock-agentcore.amazonaws.com"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.id
}
ArnLike = {
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
}
}
}]
})
tags = {
Name = "${var.stack_name}-agent-execution-role"
Module = "IAM"
}
}
# Attach AWS managed policy for AgentCore
resource "aws_iam_role_policy_attachment" "agent_execution_managed" {
role = aws_iam_role.agent_execution.name
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
}
# Inline policy for agent execution
resource "aws_iam_role_policy" "agent_execution" {
name = "AgentCoreExecutionPolicy"
role = aws_iam_role.agent_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# ECR Access
{
Sid = "ECRImageAccess"
Effect = "Allow"
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
Resource = aws_ecr_repository.server_ecr.arn
},
{
Sid = "ECRTokenAccess"
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
},
# X-Ray Tracing
{
Sid = "XRayTracing"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets"
]
Resource = "*"
},
# CloudWatch Metrics
{
Sid = "CloudWatchMetrics"
Effect = "Allow"
Action = ["cloudwatch:PutMetricData"]
Resource = "*"
Condition = {
StringEquals = {
"cloudwatch:namespace" = "bedrock-agentcore"
}
}
},
# Bedrock Model Invocation
{
Sid = "BedrockModelInvocation"
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
]
Resource = "*"
},
# Workload Access Tokens
{
Sid = "GetAgentAccessToken"
Effect = "Allow"
Action = [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
]
Resource = [
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
]
}
]
})
}
# ============================================================================
# CodeBuild Service Role - For Docker Image Building
# ============================================================================
resource "aws_iam_role" "codebuild" {
name = "${var.stack_name}-codebuild-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
tags = {
Name = "${var.stack_name}-codebuild-role"
Module = "IAM"
}
}
# Inline policy for CodeBuild
resource "aws_iam_role_policy" "codebuild" {
name = "CodeBuildPolicy"
role = aws_iam_role.codebuild.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
},
# ECR Access
{
Sid = "ECRAccess"
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
Resource = [
aws_ecr_repository.server_ecr.arn,
"*"
]
},
# S3 Source Access (for agent-code)
{
Sid = "S3SourceAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion"
]
Resource = "${aws_s3_bucket.agent_source.arn}/*"
},
{
Sid = "S3BucketAccess"
Effect = "Allow"
Action = [
"s3:ListBucket",
"s3:GetBucketLocation"
]
Resource = aws_s3_bucket.agent_source.arn
}
]
})
}
@@ -0,0 +1,47 @@
# ============================================================================
# AgentCore Runtime - Main Agent Runtime Resource
# ============================================================================
resource "aws_bedrockagentcore_agent_runtime" "mcp_server" {
agent_runtime_name = replace("${var.stack_name}_${var.agent_name}", "-", "_")
description = var.description
role_arn = aws_iam_role.agent_execution.arn
agent_runtime_artifact {
container_configuration {
container_uri = "${aws_ecr_repository.server_ecr.repository_url}:${var.image_tag}"
}
}
network_configuration {
network_mode = var.network_mode
}
# MCP Protocol Configuration
protocol_configuration {
server_protocol = "MCP"
}
# JWT Authorization with Cognito
authorizer_configuration {
custom_jwt_authorizer {
allowed_clients = [aws_cognito_user_pool_client.mcp_client.id]
discovery_url = "https://cognito-idp.${data.aws_region.current.id}.amazonaws.com/${aws_cognito_user_pool.mcp_user_pool.id}/.well-known/openid-configuration"
}
}
environment_variables = merge(
{
AWS_REGION = var.aws_region
AWS_DEFAULT_REGION = var.aws_region
},
var.environment_variables
)
depends_on = [
null_resource.trigger_build,
null_resource.set_cognito_password,
aws_iam_role_policy.agent_execution,
aws_iam_role_policy_attachment.agent_execution_managed
]
}
@@ -0,0 +1,15 @@
FROM public.ecr.aws/docker/library/python:3.11-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 8000
COPY . .
CMD ["python", "-m", "mcp_server"]
@@ -0,0 +1,21 @@
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(host="0.0.0.0", stateless_http=True)
@mcp.tool()
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together"""
return a + b
@mcp.tool()
def multiply_numbers(a: int, b: int) -> int:
"""Multiply two numbers together"""
return a * b
@mcp.tool()
def greet_user(name: str) -> str:
"""Greet a user by name"""
return f"Hello, {name}! Nice to meet you."
if __name__ == "__main__":
mcp.run(transport="streamable-http")
@@ -0,0 +1,3 @@
mcp>=1.10.0
boto3
bedrock-agentcore
@@ -0,0 +1,99 @@
output "agent_runtime_id" {
description = "ID of the created agent runtime"
value = aws_bedrockagentcore_agent_runtime.mcp_server.agent_runtime_id
}
output "agent_runtime_arn" {
description = "ARN of the created agent runtime"
value = aws_bedrockagentcore_agent_runtime.mcp_server.agent_runtime_arn
}
output "agent_runtime_version" {
description = "Version of the created agent runtime"
value = aws_bedrockagentcore_agent_runtime.mcp_server.agent_runtime_version
}
output "ecr_repository_url" {
description = "URL of the ECR repository"
value = aws_ecr_repository.server_ecr.repository_url
}
output "ecr_repository_arn" {
description = "ARN of the ECR repository"
value = aws_ecr_repository.server_ecr.arn
}
output "agent_execution_role_arn" {
description = "ARN of the agent execution role"
value = aws_iam_role.agent_execution.arn
}
output "codebuild_project_name" {
description = "Name of the CodeBuild project"
value = aws_codebuild_project.agent_image.name
}
output "codebuild_project_arn" {
description = "ARN of the CodeBuild project"
value = aws_codebuild_project.agent_image.arn
}
output "source_bucket_name" {
description = "S3 bucket containing agent source code"
value = aws_s3_bucket.agent_source.id
}
output "source_bucket_arn" {
description = "ARN of the S3 bucket containing agent source code"
value = aws_s3_bucket.agent_source.arn
}
output "source_object_key" {
description = "S3 object key for the agent source code archive"
value = aws_s3_object.agent_source.key
}
output "source_code_md5" {
description = "MD5 hash of the agent source code (triggers rebuild when changed)"
value = data.archive_file.agent_source.output_md5
}
# ============================================================================
# Cognito Outputs
# ============================================================================
output "cognito_user_pool_id" {
description = "ID of the Cognito User Pool"
value = aws_cognito_user_pool.mcp_user_pool.id
}
output "cognito_user_pool_arn" {
description = "ARN of the Cognito User Pool"
value = aws_cognito_user_pool.mcp_user_pool.arn
}
output "cognito_user_pool_client_id" {
description = "ID of the Cognito User Pool Client"
value = aws_cognito_user_pool_client.mcp_client.id
}
output "cognito_discovery_url" {
description = "Cognito OIDC Discovery URL"
value = "https://cognito-idp.${data.aws_region.current.id}.amazonaws.com/${aws_cognito_user_pool.mcp_user_pool.id}/.well-known/openid-configuration"
}
output "test_username" {
description = "Test username for authentication"
value = "testuser"
}
output "test_password" {
description = "Test password for authentication"
value = "MyPassword123!"
sensitive = true
}
output "get_token_command" {
description = "Command to get authentication token"
value = "python get_token.py ${aws_cognito_user_pool_client.mcp_client.id} testuser MyPassword123! ${data.aws_region.current.id}"
}
@@ -0,0 +1,58 @@
# ============================================================================
# S3 Bucket for MCP Server Source Code (CDK Asset Equivalent)
# ============================================================================
resource "aws_s3_bucket" "agent_source" {
bucket_prefix = "${var.stack_name}-source-"
force_destroy = true
tags = {
Name = "${var.stack_name}-mcp-server-source"
Purpose = "Store MCP server source code for CodeBuild"
}
}
# Block public access
resource "aws_s3_bucket_public_access_block" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning for source code tracking
resource "aws_s3_bucket_versioning" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
versioning_configuration {
status = "Enabled"
}
}
# ============================================================================
# Archive and Upload MCP Server Source Code
# ============================================================================
# Archive mcp-server-code/ directory
# This automatically detects ALL files including new ones
data "archive_file" "agent_source" {
type = "zip"
source_dir = "${path.module}/mcp-server-code"
output_path = "${path.module}/.terraform/mcp-server-code.zip"
}
# Upload to S3 (re-uploads when MD5 changes)
resource "aws_s3_object" "agent_source" {
bucket = aws_s3_bucket.agent_source.id
key = "mcp-server-code-${data.archive_file.agent_source.output_md5}.zip"
source = data.archive_file.agent_source.output_path
etag = data.archive_file.agent_source.output_md5
tags = {
Name = "mcp-server-source-code"
MD5 = data.archive_file.agent_source.output_md5
Timestamp = timestamp()
}
}
@@ -0,0 +1,196 @@
#!/bin/bash
# ============================================================================
# Build and Verify Docker Image for AgentCore Runtime
# ============================================================================
# This script is called by Terraform during deployment to:
# 1. Trigger CodeBuild to build the Docker image
# 2. Wait for the build to complete
# 3. Verify the image was successfully pushed to ECR
#
# Parameters:
# $1 - CodeBuild project name
# $2 - AWS region
# $3 - ECR repository name
# $4 - Image tag
# $5 - ECR repository URL
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Parameters
PROJECT_NAME="$1"
REGION="$2"
REPO_NAME="$3"
IMAGE_TAG="$4"
REPO_URL="$5"
# ============================================================================
# Print functions
# ============================================================================
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[✓]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[⚠]${NC} $1"
}
print_error() {
echo -e "${RED}[✗]${NC} $1"
}
print_header() {
echo ""
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================${NC}"
}
# ============================================================================
# Start Build Process
# ============================================================================
print_header "Building Docker Image for AgentCore Runtime"
print_info "CodeBuild Project: $PROJECT_NAME"
print_info "Region: $REGION"
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
echo ""
# Start CodeBuild
print_info "Starting CodeBuild project..."
BUILD_ID=$(aws codebuild start-build \
--project-name "$PROJECT_NAME" \
--region "$REGION" \
--query 'build.id' \
--output text 2>&1)
if [ $? -ne 0 ]; then
print_error "Failed to start CodeBuild"
echo "$BUILD_ID"
exit 1
fi
print_success "Build started: $BUILD_ID"
print_info "Waiting for build to complete (typically 5-10 minutes)..."
echo ""
# ============================================================================
# Monitor Build Progress
# ============================================================================
ATTEMPT=0
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT + 1))
STATUS=$(aws codebuild batch-get-builds \
--ids "$BUILD_ID" \
--region "$REGION" \
--query 'builds[0].buildStatus' \
--output text 2>/dev/null)
if [ "$STATUS" != "IN_PROGRESS" ]; then
print_info "Build process completed with status: $STATUS"
break
fi
# Progress indicator
if [ $((ATTEMPT % 6)) -eq 0 ]; then
MINUTES=$((ATTEMPT / 6))
print_info "Build in progress... (${MINUTES} minutes elapsed)"
fi
sleep 10
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
print_error "Build timeout after 10 minutes"
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
exit 1
fi
echo ""
# ============================================================================
# Verify Image in ECR
# ============================================================================
print_header "Verifying Docker Image in ECR"
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
print_info "Waiting for ECR propagation..."
echo ""
sleep 5 # Brief wait for ECR to register the push
VERIFY_ATTEMPT=0
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
if aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" >/dev/null 2>&1; then
print_success "Docker image successfully verified in ECR!"
echo ""
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
# Get image details
IMAGE_SIZE=$(aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" \
--query 'imageDetails[0].imageSizeInBytes' \
--output text 2>/dev/null || echo "Unknown")
if [ "$IMAGE_SIZE" != "Unknown" ]; then
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
fi
echo ""
print_success "Build and verification completed successfully!"
exit 0
fi
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
fi
sleep 5
done
# ============================================================================
# Error: Image Not Found
# ============================================================================
print_error "Docker image not found in ECR after build completion"
echo ""
print_warning "This indicates the build or push step failed."
print_info "Troubleshooting steps:"
print_info " 1. Check CodeBuild logs:"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
print_info ""
print_info " 2. Verify ECR repository:"
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
print_info ""
print_info " 3. Check IAM permissions for CodeBuild role"
exit 1
@@ -0,0 +1,31 @@
# ============================================================================
# MCP Server on AgentCore Runtime - Example Configuration
# ============================================================================
# Copy this file to terraform.tfvars and customize
# Example: cp terraform.tfvars.example terraform.tfvars
# Agent Configuration
agent_name = "MCPServerAgent"
stack_name = "agentcore-mcp-server"
description = "MCP server runtime with JWT authentication"
# Network Configuration
network_mode = "PUBLIC" # PUBLIC or PRIVATE
# Container Configuration
ecr_repository_name = "mcp-server"
image_tag = "latest"
# AWS Configuration
aws_region = "us-west-2"
environment = "dev"
# Optional: Environment Variables for MCP Server
# environment_variables = {
# LOG_LEVEL = "INFO"
# }
# Notes:
# - JWT auth via Cognito (test user: testuser/MyPassword123!)
# - MCP server code in mcp-server-code/ directory
# - Three tools included: add_numbers, multiply_numbers, greet_user
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Test script for deployed MCP server
Uses the MCP Python client library to properly communicate with the server
"""
import asyncio
import sys
from datetime import timedelta
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
def extract_region_from_arn(arn):
"""Extract AWS region from agent runtime ARN.
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
Args:
arn: Agent runtime ARN string
Returns:
str: AWS region code
Raises:
ValueError: If ARN format is invalid or region cannot be extracted
"""
try:
parts = arn.split(':')
if len(parts) < 4:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
region = parts[3]
if not region:
raise ValueError(
f"Region not found in ARN: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
return region
except IndexError:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
async def test_mcp_server(agent_arn, bearer_token, region):
"""Test the deployed MCP server."""
# Encode the ARN for URL
encoded_arn = agent_arn.replace(":", "%3A").replace("/", "%2F")
mcp_url = f"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT"
headers = {
"authorization": f"Bearer {bearer_token}",
"Content-Type": "application/json",
}
print(f"Connecting to: {mcp_url}")
print()
try:
async with streamablehttp_client(
mcp_url, headers, timeout=timedelta(seconds=120), terminate_on_close=False
) as (read_stream, write_stream, _):
async with ClientSession(read_stream, write_stream) as session:
print("🔄 Initializing MCP session...")
await session.initialize()
print("✓ MCP session initialized\n")
print("🔄 Listing available tools...")
tool_result = await session.list_tools()
print("\n📋 Available MCP Tools:")
print("=" * 50)
for tool in tool_result.tools:
print(f"🔧 {tool.name}: {tool.description}")
print("\n🧪 Testing MCP Tools:")
print("=" * 50)
# Test add_numbers
print("\n Testing add_numbers(5, 3)...")
add_result = await session.call_tool(
name="add_numbers", arguments={"a": 5, "b": 3}
)
print(f" Result: {add_result.content[0].text}")
# Test multiply_numbers
print("\n✖️ Testing multiply_numbers(4, 7)...")
multiply_result = await session.call_tool(
name="multiply_numbers", arguments={"a": 4, "b": 7}
)
print(f" Result: {multiply_result.content[0].text}")
# Test greet_user
print("\n👋 Testing greet_user('Alice')...")
greet_result = await session.call_tool(
name="greet_user", arguments={"name": "Alice"}
)
print(f" Result: {greet_result.content[0].text}")
print("\n✅ MCP tool testing completed!")
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
def main():
if len(sys.argv) < 3:
print("Usage: python test_mcp_server.py <agent_arn> <bearer_token> [region]")
print("\nRegion is optional - will be extracted from ARN if not provided")
print("\nExample:")
print(
" python test_mcp_server.py arn:aws:bedrock-agentcore:<region>:... eyJraWQiOiJ..."
)
sys.exit(1)
agent_arn = sys.argv[1]
bearer_token = sys.argv[2]
# Extract region from ARN or use provided region
if len(sys.argv) > 3:
region = sys.argv[3]
print(f"Using provided region: {region}")
else:
try:
region = extract_region_from_arn(agent_arn)
print(f"Extracted region from ARN: {region}")
except ValueError as e:
print(f"\n❌ ERROR: {e}\n")
sys.exit(1)
asyncio.run(test_mcp_server(agent_arn, bearer_token, region))
if __name__ == "__main__":
main()
@@ -0,0 +1,68 @@
variable "agent_name" {
description = "Name for the agent runtime"
type = string
default = "MCPServerAgent"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.agent_name))
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
}
}
variable "network_mode" {
description = "Network mode for AgentCore resources"
type = string
default = "PUBLIC"
validation {
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
error_message = "Network mode must be either PUBLIC or PRIVATE."
}
}
variable "image_tag" {
description = "Docker image tag"
type = string
default = "latest"
}
variable "aws_region" {
description = "AWS region for deployment (REQUIRED)"
type = string
# No default - must be explicitly provided
validation {
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
}
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "stack_name" {
description = "Stack name for resource naming"
type = string
default = "agentcore-mcp-server"
}
variable "description" {
description = "Description of the agent runtime"
type = string
default = "MCP server runtime with JWT authentication"
}
variable "environment_variables" {
description = "Environment variables for the agent runtime"
type = map(string)
default = {}
}
variable "ecr_repository_name" {
description = "Name of the ECR repository"
type = string
default = "mcp-server"
}
@@ -0,0 +1,32 @@
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.21"
}
null = {
source = "hashicorp/null"
version = "~> 3.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.9"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "AgentCore"
Pattern = "mcp-server-agentcore-runtime"
Environment = var.environment
StackName = var.stack_name
ManagedBy = "Terraform"
}
}
}
@@ -0,0 +1,25 @@
# Terraform files
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
!terraform.tfvars.example
tfplan
# Generated archives
*.zip
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
@@ -0,0 +1,561 @@
# Multi-Agent Runtime on Amazon Bedrock AgentCore (Terraform)
This Terraform module deploys a multi-agent system using Amazon Bedrock AgentCore Runtime with Agent-to-Agent (A2A) communication capabilities.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [What's Included](#whats-included)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Deployment Process](#deployment-process)
- [Authentication Model](#authentication-model)
- [Testing](#testing)
- [Agent Capabilities](#agent-capabilities)
- [Customization](#customization)
- [File Structure](#file-structure)
- [Monitoring and Observability](#monitoring-and-observability)
- [Security](#security)
- [Pricing](#pricing)
- [Troubleshooting](#troubleshooting)
- [Cleanup](#cleanup)
- [Advanced Topics](#advanced-topics)
- [Next Steps](#next-steps)
- [Resources](#resources)
- [Contributing](#contributing)
- [License](#license)
## Overview
This pattern demonstrates deploying a multi-agent system with two coordinating agents that communicate via the Agent-to-Agent (A2A) protocol. Agent1 (Orchestrator) can delegate specialized tasks to Agent2 (Specialist), enabling modular and scalable agent architectures.
**Key Features:**
- Two-agent architecture with A2A communication
- Automated Docker image building via CodeBuild
- S3-based source code management with change detection
- IAM-based security with least-privilege access
- Sequential deployment ensuring proper dependencies
This makes it ideal for:
- Building complex multi-agent workflows
- Implementing agent specialization patterns
- Creating scalable agent orchestration systems
- Learning A2A communication protocols
## Architecture
![Multi-Agent Architecture](architecture.png)
### System Components
**Agent1 (Orchestrator Agent)**
- Receives initial user requests
- Orchestrates workflow between multiple agents
- Contains a specialized tool (`call_specialist_agent`) to invoke Agent2
- Has IAM permissions to invoke Agent2's runtime
- Environment variable `AGENT2_ARN` enables A2A communication
**Agent2 (Specialist Agent)**
- Independent specialist agent with domain-specific capabilities
- Provides data analysis and processing functions
- Can be invoked by Agent1 via A2A protocol
- No dependencies on other agents
### Agent-to-Agent (A2A) Communication
The A2A communication pattern enables:
- **Orchestration**: Agent1 coordinates complex workflows
- **Specialization**: Agent2 focuses on specific capabilities
- **Scalability**: Easy to add more specialized agents
- **Security**: IAM-based authorization between agents
## What's Included
This Terraform configuration creates:
- **2 S3 Buckets**: Source code storage for both agents with versioning
- **2 ECR Repositories**: Container registries for ARM64 Docker images
- **2 CodeBuild Projects**: Automated image building and pushing
- **3 IAM Roles**:
- Agent1 execution role (with A2A permissions)
- Agent2 execution role (standard permissions)
- CodeBuild service role
- **2 Agent Runtimes**:
- Agent1 (Orchestrator) with AGENT2_ARN environment variable
- Agent2 (Specialist) independent runtime
- **Build Automation**: Automatic rebuild on code changes (MD5-based detection)
- **Supporting Resources**: S3 lifecycle policies, ECR lifecycle policies, IAM policies
**Total:** ~30 AWS resources deployed and managed by Terraform
## Prerequisites
### Required Tools
1. **Terraform** (>= 1.6)
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
2. **AWS CLI** (configured with credentials)
```bash
aws configure
```
3. **Python 3.11+** (for testing scripts)
```bash
python --version # Verify Python 3.11 or later
pip install boto3
```
4. **Docker** (for local testing, optional)
### AWS Account Requirements
- AWS Account with appropriate permissions
- Access to Amazon Bedrock AgentCore service
- Permissions to create:
- S3 buckets
- ECR repositories
- CodeBuild projects
- IAM roles and policies
- AgentCore Runtime resources
## Quick Start
### 1. Configure Variables
Copy the example variables file and customize:
```bash
cp terraform.tfvars.example terraform.tfvars
```
Edit `terraform.tfvars` with your preferred values:
- `orchestrator_name`: Name for the orchestrator agent (default: "OrchestratorAgent")
- `specialist_name`: Name for the specialist agent (default: "SpecialistAgent")
- `stack_name`: Stack identifier (default: "agentcore-multi-agent")
- `aws_region`: AWS region for deployment (default: "us-west-2")
- `network_mode`: PUBLIC or PRIVATE networking
### 2. Initialize Terraform
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
**Quick start with local state:**
```bash
terraform init
```
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
### 3. Deploy
**Method 1: Using Deploy Script (Recommended)**
```bash
chmod +x deploy.sh
./deploy.sh
```
The script validates configuration, shows the plan, and deploys all resources.
**Method 2: Direct Terraform Commands**
```bash
terraform plan
terraform apply
```
**Note**: Deployment includes creating infrastructure, building Docker images sequentially (Agent2 first, then Agent1), and establishing A2A communication. Total deployment time: **~5-10 minutes**
### 4. Verify Deployment
```bash
# View all outputs
terraform output
# Get Agent ARNs
terraform output orchestrator_runtime_arn
terraform output specialist_runtime_arn
```
## Deployment Process
### Sequential Build Process
The deployment follows a strict sequence to ensure proper dependencies:
```
1. S3 Buckets Creation (orchestrator & specialist)
2. ECR Repositories Creation (orchestrator & specialist)
3. IAM Roles Creation (with A2A permissions)
4. CodeBuild Projects Creation (orchestrator & specialist)
5. Agent2 Docker Build → Agent2 Runtime Creation
6. Agent1 Docker Build → Agent1 Runtime Creation (depends on Agent2)
```
**Critical Dependencies:**
- Agent1 runtime depends on Agent2 runtime being created first
- Agent1 build depends on Agent2 build completing successfully
- Agent1 receives `AGENT2_ARN` as an environment variable
### Build Triggers
The infrastructure automatically triggers Docker image builds:
- When source code changes (MD5 hash detection)
- When infrastructure changes require rebuild
- Sequential: Agent2 builds first, then Agent1
## Authentication Model
This pattern uses **IAM-based authentication with workload identity tokens**:
- **Service Principal**: Agents assume IAM roles via `bedrock-agentcore.amazonaws.com`
- **Workload Identity**: Agents obtain access tokens for secure operations
- **A2A Authorization**: Agent1 has `InvokeAgentRuntime` permission for Agent2
- **API Access**: Direct AWS API invocation using IAM credentials
**Note**: This is a backend infrastructure pattern with no user authentication layer. For user-facing applications, you would add Cognito or API Gateway authorizers separately.
## Testing
The included `test_multi_agent.py` script is **infrastructure-agnostic** and works with any deployment method (Terraform, CDK, CloudFormation, or manual).
### Prerequisites for Testing
Before testing, ensure you have the required packages installed:
**Option A: Using uv (Recommended)**
```bash
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install boto3 # Required for agent invocation
```
**Option B: System-wide installation**
```bash
pip install boto3 # Required for agent invocation
```
**Note**: `boto3` is required for the test script to invoke both agent runtimes via AWS API.
### Basic Testing
```bash
# Get ARNs from Terraform
ORCHESTRATOR_ARN=$(terraform output -raw orchestrator_runtime_arn)
SPECIALIST_ARN=$(terraform output -raw specialist_runtime_arn)
# Test both agents
python test_multi_agent.py $ORCHESTRATOR_ARN $SPECIALIST_ARN
```
### Test Scenarios
The script runs two tests:
1. **Simple Query**: Basic orchestrator invocation
2. **A2A Communication**: Orchestrator delegates to specialist via A2A protocol
### Expected Output
```
TEST 1: Simple Query (Orchestrator) ✅
TEST 2: Complex Query with A2A Communication ✅
✅ ALL TESTS PASSED
```
## Agent Capabilities
### Agent1 (Orchestrator)
**Tools:**
- `call_specialist_agent`: Invokes Agent2 for specialized processing
- Parameters: `query` (string)
- Returns: Processed results from Agent2
**Use Cases:**
- Complex workflow orchestration
- Multi-step data processing
- Delegation to specialized agents
### Agent2 (Specialist)
**Capabilities:**
- Domain-specific data analysis
- Detailed information processing
- Expert-level responses
**Use Cases:**
- Data analysis and transformation
- Domain-specific processing
- Specialized computations
## Customization
### Modify Agent Code
1. **Edit Agent Files**
```bash
# Orchestrator Agent
vim agent-orchestrator-code/agent.py
vim agent-orchestrator-code/requirements.txt
# Specialist Agent
vim agent-specialist-code/agent.py
vim agent-specialist-code/requirements.txt
```
2. **Redeploy**
```bash
terraform apply # Automatically detects changes and rebuilds
```
### Add More Agents
To add a new agent (e.g., Coordinator):
1. Create `coordinator-code/` directory with implementation
2. Add `coordinator.tf` for the runtime resource
3. Update `s3.tf`, `ecr.tf`, `iam.tf`, `codebuild.tf`
4. Create `buildspec-coordinator.yml`
5. Update `main.tf` for build sequence
6. Update `outputs.tf` and `variables.tf`
### Modify Network Configuration
Change from PUBLIC to PRIVATE networking:
```hcl
# terraform.tfvars
network_mode = "PRIVATE"
```
Requires VPC configuration (not included in this module).
## File Structure
```
multi-agent-runtime/
├── agent-orchestrator-code/ # Orchestrator agent source code
│ ├── agent.py # Main agent implementation
│ ├── Dockerfile # Container definition
│ └── requirements.txt # Python dependencies
├── agent-specialist-code/ # Specialist agent source code
│ ├── agent.py # Main agent implementation
│ ├── Dockerfile # Container definition
│ └── requirements.txt # Python dependencies
├── orchestrator.tf # Orchestrator runtime configuration
├── specialist.tf # Specialist runtime configuration
├── main.tf # Main Terraform configuration
├── variables.tf # Input variables
├── outputs.tf # Output definitions
├── iam.tf # IAM roles and policies
├── s3.tf # S3 buckets for source code
├── ecr.tf # ECR repositories
├── codebuild.tf # CodeBuild projects
├── versions.tf # Terraform and provider versions
├── buildspec-orchestrator.yml # Orchestrator build specification
├── buildspec-specialist.yml # Specialist build specification
├── terraform.tfvars.example # Example variable values
├── backend.tf.example # Example backend configuration
├── deploy.sh # Deployment automation script
├── destroy.sh # Cleanup automation script
├── test_multi_agent.py # Infrastructure-agnostic test script
└── README.md # This file
```
## Monitoring and Observability
### CloudWatch Logs
```bash
# Orchestrator logs
aws logs tail /aws/bedrock-agentcore/agentcore-multi-agent-orchestrator-runtime --follow
# Specialist logs
aws logs tail /aws/bedrock-agentcore/agentcore-multi-agent-specialist-runtime --follow
```
### Metrics
Access metrics in CloudWatch:
- Agent invocation count
- Agent execution duration
- Error rates
- A2A call metrics
### AWS Console
Monitor in AWS Console:
- **Bedrock AgentCore**: [Console Link](https://console.aws.amazon.com/bedrock/home#/agentcore)
- **ECR Repositories**: View Docker images
- **CodeBuild**: Monitor build status
- **CloudWatch**: View logs and metrics
## Security
### IAM Permissions
**Agent1 Execution Role:**
- Standard AgentCore permissions
- **Critical**: `bedrock-agentcore:InvokeAgentRuntime` for Agent2
**Agent2 Execution Role:**
- Standard AgentCore permissions only
- No cross-agent invocation permissions needed
**CodeBuild Role:**
- S3 access to both agent source buckets
- ECR push access to both repositories
- CloudWatch Logs write access
### Network Security
- Agents run in specified network mode (PUBLIC/PRIVATE)
- ECR repositories have account-level access controls
- S3 buckets block public access
- IAM policies follow least-privilege principle
### Secrets Management
For sensitive data:
- Use AWS Secrets Manager
- Pass secret ARNs as environment variables
- Retrieve secrets at runtime in agent code
## Pricing
For current pricing information, please refer to:
- [Amazon Bedrock Pricing](https://aws.amazon.com/bedrock/pricing/)
- [Amazon ECR Pricing](https://aws.amazon.com/ecr/pricing/)
- [AWS CodeBuild Pricing](https://aws.amazon.com/codebuild/pricing/)
- [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/)
- [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)
**Note**: Actual costs depend on your usage patterns, AWS region, and specific services consumed.
## Troubleshooting
### Common Issues
**Issue**: Agent1 fails to invoke Agent2
- **Solution**: Verify AGENT2_ARN environment variable is set
- **Check**: IAM permissions include InvokeAgentRuntime
**Issue**: Build fails
- **Solution**: Check CodeBuild logs in CloudWatch
- **Check**: Verify source code is in correct directories
**Issue**: Runtime not created
- **Solution**: Verify ECR image exists and is tagged correctly
- **Check**: Review Terraform state for errors
### Debug Commands
```bash
# Check Terraform state
terraform show
# Validate configuration
terraform validate
# View specific resource
terraform state show aws_bedrockagentcore_agent_runtime.orchestrator
# Get detailed build logs
PROJECT_NAME=$(terraform output -raw orchestrator_codebuild_project)
aws codebuild batch-get-builds --ids $(aws codebuild list-builds-for-project --project-name $PROJECT_NAME --query 'ids[0]' --output text)
```
## Cleanup
### Automated Cleanup
```bash
chmod +x destroy.sh
./destroy.sh
```
The script shows the destruction plan, requires confirmation, and destroys all resources.
### Manual Cleanup
```bash
terraform destroy
```
**Important**: Verify in AWS Console that all resources are deleted:
- Bedrock AgentCore runtimes
- ECR repositories
- S3 buckets
- CodeBuild projects
- IAM roles
## Advanced Topics
### Adding Custom Tools
1. Define tool schema in agent code
2. Implement tool handler function
3. Register tool with agent
4. Rebuild and deploy
### Implementing Memory
Add session management in agent code:
```python
session_data = {}
def handle_request(input_text, session_id):
if session_id not in session_data:
session_data[session_id] = {}
# Use session_data for context
```
### Multi-Region Deployment
For multi-region:
1. Configure backend for state locking
2. Deploy to each region separately
3. Use Route53 for failover
4. Consider cross-region replication for S3/ECR
## Next Steps
1. **Test the deployment**
```bash
python test_multi_agent.py $(terraform output -raw orchestrator_runtime_arn) $(terraform output -raw specialist_runtime_arn)
```
2. **Customize agents** for your specific use case
- Add domain-specific tools to agents
- Implement custom business logic
- Integrate with external APIs
3. **Explore related patterns**
- [MCP Server Pattern](../mcp-server-agentcore-runtime/) - MCP protocol with JWT auth
- [AgentCore Samples](https://github.com/aws-samples/amazon-bedrock-agentcore-samples) - More examples
4. **Add production features**
- Monitoring and alerting
- Custom authentication layer (if needed)
- VPC deployment for private networking
- CI/CD pipeline integration
## Resources
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html)
- [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
- [Agent-to-Agent Communication](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore-a2a.html)
- [Model Context Protocol](https://modelcontextprotocol.io/)
## Contributing
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
## License
This project is licensed under the MIT-0 license. See the [LICENSE](../../../LICENSE) file for details.
@@ -0,0 +1,17 @@
FROM public.ecr.aws/docker/library/python:3.11-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN pip install aws-opentelemetry-distro>=0.10.1
# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 8080
EXPOSE 8000
COPY . .
CMD ["opentelemetry-instrument", "python", "-m", "agent"]
@@ -0,0 +1,123 @@
from strands import Agent, tool
from typing import Dict, Any
import boto3
import json
import os
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
# Environment variable for Specialist Agent ARN (required - set by Terraform)
SPECIALIST_ARN = os.getenv('SPECIALIST_ARN')
if not SPECIALIST_ARN:
raise EnvironmentError("SPECIALIST_ARN environment variable is required")
def invoke_specialist(query: str) -> str:
"""Helper function to invoke specialist agent using boto3"""
try:
# Get region from environment (set by AgentCore runtime)
region = os.getenv('AWS_REGION')
if not region:
raise EnvironmentError("AWS_REGION environment variable is required")
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
# Invoke specialist agent runtime (using AWS sample format)
response = agentcore_client.invoke_agent_runtime(
agentRuntimeArn=SPECIALIST_ARN,
qualifier="DEFAULT",
payload=json.dumps({"prompt": query})
)
# Handle streaming response (text/event-stream)
if "text/event-stream" in response.get("contentType", ""):
result = ""
for line in response["response"].iter_lines(chunk_size=10):
if line:
line = line.decode("utf-8")
# Remove 'data: ' prefix if present
if line.startswith("data: "):
line = line[6:]
result += line
return result
# Handle JSON response
elif response.get("contentType") == "application/json":
content = []
for chunk in response.get("response", []):
content.append(chunk.decode('utf-8'))
response_data = json.loads(''.join(content))
return json.dumps(response_data)
# Handle other response types
else:
response_body = response['response'].read()
return response_body.decode('utf-8')
except Exception as e:
import traceback
error_details = traceback.format_exc()
return f"Error invoking specialist agent: {str(e)}\nDetails: {error_details}"
@tool
def call_specialist_agent(query: str) -> Dict[str, Any]:
"""
Call the specialist agent for detailed analysis or complex tasks.
Use this tool when you need expert analysis or detailed information.
Args:
query: The question or task to send to the specialist agent
Returns:
The specialist agent's response
"""
result = invoke_specialist(query)
return {
"status": "success",
"content": [{"text": result}]
}
def create_orchestrator_agent() -> Agent:
"""Create the orchestrator agent with the tool to call specialist agent"""
system_prompt = """You are an orchestrator agent.
You can handle simple queries directly, but for complex analytical tasks,
you should delegate to the specialist agent using the call_specialist_agent tool.
Use the specialist agent when:
- The query requires detailed analysis
- The query is about complex topics
- The user explicitly asks for expert analysis
Handle simple queries (greetings, basic questions) yourself."""
return Agent(
tools=[call_specialist_agent],
system_prompt=system_prompt,
name="OrchestratorAgent"
)
@app.entrypoint
async def invoke(payload=None):
"""Main entrypoint for orchestrator agent"""
try:
# Get the query from payload
query = payload.get("prompt", "Hello, how are you?") if payload else "Hello, how are you?"
# Create and use the orchestrator agent
agent = create_orchestrator_agent()
response = agent(query)
return {
"status": "success",
"agent": "orchestrator",
"response": response.message['content'][0]['text']
}
except Exception as e:
return {
"status": "error",
"agent": "orchestrator",
"error": str(e)
}
if __name__ == "__main__":
app.run()
@@ -0,0 +1,4 @@
strands-agents
boto3>=1.40.0
botocore>=1.40.0
bedrock-agentcore
@@ -0,0 +1,17 @@
FROM public.ecr.aws/docker/library/python:3.11-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN pip install aws-opentelemetry-distro>=0.10.1
# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 8080
EXPOSE 8000
COPY . .
CMD ["opentelemetry-instrument", "python", "-m", "agent"]
@@ -0,0 +1,43 @@
from strands import Agent
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
def create_specialist_agent() -> Agent:
"""Create a specialist agent that handles specific analytical tasks"""
system_prompt = """You are a specialist analytical agent.
You are an expert at analyzing data and providing detailed insights.
When asked questions, provide thorough, well-reasoned responses with specific details.
Focus on accuracy and completeness in your answers."""
return Agent(
system_prompt=system_prompt,
name="SpecialistAgent"
)
@app.entrypoint
async def invoke(payload=None):
"""Main entrypoint for specialist agent"""
try:
# Get the query from payload
query = payload.get("prompt", "Hello") if payload else "Hello"
# Create and use the specialist agent
agent = create_specialist_agent()
response = agent(query)
return {
"status": "success",
"agent": "specialist",
"response": response.message['content'][0]['text']
}
except Exception as e:
return {
"status": "error",
"agent": "specialist",
"error": str(e)
}
if __name__ == "__main__":
app.run()
@@ -0,0 +1,4 @@
strands-agents
boto3>=1.40.0
botocore>=1.40.0
bedrock-agentcore
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

@@ -0,0 +1,33 @@
# ============================================================================
# Terraform Backend Configuration - Remote State Storage
# ============================================================================
# This is an example backend configuration for storing Terraform state remotely
# Copy this file to backend.tf and customize with your backend settings
# Example: cp backend.tf.example backend.tf
# IMPORTANT: Uncomment and configure ONE of the backend options below
# Option 1: S3 Backend (Recommended for AWS)
# terraform {
# backend "s3" {
# bucket = "your-terraform-state-bucket"
# key = "agentcore/multi-agent-runtime/terraform.tfstate"
# region = "us-west-2"
# encrypt = true
# dynamodb_table = "terraform-state-lock"
# }
# }
# Option 2: Terraform Cloud
# terraform {
# cloud {
# organization = "your-organization"
# workspaces {
# name = "agentcore-basic-runtime"
# }
# }
# }
# Option 3: Local Backend (Default - No configuration needed)
# State will be stored locally in terraform.tfstate
# Not recommended for team environments or production use
@@ -0,0 +1,29 @@
version: 0.2
phases:
install:
commands:
- echo Installing dependencies...
- yum install -y unzip
pre_build:
commands:
- echo Source code already extracted by CodeBuild...
- cd $CODEBUILD_SRC_DIR
- ls -la
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image for Orchestrator Agent ARM64...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Orchestrator Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo Orchestrator Agent ARM64 Docker image pushed successfully
@@ -0,0 +1,29 @@
version: 0.2
phases:
install:
commands:
- echo Installing dependencies...
- yum install -y unzip
pre_build:
commands:
- echo Source code already extracted by CodeBuild...
- cd $CODEBUILD_SRC_DIR
- ls -la
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image for Specialist Agent ARM64...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Specialist Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo Specialist Agent ARM64 Docker image pushed successfully
@@ -0,0 +1,154 @@
# ============================================================================
# CodeBuild Project - Build and Push Orchestrator Agent Docker Image
# ============================================================================
resource "aws_codebuild_project" "orchestrator_image" {
name = "${var.stack_name}-orchestrator-build"
description = "Build Orchestrator agent Docker image for ${var.stack_name}"
service_role = aws_iam_role.codebuild.arn
build_timeout = 60
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_LARGE"
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
type = "ARM_CONTAINER"
privileged_mode = true
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "AWS_DEFAULT_REGION"
value = data.aws_region.current.id
}
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.id
}
environment_variable {
name = "IMAGE_REPO_NAME"
value = aws_ecr_repository.orchestrator.name
}
environment_variable {
name = "IMAGE_TAG"
value = var.image_tag
}
environment_variable {
name = "STACK_NAME"
value = var.stack_name
}
environment_variable {
name = "AGENT_NAME"
value = "orchestrator"
}
}
source {
type = "S3"
location = "${aws_s3_bucket.orchestrator_source.id}/${aws_s3_object.orchestrator_source.key}"
buildspec = file("${path.module}/buildspec-orchestrator.yml")
}
logs_config {
cloudwatch_logs {
group_name = "/aws/codebuild/${var.stack_name}-orchestrator-build"
}
}
tags = {
Name = "${var.stack_name}-orchestrator-build"
Module = "CodeBuild"
Agent = "Orchestrator"
}
depends_on = [
aws_iam_role_policy.codebuild
]
}
# ============================================================================
# CodeBuild Project - Build and Push Specialist Agent Docker Image
# ============================================================================
resource "aws_codebuild_project" "specialist_image" {
name = "${var.stack_name}-specialist-build"
description = "Build Specialist agent Docker image for ${var.stack_name}"
service_role = aws_iam_role.codebuild.arn
build_timeout = 60
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_LARGE"
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
type = "ARM_CONTAINER"
privileged_mode = true
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "AWS_DEFAULT_REGION"
value = data.aws_region.current.id
}
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.id
}
environment_variable {
name = "IMAGE_REPO_NAME"
value = aws_ecr_repository.specialist.name
}
environment_variable {
name = "IMAGE_TAG"
value = var.image_tag
}
environment_variable {
name = "STACK_NAME"
value = var.stack_name
}
environment_variable {
name = "AGENT_NAME"
value = "specialist"
}
}
source {
type = "S3"
location = "${aws_s3_bucket.specialist_source.id}/${aws_s3_object.specialist_source.key}"
buildspec = file("${path.module}/buildspec-specialist.yml")
}
logs_config {
cloudwatch_logs {
group_name = "/aws/codebuild/${var.stack_name}-specialist-build"
}
}
tags = {
Name = "${var.stack_name}-specialist-build"
Module = "CodeBuild"
Agent = "Specialist"
}
depends_on = [
aws_iam_role_policy.codebuild
]
}
# ============================================================================
# Note: Build triggers are defined in main.tf for proper sequencing
# Specialist builds first, then Orchestrator (which depends on Specialist ARN)
# ============================================================================
@@ -0,0 +1,252 @@
#!/bin/bash
# ============================================================================
# Deploy Script for Multi-Agent Runtime (Terraform)
# ============================================================================
# This script automates the deployment process for the Multi-Agent Terraform configuration
# Usage: ./deploy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_info "Starting Multi-Agent Runtime Deployment..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed. Please install Terraform >= 1.6"
print_info "Visit: https://www.terraform.io/downloads"
exit 1
fi
# Check Terraform version
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
print_success "Terraform version: $TERRAFORM_VERSION"
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
exit 1
fi
print_success "AWS CLI is installed"
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
print_info "Run: aws configure"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_success "AWS Account: $AWS_ACCOUNT"
print_success "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Configuration Check
# ============================================================================
print_info "Checking configuration files..."
# Check if terraform.tfvars exists
if [ ! -f "terraform.tfvars" ]; then
print_warning "terraform.tfvars not found"
print_info "Creating terraform.tfvars from example..."
if [ -f "terraform.tfvars.example" ]; then
cp terraform.tfvars.example terraform.tfvars
print_success "Created terraform.tfvars"
print_warning "Please review and update terraform.tfvars with your settings"
print_info "Then run this script again"
exit 0
else
print_error "terraform.tfvars.example not found"
exit 1
fi
fi
print_success "Configuration file found: terraform.tfvars"
echo ""
# ============================================================================
# Terraform Initialization
# ============================================================================
print_info "Initializing Terraform..."
if terraform init; then
print_success "Terraform initialized successfully"
else
print_error "Terraform initialization failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Validation
# ============================================================================
print_info "Validating Terraform configuration..."
if terraform validate; then
print_success "Terraform configuration is valid"
else
print_error "Terraform validation failed"
exit 1
fi
echo ""
# ============================================================================
# Terraform Format Check
# ============================================================================
print_info "Checking Terraform formatting..."
if terraform fmt -check -recursive > /dev/null 2>&1; then
print_success "Terraform files are properly formatted"
else
print_warning "Some files need formatting. Running terraform fmt..."
terraform fmt -recursive
print_success "Files formatted"
fi
echo ""
# ============================================================================
# Terraform Plan
# ============================================================================
print_info "Creating Terraform execution plan..."
print_warning "This may take a few moments..."
echo ""
if terraform plan -out=tfplan; then
print_success "Terraform plan created successfully"
else
print_error "Terraform plan failed"
exit 1
fi
echo ""
# ============================================================================
# Deployment Confirmation
# ============================================================================
print_warning "========================================"
print_warning "DEPLOYMENT CONFIRMATION"
print_warning "========================================"
print_info "This will deploy the following resources:"
print_info " - 2x S3 Buckets (Orchestrator & Specialist source code storage)"
print_info " - 2x ECR Repositories (Orchestrator & Specialist)"
print_info " - 2x CodeBuild Projects (Orchestrator & Specialist)"
print_info " - IAM Roles and Policies (with A2A permissions)"
print_info " - Specialist Runtime (Independent)"
print_info " - Orchestrator Runtime (Depends on Specialist)"
echo ""
print_info "The deployment includes:"
print_info " - Building ARM64 Docker images for both agents"
print_info " - Creating AWS resources with proper dependencies"
print_info " - Setting up Agent-to-Agent (A2A) communication"
echo ""
read -p "Do you want to proceed with deployment? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Deployment cancelled by user"
rm -f tfplan
exit 0
fi
# ============================================================================
# Terraform Apply
# ============================================================================
print_info "Starting deployment..."
echo ""
if terraform apply tfplan; then
print_success "Deployment completed successfully!"
else
print_error "Deployment failed"
rm -f tfplan
exit 1
fi
# Clean up plan file
rm -f tfplan
echo ""
# ============================================================================
# Deployment Summary
# ============================================================================
print_success "========================================"
print_success "DEPLOYMENT COMPLETED"
print_success "========================================"
echo ""
print_info "Retrieving deployment outputs..."
echo ""
# Get outputs
ORCHESTRATOR_ID=$(terraform output -raw orchestrator_runtime_id 2>/dev/null || echo "N/A")
ORCHESTRATOR_ARN=$(terraform output -raw orchestrator_runtime_arn 2>/dev/null || echo "N/A")
SPECIALIST_ID=$(terraform output -raw specialist_runtime_id 2>/dev/null || echo "N/A")
SPECIALIST_ARN=$(terraform output -raw specialist_runtime_arn 2>/dev/null || echo "N/A")
print_success "Orchestrator Runtime ID: $ORCHESTRATOR_ID"
print_success "Orchestrator Runtime ARN: $ORCHESTRATOR_ARN"
echo ""
print_success "Specialist Runtime ID: $SPECIALIST_ID"
print_success "Specialist Runtime ARN: $SPECIALIST_ARN"
echo ""
print_info "Next Steps:"
print_info "1. Test the multi-agent system:"
print_info " python test_multi_agent.py"
echo ""
print_info "2. View all outputs (includes test commands):"
print_info " terraform output"
echo ""
print_info "3. Monitor in AWS Console:"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_success "Deployment completed successfully!"
@@ -0,0 +1,263 @@
#!/bin/bash
# ============================================================================
# Destroy Script for Multi-Agent Runtime (Terraform)
# ============================================================================
# This script safely destroys all resources created by this Terraform configuration
# Usage: ./destroy.sh
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================================================
# Pre-flight Checks
# ============================================================================
print_warning "Starting Resource Cleanup..."
echo ""
# Check Terraform installation
if ! command_exists terraform; then
print_error "Terraform is not installed"
exit 1
fi
# Check AWS CLI installation
if ! command_exists aws; then
print_error "AWS CLI is not installed"
exit 1
fi
# Check AWS credentials
if ! aws sts get-caller-identity > /dev/null 2>&1; then
print_error "AWS credentials are not configured or invalid"
exit 1
fi
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure get region)
print_info "AWS Account: $AWS_ACCOUNT"
print_info "AWS Region: $AWS_REGION"
echo ""
# ============================================================================
# Check for Terraform State
# ============================================================================
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
print_warning "No Terraform state found"
print_info "Either no resources have been deployed, or state is stored remotely"
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Initializing Terraform to fetch remote state..."
terraform init
else
print_info "Cleanup cancelled"
exit 0
fi
fi
# ============================================================================
# Show Destruction Plan
# ============================================================================
print_info "Creating destruction plan..."
echo ""
if ! terraform plan -destroy; then
print_error "Failed to create destruction plan"
exit 1
fi
echo ""
# ============================================================================
# Destruction Confirmation
# ============================================================================
print_warning "========================================"
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
print_warning "========================================"
print_warning "This will permanently delete the following resources:"
print_warning " - Orchestrator Runtime"
print_warning " - Specialist Runtime"
print_warning " - 2x S3 Buckets (source code storage)"
print_warning " - 2x ECR Repositories (including all images)"
print_warning " - 2x CodeBuild Projects"
print_warning " - IAM Roles and Policies (including A2A permissions)"
print_warning " - CloudWatch Log Groups"
echo ""
print_warning "THIS ACTION CANNOT BE UNDONE!"
echo ""
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
echo ""
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
echo ""
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
print_info "Destruction cancelled by user"
exit 0
fi
# Double confirmation for safety
print_warning "Second confirmation required..."
read -p "Type 'DESTROY' to confirm: " -r
echo ""
if [ "$REPLY" != "DESTROY" ]; then
print_info "Destruction cancelled - confirmation text did not match"
exit 0
fi
# ============================================================================
# Execute Destruction
# ============================================================================
print_warning "Starting resource destruction..."
echo ""
if terraform destroy -auto-approve; then
print_success "All resources destroyed successfully"
else
print_error "Destruction failed"
print_warning "Some resources may still exist. Please check AWS Console"
exit 1
fi
echo ""
# ============================================================================
# Cleanup Local Files
# ============================================================================
print_info "Cleaning up local Terraform files..."
# Ask about state file cleanup
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -f terraform.tfstate
rm -f terraform.tfstate.backup
rm -f tfplan
print_success "Local state files removed"
fi
# Ask about .terraform directory
read -p "Do you want to remove .terraform directory? (yes/no): " -r
echo ""
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
rm -rf .terraform
rm -f .terraform.lock.hcl
print_success ".terraform directory removed"
fi
echo ""
# ============================================================================
# Verification
# ============================================================================
print_info "Verifying resource cleanup..."
echo ""
# Check for ECR repositories
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-basic")
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$ECR_REPOS" -eq 0 ]; then
print_success "ECR repositories cleaned up"
else
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for AgentCore runtimes (both agents)
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$RUNTIME_COUNT" -eq 0 ]; then
print_success "AgentCore runtimes cleaned up (Orchestrator and Specialist)"
else
print_warning "Found $RUNTIME_COUNT AgentCore runtimes matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
# Check for S3 buckets (both agent source buckets)
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
if [ "$S3_BUCKETS" -eq 0 ]; then
print_success "S3 buckets cleaned up (Orchestrator and Specialist source buckets)"
else
print_warning "Found $S3_BUCKETS S3 buckets matching '$STACK_NAME'"
print_info "These may need manual cleanup"
fi
echo ""
# ============================================================================
# Completion Summary
# ============================================================================
print_success "========================================"
print_success "CLEANUP COMPLETED"
print_success "========================================"
echo ""
print_info "Cleanup Summary:"
print_success " ✓ Terraform resources destroyed"
print_success " ✓ Local state files cleaned (if selected)"
echo ""
print_info "What to verify in AWS Console:"
print_info "1. Bedrock AgentCore - No runtimes remaining"
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
echo ""
print_info "2. S3 - No buckets remaining"
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
echo ""
print_info "3. ECR - No repositories remaining (Orchestrator & Specialist)"
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
echo ""
print_info "4. CodeBuild - No projects remaining (Orchestrator & Specialist)"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
echo ""
print_info "6. CloudWatch Logs - Check for orphaned log groups"
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
echo ""
print_success "Cleanup completed successfully!"
print_info "You can safely re-deploy by running: ./deploy.sh"
@@ -0,0 +1,127 @@
# ============================================================================
# ECR Repositories - Container Registries for Agent Images
# ============================================================================
# Orchestrator Agent ECR Repository
resource "aws_ecr_repository" "orchestrator" {
name = "${var.stack_name}-${var.ecr_repository_name}-orchestrator"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
force_delete = true
tags = {
Name = "${var.stack_name}-orchestrator-ecr-repository"
Module = "ECR"
Agent = "Orchestrator"
}
}
# Specialist Agent ECR Repository
resource "aws_ecr_repository" "specialist" {
name = "${var.stack_name}-${var.ecr_repository_name}-specialist"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
force_delete = true
tags = {
Name = "${var.stack_name}-specialist-ecr-repository"
Module = "ECR"
Agent = "Specialist"
}
}
# ECR Repository Policy - Orchestrator
resource "aws_ecr_repository_policy" "orchestrator" {
repository = aws_ecr_repository.orchestrator.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPullFromAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
})
}
# ECR Repository Policy - Specialist
resource "aws_ecr_repository_policy" "specialist" {
repository = aws_ecr_repository.specialist.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPullFromAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
})
}
# ECR Lifecycle Policy - Orchestrator - Keep last 5 images
resource "aws_ecr_lifecycle_policy" "orchestrator" {
repository = aws_ecr_repository.orchestrator.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 5 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 5
}
action = {
type = "expire"
}
}
]
})
}
# ECR Lifecycle Policy - Specialist - Keep last 5 images
resource "aws_ecr_lifecycle_policy" "specialist" {
repository = aws_ecr_repository.specialist.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 5 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 5
}
action = {
type = "expire"
}
}
]
})
}
@@ -0,0 +1,378 @@
# Data sources
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# ============================================================================
# Orchestrator Agent Execution Role - For AgentCore Runtime
# ============================================================================
resource "aws_iam_role" "orchestrator_execution" {
name = "${var.stack_name}-orchestrator-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AssumeRolePolicy"
Effect = "Allow"
Principal = {
Service = "bedrock-agentcore.amazonaws.com"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.id
}
ArnLike = {
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
}
}
}]
})
tags = {
Name = "${var.stack_name}-orchestrator-execution-role"
Module = "IAM"
Agent = "Orchestrator"
}
}
# Attach AWS managed policy for AgentCore - Orchestrator
resource "aws_iam_role_policy_attachment" "orchestrator_execution_managed" {
role = aws_iam_role.orchestrator_execution.name
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
}
# Inline policy for orchestrator execution
resource "aws_iam_role_policy" "orchestrator_execution" {
name = "OrchestratorCoreExecutionPolicy"
role = aws_iam_role.orchestrator_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# ECR Access for Orchestrator
{
Sid = "ECRImageAccess"
Effect = "Allow"
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
Resource = aws_ecr_repository.orchestrator.arn
},
{
Sid = "ECRTokenAccess"
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
},
# X-Ray Tracing
{
Sid = "XRayTracing"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets"
]
Resource = "*"
},
# CloudWatch Metrics
{
Sid = "CloudWatchMetrics"
Effect = "Allow"
Action = ["cloudwatch:PutMetricData"]
Resource = "*"
Condition = {
StringEquals = {
"cloudwatch:namespace" = "bedrock-agentcore"
}
}
},
# Bedrock Model Invocation
{
Sid = "BedrockModelInvocation"
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
]
Resource = "*"
},
# Workload Access Tokens
{
Sid = "GetAgentAccessToken"
Effect = "Allow"
Action = [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
]
Resource = [
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
]
}
]
})
}
# ============================================================================
# Orchestrator A2A Policy - Allows Orchestrator to Invoke Specialist
# ============================================================================
resource "aws_iam_role_policy" "orchestrator_invoke_specialist" {
name = "OrchestratorInvokeSpecialistPolicy"
role = aws_iam_role.orchestrator_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "InvokeSpecialistRuntime"
Effect = "Allow"
Action = [
"bedrock-agentcore:InvokeAgentRuntime"
]
Resource = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:runtime/*"
}
]
})
}
# ============================================================================
# Specialist Agent Execution Role - For AgentCore Runtime
# ============================================================================
resource "aws_iam_role" "specialist_execution" {
name = "${var.stack_name}-specialist-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AssumeRolePolicy"
Effect = "Allow"
Principal = {
Service = "bedrock-agentcore.amazonaws.com"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.id
}
ArnLike = {
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
}
}
}]
})
tags = {
Name = "${var.stack_name}-specialist-execution-role"
Module = "IAM"
Agent = "Specialist"
}
}
# Attach AWS managed policy for AgentCore - Specialist
resource "aws_iam_role_policy_attachment" "specialist_execution_managed" {
role = aws_iam_role.specialist_execution.name
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
}
# Inline policy for specialist execution
resource "aws_iam_role_policy" "specialist_execution" {
name = "SpecialistCoreExecutionPolicy"
role = aws_iam_role.specialist_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# ECR Access for Specialist
{
Sid = "ECRImageAccess"
Effect = "Allow"
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
Resource = aws_ecr_repository.specialist.arn
},
{
Sid = "ECRTokenAccess"
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
},
# X-Ray Tracing
{
Sid = "XRayTracing"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets"
]
Resource = "*"
},
# CloudWatch Metrics
{
Sid = "CloudWatchMetrics"
Effect = "Allow"
Action = ["cloudwatch:PutMetricData"]
Resource = "*"
Condition = {
StringEquals = {
"cloudwatch:namespace" = "bedrock-agentcore"
}
}
},
# Bedrock Model Invocation
{
Sid = "BedrockModelInvocation"
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
]
Resource = "*"
},
# Workload Access Tokens
{
Sid = "GetAgentAccessToken"
Effect = "Allow"
Action = [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
]
Resource = [
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
]
}
]
})
}
# ============================================================================
# CodeBuild Service Role - For Docker Image Building (Both Agents)
# ============================================================================
resource "aws_iam_role" "codebuild" {
name = "${var.stack_name}-codebuild-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
tags = {
Name = "${var.stack_name}-codebuild-role"
Module = "IAM"
}
}
# Inline policy for CodeBuild
resource "aws_iam_role_policy" "codebuild" {
name = "CodeBuildPolicy"
role = aws_iam_role.codebuild.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# CloudWatch Logs
{
Sid = "CloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
},
# ECR Access for both repositories
{
Sid = "ECRAccess"
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
Resource = [
aws_ecr_repository.orchestrator.arn,
aws_ecr_repository.specialist.arn,
"*"
]
},
# S3 Source Access for both agent code buckets
{
Sid = "S3SourceAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion"
]
Resource = [
"${aws_s3_bucket.orchestrator_source.arn}/*",
"${aws_s3_bucket.specialist_source.arn}/*"
]
},
{
Sid = "S3BucketAccess"
Effect = "Allow"
Action = [
"s3:ListBucket",
"s3:GetBucketLocation"
]
Resource = [
aws_s3_bucket.orchestrator_source.arn,
aws_s3_bucket.specialist_source.arn
]
}
]
})
}
@@ -0,0 +1,65 @@
# ============================================================================
# Wait for IAM propagation before triggering builds
# ============================================================================
resource "time_sleep" "wait_for_iam" {
depends_on = [
aws_iam_role_policy.codebuild,
aws_iam_role_policy.orchestrator_execution,
aws_iam_role_policy.specialist_execution
]
create_duration = "30s"
}
# ============================================================================
# Trigger CodeBuild - Sequential Build Process
# Specialist builds first (independent), then Orchestrator (depends on Specialist)
# ============================================================================
# Trigger Specialist Build (Independent - Builds First)
resource "null_resource" "trigger_build_specialist" {
triggers = {
build_project = aws_codebuild_project.specialist_image.id
image_tag = var.image_tag
ecr_repository = aws_ecr_repository.specialist.id
source_code_md5 = data.archive_file.specialist_source.output_md5
}
provisioner "local-exec" {
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.specialist_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.specialist.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.specialist.repository_url}\""
}
depends_on = [
aws_codebuild_project.specialist_image,
aws_ecr_repository.specialist,
aws_iam_role_policy.codebuild,
aws_s3_object.specialist_source,
time_sleep.wait_for_iam
]
}
# Trigger Orchestrator Build (Depends on Specialist Build Completion)
resource "null_resource" "trigger_build_orchestrator" {
triggers = {
build_project = aws_codebuild_project.orchestrator_image.id
image_tag = var.image_tag
ecr_repository = aws_ecr_repository.orchestrator.id
source_code_md5 = data.archive_file.orchestrator_source.output_md5
# Also rebuild if Specialist build changes
specialist_build = null_resource.trigger_build_specialist.id
}
provisioner "local-exec" {
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.orchestrator_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.orchestrator.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.orchestrator.repository_url}\""
}
depends_on = [
aws_codebuild_project.orchestrator_image,
aws_ecr_repository.orchestrator,
aws_iam_role_policy.codebuild,
aws_s3_object.orchestrator_source,
null_resource.trigger_build_specialist, # CRITICAL: Wait for Specialist build
time_sleep.wait_for_iam
]
}
@@ -0,0 +1,42 @@
# ============================================================================
# Orchestrator Agent Runtime - Depends on Specialist Agent
# ============================================================================
resource "aws_bedrockagentcore_agent_runtime" "orchestrator" {
agent_runtime_name = "${replace(var.stack_name, "-", "_")}_${var.orchestrator_name}"
description = "Orchestrator agent runtime for ${var.stack_name}"
role_arn = aws_iam_role.orchestrator_execution.arn
agent_runtime_artifact {
container_configuration {
container_uri = "${aws_ecr_repository.orchestrator.repository_url}:${var.image_tag}"
}
}
network_configuration {
network_mode = var.network_mode
}
# CRITICAL: Specialist Agent ARN for A2A communication
environment_variables = {
AWS_REGION = data.aws_region.current.id
AWS_DEFAULT_REGION = data.aws_region.current.id
SPECIALIST_ARN = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_arn
}
tags = {
Name = "${var.stack_name}-orchestrator-runtime"
Environment = "production"
Module = "BedrockAgentCore"
Agent = "Orchestrator"
}
# CRITICAL: Must wait for Specialist Agent to be created first
depends_on = [
aws_bedrockagentcore_agent_runtime.specialist,
null_resource.trigger_build_orchestrator,
aws_iam_role_policy.orchestrator_execution,
aws_iam_role_policy.orchestrator_invoke_specialist,
aws_iam_role_policy_attachment.orchestrator_execution_managed
]
}
@@ -0,0 +1,110 @@
# ============================================================================
# Orchestrator Agent Outputs
# ============================================================================
output "orchestrator_runtime_id" {
description = "ID of orchestrator agent runtime"
value = aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_id
}
output "orchestrator_runtime_arn" {
description = "ARN of orchestrator agent runtime"
value = aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_arn
}
output "orchestrator_runtime_version" {
description = "Version of orchestrator agent runtime"
value = aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_version
}
output "orchestrator_ecr_repository_url" {
description = "URL of the ECR repository for orchestrator agent"
value = aws_ecr_repository.orchestrator.repository_url
}
output "orchestrator_execution_role_arn" {
description = "ARN of the orchestrator agent execution role"
value = aws_iam_role.orchestrator_execution.arn
}
# ============================================================================
# Specialist Agent Outputs
# ============================================================================
output "specialist_runtime_id" {
description = "ID of specialist agent runtime"
value = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_id
}
output "specialist_runtime_arn" {
description = "ARN of specialist agent runtime"
value = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_arn
}
output "specialist_runtime_version" {
description = "Version of specialist agent runtime"
value = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_version
}
output "specialist_ecr_repository_url" {
description = "URL of the ECR repository for specialist agent"
value = aws_ecr_repository.specialist.repository_url
}
output "specialist_execution_role_arn" {
description = "ARN of the specialist agent execution role"
value = aws_iam_role.specialist_execution.arn
}
# ============================================================================
# Build & Storage Outputs
# ============================================================================
output "orchestrator_codebuild_project_name" {
description = "Name of the CodeBuild project for orchestrator agent"
value = aws_codebuild_project.orchestrator_image.name
}
output "specialist_codebuild_project_name" {
description = "Name of the CodeBuild project for specialist agent"
value = aws_codebuild_project.specialist_image.name
}
output "orchestrator_source_bucket_name" {
description = "S3 bucket containing orchestrator agent source code"
value = aws_s3_bucket.orchestrator_source.id
}
output "specialist_source_bucket_name" {
description = "S3 bucket containing specialist agent source code"
value = aws_s3_bucket.specialist_source.id
}
output "orchestrator_source_code_md5" {
description = "MD5 hash of orchestrator source code (triggers rebuild when changed)"
value = data.archive_file.orchestrator_source.output_md5
}
output "specialist_source_code_md5" {
description = "MD5 hash of specialist source code (triggers rebuild when changed)"
value = data.archive_file.specialist_source.output_md5
}
# ============================================================================
# Testing Information
# ============================================================================
output "test_orchestrator_command" {
description = "AWS CLI command to test orchestrator agent"
value = "aws bedrock-agentcore invoke-agent-runtime --agent-runtime-id ${aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_id} --qualifier DEFAULT --payload '{\"prompt\": \"Hello, how are you?\"}' --region ${data.aws_region.current.id} response.json"
}
output "test_specialist_command" {
description = "AWS CLI command to test specialist agent"
value = "aws bedrock-agentcore invoke-agent-runtime --agent-runtime-id ${aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_id} --qualifier DEFAULT --payload '{\"prompt\": \"Explain cloud computing\"}' --region ${data.aws_region.current.id} response.json"
}
output "test_script_command" {
description = "Command to test multi-agent communication"
value = "python test_multi_agent.py ${aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_arn}"
}
@@ -0,0 +1,109 @@
# ============================================================================
# S3 Buckets for Agent Source Code (CDK Asset Equivalent)
# ============================================================================
# Orchestrator Agent Source Bucket
resource "aws_s3_bucket" "orchestrator_source" {
bucket_prefix = "acma-orch-src-" # Shortened to fit 37 char limit
force_destroy = true
tags = {
Name = "${var.stack_name}-orchestrator-source"
Purpose = "Store Orchestrator agent source code for CodeBuild"
}
}
# Specialist Agent Source Bucket
resource "aws_s3_bucket" "specialist_source" {
bucket_prefix = "acma-spec-src-" # Shortened to fit 37 char limit
force_destroy = true
tags = {
Name = "${var.stack_name}-specialist-source"
Purpose = "Store Specialist agent source code for CodeBuild"
}
}
# Block public access - Orchestrator
resource "aws_s3_bucket_public_access_block" "orchestrator_source" {
bucket = aws_s3_bucket.orchestrator_source.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Block public access - Specialist
resource "aws_s3_bucket_public_access_block" "specialist_source" {
bucket = aws_s3_bucket.specialist_source.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning - Orchestrator
resource "aws_s3_bucket_versioning" "orchestrator_source" {
bucket = aws_s3_bucket.orchestrator_source.id
versioning_configuration {
status = "Enabled"
}
}
# Enable versioning - Specialist
resource "aws_s3_bucket_versioning" "specialist_source" {
bucket = aws_s3_bucket.specialist_source.id
versioning_configuration {
status = "Enabled"
}
}
# ============================================================================
# Archive and Upload Agent Source Code
# ============================================================================
# Archive agent-orchestrator-code/ directory
data "archive_file" "orchestrator_source" {
type = "zip"
source_dir = "${path.module}/agent-orchestrator-code"
output_path = "${path.module}/.terraform/agent-orchestrator-code.zip"
}
# Archive agent-specialist-code/ directory
data "archive_file" "specialist_source" {
type = "zip"
source_dir = "${path.module}/agent-specialist-code"
output_path = "${path.module}/.terraform/agent-specialist-code.zip"
}
# Upload Orchestrator source to S3
resource "aws_s3_object" "orchestrator_source" {
bucket = aws_s3_bucket.orchestrator_source.id
key = "agent-orchestrator-code-${data.archive_file.orchestrator_source.output_md5}.zip"
source = data.archive_file.orchestrator_source.output_path
etag = data.archive_file.orchestrator_source.output_md5
tags = {
Name = "agent-orchestrator-source-code"
Agent = "Orchestrator"
MD5 = data.archive_file.orchestrator_source.output_md5
}
}
# Upload Specialist source to S3
resource "aws_s3_object" "specialist_source" {
bucket = aws_s3_bucket.specialist_source.id
key = "agent-specialist-code-${data.archive_file.specialist_source.output_md5}.zip"
source = data.archive_file.specialist_source.output_path
etag = data.archive_file.specialist_source.output_md5
tags = {
Name = "agent-specialist-source-code"
Agent = "Specialist"
MD5 = data.archive_file.specialist_source.output_md5
}
}
@@ -0,0 +1,196 @@
#!/bin/bash
# ============================================================================
# Build and Verify Docker Image for AgentCore Runtime
# ============================================================================
# This script is called by Terraform during deployment to:
# 1. Trigger CodeBuild to build the Docker image
# 2. Wait for the build to complete
# 3. Verify the image was successfully pushed to ECR
#
# Parameters:
# $1 - CodeBuild project name
# $2 - AWS region
# $3 - ECR repository name
# $4 - Image tag
# $5 - ECR repository URL
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Parameters
PROJECT_NAME="$1"
REGION="$2"
REPO_NAME="$3"
IMAGE_TAG="$4"
REPO_URL="$5"
# ============================================================================
# Print functions
# ============================================================================
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[✓]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[⚠]${NC} $1"
}
print_error() {
echo -e "${RED}[✗]${NC} $1"
}
print_header() {
echo ""
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================${NC}"
}
# ============================================================================
# Start Build Process
# ============================================================================
print_header "Building Docker Image for AgentCore Runtime"
print_info "CodeBuild Project: $PROJECT_NAME"
print_info "Region: $REGION"
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
echo ""
# Start CodeBuild
print_info "Starting CodeBuild project..."
BUILD_ID=$(aws codebuild start-build \
--project-name "$PROJECT_NAME" \
--region "$REGION" \
--query 'build.id' \
--output text 2>&1)
if [ $? -ne 0 ]; then
print_error "Failed to start CodeBuild"
echo "$BUILD_ID"
exit 1
fi
print_success "Build started: $BUILD_ID"
print_info "Waiting for build to complete (typically 5-10 minutes)..."
echo ""
# ============================================================================
# Monitor Build Progress
# ============================================================================
ATTEMPT=0
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT + 1))
STATUS=$(aws codebuild batch-get-builds \
--ids "$BUILD_ID" \
--region "$REGION" \
--query 'builds[0].buildStatus' \
--output text 2>/dev/null)
if [ "$STATUS" != "IN_PROGRESS" ]; then
print_info "Build process completed with status: $STATUS"
break
fi
# Progress indicator
if [ $((ATTEMPT % 6)) -eq 0 ]; then
MINUTES=$((ATTEMPT / 6))
print_info "Build in progress... (${MINUTES} minutes elapsed)"
fi
sleep 10
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
print_error "Build timeout after 10 minutes"
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
exit 1
fi
echo ""
# ============================================================================
# Verify Image in ECR
# ============================================================================
print_header "Verifying Docker Image in ECR"
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
print_info "Waiting for ECR propagation..."
echo ""
sleep 5 # Brief wait for ECR to register the push
VERIFY_ATTEMPT=0
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
if aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" >/dev/null 2>&1; then
print_success "Docker image successfully verified in ECR!"
echo ""
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
# Get image details
IMAGE_SIZE=$(aws ecr describe-images \
--repository-name "$REPO_NAME" \
--image-ids imageTag="$IMAGE_TAG" \
--region "$REGION" \
--query 'imageDetails[0].imageSizeInBytes' \
--output text 2>/dev/null || echo "Unknown")
if [ "$IMAGE_SIZE" != "Unknown" ]; then
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
fi
echo ""
print_success "Build and verification completed successfully!"
exit 0
fi
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
fi
sleep 5
done
# ============================================================================
# Error: Image Not Found
# ============================================================================
print_error "Docker image not found in ECR after build completion"
echo ""
print_warning "This indicates the build or push step failed."
print_info "Troubleshooting steps:"
print_info " 1. Check CodeBuild logs:"
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
print_info ""
print_info " 2. Verify ECR repository:"
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
print_info ""
print_info " 3. Check IAM permissions for CodeBuild role"
exit 1
@@ -0,0 +1,37 @@
# ============================================================================
# Specialist Agent Runtime - Independent Agent
# ============================================================================
resource "aws_bedrockagentcore_agent_runtime" "specialist" {
agent_runtime_name = "${replace(var.stack_name, "-", "_")}_${var.specialist_name}"
description = "Specialist agent runtime for ${var.stack_name}"
role_arn = aws_iam_role.specialist_execution.arn
agent_runtime_artifact {
container_configuration {
container_uri = "${aws_ecr_repository.specialist.repository_url}:${var.image_tag}"
}
}
network_configuration {
network_mode = var.network_mode
}
environment_variables = {
AWS_REGION = data.aws_region.current.id
AWS_DEFAULT_REGION = data.aws_region.current.id
}
tags = {
Name = "${var.stack_name}-specialist-runtime"
Environment = "production"
Module = "BedrockAgentCore"
Agent = "Specialist"
}
depends_on = [
null_resource.trigger_build_specialist,
aws_iam_role_policy.specialist_execution,
aws_iam_role_policy_attachment.specialist_execution_managed
]
}
@@ -0,0 +1,35 @@
# ============================================================================
# Multi-Agent Runtime - Example Configuration
# ============================================================================
# Copy this file to terraform.tfvars and customize
# Example: cp terraform.tfvars.example terraform.tfvars
# Agent Configuration
orchestrator_name = "OrchestratorAgent"
specialist_name = "SpecialistAgent"
stack_name = "agentcore-multi-agent"
# Network Configuration
network_mode = "PUBLIC" # PUBLIC or PRIVATE
# Container Configuration
ecr_repository_name = "multi-agent"
image_tag = "latest"
# AWS Configuration
aws_region = "us-west-2"
environment = "dev"
# Optional: Environment Variables (if needed for custom configurations)
# environment_variables = {
# LOG_LEVEL = "INFO"
# }
# Notes:
# - Orchestrator Agent: Receives user requests and delegates to Specialist
# - Specialist Agent: Provides specialized data processing capabilities
# - Orchestrator code in agent-orchestrator-code/ directory (includes A2A invocation logic)
# - Specialist code in agent-specialist-code/ directory (independent specialist)
# - A2A communication: Orchestrator can invoke Specialist via call_specialist_agent tool
# - Sequential deployment: Specialist builds first, then Orchestrator
# - Test with: python test_multi_agent.py
@@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""
Multi-Agent System Test Script
This script tests a multi-agent system with Agent-to-Agent (A2A) communication.
It can work with agents deployed via any method (Terraform, CloudFormation, CDK, manual).
Usage:
python test_multi_agent.py <orchestrator_arn> [specialist_arn]
orchestrator_arn: ARN of the orchestrator agent (required)
specialist_arn: ARN of the specialist agent (optional, for independent testing)
Examples:
# Test orchestrator with A2A communication
python test_multi_agent.py arn:aws:bedrock-agentcore:<region>:123456789012:runtime/orchestrator-id
# Test both agents independently
python test_multi_agent.py \\
arn:aws:bedrock-agentcore:<region>:123456789012:runtime/orchestrator-id \\
arn:aws:bedrock-agentcore:<region>:123456789012:runtime/specialist-id
"""
import boto3
import json
import sys
def extract_region_from_arn(arn):
"""Extract AWS region from agent runtime ARN.
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
Args:
arn: Agent runtime ARN string
Returns:
str: AWS region code
Raises:
ValueError: If ARN format is invalid or region cannot be extracted
"""
try:
parts = arn.split(':')
if len(parts) < 4:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
region = parts[3]
if not region:
raise ValueError(
f"Region not found in ARN: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
return region
except IndexError:
raise ValueError(
f"Invalid ARN format: {arn}\n"
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
)
def test_agent(client, agent_arn, agent_name, prompt):
"""Test a single agent with a given prompt
Args:
client: boto3 bedrock-agentcore client
agent_arn: ARN of the agent runtime
agent_name: Name for display purposes
prompt: Test prompt to send
"""
print(f"\nPrompt: '{prompt}'")
print("-" * 80)
try:
response = client.invoke_agent_runtime(
agentRuntimeArn=agent_arn,
qualifier="DEFAULT",
payload=json.dumps({"prompt": prompt})
)
print(f"Status: {response['ResponseMetadata']['HTTPStatusCode']}")
print(f"Content Type: {response.get('contentType', 'N/A')}")
# Read the streaming response body
response_text = ""
if 'response' in response:
response_body = response['response'].read()
response_text = response_body.decode('utf-8')
if response_text:
try:
result = json.loads(response_text)
response_content = result.get('response', response_text)
# Truncate long responses for readability
if len(response_content) > 500:
print(f"\n✅ Response:\n{response_content[:500]}...")
print("\n[Response truncated for display]")
else:
print(f"\n✅ Response:\n{response_content}")
except json.JSONDecodeError:
if len(response_text) > 500:
print(f"\n✅ Response:\n{response_text[:500]}...")
else:
print(f"\n✅ Response:\n{response_text}")
else:
print("\n⚠️ No response content received")
return True
except Exception as e:
print(f"\n❌ Error testing {agent_name}: {e}")
return False
def test_multi_agent(orchestrator_arn, specialist_arn=None):
"""Test the multi-agent system
Args:
orchestrator_arn: Orchestrator agent runtime ARN (required)
specialist_arn: Specialist agent runtime ARN (optional)
"""
# Extract region from orchestrator ARN
try:
region = extract_region_from_arn(orchestrator_arn)
except ValueError as e:
print(f"\n❌ ERROR: {e}\n")
sys.exit(1)
print("\n" + "="*80)
print("MULTI-AGENT SYSTEM TEST")
print("="*80)
print(f"\nOrchestrator Agent ARN: {orchestrator_arn}")
if specialist_arn:
print(f"Specialist Agent ARN: {specialist_arn}")
else:
print("Specialist Agent: Not provided (will test Orchestrator only)")
print(f"Region: {region}")
# Create bedrock-agentcore client with extracted region
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
test_results = []
# Test 1: Simple query to Orchestrator
print("\n" + "="*80)
print("TEST 1: Simple Query (Orchestrator)")
print("="*80)
result = test_agent(
agentcore_client,
orchestrator_arn,
"Orchestrator",
"Hello! Can you introduce yourself and your capabilities?"
)
test_results.append(("Simple Query", result))
# Test 2: Complex query triggering A2A communication
print("\n" + "="*80)
print("TEST 2: Complex Query with A2A Communication")
print("="*80)
result = test_agent(
agentcore_client,
orchestrator_arn,
"Orchestrator",
"I need expert analysis. Please coordinate with the specialist agent to provide a comprehensive explanation of cloud computing architectures and best practices."
)
test_results.append(("A2A Communication Test", result))
# Summary
print("\n" + "="*80)
print("TEST SUMMARY")
print("="*80)
all_passed = True
for test_name, passed in test_results:
status = "✅ PASSED" if passed else "❌ FAILED"
print(f"{status} - {test_name}")
if not passed:
all_passed = False
print("\n" + "="*80)
if all_passed:
print("✅ ALL TESTS PASSED")
else:
print("⚠️ SOME TESTS FAILED")
print("="*80 + "\n")
return all_passed
def main():
"""Main entry point"""
if len(sys.argv) < 2:
print(__doc__)
print("\n❌ ERROR: Agent runtime ARN is required")
print("\nTo get your agent ARN:")
print(" - Terraform: terraform output orchestrator_runtime_arn")
print(" - CloudFormation: aws cloudformation describe-stacks --stack-name <stack> --query 'Stacks[0].Outputs'")
print(" - CDK: cdk deploy --outputs-file outputs.json")
print(" - Console: Check Bedrock Agent Core console")
sys.exit(1)
orchestrator_arn = sys.argv[1]
specialist_arn = sys.argv[2] if len(sys.argv) > 2 else None
# Validate ARN format
if not orchestrator_arn.startswith("arn:aws:bedrock-agentcore:"):
print(f"\n❌ ERROR: Invalid ARN format for orchestrator: {orchestrator_arn}")
print("Expected format: arn:aws:bedrock-agentcore:region:account:runtime/runtime-id")
sys.exit(1)
if specialist_arn and not specialist_arn.startswith("arn:aws:bedrock-agentcore:"):
print(f"\n❌ ERROR: Invalid ARN format for specialist: {specialist_arn}")
print("Expected format: arn:aws:bedrock-agentcore:region:account:runtime/runtime-id")
sys.exit(1)
# Run tests
success = test_multi_agent(orchestrator_arn, specialist_arn)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,73 @@
variable "orchestrator_name" {
description = "Name for the orchestrator agent runtime"
type = string
default = "OrchestratorAgent"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.orchestrator_name))
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
}
}
variable "specialist_name" {
description = "Name for the specialist agent runtime"
type = string
default = "SpecialistAgent"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.specialist_name))
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
}
}
variable "network_mode" {
description = "Network mode for AgentCore resources"
type = string
default = "PUBLIC"
validation {
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
error_message = "Network mode must be either PUBLIC or PRIVATE."
}
}
variable "image_tag" {
description = "Docker image tag"
type = string
default = "latest"
}
variable "aws_region" {
description = "AWS region for deployment (REQUIRED)"
type = string
# No default - must be explicitly provided
validation {
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
}
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "stack_name" {
description = "Stack name for resource naming"
type = string
default = "agentcore-multi-agent"
}
variable "environment_variables" {
description = "Environment variables for the agent runtime"
type = map(string)
default = {}
}
variable "ecr_repository_name" {
description = "Base name of the ECR repositories"
type = string
default = "multi-agent"
}
@@ -0,0 +1,32 @@
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.21"
}
null = {
source = "hashicorp/null"
version = "~> 3.0"
}
time = {
source = "hashicorp/time"
version = "~> 0.9"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "AgentCore"
Pattern = "multi-agent-runtime"
Environment = var.environment
StackName = var.stack_name
ManagedBy = "Terraform"
}
}
}