Bedrock AgentCore: One Runtime, Any Multi-Agent Framework
Bedrock AgentCore: One Runtime, Any Multi-Agent Framework

A multi-agent system that works on a laptop and one that survives production are two different engineering problems. The laptop version is a script: a crew, a swarm, or a graph that runs to completion and exits. The production version needs session isolation between users, memory that survives a container restart, secure sandboxes for code the LLM writes, and enough observability to answer “why did agent B never get the handoff from agent A?” at 2 a.m.
Building that infrastructure yourself, per framework, is months of work you didn’t sign up for. Amazon Bedrock AgentCore exists to make that infrastructure a managed layer instead — and its most useful property is that it does not care which orchestration framework produced your agents. Strands Swarms, CrewAI crews, LangGraph graphs, and Pydantic AI agents all deploy through the same entrypoint, the same memory service, and the same observability dashboard.
This post walks through what AgentCore actually is, the one decorator that makes any agent — single or multi — deployable, and how the shared services (Memory, Runtime, Observability) let a multi-framework organization standardize on infrastructure without standardizing on a framework.
What AgentCore actually is
AgentCore is not one product — it’s a set of independent services you can use separately or together:
| Service | Purpose | When to use it |
|---|---|---|
| Runtime | Managed hosting for AI agents | Deploy any agent for automatic scaling and observability |
| Memory | Persistent conversation storage + fact extraction | Need conversation history or cross-session preferences |
| Gateway | Managed hosting for MCP tools | Connect agents to existing business systems and APIs |
| Code Interpreter | Secure Python sandbox with pre-installed libraries | Agent needs calculations, data analysis, or charts |
| Browser Tool | Secure, cloud-based browser for agents | Web scraping, automated testing, legacy UI integration |
| Identity | Identity and access management for agents | Agents acting on behalf of a specific authenticated user |
| Observability | Monitoring and debugging for agent workflows | Need visibility into performance and failures |
You reach these services through three layers, each a different level of abstraction:
- Control Plane / Data Plane APIs — raw
boto3access, maximum control, minimum convenience. - AgentCore SDK — Python helpers (
BedrockAgentCoreApp, memory config classes, session managers) that you import directly into agent code. - Starter Toolkit / CLI —
agentcore configure,agentcore launch,agentcore invoke,agentcore status,agentcore destroy. This is what automates containerization, ECR, IAM roles, and wiring the services together.
Almost everything in this post uses layers 2 and 3.
The one decorator that deploys anything
The entire integration surface between “local script” and “production agent” is one decorator:
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
@app.entrypoint
def invoke(payload, context):
"""AgentCore Runtime entry point"""
result = my_agent(payload.get("prompt", "Hello!"))
return {"response": result}
if __name__ == "__main__":
app.run()payload carries the request, context carries session/actor headers and the AgentCore session ID. Everything inside invoke() is yours — a single Strands Agent, a CrewAI crew().kickoff(), or a compiled LangGraph graph’s .invoke(). AgentCore doesn’t inspect what’s inside the function; it just needs something that takes a payload and returns a result.
Deployment is the same three commands regardless of what’s inside:
uv run agentcore configure -e agent_deployment/agent.py # wire up IAM, ECR, memory
uv run agentcore launch # build container, deploy
uv run agentcore invoke '{"prompt": "..."}' # test the live endpointagentcore launch --local runs the same container on your machine first, so you’re not debugging Docker and AWS IAM at the same time.
The same pattern, three frameworks
Because the entrypoint contract is just “function in, JSON out,” the framework-specific code only changes what happens inside invoke().
CrewAI — a multi-agent crew becomes one entrypoint call:
from bedrock_agentcore import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
@app.entrypoint
def invoke(payload, context=None) -> str:
result = AgentcoreCrewAi().crew().kickoff(inputs=payload)
return result.rawTwo agents — a Researcher and a Reporting Analyst, each with a role, goal, and backstory defined in YAML — run through Process.sequential inside that single call. AgentCore never sees the handoff between them; it only sees one request and one response.
LangGraph — a compiled state graph, invoked the same way:
from typing import Annotated, TypedDict
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
@app.entrypoint
def invoke(payload, context=None):
enhanced = f"{get_memory_context(context)}\n\nCurrent user message: {payload['prompt']}"
result = graph.invoke({"messages": [("user", enhanced)]})
store_conversation(context, payload["prompt"], result) # store the raw prompt, not the enhanced one
return {"response": result["messages"][-1].content}Note the detail that will bite you if you skip it: you enhance the outgoing prompt with retrieved memory context, but you store the original prompt back to Memory. Store the enhanced version instead and each turn’s memory retrieval compounds into the next, until your context window is mostly recycled memory.
Strands — a Swarm or Graph instead of a single Agent, same decorator:
from strands.multiagent import Swarm
@app.entrypoint
def invoke(payload, context=None):
swarm = Swarm([researcher, coder, reviewer], entry_point=researcher, max_handoffs=20)
result = swarm(payload.get("prompt", "Hello!"))
return {"response": str(result)}The multi-agent complexity — swarm handoffs, graph edges, crew roles, LangGraph conditional routing — is entirely internal to the framework you picked. AgentCore’s job stops at the function boundary. That’s the whole point: the deployment story doesn’t change when the orchestration story gets more complicated.
Why every example uses lazy agent creation
Almost every AgentCore tutorial builds the agent inside a get_or_create_agent() function guarded by a module-level None, instead of at import time. This isn’t boilerplate — it’s forced by how Runtime actually schedules work:
- Each session ID gets its own dedicated container, alive for up to 8 hours or 15 minutes of inactivity.
actor_idandsession_idare only known once a request arrives — not at container start.- Memory configuration needs both before it can be constructed.
- You want to build the agent/crew/graph exactly once per container and reuse it for every subsequent invocation in that session.
_agent = None
def get_or_create_agent(actor_id, session_id):
global _agent
if _agent is None:
_agent = Agent(session_manager=AgentCoreMemorySessionManager(
AgentCoreMemoryConfig(memory_id=MEMORY_ID, session_id=session_id, actor_id=actor_id),
REGION,
))
return _agentThis is also why session IDs must be 33+ characters: they’re not just a lookup key, they’re what AgentCore uses to route a request to a dedicated, isolated container and scope every memory operation. Short or predictable session IDs are a session-hijacking surface.
Deployment lifecycle, at a glance
| Command | What it does |
|---|---|
agentcore configure -e agent.py |
Interactive setup: IAM role, ECR repo, memory (STM/LTM), pyproject.toml |
agentcore launch --local |
Builds and runs the container locally for testing |
agentcore launch |
Deploys to AgentCore Runtime on AWS |
agentcore invoke '{"prompt": "..."}' |
Calls the live endpoint |
agentcore status |
Shows endpoint ARN, memory status (color-coded), observability dashboard URL |
agentcore destroy |
Tears down Runtime, Memory, ECR, IAM roles, log groups |
For invoking from an actual application rather than the CLI, it’s plain boto3:
client = boto3.client("bedrock-agentcore", region_name="us-west-2")
response = client.invoke_agent_runtime(
agentRuntimeArn=agent_arn,
runtimeSessionId="production_session_2026_user456_webapp_xyz123", # 33+ chars
payload=b'{"prompt": "What is 25 * 4 + 10?"}',
)No SDK-specific client, no framework detection — it’s the same call whether the agent behind that ARN is a single Strands agent or a five-node LangGraph graph.
Observability doesn’t need extra wiring
agentcore launch enables a CloudWatch GenAI Observability dashboard automatically — no separate instrumentation step. It gives you a service map (agent → Memory → Code Interpreter → model calls), request-level latency/throughput/error metrics, and X-Ray traces you can drill into for individual invocations, memory retrievals, and tool executions. For a multi-agent system, this matters more than for a single agent: it’s the difference between “the crew produced a wrong answer” and “the Researcher agent’s tool call timed out and the Reporting Analyst silently continued with a partial result.”
The traps
Flat-layout packaging errors on agentcore launch. If your agent code and its supporting modules sit directly in the deployment folder instead of a src/ layout, setuptools’ build backend can refuse with “Multiple top-level modules discovered in a flat-layout.” Structure the deployment package properly rather than fighting the build backend.
Storing the memory-enhanced prompt instead of the original. Covered above, but worth repeating: enhance the outgoing prompt with retrieved context, persist the original. Otherwise each retrieved memory becomes part of the next memory.
Skipping the memory prompts during configure. If you answer “no” to long-term memory extraction, or pick an existing memory resource instead of letting the toolkit create a new one, you’ll need to reconfigure — and an existing memory resource isn’t cleaned up by agentcore destroy the way an auto-created one is.
Library import paths move. bedrock_agentcore, bedrock-agentcore-starter-toolkit, and framework-specific integration packages (strands-agents-tools[agent_core_code_interpreter], etc.) are young enough that import paths for things like the Code Interpreter tool have already shifted between releases. Pin versions and check the changelog before copying tutorial code verbatim.
So when does this actually matter?
If you’re deploying one agent, in one framework, for one team — AgentCore is still worth it for Runtime’s session isolation and Observability alone, but you could plausibly build a thinner version yourself.
The case gets much stronger the moment you have multiple teams choosing different frameworks — one team on CrewAI because they think in roles, another on LangGraph because they need custom state reducers, another on Strands because they want Swarm’s built-in handoff safety rails. Standardizing Memory, Identity, and Observability at the AgentCore layer means that choice stays a team-level decision instead of an organization-level one, without anyone losing production-grade infrastructure for picking the “wrong” framework.
Sources: Mike Chambers’ Turn Your AI Script into a Production-Ready Agent on AWS Builder Center, and the Building Production-Ready AI Agents multi-framework series on dev.to (Strands, CrewAI, Pydantic AI, LlamaIndex, and LangGraph parts), with code from AWS’s agentcore-multi-framework-examples repository. AgentCore’s CLI and SDK are evolving quickly — check version numbers before copying commands.