DEV Community

Cover image for 🧬 AWS Bedrock AgentCore & Terraform: Strands Agentic Blog Creator on AgentCore Runtime, Memory

🧬 AWS Bedrock AgentCore & Terraform: Strands Agentic Blog Creator on AgentCore Runtime, Memory

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
Enter fullscreen mode Exit fullscreen mode

AgentCoreApp

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

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Runtime

RuntimeDetails

Memory

MemoryInvoke

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

To read memory:

# python3 invoke_client.py --read-memory <session-id>
python3 invoke_client.py --read-memory 7495615f-fb3d-4e89-9bf4-5abb161339fc
Enter fullscreen mode Exit fullscreen mode

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 🧐

References

Top comments (0)