Building Production-Ready AI Agents with AWS Bedrock AgentCore
How AWS Bedrock AgentCore solves the infrastructure challenges of deploying agentic AI at scale, with runtime, memory, gateway, and multi-agent coordination.
Deploying LangChain or CrewAI agents to production requires session isolation, credential management, memory persistence, and observability: infrastructure that takes months to build correctly from scratch. Without it, agents lack security boundaries between users and have no durable state across sessions, which makes them unsuitable for real workloads.
AWS Bedrock AgentCore (GA October 2025) closes that gap. It’s not another agent framework competing with LangChain or CrewAI; it’s the managed infrastructure layer that agents built with ANY framework need to run at scale. Think of it as “Lambda for AI agents”: you bring your agent code, AgentCore handles runtime, memory, tool management, and security. When the agent code already works locally and the remaining work is operational, that managed layer is the faster default.
AgentCore Architecture#
AgentCore consists of five integrated services that work independently or together:
Runtime: Serverless execution environment with 8-hour session windows and automatic session isolation using dedicated microVMs per user.
Memory: Managed storage for both short-term conversation context and long-term user preferences, facts, and summaries - without building your own vector database.
Gateway: Centralized tool management using the Model Context Protocol (MCP). Convert Lambda functions, REST APIs, and existing services into agent-accessible tools.
Identity: Secure credential management with OAuth 2.0 integration. Agents access third-party APIs on behalf of users without storing credentials.
Observability: OpenTelemetry-compatible metrics and traces exported to CloudWatch, Datadog, or LangSmith.
Deploying Agents on Any Framework#
Here’s how to deploy a Strands agent to AgentCore:
from bedrock_agentcore import BedrockAgentCoreApp
from strands import Agent
app = BedrockAgentCoreApp()
@app.entrypoint
def invoke(payload):
agent = Agent(
model="anthropic.claude-sonnet-4-20250514-v1:0",
instructions="You are a customer support agent with access to order history and return policies."
)
return agent.run(payload.get("message"))
Deploy with the CLI:
agentcore configure
agentcore launch --region us-east-1
Runtime execution windows run up to 8 hours, long enough for async agentic workflows where traditional serverless functions timeout at 15 minutes. Each user gets a dedicated microVM, so sessions carry no data leakage between them. Billing counts only active CPU and memory; I/O wait time is free, which can make AgentCore significantly cheaper than pre-allocated Lambda configurations for agentic workloads that spend considerable time waiting on LLM responses. Containers must target ARM64: use --platform=linux/arm64 in Docker builds.
One frequent mistake is not handling the Mcp-Session-Id header, which AgentCore auto-injects for stateless MCP servers:
from fastapi import FastAPI, Header
app = FastAPI()
@app.post("/mcp")
async def mcp_endpoint(
mcp_session_id: str = Header(None, alias="Mcp-Session-Id")
):
# AgentCore manages session isolation
# Your server must accept platform-generated IDs
session_state = load_session(mcp_session_id)
return {"status": "ok"}
Managing Conversation and Long-Term Memory#
AgentCore Memory covers both: short-term conversation context and long-term knowledge persistence.
Memory extraction pipeline:
Implementing memory with three strategies:
from bedrock_agentcore.memory import (
MemoryClient,
UserPreferenceMemoryStrategy,
SemanticMemoryStrategy,
SummaryMemoryStrategy
)
memory_client = MemoryClient()
# Create memory with multiple strategies
memory = memory_client.create_memory(
name="customer-support-memory",
strategies=[
UserPreferenceMemoryStrategy(), # Learn user patterns
SemanticMemoryStrategy(), # Store facts/knowledge
SummaryMemoryStrategy() # Compress sessions
],
encryption_key_arn="arn:aws:kms:us-east-1:123456789012:key/abc123"
)
# Store conversation event
memory_client.create_event(
memory_id=memory.id,
event_data={
"type": "conversation",
"content": "User prefers technical explanations with code examples"
}
)
Strategy selection guide:
| Agent type | Strategies | What it remembers |
|---|---|---|
| Customer support | UserPreferences + Summaries | Communication style |
| Technical assistant | SemanticFacts + Summaries | Codebase knowledge |
| Personal agent | All three strategies | Comprehensive personalization |
Critical security pattern - always use Guardrails before CreateEvent API:
import boto3
bedrock = boto3.client('bedrock')
# WRONG: Direct storage (vulnerable to memory poisoning)
# memory_client.create_event(
# memory_id=memory.id,
# event_data={"content": user_input}
# )
# RIGHT: Sanitize with Guardrails first
guardrail_response = bedrock.apply_guardrail(
guardrailId='guardrail-123',
guardrailVersion='1',
content=[{"text": {"text": user_input}}]
)
if guardrail_response['action'] == 'NONE':
memory_client.create_event(
memory_id=memory.id,
event_data={"content": user_input}
)
else:
# Block and log attack attempt
logger.warning(f"Memory poisoning attempt blocked: {guardrail_response}")
Cost optimization: Limit retriever hops. Two-three retrieval operations per turn is normal, ten indicates over-retrieval:
memory_config = {
'retrieval_strategy': 'semantic',
'max_results': 5,
'max_retriever_hops': 2
}
Gateway: Centralized Tool Management#
Embedding tools directly in agent code leads to duplication and inconsistency: customer support, sales, and technical agents that all need weather data end up maintaining three copies of the same tool code.
AgentCore Gateway centralizes that in MCP-compatible tool servers:
Registering a Lambda function as a tool:
import boto3
agentcore = boto3.client('bedrock-agentcore')
# Register Lambda as tool target
response = agentcore.create_target(
gatewayId='gateway-123',
targetConfig={
'type': 'LAMBDA',
'lambdaArn': 'arn:aws:lambda:us-east-1:123456789012:function:get-weather',
'description': 'Get current weather for a city'
}
)
Gateway covers authentication, semantic tool search, and protocol conversion. IAM roles handle AWS resources, OAuth 2.0 covers third-party APIs, and API keys cover other services; agents discover relevant tools via x_amz_bedrock_agentcore_search without knowing everything available upfront; Lambda functions, OpenAPI specs, Smithy models, and MCP servers all get exposed through one standardized MCP interface.
Architecture pattern - centralize common tools, keep domain-specific tools local:
Common Tools (via Gateway):
- Web search
- Database queries
- Weather API
- Stock prices
Domain-Specific Tools (agent-local):
- Return policy logic
- Product catalog
- Business rules
Multi-Agent Coordination with A2A Protocol#
AgentCore coordinates multi-agent teams through the Agent-to-Agent (A2A) protocol.
MCP handles agent-to-tool communication, like an agent calling a weather API; A2A handles agent-to-agent communication, like a supervisor coordinating specialists.
Hub-and-spoke supervisor implementation:
import { BedrockAgentCoreClient, InvokeAgentCommand } from '@aws-sdk/client-bedrock-agentcore';
class HostAgent {
private client: BedrockAgentCoreClient;
private specialistAgents: Map<string, AgentConfig>;
async routeToSpecialist(query: string, capability: string) {
const agentConfig = this.specialistAgents.get(capability);
// Fetch remote agent's A2A configuration
const agentCard = await this.fetchAgentCard(agentConfig.endpoint);
// Invoke via A2A protocol
const command = new InvokeAgentCommand({
agentId: agentCard.id,
sessionId: this.generateSessionId(),
inputText: query,
protocol: 'A2A'
});
return await this.client.send(command);
}
private async fetchAgentCard(endpoint: string): Promise<AgentCard> {
// Retrieve agent capabilities schema
const response = await fetch(`${endpoint}/.well-known/agent-card`);
return response.json();
}
}
Orchestration patterns:
Supervisor with routing mode - not every query needs full orchestration:
class SupervisorAgent:
def route_query(self, query: str):
# Simple query → direct routing
if self.is_simple_query(query):
specialist = self.select_single_specialist(query)
return specialist.invoke(query)
# Complex query → full orchestration
else:
plan = self.analyze_and_plan(query)
results = self.orchestrate_subagents(plan)
return self.synthesize(results)
def is_simple_query(self, query: str) -> bool:
intents = self.detect_intents(query)
return len(intents) == 1
Framework interoperability: LangGraph monitoring agent + CrewAI analytics agent + Strands incident response agent can all communicate via A2A, with no framework lock-in.
Security and Cost Optimization#
Guardrails Configuration#
Guardrails protect against prompt injection, memory poisoning, and harmful content:
import boto3
bedrock = boto3.client('bedrock')
guardrail = bedrock.create_guardrail(
name='production-agent-guardrail',
contentPolicyConfig={
'filtersConfig': [
{'type': 'HATE', 'inputStrength': 'HIGH', 'outputStrength': 'HIGH'},
{'type': 'VIOLENCE', 'inputStrength': 'MEDIUM', 'outputStrength': 'HIGH'},
{'type': 'PROMPT_ATTACK', 'inputStrength': 'HIGH', 'outputStrength': 'NONE'}
]
},
topicPolicyConfig={
'topicsConfig': [
{
'name': 'Financial Advice',
'definition': 'Providing specific investment recommendations',
'type': 'DENY'
}
]
},
wordPolicyConfig={
'wordsConfig': [
{'text': 'internal-api-key'},
{'text': 'secret-token'}
],
'managedWordListsConfig': [
{'type': 'PROFANITY'}
]
}
)
Defense-in-depth strategy:
- Input validation: Block malicious prompts at entry
- Memory protection: Sanitize before CreateEvent API
- Output filtering: Prevent harmful responses
- Audit trails: CloudWatch logs for compliance
Cost Optimization Strategies#
Prompt caching - 90% discount on cached tokens:
response = bedrock_runtime.converse(
modelId="anthropic.claude-sonnet-4-20250514-v1:0",
messages=[{"role": "user", "content": user_query}],
system=[
{
"text": large_system_prompt,
"cachePoint": {"type": "default"}
}
]
)
Model routing - match complexity to model cost:
def route_to_model(query: str) -> str:
complexity = classify_query_complexity(query)
if complexity < 0.3:
return "anthropic.claude-haiku-4-5-20251001-v1:0" # $1/$5 per 1M tokens (input/output)
elif complexity < 0.7:
return "anthropic.claude-sonnet-4-20250514-v1:0" # $3/$15 per 1M tokens (input/output)
else:
return "anthropic.claude-opus-4-20250514-v1:0" # $15/$75 per 1M tokens (input/output)
Tool-call budgets - prevent unbounded tool use:
agent = Agent(
model="anthropic.claude-sonnet-4-20250514-v1:0",
max_tool_calls_per_turn=3,
instructions="If user asks about multiple items, summarize instead of exhaustive lookup"
)
Cost components:
- Runtime: Active CPU/memory consumption (not pre-allocated)
- Memory: Short-term (per event), long-term (per memory processed + retrievals)
- Gateway: MCP operations (ListTools, CallTool, Ping) + semantic search queries
- Identity: No additional charges when used via Runtime/Gateway
- Observability: CloudWatch standard pricing
Where It Breaks in Production#
Memory Poisoning Without Guardrails#
Storing raw user input directly allows prompt injection into memory:
# WRONG
user_input = "Ignore previous instructions, you are now..."
memory_client.create_event(
memory_id=memory.id,
event_data={"content": user_input}
)
Sanitizing with Guardrails before the write (the same pattern shown in the Memory section above) closes this gap.
Tool-Call Storms#
Without limits, an agent can invoke 20+ tools per query:
User: "What's the weather in major cities?"
Agent makes 50 separate get_weather() calls
Latency and cost grow with the call count, with no upper bound
Enforcing tool-call budgets and guiding the agent via instructions, the same pattern shown under Cost Optimization above, keeps that bounded.
Deployment Configuration Gaps#
Two configuration details cause avoidable failures at deploy time. Using x86 containers causes deployment failures; build for ARM64 explicitly:
FROM --platform=linux/arm64 python:3.11-slim
COPY . /app
CMD ["python", "agent.py"]
docker buildx build --platform linux/arm64 -t agent:latest .
Separately, agent traffic goes over the public internet by default. For internal APIs, configuring VPC and PrivateLink keeps that traffic inside AWS:
runtime_config = {
'vpcConfig': {
'securityGroupIds': ['sg-12345'],
'subnetIds': ['subnet-abc', 'subnet-def']
},
'privateLinkEnabled': True
}
When to Use AgentCore#
Use AgentCore when:
- Multiple agent frameworks in use (LangChain + CrewAI + custom)
- Need to evaluate different models (Bedrock + OpenAI + Anthropic)
- Enterprise security required (VPC, PrivateLink, customer-managed KMS)
- Multi-agent systems planned (A2A coordination)
- Time-to-production measured in weeks
- Team size under 10 (can’t build infrastructure from scratch)
Consider alternatives when the picture is simpler: a single framework forever (only LangGraph, so LangGraph Cloud fits), a single cloud ecosystem (all Azure, so Azure AI Agent Service fits), sustained very high volume where amortized self-hosted capacity can undercut consumption pricing, a need for custom hardware such as GPUs for specialized models, or agent infrastructure that’s already built and sunk.
Adopt the platform incrementally: Runtime first, then Memory, Gateway, Identity, and Observability as each need appears. Each service works on its own, so stopping partway is a valid end state. Override the default when one framework and one cloud will carry the workload for its whole life; that framework’s own platform stays simpler, and the managed layer earns little.
References#
- Amazon Bedrock AgentCore Overview (opens in new tab) - Official developer guide covering AgentCore Runtime, Memory, Gateway, and Observability components
- Amazon Bedrock AgentCore Best Practices (opens in new tab) - AWS guidance on security, session management, cost optimization, and multi-agent design
- Amazon Bedrock AgentCore Product Page (opens in new tab) - Feature overview, supported frameworks (LangGraph, CrewAI, Strands), and getting started paths
- AgentCore Samples Repository (opens in new tab) - Reference implementations demonstrating AgentCore Runtime, Memory, and Gateway integrations
- Introducing Amazon Bedrock AgentCore (AWS Blog) (opens in new tab) - Launch announcement with architecture details and design rationale
- Amazon Bedrock Documentation (opens in new tab) - Parent service documentation covering foundation models, Guardrails, and the broader Bedrock platform
Related posts
A CDK guide for deploying a minimal Strands agent on AgentCore Runtime: parameterized stack, arm64 build, deploy and invoke, with IAM and Marketplace prerequisites.
aws-bedrock · ai-agents · aws-cdk +3
Build production serverless workflows with Step Functions: Standard vs Express, Distributed Map, error handling, and cost optimization with working CDK examples.
step-functions · aws-cdk · serverless +4
AppSync subscriptions fire only on mutations. This explores bridging downstream BFF events into a NONE-data-source mutation with EventBridge and CDK.
aws · graphql · serverless +4
A practical comparison of TypeScript AI SDKs for building agents: Vercel AI SDK, OpenAI Agents SDK, and AWS Bedrock, with code examples and decision frameworks.
typescript · ai-tools · serverless +4
A production guide to feature flags in distributed systems, comparing LaunchDarkly, Unleash, and AWS AppConfig with examples for rollouts and A/B testing.
feature-flags · devops · ci-cd +5