← Back to all articles
Deep Dive · Production Agentic AI

Building Production-Grade
AI Agents and Agentic
Workflows on AWS

A practitioner's guide to Amazon Bedrock AgentCore — Runtime, Memory, Gateway, Observability — combined with CrewAI and Bedrock foundation models to ship multi-agent systems that survive contact with real users.

GS
GSaralCloud Agentic Systems · AWS
18 min read
May 2026

The PoC-to-Production Wall

Almost every team building agentic AI in 2026 is stuck on the same step. The proof of concept works — a single notebook, a few foundation-model calls, a handful of tools, a demo that gets applause in a Friday review. The production version does not work. It does not scale, it does not remember, it cannot be observed, it cannot be governed, and the team that built the PoC is now staring at six weeks of infrastructure work before a single real user ever talks to it.

The gap is not a model gap. Foundation models on Amazon Bedrock — Claude, Nova, Llama, Mistral — are more than capable. The gap is everything that surrounds the model: the runtime that executes the agent loop, the memory that persists context across sessions, the gateway that turns enterprise APIs into agent-callable tools, the observability that lets you debug a four-hour multi-agent run, the identity layer that keeps an agent from doing something the user is not entitled to do.

Definition

Amazon Bedrock AgentCore is a modular agentic platform on AWS that provides Runtime, Memory, Gateway, Identity, Observability, Code Interpreter, and Browser services. Each service is independently usable, framework-agnostic (CrewAI, LangGraph, LlamaIndex, Strands, custom), and model-agnostic — including foundation models hosted outside Amazon Bedrock.

This article is a working blueprint for how to cross that wall. We will build a real-time customer-support agent end-to-end: a multi-agent crew authored in CrewAI, powered by Anthropic's Claude on Amazon Bedrock, hosted on AgentCore Runtime, given persistent memory through AgentCore Memory, equipped with enterprise tools through AgentCore Gateway, and instrumented for production through AgentCore Observability. The reference architecture is the one shown in the diagram below.

8 hr
Maximum agent execution time on AgentCore Runtime per session
100 MB
Per-invocation payload, including multimodal inputs
microVM
Per-session isolation; CPU, memory, and filesystem are sanitized at end
Any
Framework (CrewAI, LangGraph, Strands) and any foundation model

The AgentCore Service Family

AgentCore is best understood as a set of independent services that compose. You can adopt them one at a time. Many teams begin with Runtime alone — host the agent, do nothing else differently — and add Memory and Gateway over the following sprints as the system's needs emerge.

RT

AgentCore Runtime

A serverless host for agents and tools, exposed over streamable HTTP or MCP. Each user session runs in its own dedicated microVM with isolated CPU, memory, and filesystem; the microVM is terminated and memory sanitized at session end. Supports synchronous request/response and long-running asynchronous tasks up to eight hours per session.

MM

AgentCore Memory

A managed memory service with two tiers. Short-term memory stores raw events (typically conversation turns) within a session. Long-term memory asynchronously extracts insights into one of three built-in strategies — Semantic (facts and knowledge), Summary (running session summaries), and User Preferences — or a custom strategy with your own extraction prompt and model.

GW

AgentCore Gateway

A managed translation layer that turns existing Lambda functions, OpenAPI specs, and Smithy models into MCP-compatible tools. Any framework that speaks MCP — CrewAI, LangGraph, Strands — can discover and call them through a single authenticated endpoint, without modifying the underlying APIs.

ID

AgentCore Identity

Identity and access management designed for agents. Integrates with existing IdPs (Cognito, Okta, Azure AD), supports OAuth 2.0 token vaults so agents can act on behalf of a user against third-party services, and enforces just-enough access — the agent gets only the scopes the task requires.

OB

AgentCore Observability

Built on the OpenTelemetry standard via the AWS Distro (ADOT). Emits a hierarchy of sessions → traces → spans into CloudWatch and X-Ray, with built-in metrics for token usage, latency, and error rates. The CloudWatch GenAI Observability dashboard surfaces per-agent traces, custom span metrics, and error breakdowns.

CI

Code Interpreter & Browser

Two built-in tool runtimes. Code Interpreter executes generated code in a sandbox for data analysis, chart generation, and computation. Browser provides a secure headless browser the agent can drive to complete web-based workflows that have no API. Both are managed; you call them through Runtime.

Why this matters

The single most important property of AgentCore is that adopting one service does not lock you into the others. You can run an agent on AgentCore Runtime today, keep your existing memory layer, and migrate to AgentCore Memory next quarter. The composability is what makes the platform safe to adopt incrementally.

