Exam Room · Advanced GenAI

Choosing an Agent Framework for the AgentCore Runtime

August 15, 2026 · 31 min read

Generative AI Development · part of The Exam Room

The situation

The subscriber help desk runs a reasoning loop the team wrote themselves, hosted on the AgentCore runtime. That decision is settled: they wanted control over the prompt structure, the tool-calling contract, and which model answers each step, and the harness would have taken all three.

Now a second agent is coming. Operations want one that reconciles supplier invoices against delivery records, and it is different enough from the help desk that nobody wants to bolt it onto the existing prompt. Two agents means the framework choice stops being an accident of whoever wrote the first one, and the team would rather settle it deliberately before there are four.

What makes this a real decision rather than a preference is that the runtime underneath is fixed. AgentCore hosts any framework, so nothing is ruled out on compatibility, and the differences that remain are about what each one hands you and what it leaves you to build. The team’s list is short: they need traces they can actually read when a reconciliation goes wrong, tools shared between both agents rather than reimplemented twice, and a story for the day the two agents have to talk to each other.

What actually matters

The first thing to name is that the loop is the least interesting part. Every framework here runs the same cycle: the model reads the context, decides whether to call a tool, something executes it, the result goes back, repeat until the model stops. Choosing between them on loop syntax is choosing on the part that varies least. What varies is everything arranged around the loop.

The second is observability, where the field genuinely splits, and it splits on a technicality with large consequences. Reconstructing an agent run on this runtime means emitting OpenTelemetry spans, because metrics arrive by default and spans do not. A framework that already speaks OpenTelemetry, and specifically the GenAI semantic conventions for agent and tool spans, means auto-instrumentation produces readable traces with almost no work. A framework that does not means writing the tracer, deciding what a span is, and naming the attributes yourself, then discovering during an incident which ones you failed to record. The same reasoning that makes tracing an agent’s decisions an up-front decision applies here: you are choosing how much of that work is already done.

The third is how tools reach the agent, and whether two agents can share them. A framework with native support for the Model Context Protocol can consume a gateway’s tool surface directly, so the invoice agent and the help desk agent point at the same gateway and inherit the same authorisation, the same credentials, and the same tool definitions. A framework without it needs an adapter layer, which is code that exists only to bridge two things that were meant to fit, and which becomes a place for the two agents’ tool behaviour to drift apart.

The fourth is what multi-agent looks like when you get there, because the second agent is the one that reveals whether the framework has a plan for the third. Some express coordination as first-class structures, a graph of agents or a swarm working the same problem. Some express it as agents exposed to each other as tools. Some leave it entirely to you. None of these is wrong, but adopting a framework whose multi-agent story is “write it yourself” and then needing multi-agent six months later is the expensive order to discover that in.

Underneath all of it: the language the team already writes matters more than any feature comparison. A framework that fits the code the team can maintain beats a marginally better one in a language they will avoid touching.

What we’ll filter on

  1. Does it emit OpenTelemetry spans with GenAI semantic conventions, so traces arrive from auto-instrumentation rather than instrumentation work?
  2. Does it consume MCP tools natively, so a gateway is a first-class tool source rather than an adapter?
  3. What is the multi-agent story when one agent becomes several?
  4. How much control does it give over the loop, the prompt structure, and the model per step?
  5. Is it available in the language the team actually maintains?
  6. How much of the deployment path to the runtime is already written?

The landscape

Strands Agents

AWS’s own open-source agent SDK, and the one the runtime’s documentation reaches for first. It is model-driven by design: rather than you drawing a workflow, the model directs its own steps and decides when to use a tool, which is the same shape the other frameworks run but stated as the organising idea rather than one mode among several.

Tools are Python or TypeScript functions with a decorator, and MCP is native, so a gateway’s tools are consumed directly rather than wrapped. Model providers are broad, Bedrock and Nova, Anthropic, OpenAI, Gemini, Ollama, Mistral, and custom providers behind the same interface, so the model behind a step is a configuration decision rather than a rewrite.

The loop has the controls a production agent needs and most frameworks make you add. Invocation limits cap turns, output tokens, and cumulative tokens on a single call. agent.cancel() stops a run from outside, and TypeScript takes an AbortSignal. An idempotency token deduplicates a retried invocation against one already in flight, which matters more than it sounds when a client retries a slow agent. Tool failures come back to the model as results rather than raised exceptions, so the model gets a chance to recover instead of the run dying.

Deployment to the runtime is a wrapper and two dependencies:

from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands import Agent

