A production-ready template for building AI agents with Strands Agents SDK and deploying them to Amazon Bedrock AgentCore Runtime. Infrastructure is managed with Terraform.
flowchart TB
subgraph dev [Developer]
Code[Agent Code]
TF[Terraform]
end
subgraph build [Build and Package]
Docker[Docker ARM64 Image]
ECR[Amazon ECR]
end
subgraph runtime [AgentCore Runtime]
RT[Agent Runtime MicroVM]
Strands[Strands Agent Loop]
Tools[Agent Tools]
Memory[AgentCore Memory]
end
subgraph infra [AWS Infrastructure]
IAM[IAM Roles and Policies]
CW[CloudWatch and OTEL]
Bedrock[Bedrock Model Access]
Actions[GitHub Actions OIDC]
end
Code --> Docker --> ECR --> RT
TF --> IAM
TF --> Memory
TF --> Actions
TF --> RT
RT --> Strands --> Tools
Tools --> Memory
Strands --> Bedrock
RT --> CW
bedrock-agent-blueprint/
├── agents/ # Agent code (what you edit)
│ ├── Dockerfile
│ ├── pyproject.toml
│ ├── uv.lock
│ ├── main.py
│ ├── memory.py
│ └── tools.py
│
├── infra/ # Terraform (ECR, IAM, AgentCore Runtime, Memory)
│ ├── main.tf
│ ├── variables.tf
│ ├── iam.tf
│ ├── ci.tf
│ ├── ecr.tf
│ ├── agent.tf
│ ├── memory.tf
│ ├── outputs.tf
│ ├── backend.tf.example
│ └── terraform.tfvars.example
│
├── examples/github-workflows/ # Example workflows; not active in this repo
│ ├── ci.yml # PR tests and Terraform validation
│ ├── deploy.yml # Build, push, and deploy on main
│ └── rollback.yml # Roll back to a previous ECR image tag
│
├── scripts/
│ ├── build_and_push.sh # Build Docker image and push to ECR
│ └── invoke.py # Call the deployed agent
│
├── tests/
│ ├── test_agent.py
│ └── test_memory.py
│
├── .gitignore
└── README.md
- AWS Account with AgentCore permissions
- Python 3.10+ and uv
- Terraform >= 1.5
- Docker (with buildx support) for building ARM64 images
- AWS CLI configured with credentials
Tip: Terraform uses the default AWS credential chain. To use a named profile, run
export AWS_PROFILE=my-profilebefore any Terraform or AWS CLI commands.
git clone <this-repo>
cd bedrock-agent-blueprint
cp infra/terraform.tfvars.example infra/terraform.tfvars
# Edit infra/terraform.tfvars with your AWS region, project name, etc../scripts/build_and_push.shThe script creates the ECR repository if it doesn't exist yet, builds an ARM64 Docker image, tags it with the current git short SHA (e.g. a1b2c3d) and latest, and pushes both to ECR.
terraform -chdir=infra init
terraform -chdir=infra apply -var="container_tag=<git-sha>"Use the tag printed at the end of the build script. Terraform imports the ECR repository the build script created, provisions IAM roles, and creates the AgentCore runtime pointing at your image.
# Uses the built-in get_weather tool
python scripts/invoke.py --prompt "What's the weather in Seattle?"
# Uses the built-in calculate tool
python scripts/invoke.py --prompt "What is sqrt(144) + 3 * 2?"
# Uses the built-in lookup_item tool
python scripts/invoke.py --prompt "Look up item ITEM-001 in inventory"You can also pass the runtime ARN directly:
python scripts/invoke.py \
--arn "arn:aws:bedrock-agentcore:eu-west-1:123456789012:runtime/my-agent" \
--prompt "What's the weather in Seattle?"After the initial setup, the development loop is always the same two commands:
./scripts/build_and_push.sh
terraform -chdir=infra apply -var="container_tag=<new-sha>"The blueprint includes optional AgentCore Memory resources and a reusable Python helper in agents/memory.py.
For guidance on when to use memory, what to store, and how to verify it, see docs/memory.md.
When agent_memory_enabled = true, Terraform creates:
- an AgentCore Memory resource
- a semantic memory strategy
- runtime environment variables:
AGENTCORE_MEMORY_IDAGENTCORE_MEMORY_STRATEGY_IDAGENTCORE_MEMORY_NAMESPACE
- IAM permissions for
CreateEvent,BatchCreateMemoryRecords, and semantic retrieval
The helper writes both an event and a directly queryable semantic record. This matters because event ingestion and semantic retrieval can behave differently; writing a direct record makes new memories available to retrieval quickly.
Example tool usage:
from memory import retrieve_memory_records, store_memory_record
def summarize_task(task_id: str, summary: dict) -> dict:
prior = retrieve_memory_records(f"similar task {summary['kind']}")
write = store_memory_record(
summary,
actor_id="agent",
session_id=task_id,
purpose="task-summary",
metadata={"kind": summary["kind"], "status": summary["status"]},
)
return {"prior": prior, "write": write}Memory records use content-hashed idempotency tokens. Identical retries are safe, and enriched content can create a new record instead of failing with a duplicate-token hash mismatch.
Keep public-template memory generic. Store reusable facts, task summaries, user-approved preferences, and operational metadata. Do not store secrets, credentials, raw private documents, or project-specific identifiers unless your own application has explicit permission and retention rules.
The template includes three example GitHub Actions workflows under examples/github-workflows/.
They are intentionally not stored in .github/workflows, so this public template repository does not run CI/CD by default. To enable them in your own generated project, copy the files into .github/workflows/.
ci.ymlruns Python tests and Terraform formatting/validation on pull requests.deploy.ymlruns tests, builds an ARM64 image, pushes it to ECR using the merge SHA andlatesttags, then applies Terraform withcontainer_tag=${GITHUB_SHA}.rollback.ymlverifies an existing ECR tag and reapplies Terraform with that tag.
For GitHub Actions deployments:
- Create a remote Terraform state bucket.
- Enable the optional backend:
cp infra/backend.tf.example infra/backend.tf- Set repository variables:
AWS_REGION # for example eu-west-1
TF_STATE_BUCKET # your Terraform state bucket
TF_STATE_KEY # for example bedrock-agent-blueprint/dev/terraform.tfstate
- Create or supply an IAM role for GitHub Actions OIDC and store it as the repository secret
AWS_ROLE_TO_ASSUME.
Terraform can create a starter deploy role:
github_actions_oidc_enabled = true
github_repository = "owner/repo"
github_oidc_provider_arn = "" # optional: reuse an existing provider ARN
github_deploy_branch = "main"
terraform_state_bucket = "your-tfstate-bucket"
terraform_state_key = "bedrock-agent-blueprint/dev/terraform.tfstate"Apply once from a trusted local machine, then copy the ci_deploy_role_arn output into the AWS_ROLE_TO_ASSUME secret.
You can test the agent locally without deploying to AWS.
cd agents
uv sync
uv run python main.pyThen, in another terminal:
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello!"}'cd agents
uv sync --dev
uv run pytest ../tests/ -vThe tests exercise tool functions directly -- no AWS credentials needed.
The included agent (agents/) demonstrates custom tool integration using the Strands @tool decorator. It comes with three example tools:
get_weather-- Returns weather data for a city (mock, replace with a real API)calculate-- Safely evaluates math expressionslookup_item-- Searches a database by item ID (mock, replace with DynamoDB/RDS)
- Create a new function in
tools.py(or a new file):
from strands import tool
@tool
def my_tool(param: str) -> dict:
"""Description of what this tool does.
Args:
param: Description of the parameter.
"""
return {"status": "success", "content": [{"text": do_something(param)}]}- Import and add it to the agent's
toolslist inmain.py:
from tools import my_tool
agent = Agent(
tools=[my_tool],
system_prompt="...",
)The agent uses Claude Sonnet 4.5 via cross-region inference (eu.anthropic.claude-sonnet-4-5-20250929-v1:0) by default. To use a different model or region prefix, update main.py:
from strands.models.bedrock import BedrockModel
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0")
agent = Agent(
model=model,
system_prompt="...",
)In infra/terraform.tfvars:
network_mode = "VPC"You will also need to add subnets and security_groups to the network configuration in infra/agent.tf.
Add an authorizer_configuration block to the runtime resource in infra/agent.tf:
authorizer_configuration {
custom_jwt_authorizer {
discovery_url = "https://accounts.google.com/.well-known/openid-configuration"
allowed_audience = ["my-app"]
allowed_clients = ["client-123"]
}
}The Dockerfile includes opentelemetry-instrument, which automatically sends traces and metrics to CloudWatch -- no code changes needed.
To view your agent's observability data:
- Enable CloudWatch Transaction Search (one-time setup)
- Open the CloudWatch console
- Navigate to GenAI Observability to see traces, metrics, and logs
To remove all deployed resources and avoid ongoing charges:
terraform -chdir=infra destroyThis deletes the AgentCore runtime, IAM roles, and the ECR repository (including all pushed images for non-prod environments).
Note: In production (
environment = "prod"), the ECR repository has deletion protection enabled. You will need to manually empty and delete it, or setforce_delete = trueininfra/ecr.tfbefore destroying.
Use Strands + AgentCore Runtime when:
- You want full control over agent reasoning
- You care about research-grade agent design
- You expect agents to evolve rapidly
- You still want serverless scaling, IAM, and observability
Consider native Bedrock Agents instead if:
- You want the simplest possible Bedrock-native agent
- You're fine with AWS-managed planning logic
MIT