Reference Architecture

The architecture below is the one we deploy for production agent workloads. Each box is an AWS service with a well-defined contract; arrows are real network paths, not abstractions. A user-facing client application — a web SPA hosted on AWS Amplify, authenticated through Amazon Cognito — invokes the agent through a streamable HTTP endpoint. The agent runs on AgentCore Runtime and reaches the rest of the AgentCore family for memory, tools, code execution, and telemetry.

User Client Web / Mobile Amplify Hosting + Auth Cognito Identity API Gateway App API Lambda App logic DynamoDB App data AMAZON BEDROCK AGENTCORE AgentCore Runtime microVM / session AgentCore Memory short + long term AgentCore Gateway MCP tools Code Interpreter sandboxed exec Observability OTEL spans Custom Lambda domain tool CloudWatch metrics + logs AWS X-Ray traces
FIG 1 · Production AWS architecture for an AgentCore-hosted multi-agent system

Two design principles drive this layout. First, the agent path and the application path are separate. The browser talks to the agent over a streaming endpoint to AgentCore Runtime; it talks to traditional CRUD APIs through API Gateway and Lambda to DynamoDB. Mixing them — letting the agent be the only path into your backend — is the most common production mistake we see. Don't.

Second, the agent never reaches a backend service directly. It reaches them through AgentCore Gateway, which is the only place tool catalogues, OAuth tokens, and rate limits are configured. This is the trust boundary. When a compliance reviewer asks "what can this agent actually do?", the answer is the Gateway tool list — not "whatever it convinces itself it can do."

"Agents reason. Gateway acts. Runtime hosts. Memory remembers. Observability proves it."

Case Study: A Real-Time Customer-Support Crew

Consider a fintech that handles tens of thousands of weekly customer queries about transactions, card limits, and KYC status. The previous system was a rule-based chatbot bolted onto a ticketing tool. We replace it with a multi-agent crew that researches the customer, retrieves transaction context, drafts a response, and escalates to a human when confidence is low. The crew is built with CrewAI, the models are Claude Haiku and Sonnet on Amazon Bedrock, and the entire system runs on AgentCore.

The Crew Composition

Three specialist agents, one supervisor, all defined in CrewAI. Each agent has a narrow role, a small set of tools, and its own system prompt. The supervisor orchestrates the sequence and decides when the response is good enough to send.

A1

Profile Agent — Customer Context

Pulls the customer's profile, KYC status, account tier, and recent support history through Gateway-exposed tools. Returns a structured profile blob to the shared crew context.

A2

Transaction Agent — Account Activity

Queries the transactions service for the relevant time window, identifies anomalies, and decides whether the question requires fraud review. Has read-only access to a single transaction-search tool and a fraud-flag tool with explicit scopes.

A3

Response Agent — Drafting & Escalation

Combines the profile and transaction context with retrieved policy snippets from a Bedrock Knowledge Base, drafts the response, scores its own confidence, and either sends or hands off to a human queue with a reasoning trace attached.

Step 1 — Build the Agent Locally with CrewAI and Bedrock

We use the standard CrewAI structure. The model is configured to call Anthropic Claude through Amazon Bedrock, which keeps inference inside the customer's AWS account, removes the need for a separate API key, and inherits IAM-based access control. The crew is wrapped in a BedrockAgentCoreApp with an @app.entrypoint decorator — that single decorator is what makes the same code runnable on localhost:8080 for testing and on AgentCore Runtime in production.

support_crew.py · CrewAI + Bedrock + AgentCore entrypoint
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()

# Lazy imports keep AgentCore cold-start under the 30s timeout.
# CrewAI's transitive dependency tree is heavy; never import at module top.