app = BedrockAgentCoreApp()
agent = Agent()

@app.entrypoint
def invoke(payload):
    result = agent(payload.get("prompt", ""))
    return {"result": result.message}

if __name__ == "__main__":
    app.run()

Observability is native OpenTelemetry, and multi-agent has first-class graph and swarm primitives plus A2A alongside MCP.

LangGraph

The graph-shaped member of the LangChain family, and the one to reach for when you want the control flow drawn rather than discovered. You define nodes and edges, and the model runs inside nodes while the graph decides the order, which makes it the closest thing here to a state machine that happens to contain a model.

That explicitness is the attraction and the cost. Cycles, branches, and checkpoints are yours to specify, which is exactly right when a workflow has a shape you can state and want enforced, and it is a lot of scaffolding when the path genuinely has to be discovered. LangChain’s tooling ecosystem is the largest of any option here.

It runs on the runtime, and it already emits OpenTelemetry, so auto-instrumentation works. Tracing has historically pointed at LangSmith, which is a separate product and a separate subscription, so the thing to check is that spans reach CloudWatch rather than only the vendor’s console.

CrewAI

Organises work as a crew of role-playing agents with assigned goals, which makes multi-agent the default shape rather than something you grow into. When the problem genuinely decomposes into named roles, that framing is a fast way to express it, and the vocabulary carries well in conversation with people who are not going to read the code.

The same framing is the constraint. A single-agent task expressed as a crew of one carries the ceremony without the benefit, and role metaphors can encourage splitting work that one agent would hold more cheaply. It runs on the runtime and emits OpenTelemetry.

The OpenAI Agents SDK and other framework agents

The runtime is deliberately framework-agnostic, so an agent written against the OpenAI Agents SDK, the Claude Agent SDK, or anything else deploys the same way: a container exposing /invocations and /ping, built for linux/arm64, listening on port 8080. Nothing about the platform pushes you off a framework a team already runs well.

What you check is the same list. Whether it emits OpenTelemetry with the GenAI conventions decides whether observability is a dependency or a project, and whether it speaks MCP decides whether the gateway is a tool source or an adapter.

A custom loop over Converse

No framework at all: your code calls the Converse API with a toolConfig, reads the toolUse block, runs the tool, returns a toolResult, and calls again. Total control, and for a genuinely small agent it is less machinery than adopting a framework to do the same thing.

Everything else is yours. Spans, retries, cancellation, token budgets, multi-agent coordination, and the MCP client are all code you write and maintain, and each one is a place to be subtly wrong in a way that only shows up in production. Worth it when the agent is small and permanent. Expensive when it grows.

Not a framework at all

Worth naming to rule out for this team rather than in general. If neither agent needed a custom loop, the managed harness would take a declared model, system prompt, and tool list and run the cycle, and the framework question would not arise. The help desk gave up the harness deliberately, and the invoice agent will share its tools and its traces, so both sit on the runtime. A team without that constraint should check the harness first.

Evaluation

Side by side

Option OTel + GenAI spans Native MCP Multi-agent story Loop control Languages Deploy path
Strands Agents ✓ native ✓ graph, swarm, A2A ✓ limits, cancel, idempotency Python, TypeScript ✓ documented wrapper
LangGraph ✓ (you draw the graph) ✓ explicit, verbose Python, JavaScript ✓ container
CrewAI ✓ roles by default Partial (framework decides) Python ✓ container
Other framework SDKs Check per framework Check per framework Varies Varies Varies ✓ container
Custom Converse loop ✗ (you write it) ✗ (you write it) ✓ total Any ✓ container

Reading it for this team: nothing is disqualified, which is the honest starting position, and the columns that separate the field are the first two. A framework that arrives already emitting the right spans and already consuming MCP means the gateway and the observability setup they built for the help desk extend to the invoice agent for free. Everything in the custom-loop row that reads as control also reads as work.

The solution

Write both agents on Strands, and point them at the same gateway. It is the only option that scores clean on every column the team actually listed, and the two that decide it are observability and tools.

Traces arrive as a dependency rather than a project. Strands emits OpenTelemetry with the GenAI semantic conventions natively, so adding the ADOT SDK and running under opentelemetry-instrument produces a readable span tree for each run: the invocation at the top, reasoning turns and tool calls beneath it. Set up CloudWatch Transaction Search once for the account and both agents land in the same place, correlated by session and trace id. The help desk already paid that setup cost, and the invoice agent inherits it.

