Two Harnesses, One Substrate: LangChain DeepAgents on Bedrock AgentCore
The DeepAgents lift onto AgentCore is boring on purpose; the real lesson is where two opposite harnesses hide the seven components while signing the same Runtime contract.

The same decorator hosts a completely different agent. What matters is not that both work; it is where each one hides the harness.
You brace for an integration project and get a paste. The interesting feeling arrives a beat later, when you notice the two frameworks disagree about almost everything except the boundary.
In this article: You will put LangChain DeepAgents behind the same Bedrock AgentCore Runtime decorator used for the Claude Agent SDK, prove the platform is framework-agnostic with a boring lift, and map where each harness places context, memory, tools, and trust. By the end you can choose internalized vs. externalized harness design without turning that choice into a cloud decision.
One data point is an anecdote. Host a Claude Agent SDK loop on AgentCore Runtime once, and "framework-agnostic" is still marketing. Every platform says it.
So take a completely different harness, built on LangGraph rather than the Claude engine, with the opposite design philosophy, and put it behind the same decorator. If AgentCore is what it claims, the lift should be boring.
It is boring. That is the finding, and it takes about ninety seconds. The rest of this article is the useful part: what you learn when two harnesses that disagree about almost everything sign the identical contract.
The other harness
LangChain DeepAgents does not call itself a framework. Its README calls it an agent harness: "an opinionated agent that runs out of the box," built on LangChain's create_agent on LangGraph. It bundles subagents, a filesystem, context management, persistent memory, human-in-the-loop, skills, and tools, and it is model-agnostic for any tool-calling LLM.
That last property is why it fits without ceremony. DeepAgents accepts any LangChain chat model, so langchain-aws's ChatBedrockConverse drops in and the stack stays on AWS with no environment-variable trick. That is not a coincidence: ChatBedrockConverse is the model class the AgentCore SDK's own LangGraph A2A example uses. AWS already shows this combination; it does not call it an integration, because there is nothing to integrate.
Local first:
A local DeepAgents embryo: wire a Bedrock chat model into create_deep_agent, then run one invoke. The model object is the only AWS-specific line.
# local_agent.py
from langchain_aws import ChatBedrockConverse
from deepagents import create_deep_agent
llm = ChatBedrockConverse(model="us.anthropic.claude-sonnet-4-20250514") # ①
agent = create_deep_agent( # ②
model=llm,
tools=[],
system_prompt="You are a market-intelligence analyst.",
)
result = agent.invoke({"messages": "Summarize the pricing tiers in ./pricing-snapshot.html"}) # ③
print(result["messages"][-1].content) # ④
① ChatBedrockConverse is a LangChain chat model pointed at Bedrock, so inference stays on AWS without a separate Anthropic key.
② create_deep_agent builds the LangGraph-backed harness from the model, tools, and system prompt.
③ invoke runs the agent loop to completion on a single user message about the local pricing snapshot.
④ The final answer is the last message's content on the result state, the same shape as any LangGraph runnable.
Note: The full extracted listing at code/agent-core/part-03-deepagents-same-substrate/listings/01-local-agent.py is the complete runnable program.
create_deep_agent returns a LangGraph runnable with .invoke() and .astream(). That object is what every later integration wraps, augments, or serves.
The lift, which is the same lift
Hosted form: same Runtime decorator and request unwrap; only the middle loop swaps. The entrypoint is an async generator so astream events yield to SSE.
# agent.py
from langchain_aws import ChatBedrockConverse
from deepagents import create_deep_agent
from bedrock_agentcore import BedrockAgentCoreApp
llm = ChatBedrockConverse(model="us.anthropic.claude-sonnet-4-20250514") # ①
agent = create_deep_agent( # ②
model=llm,
tools=[],
system_prompt="You are a market-intelligence analyst running on Bedrock AgentCore.",
)
app = BedrockAgentCoreApp() # ③
@app.entrypoint # ④
async def handler(request):
prompt = request.get("prompt") or request.get("inputText") or "" # ⑤
async for event in agent.astream({"messages": [("user", prompt)]}): # ⑥
yield event # ⑦
if __name__ == "__main__":
app.run() # ⑧
① The same Bedrock model class as the local embryo; hosting does not change how you pick the LLM.
② The DeepAgents harness is built once at import time, outside the request path.
③ BedrockAgentCoreApp is the Starlette host that owns /invocations and /ping.
④ @app.entrypoint is the identical decorator from the Claude Agent SDK hosting path; Runtime still does not know which harness is inside.
⑤ The request unwrap accepts either prompt or inputText, matching the contract the Claude Agent SDK entrypoint used.
⑥ astream is the LangGraph streaming surface: events leave the loop as they are produced.
⑦ Yielding each event makes the handler an async generator, which Runtime bridges to SSE.
⑧ app.run() starts the local server on the contract address so the container and CLI can reach it.
Note: The full extracted listing at code/agent-core/part-03-deepagents-same-substrate/listings/02-agentcore-entrypoint.py is the complete runnable program.
Decorator identical. Request unwrap identical. The difference is the three middle lines plus one shape change: async generator instead of async function, because astream() yields events and there is no reason to swallow them. Runtime bridges async generators to SSE through a worker loop.