@app.entrypoint
def handler(payload, context):
    from crewai import Agent, Task, Crew, Process
    from crewai.llm import LLM

    bedrock_llm = LLM(
        model="bedrock/anthropic.claude-sonnet-4-20250514-v1:0",
        temperature=0.2,
    )

    profile_agent = Agent(
        role="Customer Profile Specialist",
        goal="Retrieve and summarise the customer's profile and KYC status.",
        backstory="You are precise. You only return verified profile data.",
        tools=[gateway_tool("get_customer_profile")],
        llm=bedrock_llm,
        verbose=True,
    )

    transaction_agent = Agent(
        role="Transaction Analyst",
        goal="Identify the transactions relevant to the customer's question.",
        backstory="You flag anomalies. You never modify records.",
        tools=[gateway_tool("search_transactions"),
               gateway_tool("flag_for_fraud_review")],
        llm=bedrock_llm,
    )

    response_agent = Agent(
        role="Response Author",
        goal="Draft a clear, accurate response or escalate.",
        backstory="You write in the bank's tone. You escalate when uncertain.",
        tools=[gateway_tool("search_policy_kb"),
               gateway_tool("escalate_to_human")],
        llm=bedrock_llm,
    )

    crew = Crew(
        agents=[profile_agent, transaction_agent, response_agent],
        tasks=build_tasks(payload["query"]),
        process=Process.sequential,
        memory=True,  # wire to AgentCore Memory in step 3
    )

    result = crew.kickoff(inputs={"query": payload["query"],
                                  "customer_id": payload["customer_id"]})
    return {"response": str(result), "session_id": context.session_id}

if __name__ == "__main__":
    app.run()  # local: localhost:8080 — same code in prod on AgentCore Runtime

Step 2 — Expose Backend Services as Tools through AgentCore Gateway

The agents above call gateway_tool("get_customer_profile") rather than invoking a Lambda or hitting an internal REST endpoint directly. AgentCore Gateway is the layer that makes this work. We register an OpenAPI spec or a Lambda ARN with the Gateway; it produces an MCP-compatible tool that any agent framework can call, with authentication, rate limits, and per-tool access policies handled centrally.

gateway_setup.py · register a Lambda as an MCP tool
import boto3

agentcore = boto3.client("bedrock-agentcore-control")

gateway = agentcore.create_gateway(
    name="support-tools",
    protocol="MCP",
    authorizer={
        "type": "COGNITO",
        "userPoolArn": COGNITO_POOL_ARN,
    },
)

agentcore.create_gateway_target(
    gatewayId=gateway["gatewayId"],
    name="get_customer_profile",
    targetConfiguration={
        "lambda": {
            "functionArn": GET_PROFILE_LAMBDA_ARN,
            "toolSchema": {
                "description": "Returns customer profile and KYC status.",
                "inputSchema": {"customer_id": {"type": "string"}},
            },
        }
    },
)

From the agent's perspective, the tool now exists with a clean schema — no Lambda, no IAM policy, no SDK detail. From the platform's perspective, every call through the tool is logged, traced, and rate-limited at the Gateway. This is the single-most-leveraged piece of the architecture: it is the difference between an agent that could reach any service and an agent that can reach exactly the services on its tool list.

Step 3 — Add Persistent Context with AgentCore Memory

Without memory, every customer conversation begins from zero. With AgentCore Memory, the crew remembers that this customer's last three contacts were about a disputed transaction, that they prefer formal language, and that they have a Premier-tier account. We create a memory resource with two strategies — Semantic for facts, UserPreferences for tone and channel preferences — and the crew writes events to it on every turn.

memory_setup.py · create a memory store with two strategies
import boto3
from bedrock_agentcore_starter_toolkit.memory import MemoryClient

mc = MemoryClient()

memory = mc.create_memory_and_wait(
    name="support-crew-memory",
    description="Long-term memory for the customer-support crew.",
    event_expiry_days=90,
    strategies=[
        {"type": "SEMANTIC",
         "name": "customer_facts",
         "namespace": "/customers/{customerId}"},
        {"type": "USER_PREFERENCES",
         "name": "customer_prefs",
         "namespace": "/customers/{customerId}/prefs"},
    ],
)

# In the crew handler — write the conversation event after every turn:
mc.create_event(
    memoryId=memory["id"],
    actorId=f"customer-{customer_id}",
    sessionId=context.session_id,
    messages=[
        {"role": "USER", "content": query},
        {"role": "ASSISTANT", "content": response},
    ],
)

# Before answering — retrieve the most relevant long-term memories:
recall = mc.retrieve_memory_records(
    memoryId=memory["id"],
    namespace=f"/customers/{customer_id}",
    searchCriteria={"searchQuery": query, "topK": 5},
)

Long-term extraction runs asynchronously in the background after the event is written. This is a deliberate design choice — it keeps the live request path fast — but it means your application has to tolerate a lag between writing an event and the resulting memory record being retrievable. We use short-term memory for in-session continuity and lean on long-term memory only across sessions.

Step 4 — Deploy to AgentCore Runtime

Deployment is one command. The AgentCore CLI containerises the agent, registers it as an AgentCore Runtime, and returns an endpoint ARN. There is no Dockerfile to write, no autoscaling group to size, no load balancer to provision. Each user session lands on its own microVM, gets its session ID and the eight-hour execution budget, and is sanitized at session end.

