Bedrock AgentCore is AWS's hosting layer for any agent framework, not a framework itself. This guide builds a shipment-support agent with the Strands Agents SDK, wraps it in BedrockAgentCoreApp, and deploys it with the agentcore CLI. One tool reads shipment status freely; a second issues a shipping credit only when called with an approved flag a human sets after reviewing the first response. Budget 45-60 minutes and an AWS account with Bedrock model access enabled.
| Fact | Value |
|---|---|
| Tool | AWS Bedrock AgentCore |
| Difficulty | Advanced |
| Time required | 45-60 min |
| Compatibility | Python 3.10+, AWS account with Bedrock model access, Node.js 20+ for the agentcore CLI, macOS/Linux/WSL |
| Last updated | November 2026 |
What you'll build
You'll build a shipment-support agent for an e-commerce backend. It can look up a shipment's status freely, and when a delay warrants a goodwill credit, it drafts the amount and reason but only issues it when the request carries approved: true. In practice that means the first call back to the customer gets a drafted amount and 'a support lead will confirm this,' and the actual credit only goes out on a second invoke made after that lead says yes, outside the conversation entirely.
Bedrock AgentCore doesn't care which framework wrote your agent; it's a Runtime, a Gateway for turning existing APIs into MCP tools, Memory, Identity, and Observability sitting underneath whatever agent code you bring. This guide uses Strands, AWS's own agent SDK, because it pairs natively with AgentCore, but the same BedrockAgentCoreApp wrapper works around a LangGraph, CrewAI, or OpenAI Agents SDK agent just as well. The only AWS-specific code is the wrapper itself: a few lines that turn your existing agent into something the Runtime can invoke.
Choose AgentCore when your agent already runs somewhere on AWS, or needs to, and you want a managed runtime, credential handling, and observability instead of standing up your own container infrastructure. If you're not on AWS, the Claude Agent SDK guide builds the same approval boundary as a process you run and host yourself.
Prerequisites
- Python 3.10+ and an AWS account with access to a Claude model enabled in the Bedrock console's Model access page
- AWS credentials configured locally (aws configure) with permission to create AgentCore resources
- Node.js 20+ for the agentcore CLI, and uv for the Python agent build step
- A shipments API endpoint to call (or a mock one for testing)
Step-by-step guide
Install the SDK, the CLI, and Strands
Install the Bedrock AgentCore Python package, the Strands Agents SDK, and the agentcore CLI, which is distributed through npm rather than pip since it's a standalone deployment tool, not a Python library. Confirm you have at least one Claude model enabled under Bedrock's Model access page in your account's region before continuing; AgentCore Runtime won't help if the underlying model call is denied.
1pip install bedrock-agentcore strands-agents boto32npm install -g @aws/agentcore3aws configure # set your AWS access key, secret, and regionExpected result: agentcore --version prints a version, and python -c "import strands" runs with no error.
Define the read-only tool: get_shipment_status
Write get_shipment_status with Strands' @tool decorator. Its docstring is what the model reads to decide when to call it, so describe exactly what it returns. This tool only reads from your shipments API; keep any write logic entirely out of it, the same separation of concerns that makes the next tool's approval gate meaningful.
1from strands import Agent, tool2import httpx34@tool5def get_shipment_status(shipment_id: str) -> dict:6 """Look up a shipment's current status, carrier, and expected delivery date."""7 resp = httpx.get(f"https://api.example-store.com/shipments/{shipment_id}")8 resp.raise_for_status()9 return resp.json()Expected result: A plain Python function decorated as a Strands tool, callable on its own for a quick test against your shipments API.
Define the gated tool: issue_shipping_credit
issue_shipping_credit takes an approved flag that defaults to False. When it's False, the tool returns a drafted amount and reason without charging anything; when a human has reviewed the draft and the next invoke passes approved=True, it actually calls your payments API. The function itself enforces the gate, so no amount of clever prompting in the conversation can skip it.
1@tool2def issue_shipping_credit(shipment_id: str, amount: float, reason: str, approved: bool = False) -> dict:3 """Draft or issue a shipping credit. Only issues the credit when approved is True;4 otherwise returns a draft for a human to review."""5 if not approved:6 return {"status": "drafted", "amount": amount, "reason": reason,7 "note": "A support lead must confirm this before it's issued."}8 # In production this calls your payments API (e.g. Stripe credit notes)9 return {"status": "issued", "amount": amount, "reason": reason}Expected result: Calling the function directly with approved=False returns a draft; with approved=True it returns an issued result.
Build the Strands agent
Wrap both tools in a Strands Agent with a system prompt that states the rule in plain language: always check status before promising anything, and never tell the customer a credit is confirmed unless the tool actually returned status issued. Strands talks to Bedrock directly; leave model unset to use your account's available Claude model, or pass model with the exact Bedrock model ID shown on your Model access page if you need to pin one.
1SYSTEM_PROMPT = (2 "You are a shipment support agent. Always call get_shipment_status before "3 "discussing a shipment. If a delay justifies a credit, call issue_shipping_credit "4 "with approved left as False and tell the customer a support lead will confirm it. "5 "Never say a credit was issued unless the tool result says status: issued."6)78agent = Agent(9 system_prompt=SYSTEM_PROMPT,10 tools=[get_shipment_status, issue_shipping_credit],11)Expected result: agent("My order SHIP-204 is late, can I get something for the delay?") returns a drafted credit with no actual charge made.
Wrap it for AgentCore Runtime
BedrockAgentCoreApp is the only AWS-specific code in this guide. Its @app.entrypoint decorator marks the function AgentCore Runtime calls on every invoke; read the prompt and the approved flag out of the incoming request and pass them through to the Strands agent. app.run() starts the server AgentCore's Runtime expects.
1from bedrock_agentcore import BedrockAgentCoreApp23app = BedrockAgentCoreApp()45@app.entrypoint6def handler(request):7 prompt = request.get("prompt", "")8 approved = request.get("approved", False)9 if approved:10 prompt += " The pending credit has been approved by a support lead; issue it now."11 result = agent(prompt)12 return {"result": str(result)}1314app.run()Expected result: Running the file locally with python agent.py starts a local server that AgentCore's dev mode can hit for testing.
Deploy with the agentcore CLI
agentcore create scaffolds the project configuration around your existing file, agentcore build packages it, and agentcore deploy ships it to AgentCore Runtime. Use agentcore dev first if you want to test against a browser-based Agent Inspector before deploying for real.
1agentcore create --name shipment-support --template agent-python-strands2agentcore dev --mode browser # optional local test with Agent Inspector3agentcore build4agentcore deployExpected result: agentcore deploy finishes with a Runtime ARN and status Active, visible again from agentcore status.
Invoke it twice: once drafted, once approved
Call the deployed agent with approved left out (or false) and confirm you get a drafted credit, not an issued one. Then simulate the human review step, a support lead reading the draft in your own ticketing tool, and send a second invoke with approved: true for the same shipment and amount. Only this second call should show status: issued in the tool's output.
1agentcore invoke --runtime shipment-support --payload '{"prompt": "SHIP-204 is 3 days late, can I get a credit?"}'2# ... support lead reviews the drafted amount ...3agentcore invoke --runtime shipment-support --payload '{"prompt": "Issue the credit for SHIP-204", "approved": true}'Expected result: The first invoke's response shows a drafted credit; the second, approved invoke shows the credit as issued.
Complete working example
1"""Shipment support agent: looks up shipment status freely, issues a credit2only on a second, explicitly approved invoke.3Run: python agent.py (pip install bedrock-agentcore strands-agents httpx)4"""5import httpx6from strands import Agent, tool7from bedrock_agentcore import BedrockAgentCoreApp8910@tool11def get_shipment_status(shipment_id: str) -> dict:12 """Look up a shipment's current status, carrier, and expected delivery date."""13 resp = httpx.get(f"https://api.example-store.com/shipments/{shipment_id}", timeout=10)14 resp.raise_for_status()15 return resp.json()161718@tool19def issue_shipping_credit(shipment_id: str, amount: float, reason: str, approved: bool = False) -> dict:20 """Draft or issue a shipping credit for a delayed shipment.2122 Only actually issues the credit when approved is True. Otherwise returns23 a draft a human support lead must confirm first.24 """25 if not approved:26 return {27 "status": "drafted",28 "shipment_id": shipment_id,29 "amount": amount,30 "reason": reason,31 "note": "A support lead must confirm this before it's issued.",32 }33 # In production this calls your payments API (e.g. Stripe credit notes)34 return {"status": "issued", "shipment_id": shipment_id, "amount": amount, "reason": reason}353637SYSTEM_PROMPT = (38 "You are a shipment support agent. Always call get_shipment_status before "39 "discussing any shipment. If a delay justifies a credit, call "40 "issue_shipping_credit with approved left as False, and tell the customer a "41 "support lead will confirm it. Never tell a customer a credit was issued "42 "unless the tool result's status is 'issued'."43)4445agent = Agent(46 system_prompt=SYSTEM_PROMPT,47 tools=[get_shipment_status, issue_shipping_credit],48)4950app = BedrockAgentCoreApp()515253@app.entrypoint54def handler(request: dict) -> dict:55 prompt = request.get("prompt", "")56 approved = request.get("approved", False)57 if approved:58 prompt += (59 " The pending credit for this shipment has just been approved by a "60 "support lead; call issue_shipping_credit again with approved=true and issue it."61 )62 result = agent(prompt)63 return {"result": str(result)}646566if __name__ == "__main__":67 app.run()68Common mistakes
Why it's a problem: Letting the model set approved=True on its own because the conversation 'sounds confident'
How to avoid: approved should only ever come from your own entrypoint logic after a real external confirmation, never from the model's own judgment inside a single conversation.
Why it's a problem: Deploying before checking Bedrock Model access for the Claude model your agent needs
How to avoid: A model that isn't enabled in your account's region fails at invoke time with an access error that has nothing to do with your agent code. Check Model access first.
Why it's a problem: Putting write logic and read logic in the same tool function
How to avoid: Keep get_shipment_status read-only and issue_shipping_credit as the only function that can ever change a real record. Mixing them removes the one clean boundary the approval gate depends on.
Why it's a problem: Hardcoding the shipments API's credentials in the tool function instead of using AgentCore Identity
How to avoid: AgentCore Identity exists specifically so tools authenticate to downstream APIs without embedding static secrets in your deployed code.
Why it's a problem: Assuming the agentcore CLI works the same as the older bedrock-agentcore-starter-toolkit package
How to avoid: The starter toolkit is deprecated; install the current agentcore CLI via npm install -g @aws/agentcore and use its create/build/deploy/invoke commands instead of the old configure/launch verbs.
Best practices
- Keep every tool that can change a real record separate from tools that only read, and gate the write tool's own function on an explicit flag your deployment code controls.
- Use AgentCore Identity for downstream API credentials instead of environment variables baked into the deployed package.
- Use AgentCore Memory if the same customer's shipment conversation needs to span more than one invoke; don't rely on the model remembering across separate Runtime calls.
- Route AgentCore's observability data into CloudWatch and alert on repeated drafted-but-never-approved credits; that pattern usually means the approval step isn't being followed up on.
- State the exact tool-result field the model must check (status: issued) in the system prompt, not a vague instruction like 'only say it's done if it's really done.'
- Test agentcore dev locally with the Agent Inspector before every deploy; catching a bad tool call locally is much cheaper than debugging it against the live Runtime.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Strands agent deployed to Bedrock AgentCore with a get_shipment_status tool and an issue_shipping_credit tool gated behind an approved flag passed through a second invoke (code below). Add a third tool, check_return_eligibility, that the agent should call before offering a credit on anything past a 30-day window. [paste complete_code]
Ask Amazon Q Developer or your IDE's AWS assistant: 'Add AgentCore Memory to this Strands agent so a customer's shipment ID carries over between invokes in the same session, instead of being repeated every time.'
Frequently asked questions
Do I have to use Strands to deploy an agent to Bedrock AgentCore?
No. AgentCore Runtime is framework-agnostic; it hosts agents built with LangChain, LangGraph, CrewAI, the OpenAI Agents SDK, or the Claude Agent SDK the same way, through the same BedrockAgentCoreApp wrapper. This guide uses Strands because it's AWS's own agent SDK and pairs natively with AgentCore.
What's the difference between Bedrock AgentCore and the older Bedrock Agents?
Bedrock Agents (now called Agents Classic) has been in maintenance mode and closed to new customers since July 30, 2026. AgentCore is the current platform: a Runtime, Gateway, Memory, Identity, and Observability layer that works with any framework instead of Bedrock's own built-in agent format.
Why does the agentcore CLI install through npm instead of pip?
The current agentcore CLI (package @aws/agentcore) is a standalone deployment tool distributed via npm, separate from the bedrock-agentcore Python SDK your agent code imports. The older bedrock-agentcore-starter-toolkit pip package that used to provide a CLI is now deprecated in favor of it.
Can AgentCore Gateway replace the direct HTTP call in get_shipment_status?
Yes. Gateway turns an existing API or Lambda function into an MCP tool with centralized auth, so instead of calling httpx directly inside the tool you'd call it through Gateway and get permission checks and logging for free. This guide calls the API directly to keep the first deploy simple.
What does running this cost?
You pay standard Bedrock model token pricing for each agent call, plus AgentCore Runtime at roughly $0.09/vCPU-hour and $0.0095/GB-hour of compute while the agent is active, metered per invoke rather than as a flat monthly fee.
How do I know the approval gate actually can't be bypassed from inside the conversation?
Because the check lives in the issue_shipping_credit function itself, not in the system prompt. A customer or a cleverly worded message can influence what the model says, but it can't flip the approved parameter your own entrypoint code controls.
Can someone build this for me instead of deploying it myself?
Yes. RapidDev builds production agents on AWS Bedrock AgentCore, the Claude Agent SDK, and LangGraph, including the approval gating and observability wiring most tutorials skip. A free 30-minute consultation is the fastest way to scope your version.
Talk to an Expert
Our team has built 1,000+ apps. Get personalized help with your project.
Book a free consultation