Tools stay in one place. Native MCP means the gateway is a tool source rather than something to adapt, so getSubscription is defined once, authorised once, and consumed by both agents. When a tool’s schema changes, it changes for both, and there is no adapter layer for the two agents’ behaviour to drift apart inside.

The loop controls matter more for the invoice agent than the help desk. A reconciliation run over a batch is exactly where an agent can spin: invocation limits cap turns and cumulative tokens on a single call, agent.cancel() gives an external timeout something to call, and an idempotency token means a client retrying a slow reconciliation waits for the original rather than starting a second one against the same invoices. Tool failures returning to the model as results rather than exceptions is what keeps a single bad supplier record from killing a batch.

Deployment is a wrapper and two dependencies. BedrockAgentCoreApp with an @app.entrypoint function, bedrock-agentcore and strands-agents in the requirements, and the container contract the runtime expects: /invocations for POST, /ping for GET, linux/arm64, port 8080. The AgentCore CLI covers create, dev, deploy, and invoke, so the path from a local run to a deployed agent is short enough that nobody builds a bespoke one.

Keep the multi-agent primitives in reserve rather than reaching for them now. Two agents that share tools and do not call each other are two agents, and coordinating them is a problem you should have before you buy a solution for it. What the graph, swarm, and A2A support buy today is the knowledge that the answer exists when the invoice agent needs to ask the help desk agent something, which is the risk that made the framework choice worth settling deliberately.

Why not LangGraph. Drawing the control flow is the right instinct when a workflow has a shape you want enforced, and both of these agents are meant to discover their path. A drawn graph would be scaffolding around a decision the model is supposed to make. Check where its spans land too: the tracing story has pointed at a separate product, and what you want is spans in CloudWatch next to everything else.

Why not CrewAI. Roles are a good fit for a problem that decomposes into named specialists, and neither of these is that problem yet. Expressing a single-agent job as a crew of one carries the ceremony without the benefit, and the role metaphor nudges toward splitting work that one agent holds more cheaply.

Why not a custom loop. The team already owns a reasoning loop and knows what it costs. Writing a second one means writing spans, retries, cancellation, token budgets, and an MCP client again, each a place to be subtly wrong in a way that surfaces in production rather than in review.

Worked example

The invoice agent gets three tools, all of them gateway targets that already exist for the help desk or are added alongside them: getDeliveryRecord, getSupplierInvoice, and flagDiscrepancy. None is reimplemented in the agent, because the gateway publishes them as MCP tools and Strands consumes them directly.

The agent is a handful of lines. Agent() with the gateway attached as a tool source and a system prompt describing the reconciliation rules, wrapped in BedrockAgentCoreApp with an @app.entrypoint that takes an invoice id. Invocation limits cap it at a dozen turns, because a reconciliation that has not resolved in a dozen turns is stuck rather than thorough.

A run against a mismatched invoice looks like this in the trace. Turn one calls getSupplierInvoice, turn two calls getDeliveryRecord, turn three reasons about a quantity that differs by two crates, turn four calls flagDiscrepancy with the invoice id and the difference. Every step is a span, every tool call carries its arguments and its result, and the whole run is grouped under one trace id and the batch’s session id. When operations ask why invoice 4471 was flagged, the answer is a query.

The failure that proves the setup is a supplier whose delivery record is missing. getDeliveryRecord returns an error, and because Strands hands tool errors back to the model as results rather than raising, the agent reads the error, flags the invoice as unverifiable rather than as a discrepancy, and carries on to the next one. One bad record costs one invoice. Without that behaviour it costs the batch.

What’s worth remembering

  1. AgentCore hosts any framework, so nothing is decided on compatibility; the choice is about what each framework hands you and what it leaves you to build.
  2. Whether a framework emits OpenTelemetry with the GenAI semantic conventions decides whether traces are a dependency or a project, and it separates the field further than loop syntax does.
  3. Native MCP support makes a gateway a tool source rather than an adapter, so several agents share one definition, one authorisation, and one place to change it.
  4. Strands is AWS’s own SDK and scores cleanly on both: native OpenTelemetry, native MCP, graph and swarm primitives for later, and a documented wrapper for the runtime.
  5. Check the multi-agent story before you need it, because discovering the framework has none at the point you need it is the expensive order to find out.

Both agents ship on Strands against a shared gateway, with the traces landing where the help desk’s already land. The invoice agent takes a fraction of the setup the first one did, which is the return on settling the framework question deliberately rather than inheriting it from whoever wrote the first agent.

These posts are LLM-aided. Backbone, original writing, and structure by Craig. Research and editing by Craig + LLM. Proof-reading by Craig.