shell · deploy and invoke
# Configure once per project
agentcore configure --entrypoint support_crew.py --name support-crew

# Test locally — same code path as production
agentcore invoke --local '{"query":"why was my card declined?","customer_id":"c-1842"}'

# Deploy to AWS
agentcore deploy

# Invoke the production endpoint
agentcore invoke '{"query":"why was my card declined?","customer_id":"c-1842"}'

# Tail logs in real time
aws logs tail /aws/bedrock-agentcore/runtimes/support-crew-DEFAULT --follow

Step 5 — Turn on Observability

Observability for AgentCore is OpenTelemetry-native through the AWS Distro (ADOT). Once Transaction Search is enabled in CloudWatch and the standard ADOT environment variables are set, the agent emits a three-tier hierarchy of sessions → traces → spans into CloudWatch and X-Ray. The CloudWatch GenAI Observability dashboard then surfaces per-session traces, token usage, latency percentiles, tool-call breakdowns, and error patterns.

observability env vars · added at runtime configuration
AGENT_OBSERVABILITY_ENABLED=true
OTEL_PYTHON_DISTRO=aws_distro
OTEL_PYTHON_CONFIGURATOR=aws_configurator
OTEL_RESOURCE_ATTRIBUTES=service.name=support-crew,\
  aws.log.group.names=/aws/bedrock-agentcore/runtimes/support-crew-DEFAULT
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_EXPORTER=otlp

For frameworks AgentCore instruments natively — CrewAI, LangGraph, Strands, LangChain — that is the entirety of the wiring. For others, you add an OpenInference instrumentation library to emit GenAI semantic-convention spans. Either way, every span lands in the same CloudWatch log group, every trace is reachable from X-Ray, and every session is queryable on the GenAI Observability page. When a customer reports that "the agent gave me the wrong answer at 14:32", you can pull the exact session in CloudWatch, see every tool call the agent made, the prompts it sent, the tokens it consumed, and the model's full reasoning trace.

Practitioner Note

Enable CloudWatch Transaction Search before you deploy. It is a one-time, account-level setting; without it, traces won't be queryable in the dashboard even though spans are being emitted. We have lost half-day debugging sessions to this single missed step.

Why CrewAI for Multi-Agent Orchestration

CrewAI's mental model is unusually clear: agents have roles, agents have goals, agents have backstories, and they execute tasks under a process (sequential or hierarchical). That vocabulary maps cleanly onto how human teams actually work, which makes the resulting code reviewable by people who are not LLM specialists.

Two CrewAI features matter most in production. Hierarchical process lets a manager agent dynamically delegate work to specialists rather than running them in a fixed sequence — useful when the right plan depends on the customer's question. And Flows add an event-driven layer above Crews, allowing conditional logic, loops, and state across multiple crew invocations — useful when the workflow is not a single conversation but a multi-stage pipeline.

Communication Between Agents

Agents in a CrewAI crew communicate through the shared crew context: the output of one task becomes input to the next, and a manager agent can ask any specialist for clarification mid-run. With AgentCore Memory wired in, that communication also persists across sessions — agent A from yesterday's session can leave a note that agent B picks up tomorrow, scoped to the same customer namespace.

Pattern

For most production crews we recommend the sequential process with explicit, named tasks. It is more deterministic than hierarchical delegation, easier to test, and easier to debug from the Observability dashboard. Reach for hierarchical only when the dynamic routing is genuinely needed — not because it feels more "agentic."

PoC Stack vs. Production AgentCore Stack

The contrast between a notebook prototype and the production architecture above is sharp on every axis a platform team cares about:

Dimension PoC Stack AgentCore Production Stack
Hosting Notebook or Fargate task; manual scaling AgentCore Runtime; serverless, microVM-isolated, autoscaling
Session execution Single request/response; long jobs time out Up to 8-hour sessions, sync or async, with isolated state
Memory In-process variable; lost at restart Short-term events plus async long-term Semantic/Summary/Preferences
Tool integration Hard-coded SDK calls inside the agent AgentCore Gateway; Lambdas and OpenAPI specs as MCP tools
Identity Long-lived API keys checked into env vars AgentCore Identity; OAuth token vault, IdP federation, JIT scopes
Observability print() statements; ad-hoc log scraping OTEL sessions/traces/spans in CloudWatch + X-Ray dashboards
Failure isolation One bad request can crash the host Per-session microVM; failures cannot bleed across users
Time to deploy Weeks of infra plumbing per agent Single agentcore deploy command; minutes

