Published @ AWS Builder Center
In the previous blog post, as a proof of concept, I built an agentic blog post writer using AWS Strands Agents. The workflow follows a Planner–Generator–Evaluator architecture: the Planner orchestrates the process, the Generator creates the content, and the Evaluator reviews the output and provides iterative feedback for improvement.
In this iteration, I’m taking the application from a local proof of concept to a fully deployed agentic application on AWS. I’ll provision Amazon Bedrock AgentCore, containerize the application with a Dockerfile, build and push the image to the provisioned Amazon ECR repository, and deploy it to the Bedrock AgentCore Runtime.
I’ll also replace the manually managed memory.md files with AgentCore Memory, allowing the application to leverage managed, persistent memory as part of the agent workflow.
The goal is to move beyond a simple multi-agent prototype and demonstrate how the same architecture can be containerized, deployed, and integrated with managed agent infrastructure on AWS.
Architecture:
Architecture
────────────
PlannerAgent: decides what to do next (plan / replan dynamically)
- calls @tool: generate_blog → GeneratorAgent (research + write)
- calls @tool: evaluate_blog → EvaluatorAgent (quality gate)
- calls @tool: summarize_article → SummarizerAgent (key-point extraction)
- calls @tool: search_web → DDGS search
- calls @tool: fetch_page → HTTP scraper (full text, no truncation)
- calls @tool: read_memory → AWS Bedrock AgentCore Memory
- calls @tool: write_memory → AWS Bedrock AgentCore Memory
Code GitHub Link: Project on GitHub
Whether you're exploring agent design or building your own system, this will give you a clear, practical starting point 😉
Table of Contents
- Terraform: Enabling Bedrock AgentCore Runtime and Memory, ECR
- Blog Creator Agents & Tools
- All Code & Demo
- Client Code to Invoke Agent on AgentCore
- Conclusion
- References
Terraform: Enabling Bedrock AgentCore Runtime and Memory, ECR
Terraform / OpenTofu, CDK, or CloudFormation is required to provision the AWS infrastructure, including IAM roles to grant permissions to services, an ECR(Elastic Container Registry) repository, the Docker image needed to run the application on AgentCore Runtime, AgentCore Runtime, AgentCore Memory, and Bedrock LLM Invoke.
Terraform providers and version:
# provider
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.18.0" # first version with aws_bedrockagentcore_* resources
}
}
}
provider "aws" {
region = var.aws_region
}
ECR Private Repo to store Agent App Image after building on local and push it to the ECR repo:
resource "aws_ecr_repository" "agent" {
name = var.project_name
image_tag_mutability = "MUTABLE"
force_delete = true
image_scanning_configuration {
scan_on_push = true
}
}
Assume Role for Bedrock AgentCore Service:
data "aws_caller_identity" "current" {}
data "aws_iam_policy_document" "runtime_trust" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["bedrock-agentcore.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "aws:SourceAccount"
values = [data.aws_caller_identity.current.account_id]
}
condition {
test = "ArnLike"
variable = "aws:SourceArn"
values = ["arn:aws:bedrock-agentcore:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"]
}
}
}
resource "aws_iam_role" "runtime_execution" {
name = "${var.project_name}-runtime-role"
assume_role_policy = data.aws_iam_policy_document.runtime_trust.json
}
Runtime permissions to pull image from ECR, to invoke Bedrock LLM, to store log, to manage AgentCore Memory:
data "aws_iam_policy_document" "runtime_permissions" {
statement {
sid = "BedrockModelInvoke"
effect = "Allow"
actions = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
resources = [
"arn:aws:bedrock:*::foundation-model/*",
# Wildcarded across regions, not just var.aws_region: "us.*" model IDs are cross-region inference profiles invoked from us-east-1 regardless of
# where the Runtime itself is deployed (see agent.py's BEDROCK_REGION).
"arn:aws:bedrock:*:${data.aws_caller_identity.current.account_id}:inference-profile/*",
]
}
statement {
sid = "EcrPull"
effect = "Allow"
actions = ["ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability"]
resources = [aws_ecr_repository.agent.arn]
}
statement {
sid = "EcrAuth"
effect = "Allow"
actions = ["ecr:GetAuthorizationToken"]
resources = ["*"]
}
statement {
sid = "Logs"
effect = "Allow"
actions = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams"]
resources = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/bedrock-agentcore/*"]
}
statement {
sid = "Xray"
effect = "Allow"
actions = ["xray:PutTraceSegments", "xray:PutTelemetryRecords"]
resources = ["*"]
}
statement {
sid = "AgentCoreMemory"
effect = "Allow"
actions = [
"bedrock-agentcore:CreateEvent",
"bedrock-agentcore:ListEvents",
"bedrock-agentcore:GetEvent",
"bedrock-agentcore:GetMemory",
"bedrock-agentcore:RetrieveMemoryRecords",
]
resources = [aws_bedrockagentcore_memory.agent.arn]
}
}
resource "aws_iam_role_policy" "runtime_permissions" {
name = "${var.project_name}-runtime-permissions"
role = aws_iam_role.runtime_execution.id
policy = data.aws_iam_policy_document.runtime_permissions.json
}
Bedrock AgentCore Memory to configure:
resource "aws_bedrockagentcore_memory" "agent" {
name = replace(var.project_name, "-", "_")
description = "Per-run scratch memory (Research/Sources/Critiques/Log) for the Strands blog pipeline."
event_expiry_duration = var.memory_event_expiry_days
}
Bedrock AgentCore Runtime:
resource "aws_bedrockagentcore_agent_runtime" "agent" {
count = var.container_image_uri == "" ? 0 : 1
agent_runtime_name = replace(var.project_name, "-", "_")
role_arn = aws_iam_role.runtime_execution.arn
agent_runtime_artifact {
container_configuration {
container_uri = var.container_image_uri
}
}
network_configuration {
network_mode = "PUBLIC"
}
environment_variables = {
AGENTCORE_MEMORY_ID = aws_bedrockagentcore_memory.agent.id
AWS_REGION = var.aws_region
}
}
Also, variables.tf, output.tf implemented, you can reach from => Terraform Codes. We can run these TF files under deploy.sh.
Blog Creator Agents & Tools
In the previous post, we implemented multi-agent blog post generator, you can see details from this LINK.
We added some parts to run on Bedrock Agent Core.
Implemented AgentCoreMemory class:
class AgentCoreMemory:
"""Research/Sources/Critiques/Log persisted as AWS Bedrock AgentCore Memory events
A fresh session_id per run makes this "per-run scratch": memory never carries over between pipeline runs, only within one."""
def __init__(self, session_id: Optional[str] = None):
if not AGENTCORE_MEMORY_ID:
raise RuntimeError(
"AGENTCORE_MEMORY_ID is not set. Deploy the Terraform stack in terraform/, then:\n"
" export AGENTCORE_MEMORY_ID=$(terraform -chdir=terraform output -raw memory_id)"
)
from bedrock_agentcore.memory import MemoryClient
self.client = MemoryClient()
self.memory_id = AGENTCORE_MEMORY_ID
self.actor_id = AGENTCORE_ACTOR_ID
# AgentCore Runtime session ids must be 33+ chars; pad a fresh uuid when none is given.
self.session_id = (session_id or uuid.uuid4().hex).ljust(33, "0")
self._header = ""
def init_run(self, topic: str) -> None:
self._header = topic
self.append("Header", f"Topic: {topic}")
def append(self, section: str, content: str) -> None:
try:
self.client.create_event(
memory_id=self.memory_id, actor_id=self.actor_id, session_id=self.session_id,
messages=[(content.strip(), "ASSISTANT")],
metadata={"section": {"stringValue": section}}, # metadata values must be typed, e.g. {"stringValue": ...}
extraction_mode="SKIP",
)
except Exception as e:
print(f" ⚠ AgentCore memory write failed: {e}")
def read(self) -> str:
try:
events = self.client.list_events(
memory_id=self.memory_id, actor_id=self.actor_id, session_id=self.session_id, max_results=100,
)
except Exception as e:
print(f" ⚠ AgentCore memory read failed: {e}")
return ""
by_section = {s: [] for s in MEM_SECTIONS}
for event in events:
section = (event.get("metadata") or {}).get("section", {}).get("stringValue", "Log")
texts = [
item["conversational"]["content"]["text"]
for item in event.get("payload") or []
if isinstance(item, dict) and isinstance(item.get("conversational", {}).get("content"), dict)
]
if texts and section in by_section:
by_section[section].append("\n".join(texts))
parts = [f"# Memory — {self._header}\n\n---\n"]
parts += [f"\n## {s}\n\n" + "\n".join(by_section[s]) for s in MEM_SECTIONS]
return "\n".join(parts)
Main function that includes BedrockAgentCoreApp() and @app.entrypoint:
DEFAULT_TOPIC = "AI Agents with Memory on AWS Bedrock AgentCore"
if __name__ == "__main__":
if "--serve" in sys.argv:
# AWS Bedrock AgentCore Runtime entrypoint: serves POST /invocations on :8080.
# Deployed via the Dockerfile + Terraform in this directory (see Readme.md).
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
@app.entrypoint
def _invoke(payload: dict, context=None) -> dict:
session_id = payload.get("session_id") or getattr(context, "session_id", None)
if payload.get("action") == "read_memory":
# Lets you inspect a prior run's AgentCore Memory through the Runtime's own
# execution role, without giving the caller direct Memory IAM permissions.
if not session_id:
return {"error": "read_memory requires the session_id returned by a previous run"}
return {"session_id": session_id, "memory": AgentCoreMemory(session_id).read()}
topic = payload.get("topic") or payload.get("prompt") or DEFAULT_TOPIC
return run(topic, int(payload.get("max_iter", MAX_ITER)), session_id=session_id)
app.run()
else:
run(" ".join(sys.argv[1:]) or DEFAULT_TOPIC)
DockerFile to run on the AgentCore Runtime:
FROM --platform=linux/arm64 python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python3", "agent.py", "--serve"]
deploy.sh to provision and pushing image, later to trigger to AgentCore runtime:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
REGION="${AWS_REGION:-eu-central-1}"
TAG="${IMAGE_TAG:-$(date +%Y%m%d%H%M%S)}"
echo "==> 1/4 terraform apply (ECR + IAM + Memory)"
terraform -chdir=terraform init -upgrade
terraform -chdir=terraform apply -auto-approve
ECR_URL=$(terraform -chdir=terraform output -raw ecr_repository_url)
REGISTRY="${ECR_URL%/*}"
IMAGE_URI="${ECR_URL}:${TAG}"
echo "==> 2/4 docker buildx build --push (linux/arm64) -> ${IMAGE_URI}"
aws ecr get-login-password --region "$REGION" | docker login --username AWS --password-stdin "$REGISTRY"
docker buildx inspect agentcore-builder >/dev/null 2>&1 || docker buildx create --name agentcore-builder --use
docker buildx use agentcore-builder
docker buildx build --platform linux/arm64 -t "$IMAGE_URI" --push .
echo "==> 3/4 terraform apply (Agent Runtime, image=${IMAGE_URI})"
terraform -chdir=terraform apply -auto-approve -var="container_image_uri=${IMAGE_URI}"
echo "==> 4/4 Done."
terraform -chdir=terraform output
Code to invoke the app on the AgentCore:
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("topic", nargs="*", default=["AI Agents with Memory on AWS Bedrock AgentCore"])
parser.add_argument("--read-memory", metavar="SESSION_ID", help="Read AgentCore Memory for a prior run's session_id instead of running the pipeline")
parser.add_argument("--runtime-arn", default=os.getenv("AGENT_RUNTIME_ARN"))
parser.add_argument("--region", default=os.getenv("AWS_REGION", "eu-central-1"))
parser.add_argument("--max-iter", type=int, default=3)
args = parser.parse_args()
if not args.runtime_arn:
sys.exit("Set --runtime-arn or AGENT_RUNTIME_ARN (see: terraform output agent_runtime_arn)")
if args.read_memory:
payload = {"action": "read_memory", "session_id": args.read_memory}
else:
payload = {"topic": " ".join(args.topic), "max_iter": args.max_iter}
client = boto3.client("bedrock-agentcore", region_name=args.region)
response = client.invoke_agent_runtime(
agentRuntimeArn=args.runtime_arn,
qualifier="DEFAULT",
payload=json.dumps(payload).encode(),
)
body = json.loads(response["response"].read())
print(body["memory"] if args.read_memory and "memory" in body else json.dumps(body, indent=2))
if __name__ == "__main__":
main()
All Code & Demo
GitHub Link: Project on GitHub
Run:
./deploy.sh
## creates agentcore runtime, ecr private repo, agentcore memory, iam role with trusted assume policy, iam role with runtime permission
export AGENT_RUNTIME_ARN=$(terraform -chdir=terraform output -raw agent_runtime_arn)
python3 invoke_client.py "AI Agents with Memory on AWS Bedrock AgentCore"
Client Code to Invoke Agent on AgentCore
Please install dependencies:
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
# deactivate
requirements.txt:
strands-agents>=0.1.0
strands-agents-tools>=0.1.0
boto3>=1.34.0
python-dotenv>=1.0.0
duckduckgo-search>=8.1.1
ddgs>=9.14.1
requests>=2.33.0
Invoke Agent using invoke_client.py:
python3 invoke_client.py "AI Agents with Memory on AWS Bedrock AgentCore"
{
"topic": "AI Agents with Memory on AWS Bedrock AgentCore",
"iterations": 1,
"accepted": true,
"blog": "# AI Agents with Memory on AWS Bedrock AgentCore\n\n## Introduction\n\nArtificial Intelligence (AI) agents are becoming increasingly sophisticated,...[AWS Bedrock Documentation]",
"sources": [
"https://dev.to/aws/bring-ai-agents-with-long-term-memory-into-production-in-minutes-338l",
..."
],
"session_id": "7495615f-fb3d-4e89-9bf4-5abb161339fc"
}
To read memory:
# python3 invoke_client.py --read-memory <session-id>
python3 invoke_client.py --read-memory 7495615f-fb3d-4e89-9bf4-5abb161339fc
GitHub Link Memory on AgentCore: Memory output on GitHub
GitHub Link CloudWatch Logs Printscreen: CloudWatch Logs on GitHub
Conclusion
In this post, we mentioned:
- how to create agentic app with AWS Strands Agent and Terraform on Bedrock AgentCore runtime,
- how to use Bedrock AgentCore memory,
- how to enable ECR, permission roles,
- how to use AWS Bedrock Nova.
If you found the tutorial interesting, I’d love to hear your thoughts in the blog post comments. Feel free to share your reactions or leave a comment. I truly value your input and engagement 😉
For other posts 👉 https://dev.to/omerberatsezer 🧐





Top comments (0)