Runtime contract unchanged: ARM64, 0.0.0.0:8080, POST /invocations, GET /ping, session-isolated microVM, 15-minute inactivity termination, 8-hour ceiling. The container does not know which harness is inside.
Tooling note: use the AgentCore CLI (npm install -g @aws/agentcore, then agentcore create, agentcore dev, agentcore deploy). Its create wizard scaffolds LangGraph projects, so DeepAgents starts closer to done than Claude Agent SDK. The older Python Starter Toolkit CLI (agentcore configure, agentcore launch) is unsupported and steals the agentcore name; uninstall it if present.
Pattern check: one decorator works for both because Runtime implements isolation and hosting, not your loop. Clean boundary; strongest evidence for "rent the substrate." Also: AgentCore cannot help either harness with a stopping condition, because it cannot see the loop.
Where the harness lives
Two production-credible harnesses on one substrate distribute seven components in opposite ways: context manager, memory, validators, tools, observability, recovery, coordination.
The Claude Agent SDK internalizes. query() gives you a loop with context manager, tool executor, permission layer, sessions, and subagents already inside. Doors when you need them: hooks (PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd, UserPromptSubmit) for validators and observability; AgentDefinition for delegation; allowed_tools / disallowed_tools for permissions; built-ins (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch, AskUserQuestion) as the executor. Opinionated, mostly filesystem-shaped.
DeepAgents externalizes. create_deep_agent is the harness as parameters: model, tools, system_prompt, middleware, subagents, skills, memory, backend, checkpointer, store. Backends: StateBackend (default, thread-scoped), FilesystemBackend, StoreBackend, ContextHubBackend, CompositeBackend (path routing). Short-term memory: LangGraph checkpointer on thread_id. Long-term: namespace-scoped, cross-thread store. Nothing hidden beyond defaults.

Neither is correct. Same trade, opposite directions. Claude Agent SDK: ergonomics over visibility. DeepAgents: visibility over ergonomics (ten arguments you must own, legible by construction).
Running both is the pedagogy. DeepAgents names the components. The Claude Agent SDK shows what they feel like after someone else made the calls. The seam between loop and substrate becomes something you can point at.
What the coverage map says
Against a pattern catalog, not marketing:
Claude Agent SDK ships subagent delegation, parallelization, and orchestrator-workers via AgentDefinition and the Agent tool (fresh conversation, isolated context per subagent). Context assembly through sessions and CLAUDE.md is off the shelf. Observability via OpenTelemetry hooks. Gap: prompt chaining, routing, and evaluator-optimizer are build-your-own (three of five workflow patterns). Accept that cost on day one, not in sprint three.
DeepAgents ships the richest off-the-shelf workflow coverage in that catalog: write_todos planning, context compaction, virtual filesystem, interrupt_on human-in-the-loop, task subagents, LangGraph store memory, LangSmith observability. Provider routing is a string change. LangSmith Engine makes evaluator-optimizer configurable.
Discriminator: Claude Agent SDK is Claude-only. Provider portability in the deployment contract fails that by design. DeepAgents runs twenty-plus providers via provider:model, including Bedrock, so flexibility stays on AWS.
Pick Claude Agent SDK when the harness is Anthropic-model-centric and SKILL.md authoring is primary. Pick DeepAgents when provider portability or explicit harness control is a requirement.

AgentCore is orthogonal. "Rent the substrate" keeps the framework argument a framework argument, not a cloud argument.
The trust model, which is the interesting collision
DeepAgents states its trust model plainly: trust the LLM; enforce boundaries at the tool and sandbox level rather than expecting the model to police itself.
That is mechanical enforcement as a design principle, with a hole: DeepAgents does not ship the sandbox. It tells you to have one. It identifies the enforcement layer and declines to be a cloud provider.
AgentCore is that layer for sale. Code Interpreter is the sandbox DeepAgents assumes. Runtime's per-session microVM is isolation. Gateway is the scoped tool surface. Identity keeps credentials off the model DeepAgents tells you to trust.

Not two products that happen to fit. A framework whose security model needs infrastructure it does not provide, and a platform that sells that infrastructure without wanting your loop. The seam is where each stops on purpose.
The Claude Agent SDK holds more enforcement in-process via permissions and hooks, until you need that enforcement to survive a compromised process. Code Interpreter is the later replacement for in-process Bash when the boundary must live outside the process.
The convention for integration work
Every later AgentCore service integration follows this order:
- The service first. What it does, which harness pattern it implements, what it does not cover.
- Claude Agent SDK wiring. Usually
@toolincreate_sdk_mcp_server, or an MCP endpoint inmcp_servers. - DeepAgents wiring. Usually a callable in
tools, or a backend, store, or middleware slot. - Judgment call. Which harness wants this service more, and why.

Some code is first-party and verified; some is an adapter you write (for example AgentCoreMemoryStore(BaseStore) or a sandbox-backed filesystem backend). Label which is which. For adapters, verify signatures against installed versions. A skeleton sold as an API wastes your afternoon.
Do this today
- Run the local DeepAgents embryo with
ChatBedrockConverse; confirminvokereturns a final message without hosting. - Put the same agent behind
@app.entrypoint, yield fromastream, and callPOST /invocationsas you would for Claude Agent SDK. - One-page map of the seven components: internalized by Claude Agent SDK vs. named as a DeepAgents parameter.
- Team non-negotiable: Claude-only plus
SKILL.md, or provider portability plus explicit constructor control. Not AWS vs. not-AWS. - Uninstall old
agentcore configure/agentcore launchif it ownsagentcore; install@aws/agentcoreforcreate,dev,deploy.
What you have now
Two philosophically opposed agent loops on identical infrastructure. Framework decision decoupled from cloud decision. That decoupling is the product, verified twice.
What you lack is a proper contract client. Both entrypoints still treat Runtime as a JSON function call. That fails when a four-minute run needs progress, or a follow-up finds an agent with no idea who is asking.
Next: streaming over SSE, RequestContext and the session ID you must thread from platform into framework, Active / Idle / Terminated as the outermost stopping condition, and the state-eater: AgentCore session and framework session are not the same thing, do not share a lifetime, and join only on an ID you remembered to pass.
The boring lift was never the point. Once the substrate stops caring which loop you run, you can finally argue about the right thing: where the harness should live.