What We Learned Putting This Into Production

The honest version of any production-AI write-up is the list of things that did not work the first time. Ours fall into a few clear categories.

What Worked

  • Lazy imports for CrewAI inside the entrypoint — the dependency tree is too heavy for a cold-start top-level import
  • One Gateway tool per business capability, not per backend service — agents reason better at the capability layer
  • Sequential CrewAI process for predictable, reviewable runs; hierarchical only where genuinely required
  • Customer-scoped Memory namespaces (/customers/{id}) so retrieval is naturally bounded
  • Bedrock Knowledge Bases for policy retrieval, fronted by a Gateway tool, rather than fine-tuning the model
  • Enabling CloudWatch Transaction Search before the first deploy

What Bit Us

  • Treating long-term memory as synchronous — extraction is async, and our first version assumed records were immediately retrievable
  • Letting agents write directly to backend services without going through Gateway; this destroyed our audit trail
  • Over-prompting agent backstories — long backstories raise token cost without measurably improving quality
  • Default temperatures (0.7) on a financial-tone Response agent; we now run at 0.1–0.2 with explicit style guidance
  • Forgetting that AgentCore Runtime requires Bedrock-compatible models — we had to swap an external provider for Bedrock-hosted Claude
  • Underestimating reviewer onboarding for the GenAI Observability dashboard; reviewers needed a tour to read traces effectively

Non-Negotiable Production Guardrails

These are the controls without which we would not run an AgentCore stack in front of real customers:

  • All agent tool calls go through Gateway. No direct Lambda invocation, no in-agent SDK calls, no embedded API keys. Gateway is the audit boundary and the rate-limit boundary. There is no second path.
  • Identity is JIT-scoped. AgentCore Identity issues the agent only the OAuth scopes the current task requires. The Profile agent cannot move money. The Transaction agent cannot edit the profile.
  • Every response carries its session ID. Returned to the client, logged on the server, written to the ticketing system. When something goes wrong, finding the trace is one query, not a forensic exercise.
  • Confidence-gated escalation. The Response agent self-scores; below a tuned threshold, the response routes to a human queue with the full reasoning trace attached. We never let the agent send under low confidence.
  • Memory has TTLs. Short-term memory defaults to 30 days, long-term to 90 in our deployment. Customer data does not accumulate forever in a memory store just because the service supports it.
  • Per-tool rate limits and circuit breakers at the Gateway, not in the agent. An agent in a retry loop should fail fast and loud, not melt a downstream service.
Hard-Earned Lesson

The first version of our crew let the Response agent decide when to send the reply versus escalate. It optimised aggressively for "send" — sending was the path of least resistance in its prompt. We moved escalation logic out of the agent and into the supervisor's deterministic post-processing step. Confidence is now an agent output; the decision is platform code. This single change cut the false-send rate by an order of magnitude.

What This Pattern Generalises To

Customer support is a particularly visible use case, but the same architecture fits a much wider class of problems: SRE assistants that triage incidents across Kubernetes, monitoring, and runbooks; coding agents that maintain context across an engineer's full week of work; field-operations agents that combine maps, schedules, and parts inventories; financial-research agents that compose proprietary data with public filings. Anything where the work is multi-step, multi-tool, and benefits from memory across sessions fits the same shape.

The shared blueprint is consistent. Author the agents in the framework that fits the team's mental model — CrewAI for role-based crews, LangGraph for explicit state machines, Strands for AWS-native development. Power them with the right Bedrock foundation model for the cost/quality trade-off the workload demands. Host them on AgentCore Runtime so the infrastructure stops being a distraction. Reach the rest of the enterprise through Gateway, remember through Memory, prove correctness through Observability, and bound permissions through Identity. The pieces are independent; the discipline is treating them as a system from day one.

The Real Win

The headline metric for an AgentCore migration is usually latency or scale. The actual win is that every agentic workload in your estate now produces the same kind of evidence — the same traces, the same metrics, the same audit trail. That legibility is what turns agentic AI from a series of demos into a platform your compliance, FinOps, and SRE teams can actually own.

Ship Agents, Not Infrastructure.

Foundation models are not the bottleneck. The runtime, memory, tool-access, and observability layers around them are. AgentCore + Bedrock + CrewAI is the most complete answer AWS has shipped to that problem — and it is ready for production workloads today.

Tagged in
AWS Bedrock AgentCore CrewAI Multi-Agent Production AI Claude MCP OpenTelemetry Observability 2026