<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jonathan Vogel</title>
    <description>The latest articles on DEV Community by Jonathan Vogel (@jvogel).</description>
    <link>https://dev.to/jvogel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3802327%2F145dca0a-b42c-4355-a0cd-5f56f0054687.jpg</url>
      <title>DEV Community: Jonathan Vogel</title>
      <link>https://dev.to/jvogel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jvogel"/>
    <language>en</language>
    <item>
      <title>How to Build and Deploy an AI Agent on AWS with Bedrock, Strands, and AgentCore</title>
      <dc:creator>Jonathan Vogel</dc:creator>
      <pubDate>Mon, 03 Aug 2026 16:45:16 +0000</pubDate>
      <link>https://dev.to/aws/how-to-build-and-deploy-an-ai-agent-on-aws-with-bedrock-strands-and-agentcore-1n1n</link>
      <guid>https://dev.to/aws/how-to-build-and-deploy-an-ai-agent-on-aws-with-bedrock-strands-and-agentcore-1n1n</guid>
      <description>&lt;p&gt;&lt;strong&gt;A foundational look at building and deploying an AI agent on AWS, from a single model call to a managed cloud endpoint, so you understand what Bedrock, Strands and AgentCore each do and how they fit together.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you prefer video format, check out this &lt;a href="https://youtu.be/igsklrIO3fo" rel="noopener noreferrer"&gt;content on our YouTube channel.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What you build:&lt;/strong&gt; the same question, "What should I make for dinner?", answered three ways. A raw model call, a local agent with one tool, then that same agent deployed to AWS. Each step shows what the next AWS layer adds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The three layers:&lt;/strong&gt; &lt;a href="https://aws.amazon.com/bedrock/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt; is the model, the brain. &lt;a href="https://strandsagents.com/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands&lt;/a&gt; is the harness that gives it a tool and a loop. &lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt; runs it in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What it costs:&lt;/strong&gt; close to nothing. Chapters 1 and 2 are just local Node and a Bedrock call. Chapter 3 creates real resources you tear down at the end. &lt;a href="https://aws.amazon.com/free/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Free Tier eligible accounts&lt;/a&gt; can cover it, and new AWS users can get up to $200 in credits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; 30 minutes to breeze thru or 1+ hour if you're really taking your time to unpack each piece.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I asked an AI model what I should make for dinner. It gave me some suggestions and it asked me what ingredients I had on hand.&lt;/p&gt;

&lt;p&gt;Duh! This is a critical question to get an idea of what to suggest and it had no idea. I had eggs, spinach, garlic, rice and some cheddar around the kitchen and the model couldn't see any of it. A sharp brain with no access to my world, getting one shot to guess.&lt;/p&gt;

&lt;p&gt;That gap is the whole story of this post. A model on its own is smart and blind. To make it useful you wrap it in a harness that gives it tools and a loop. Then, once it works on your machine, you hit the next wall: running it for other people, around the clock, without babysitting a server.&lt;/p&gt;

&lt;p&gt;I'll answer the same question three times, "What should I make for dinner?", and change what sits behind it. First a raw model call with nothing else. Then a local &lt;strong&gt;pantry chef&lt;/strong&gt; agent with a single tool, &lt;code&gt;get_pantry&lt;/code&gt;, that can actually check the kitchen. Then that same agent running on AWS. The tool only shows up in chapters two and three, once there's an agent to use it.&lt;/p&gt;

&lt;p&gt;Here's the progression:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Raw Amazon Bedrock.&lt;/strong&gt; Just the model. It answers, but it can't see the pantry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A local Strands agent.&lt;/strong&gt; Add one tool and a loop. Now it checks the pantry and grounds the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same agent on Amazon Bedrock AgentCore Runtime.&lt;/strong&gt; Deployed to AWS. Now running in the cloud from a managed endpoint anyone you authorize can call.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end you'll have run all three and you'll know which AWS piece does what.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in this post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The mental model&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Chapter 1: Raw Bedrock, a brain with no eyes&lt;/li&gt;
&lt;li&gt;Chapter 2: A local Strands agent, the loop that grounds the answer&lt;/li&gt;
&lt;li&gt;Chapter 3: Deploy to AgentCore Runtime&lt;/li&gt;
&lt;li&gt;Who can actually call this thing?&lt;/li&gt;
&lt;li&gt;Cost and teardown&lt;/li&gt;
&lt;li&gt;The extras you grow into: memory, gateway, observability&lt;/li&gt;
&lt;li&gt;The whole stack in one picture&lt;/li&gt;
&lt;li&gt;Reproduce this yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;One idea to hold onto before we write any code.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;agent is a model plus a harness.&lt;/strong&gt; The model is the brain, the part that reads your request and reasons about it. The harness is the code around the brain that gives it tools, instructions and a loop to use them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijfobam9ikudmsx2pfd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijfobam9ikudmsx2pfd9.png" alt="Image showing diagram: model + harness = agent." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Map that onto AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/bedrock/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt;&lt;/strong&gt; gives you the brain. It's a managed, serverless way to call top models from Anthropic, Meta and others, with no GPUs to rent and no servers to run. You pick a model, send a prompt, get a response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://strandsagents.com/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands&lt;/a&gt;&lt;/strong&gt; is the harness. It's an open source SDK from AWS that runs the tool-calling loop for you and works with almost any model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore&lt;/a&gt;&lt;/strong&gt; is where the finished agent runs in production. Managed, serverless hosting for the agent, plus the extras it needs to operate out there like memory, a gateway to your APIs, observability and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three layers that snap together. Pick your brain, build your agent, run it for real. The rest of this post is that sentence, in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An AWS account.&lt;/strong&gt; A personal one is fine. &lt;a href="https://aws.amazon.com/free/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Free Tier eligible accounts&lt;/a&gt; can cover this whole thing, and new AWS users can get up to $200 in credits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock model access.&lt;/strong&gt; I use Claude in this demo, but you can use whatever model you want. Make sure it's enabled for your preferred Region in the &lt;a href="https://console.aws.amazon.com/bedrock/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 22 or newer.&lt;/strong&gt; I ran v24. Check with &lt;code&gt;node --version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI v2, configured.&lt;/strong&gt; Run &lt;code&gt;aws configure&lt;/code&gt; (or SSO), set a default Region, then confirm with &lt;code&gt;aws sts get-caller-identity&lt;/code&gt;. If that returns your account, you're good. &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Install guide here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Region.&lt;/strong&gt; I used &lt;strong&gt;us-east-1&lt;/strong&gt; for everything. The &lt;code&gt;us.*&lt;/code&gt; model inference profiles resolve there and AgentCore Runtime is available there. If you pick a different Region, re-check that your model and AgentCore Runtime both exist in it before you start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Chapter 3 (the deploy), you also need two more things. You don't need them for Chapters 1 and 2, so you can install them later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/cdk/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;&lt;/strong&gt;, installed globally with &lt;code&gt;npm install -g aws-cdk&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A one-time CDK bootstrap&lt;/strong&gt; of your account and Region. More on this when we get there. The short version: it creates a small &lt;a href="https://aws.amazon.com/cloudformation/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt; stack in your account called &lt;code&gt;CDKToolkit&lt;/code&gt;. It lives in the cloud, not in your project folder, and you only do it once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AgentCore CLI&lt;/strong&gt;, installed with &lt;code&gt;npm install -g @aws/agentcore&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Chapters 1 and 2 run on nothing but Node and your AWS credentials. If you only want to see a model answer and then an agent ground that answer, you can stop after Chapter 2 and never install the CDK or the AgentCore CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Chapter 1: Raw Bedrock, a brain with no eyes
&lt;/h2&gt;

&lt;p&gt;For this we're simply calling the model directly. We get a good answer but it ultimately ends by asking what's in your kitchen. That missing context is the gap the next chapter closes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;About the model and why your output will look different from mine.&lt;/strong&gt; Every code sample here uses &lt;code&gt;us.anthropic.claude-sonnet-5&lt;/code&gt;, the US cross-region inference profile for Claude Sonnet 5. One model across all three chapters keeps the comparison honest. Just know that model output is non-deterministic. Ask "What should I make for dinner?" twice and you might get the same dish in different words, or a different dish entirely. That's expected. Your recipe might not match mine and two of your own runs might not match either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Set up the project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;01-bedrock-raw &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;01-bedrock-raw
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm pkg &lt;span class="nb"&gt;set type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;module
npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-sdk/client-bedrock-runtime
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tsx typescript @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm pkg set type=module&lt;/code&gt; matters. The code uses ES module &lt;code&gt;import&lt;/code&gt; syntax and a top-level &lt;code&gt;await&lt;/code&gt;, and that flag tells Node to treat the file as a module. We run TypeScript directly with &lt;a href="https://tsx.is/" rel="noopener noreferrer"&gt;&lt;code&gt;tsx&lt;/code&gt;&lt;/a&gt;, so there's no separate compile step.&lt;/p&gt;

&lt;h3&gt;
  
  
  The whole file
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;bedrock.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BedrockRuntimeClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ConverseCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-bedrock-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockRuntimeClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConverseCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us.anthropic.claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What should I make for dinner?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through and explain each part.&lt;/p&gt;

&lt;p&gt;The client points at Bedrock in one Region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockRuntimeClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ConverseCommand&lt;/code&gt; is the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Converse API&lt;/a&gt;, one consistent way to talk to any chat model on Bedrock. You name the model and hand it a list of messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConverseCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us.anthropic.claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What should I make for dinner?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you dig the text out of the response. The path looks fussy because a message can hold more than one content block, so you reach for the first one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx bedrock.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What comes back
&lt;/h3&gt;

&lt;p&gt;You get a helpful, generic answer. Here's a trimmed run (yours will differ, and the full list is longer):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dinner Ideas

I'd love to help! To give you good suggestions, it helps to know a bit more:

- What ingredients do you have on hand (or are willing to shop for)?
- How much time do you want to spend cooking?
- Any dietary preferences/restrictions?

...

Let me know what you've got in the fridge/pantry, and I can suggest something more specific!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that last line again. The model is asking me what's in my kitchen. It has no way to know so it makes some recommendations inspired by its training data and leaves us hanging a bit. Nothing is wrong with the model. It's sharp. It's just blind and it gets one shot to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 2: A local Strands agent, the loop that grounds the answer
&lt;/h2&gt;

&lt;p&gt;Now the same brain gets one tool and a loop. It checks the pantry before it answers and the reply changes completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up the project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;02-strands-agent &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;02-strands-agent
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm pkg &lt;span class="nb"&gt;set type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;module
npm &lt;span class="nb"&gt;install&lt;/span&gt; @strands-agents/sdk
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tsx typescript @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The whole file
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;agent.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BedrockModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPantry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_pantry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return the ingredients the user has at home right now.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eggs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spinach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;garlic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cheddar cheese&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us.anthropic.claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getPantry&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Suggest a recipe to make, check the pantry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What should I make for dinner?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still short. The last line is the same request from Chapter 1. Everything above it is the harness. Let's break down the three pieces that matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool.&lt;/strong&gt; This is the agent's connection to my world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPantry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_pantry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return the ingredients the user has at home right now.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eggs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spinach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;garlic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cheddar cheese&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;description&lt;/code&gt; is not a comment. The model reads it to decide when to call the tool, so write it for the model. This tool takes no arguments, so there's nothing else to declare. The &lt;code&gt;callback&lt;/code&gt; is the code that runs when the model calls the tool. Mine returns a hardcoded array, which is perfect for a demo. In a real app this is where you might hit a database or an API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent.&lt;/strong&gt; Model, tools, instructions, wired together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us.anthropic.claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getPantry&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Suggest a recipe to make, check the pantry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same model as Chapter 1, on purpose, so you can see the brain didn't change. I pass it explicitly here, though the Strands TS SDK defaults to a Bedrock Claude Sonnet model if you leave it out. The &lt;code&gt;systemPrompt&lt;/code&gt; tells the agent what to do and points it at the tool. The &lt;code&gt;tools&lt;/code&gt; array is the list it's allowed to reach for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The invocation.&lt;/strong&gt; One line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What should I make for dinner?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No orchestration code. That's the part worth pausing on.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the loop actually does
&lt;/h3&gt;

&lt;p&gt;When you call &lt;code&gt;invoke&lt;/code&gt;, Strands runs a cycle you didn't have to write:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The model reads the request and reasons about it.&lt;/li&gt;
&lt;li&gt;It decides to call &lt;code&gt;get_pantry&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Strands runs the tool and feeds the result back to the model.&lt;/li&gt;
&lt;li&gt;The model looks at the ingredients and decides if it's done. If not, it goes again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That cycle is the &lt;strong&gt;agentic loop&lt;/strong&gt;. The whole reason to use an SDK like Strands is that you get the loop, the tool calling and the message plumbing for free. For a simple agent, all you need to bring is a tool and a prompt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9l6c018str70x4ebb2r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9l6c018str70x4ebb2r0.png" alt="Diagram showing the agentic loop: model reasoning, model picking tool, harness runs tool, results return" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Run it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx agent.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What comes back
&lt;/h3&gt;

&lt;p&gt;The Strands TypeScript SDK ships with a console printer that's on by default, so you see the agent think, call the tool and answer, with no logging code from you. A representative run (again, wording will vary):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenr8ov9hskbblqnrcvb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenr8ov9hskbblqnrcvb9.png" alt="Terminal running npx tsx agent.ts where we see the agent call the pantry tool and return a recipe" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same brain. Same question. Completely different answer. It saw the eggs, spinach, garlic, rice and cheddar, and it built a real recipe around them instead of asking me what I had. I didn't write a loop, a parser or an orchestrator. I gave the model a tool and let Strands run the back and forth.&lt;/p&gt;

&lt;p&gt;That's a working agent. On my laptop. Which is exactly where the next problem starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 3: Deploy to AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;My agent ran great on my machine. Then I thought about letting other people use it. Now I'm thinking about hosting, scaling and keeping it healthy when more than one person shows up at once. I did not want to write and operate a web server just to expose one function.&lt;/p&gt;

&lt;p&gt;That's what &lt;strong&gt;&lt;a href="https://aws.amazon.com/bedrock/agentcore/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt;&lt;/strong&gt; handles. It's a managed, serverless way to run your agent in production. You bring the agent you already wrote, the CLI wraps it and ships it, and you get an endpoint back. Same agent logic, no server for you to run.&lt;/p&gt;

&lt;p&gt;Same pantry chef from Chapter 2, now put behind a managed endpoint with the AgentCore CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the deploy tooling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @aws/agentcore aws-cdk
agentcore &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# I had 0.21.1&lt;/span&gt;
cdk &lt;span class="nt"&gt;--version&lt;/span&gt;         &lt;span class="c"&gt;# I had 2.1128.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bootstrap once (this is the "we bootstrap this thing" step)
&lt;/h3&gt;

&lt;p&gt;AgentCore deploys through the AWS CDK, and the CDK needs a one-time setup per account and Region called a bootstrap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk bootstrap aws://&amp;lt;ACCOUNT_ID&amp;gt;/us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap in your 12-digit account ID. This creates a CloudFormation stack named &lt;code&gt;CDKToolkit&lt;/code&gt; and a small supporting S3 bucket. You only do this once per account and Region, so if you've bootstrapped here before you can skip it. To check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="nt"&gt;--stack-name&lt;/span&gt; CDKToolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns a stack with status &lt;code&gt;CREATE_COMPLETE&lt;/code&gt;, you're already bootstrapped. &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;More on bootstrapping here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffold the project
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;agentcore create&lt;/code&gt; command scaffolds a new agent project. It can walk you through an interactive wizard, but I'll pass the options directly so the step is repeatable and you can see exactly what we picked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project-name&lt;/span&gt; PantryChef &lt;span class="nt"&gt;--name&lt;/span&gt; PantryChef &lt;span class="nt"&gt;--type&lt;/span&gt; create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build&lt;/span&gt; CodeZip &lt;span class="nt"&gt;--language&lt;/span&gt; TypeScript &lt;span class="nt"&gt;--framework&lt;/span&gt; Strands &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-provider&lt;/span&gt; Bedrock &lt;span class="nt"&gt;--memory&lt;/span&gt; none &lt;span class="nt"&gt;--skip-git&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those flags say: a TypeScript project called &lt;code&gt;PantryChef&lt;/code&gt;, built as a &lt;strong&gt;CodeZip&lt;/strong&gt; (your code shipped as a zip), on the &lt;strong&gt;Strands&lt;/strong&gt; framework with &lt;strong&gt;Bedrock&lt;/strong&gt; as the model provider and no memory feature for now. It runs &lt;code&gt;npm install&lt;/code&gt; under the hood, so give it a moment. When it's done you have a &lt;code&gt;PantryChef/&lt;/code&gt; directory that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PantryChef/
  agentcore/
    agentcore.json          # runtime config: CodeZip, NODE_22, PUBLIC, HTTP
    cdk/                    # the CDK app the CLI deploys for you
  app/PantryChef/
    main.ts                 # entrypoint: wraps your agent in a runtime handler
    model/load.ts           # the model config lives HERE, not in main.ts
    mcp_client/client.ts    # an example MCP client, unused by our agent
    package.json
    tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The scaffold is a sample agent, not our agent
&lt;/h3&gt;

&lt;p&gt;Here's the thing the video does off camera. &lt;code&gt;agentcore create&lt;/code&gt; does not hand you a blank project. It generates a &lt;strong&gt;working sample agent&lt;/strong&gt;, and the sample is not the pantry chef. Two files ship with content you have to replace.&lt;/p&gt;

&lt;p&gt;First, the model. Open &lt;code&gt;app/PantryChef/model/load.ts&lt;/code&gt; and you'll see this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BedrockModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/models/bedrock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadModel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;BedrockModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;global.anthropic.claude-sonnet-4-5-20250929-v1:0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a real, pinned model ID, and it is not the one this demo uses. The scaffold defaults to Claude Sonnet 4.5. We've been running Sonnet 5 everywhere, so this file has to change.&lt;/p&gt;

&lt;p&gt;Second, the agent itself. Open &lt;code&gt;app/PantryChef/main.ts&lt;/code&gt; and you'll find a sample that adds two numbers and wires up an example MCP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BedrockAgentCoreApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bedrock-agentcore/runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ToolList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./model/load.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getStreamableHttpMcpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./mcp_client/client.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define a collection of MCP clients (filter out anything that failed to initialize)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mcpClients&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;getStreamableHttpMcpClient&lt;/span&gt;&lt;span class="p"&gt;()].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;McpClient&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Define a collection of tools used by the model&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Define a simple function tool — the Zod schema gives us type inference and runtime validation for free&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addNumbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add_numbers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return the sum of two numbers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addNumbers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add MCP clients to tools&lt;/span&gt;
&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;mcpClients&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You are a helpful assistant. Use tools when appropriate.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... the rest of the file (the runtime handler) is shown below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New to MCP? Don't worry about it too much right now. It's a standard way to plug external tools into an agent. The scaffold includes an example client to show it's possible but the pantry chef doesn't need it. We're about to replace the whole sample with our own tool and prompt.&lt;/p&gt;

&lt;p&gt;So "the agent logic stays the same" is true for the tool, the prompt and the model, but there's real runtime plumbing around it that the CLI wrote for you. Turning this sample into the pantry chef is exactly &lt;strong&gt;two edits&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Edit 1: swap the model in &lt;code&gt;app/PantryChef/model/load.ts&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Replace the whole file with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BedrockModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/models/bedrock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadModel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;BedrockModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us.anthropic.claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two changes from the scaffold. The model ID is now &lt;code&gt;us.anthropic.claude-sonnet-5&lt;/code&gt;, and I added &lt;code&gt;region: 'us-east-1'&lt;/code&gt; so the model resolves in the Region we've been using.&lt;/p&gt;

&lt;h4&gt;
  
  
  Edit 2: make it the pantry chef in &lt;code&gt;app/PantryChef/main.ts&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Replace the whole file with this. It's our &lt;code&gt;get_pantry&lt;/code&gt; tool and prompt from Chapter 2, dropped into the runtime handler the CLI generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BedrockAgentCoreApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bedrock-agentcore/runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ToolList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./model/load.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The one tool this agent has: what is in the kitchen right now.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPantry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_pantry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return the ingredients the user has at home right now.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eggs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spinach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;garlic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cheddar cheese&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getPantry&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
Suggest a recipe to make, check the pantry
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cachedAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOrCreateAgent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cachedAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;cachedAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cachedAgent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;invocationHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getOrCreateAgent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelStreamUpdateEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelContentBlockDeltaEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textDelta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top half is the Chapter 2 agent, unchanged. Notice there's no &lt;code&gt;zod&lt;/code&gt; here. The scaffold imported it for its sample &lt;code&gt;add_numbers&lt;/code&gt; tool, which takes arguments that need a schema, but &lt;code&gt;get_pantry&lt;/code&gt; takes none. The Strands &lt;code&gt;tool()&lt;/code&gt; helper treats &lt;code&gt;inputSchema&lt;/code&gt; as optional and defaults to an empty schema, so dropping zod changes nothing about the tool the model sees. The bottom half is the part the CLI gave you, and it's worth understanding because it's what makes this a deployable service instead of a script.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getOrCreateAgent&lt;/code&gt; builds the agent once and caches it, so you're not rebuilding it on every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cachedAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOrCreateAgent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cachedAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;cachedAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cachedAgent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BedrockAgentCoreApp&lt;/code&gt; is the runtime handler. This is the part you'd otherwise hand write as a web server. The &lt;code&gt;process&lt;/code&gt; generator receives the incoming request payload, streams the agent's output and yields just the text as it's produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;invocationHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getOrCreateAgent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelStreamUpdateEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelContentBlockDeltaEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textDelta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what this handler streams: only the text deltas. So a caller sees the recipe but not the &lt;code&gt;get_pantry&lt;/code&gt; tool-call line. The tool still runs on the server. You just don't stream that part to the client. Hold that thought for the observability note near the end.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You do not need to run &lt;code&gt;npm run build&lt;/code&gt;.&lt;/strong&gt; Local dev runs your TypeScript directly, and the deploy compiles and bundles during the CDK step. There's no manual build in this workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Run it locally first
&lt;/h3&gt;

&lt;p&gt;The CLI gives you a local server that behaves like the deployed one. Open two terminals, both inside the &lt;code&gt;PantryChef&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Terminal 1, start the server (give it a few seconds to come up):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;PantryChef
agentcore dev &lt;span class="nt"&gt;--logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terminal 2, send the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore dev &lt;span class="s2"&gt;"What should I make for dinner?"&lt;/span&gt; &lt;span class="nt"&gt;--stream&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the fried rice recipe stream back in Terminal 2. Over in Terminal 1, the &lt;code&gt;--logs&lt;/code&gt; output shows the &lt;code&gt;get_pantry&lt;/code&gt; tool firing on the server side, which is the tool call the streamed client output doesn't show.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy it
&lt;/h3&gt;

&lt;p&gt;If you want to see what the deploy will do before it does it, preview first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore deploy &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ship it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore deploy &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This takes a minute or so. Under the hood the CLI zips your code and uses the CDK to create a handful of resources: a CloudFormation stack named &lt;code&gt;AgentCore-PantryChef-default&lt;/code&gt;, an IAM execution role with its policy and the &lt;code&gt;AWS::BedrockAgentCore::Runtime&lt;/code&gt; itself. When it finishes it prints the outputs, which look like this (account ID shown as a placeholder):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runtime ARN: arn:aws:bedrock-agentcore:us-east-1:111122223333:runtime/PantryChef_PantryChef-xxxxxxxxxx
Runtime ID:  PantryChef_PantryChef-xxxxxxxxxx
Role ARN:    arn:aws:iam::111122223333:role/AgentCore-PantryChef-defa-ApplicationAgentPantryChe-xxxxxxxxxxxx
Stack:       AgentCore-PantryChef-default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoke it from the cloud
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="s2"&gt;"What should I make for dinner?"&lt;/span&gt; &lt;span class="nt"&gt;--stream&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few seconds later, the same kitchen assistant answers, this time from the managed runtime instead of your laptop:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3tay1i00kes836twwcte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3tay1i00kes836twwcte.png" alt="Terminal command agentcore invoke to test our running the agent from a managed endpoint in the cloud" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;Session&lt;/code&gt; ID at the end is worth noticing. Each &lt;code&gt;invoke&lt;/code&gt; without a session ID starts a fresh conversation. Same grounded answer as the laptop, now coming from an endpoint other people can call, with no server for you to run.&lt;/p&gt;

&lt;p&gt;Want to check on it later?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore status
&lt;span class="c"&gt;# PantryChef: Deployed - Runtime: READY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The four commands, start to finish
&lt;/h3&gt;

&lt;p&gt;That's the whole deploy loop, and it really is four commands once the tooling is in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create   &lt;span class="c"&gt;# scaffold: TypeScript, Strands, CodeZip&lt;/span&gt;
agentcore dev      &lt;span class="c"&gt;# run and test locally&lt;/span&gt;
agentcore deploy   &lt;span class="c"&gt;# ship to AWS via CDK&lt;/span&gt;
agentcore invoke &lt;span class="s2"&gt;"What should I make for dinner?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing the video hides between &lt;code&gt;create&lt;/code&gt; and &lt;code&gt;dev&lt;/code&gt; is the two-file edit you just did by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who can actually call this thing?
&lt;/h2&gt;

&lt;p&gt;The runtime we deployed uses &lt;code&gt;networkMode: PUBLIC&lt;/code&gt;. That phrase sounds alarming, so let's be precise about what it means, because it does not mean an open, anonymous endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public here means reachable over the internet, not open to everyone.&lt;/strong&gt; By default an AgentCore Runtime endpoint is public on the network, but every request has to be authenticated, either with AWS IAM (SigV4) or an &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-oauth.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;OAuth bearer token&lt;/a&gt;. With the default IAM setup, "anyone can call it" really means "anyone you grant the &lt;code&gt;bedrock-agentcore:InvokeAgentRuntime&lt;/code&gt; permission to." No credentials, no call. It is not a URL a stranger can hit.&lt;/p&gt;

&lt;p&gt;If you've read &lt;a href="https://builder.aws.com/content/3Dtek0fc91rqFkg0B7on86aiQkQ/access-denied-what-every-aws-beginner-gets-wrong-about-iam?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;my IAM post&lt;/a&gt;, you know where this is going. The execution role and policy the AgentCore CLI generated are fine for a demo, but AWS is explicit that &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-security-best-practices.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CLI-generated policies are meant for development and testing, not production&lt;/a&gt;. Before you put anything real behind this, scope the permissions down to the specific runtime ARN and the specific callers that need it. Least privilege, same as everywhere else in AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and teardown
&lt;/h2&gt;

&lt;p&gt;The deploy left real, billable resources running: the AgentCore runtime, an IAM role and a CloudFormation stack. When you're done experimenting, tear them down. From inside the &lt;code&gt;PantryChef&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore remove all &lt;span class="nt"&gt;-y&lt;/span&gt;     &lt;span class="c"&gt;# clears the agentcore config&lt;/span&gt;
agentcore deploy &lt;span class="nt"&gt;-y&lt;/span&gt;         &lt;span class="c"&gt;# applies the removal, tears down the AWS resources&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, you run &lt;code&gt;deploy&lt;/code&gt; to tear down. The first command empties the config, the second pushes that empty state to AWS, which removes the stack.&lt;/p&gt;

&lt;p&gt;Verify it's actually gone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; AgentCore-PantryChef-default
&lt;span class="c"&gt;# should error: Stack ... does not exist&lt;/span&gt;

aws bedrock-agentcore-control list-agent-runtimes &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"agentRuntimes[].agentRuntimeName"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;span class="c"&gt;# your PantryChef runtime should no longer be listed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave the &lt;code&gt;CDKToolkit&lt;/code&gt; bootstrap stack in place. It costs almost nothing, it's shared by any CDK work in the account, and you don't want to re-bootstrap next time. Only remove it if you're certain nothing else in that account and Region uses the CDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  The extras you grow into: memory, gateway, observability
&lt;/h2&gt;

&lt;p&gt;Hosting is the headline, but AgentCore brings more building blocks you pull in when you actually need them. Three worth naming.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt; lets your agent remember people across conversations. My pantry chef could remember that I like spicy food without me saying it every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway&lt;/strong&gt; turns APIs and Lambda functions you already have into tools the agent can call, so you're not hand writing every integration. Today &lt;code&gt;get_pantry&lt;/code&gt; returns a hardcoded list. A real version would call an API, and Gateway is how you'd wire that up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; shows you what the agent actually did. Remember how the streamed output hid the &lt;code&gt;get_pantry&lt;/code&gt; tool call? This is where you'd see it, the full trace of the agent's reasoning and tool use, for when something looks off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You reach for these when your agent needs them. Not before. There's more we didn't cover, like Identity and payments. If you're curious, the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore docs&lt;/a&gt; walk through the full set.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole stack in one picture
&lt;/h2&gt;

&lt;p&gt;Step back and look at what you built.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock&lt;/strong&gt; is the brain. It reasons, but on its own it can't see your world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strands&lt;/strong&gt; is the harness. It gives the brain a tool and runs the agentic loop, which turns a smart guess into a grounded answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentCore&lt;/strong&gt; is production. It takes the agent off your laptop and runs it as a managed endpoint, with memory, gateway and observability waiting when you need them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three layers that snap together. Pick your brain, build your agent, run it for real. Same pantry chef the whole way, same dinner question, and you watched the answer go from a generic list to a real recipe to that same recipe served from the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3mtdztfbx4iyb3ult57t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3mtdztfbx4iyb3ult57t.png" alt="Full AI stack showing bedrock, strands and agentcore" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce this yourself
&lt;/h2&gt;

&lt;p&gt;Everything above runs on AWS today. Here's the checklist I use to confirm a clean run, top to bottom.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; returns the account you intend to use, and your Region is set.&lt;/li&gt;
&lt;li&gt;[ ] Bedrock model access is enabled for &lt;code&gt;us.anthropic.claude-sonnet-5&lt;/code&gt; in your Region.&lt;/li&gt;
&lt;li&gt;[ ] The model resolves: &lt;code&gt;aws bedrock list-inference-profiles --region us-east-1&lt;/code&gt; shows it &lt;code&gt;ACTIVE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Chapter 1: &lt;code&gt;npx tsx bedrock.ts&lt;/code&gt; prints a generic answer that asks what ingredients you have.&lt;/li&gt;
&lt;li&gt;[ ] Chapter 2: &lt;code&gt;npx tsx agent.ts&lt;/code&gt; shows the &lt;code&gt;get_pantry&lt;/code&gt; tool call and a recipe built from the pantry.&lt;/li&gt;
&lt;li&gt;[ ] Deploy tooling: &lt;code&gt;agentcore --version&lt;/code&gt; and &lt;code&gt;cdk --version&lt;/code&gt; both succeed.&lt;/li&gt;
&lt;li&gt;[ ] CDK is bootstrapped, or &lt;code&gt;CDKToolkit&lt;/code&gt; already shows &lt;code&gt;CREATE_COMPLETE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;agentcore create&lt;/code&gt; produced the &lt;code&gt;PantryChef/&lt;/code&gt; scaffold.&lt;/li&gt;
&lt;li&gt;[ ] Both edits are applied: the model in &lt;code&gt;model/load.ts&lt;/code&gt; and the &lt;code&gt;get_pantry&lt;/code&gt; tool plus prompt in &lt;code&gt;main.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;agentcore dev&lt;/code&gt; with &lt;code&gt;--stream&lt;/code&gt; returns the grounded recipe locally.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;agentcore deploy&lt;/code&gt; succeeds and prints the runtime outputs.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;agentcore invoke --stream&lt;/code&gt; returns the recipe from the cloud and a &lt;code&gt;Session&lt;/code&gt; ID.&lt;/li&gt;
&lt;li&gt;[ ] Teardown done: the stack and the runtime are both gone, and &lt;code&gt;CDKToolkit&lt;/code&gt; is left in place.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Your turn
&lt;/h3&gt;

&lt;p&gt;You've watched me type every command so we could see the concepts with nothing in the way. In practice, most people building agents aren't typing this by hand. You might be using an agent to help you move faster, examples include agentic coding tools like Kiro, Claude Code, Codex or something similiar to move faster. Whatever you use, set up the &lt;a href="https://aws.amazon.com/products/developer-tools/agent-toolkit-for-aws/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt; so your agent knows best how to work with AWS.&lt;/p&gt;

&lt;p&gt;Build something and ship it. Tell me what your agent does in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A model on its own is all brains. Make it an agent, give it a tool, a loop and a place to run, and it gets to work.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bedrock</category>
      <category>agents</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>us-east-1 or Somewhere Closer? How to Pick an AWS Region Without Overthinking It</title>
      <dc:creator>Jonathan Vogel</dc:creator>
      <pubDate>Fri, 05 Jun 2026 15:21:21 +0000</pubDate>
      <link>https://dev.to/aws/us-east-1-or-somewhere-closer-how-to-pick-an-aws-region-without-overthinking-it-1a78</link>
      <guid>https://dev.to/aws/us-east-1-or-somewhere-closer-how-to-pick-an-aws-region-without-overthinking-it-1a78</guid>
      <description>&lt;p&gt;&lt;strong&gt;A 30-second decision on your very first screen that saves a lot of confusion later.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You sign up for AWS, open the console for the first time, and before you've built anything there's a dropdown in the top-right corner asking you to pick a Region. N. Virginia. Ohio. Ireland. Tokyo. A couple dozen options and no context for what any of them mean or why you'd choose one over another.&lt;/p&gt;

&lt;p&gt;So you do what most people do. You leave it on whatever it defaulted to, or you pick one that sounds close, and you move on. Then a week later you come back, switch something, and your S3 bucket is gone. Your EC2 instance is gone. Everything you built looks like it vanished.&lt;/p&gt;

&lt;p&gt;Not a good feeling until you realize it's all good, everything's there, you're simply looking in the wrong Region.&lt;/p&gt;

&lt;p&gt;I talk to students and AWS beginners who run into this scenario. What's up with the Region drop down and why does it matter? By the end of this post you'll know what a Region is, the four things that go into picking one, why most of them don't matter for you yet, and why your stuff seems to disappear when you switch.&lt;/p&gt;

&lt;p&gt;Quick note before we start. If you search around, most Region guidance is written for companies shipping production workloads. The advice is good and I link to the best of it below, but it carries an unspoken assumption: that this choice is heavy and you'd better get it right. For a student on a first project, that framing is backwards. Your Region choice is low-stakes and easy to redo. I regularly get asked by folks getting started with AWS which region to pick. This post is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Region actually is
&lt;/h2&gt;

&lt;p&gt;A Region is a physical location in the world where AWS runs a cluster of data centers. US East (N. Virginia) is a real set of buildings in Virginia. Europe (Ireland) is a real set of buildings in Ireland. When you launch an EC2 instance or create an S3 bucket in a Region, your stuff physically lives in that part of the world.&lt;/p&gt;

&lt;p&gt;The list of AWS regions continues to grow. In June 2026, AWS runs &lt;a href="https://aws.amazon.com/about-aws/global-infrastructure/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;39 Regions and 123 Availability Zones around the world&lt;/a&gt;, with more announced. You don't need to memorize them. You need to pick one and understand the reasons why people end up in one region or another. The high level reasoning doesn't change even as more regions continue to launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four things that actually matter
&lt;/h2&gt;

&lt;p&gt;AWS publishes a short list of what goes into a Region choice. There are &lt;a href="https://aws.amazon.com/blogs/architecture/what-to-consider-when-selecting-a-region-for-your-workloads/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;four factors&lt;/a&gt; you should be aware of. While it might be worth bookmarking that post, it is aimed at teams choosing a home for a real production workload. Let's walk through the same four factors through a beginner lens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Latency.&lt;/strong&gt; This is the big one for anything people interact with. The closer a Region is to whoever uses your app, the faster it feels, because the data has less physical distance to travel. A site hosted in Tokyo will feel snappy in Osaka compared to say Toronto. For a student building a portfolio project, "whoever uses your app" is mostly you and whoever clicks the link on your resume, so closer to you wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cost.&lt;/strong&gt; AWS prices the same service differently depending on the Region. The differences come from real-world costs like land, power and taxes in each location. The gaps are real but small at the scale you'll be working at. You can check exact numbers in the &lt;a href="https://calculator.aws/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Pricing Calculator&lt;/a&gt; when it matters. One thing to put out of your mind: free tier limits are account-wide, not Region-specific, so your Region choice won't affect your free tier eligibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Service availability.&lt;/strong&gt; AWS rolls new services and features out Region by Region. A smaller Region might not have that brand-new service you read about yet, though it's just as reliable, the newest features simply land in the bigger Regions first. For the core building blocks a beginner uses, EC2, S3, Lambda, RDS, every Region has them (you can check what's where on the &lt;a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Region services list&lt;/a&gt; or the &lt;a href="https://builder.aws.com/capabilities/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Builder Center's visual capabilities page&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Compliance and data residency.&lt;/strong&gt; Some data is legally required to stay inside a specific country or jurisdiction. If you're handling that kind of data, this factor overrides the other three. As a student on a personal project, this almost never applies to you. It's worth knowing it exists, because the day a job hands you regulated data, this becomes the first question you ask, not the last.&lt;/p&gt;

&lt;p&gt;Notice the order of who cares about what. A bank cares about compliance first. A game backend cares about latency first. A data-crunching batch job that no human waits on cares about cost first. Right now, you care about latency, which conveniently points to the simplest possible answer.&lt;/p&gt;

&lt;p&gt;There's technically a fifth factor AWS publishes for teams with sustainability goals: some Regions run on cleaner energy than others. Don't worry about this as a beginner. If you care about your footprint, you'll have far more impact by turning off resources you're not using than by hunting for a greener Region. This same instinct will help keep your bill lower too!&lt;/p&gt;

&lt;h2&gt;
  
  
  For your first project, pick the closest one and move on
&lt;/h2&gt;

&lt;p&gt;The beginner shortcut: pick the Region closest to you and stick with it for everything. This move will ensure you don't have to worry about latency for a personal project and give you the services you need as a beginner. &lt;/p&gt;

&lt;p&gt;One nuance worth a sentence. A lot of tutorials and AWS examples default to &lt;strong&gt;us-east-1&lt;/strong&gt; (N. Virginia), and some guides quietly assume you're in it. It's worth noting us-east-1 is often the first Region to get the latest goodies AWS drops, new services tend to start there before they're available anywhere else. If you're following a step-by-step guide and something won't line up, check whether the author is in us-east-1 while you're somewhere else. For your own building, closest-to-you is the better default. For following along with a tutorial, matching the tutorial's Region can save you a headache.&lt;/p&gt;

&lt;p&gt;The part that matters more than which Region you pick is &lt;strong&gt;picking one and being consistent&lt;/strong&gt;. Which brings us to the thing that trips up almost everyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  "But what if I pick wrong?"
&lt;/h3&gt;

&lt;p&gt;You won't and you're not stuck there. If you start in Ohio and later decide Ireland is closer to your users, you spin up fresh resources in Ireland and tear down the old ones. There's no penalty, no lock-in, no big migration task for a personal app with a handful of resources. The companies that agonize over this are moving terabytes of data and thousands of resources, where moving might take a bit more work. You are moving a bucket and an instance. Pick one, learn on it, change your mind freely. The cost of "wrong" at your scale is measured in minutes instead of weeks or months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your bucket "disappeared" (one of the gotchas)
&lt;/h2&gt;

&lt;p&gt;Most AWS resources are Region-scoped. That means a resource you create lives in exactly one Region and shows up only when you're viewing that Region in the console. &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Each Region is fully isolated from the others&lt;/a&gt;, by design, so a problem in one Region can't take down another.&lt;/p&gt;

&lt;p&gt;So picture this. You create an EC2 instance in Ireland on Monday. On Wednesday you open the console, the Region dropdown happens to say Ohio, and you go looking for your instance. It's not there. Panic.&lt;/p&gt;

&lt;p&gt;Nothing got deleted. You're standing in a different room. Switch to Ireland and your instance is right where you left it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbz9dyyvspm6pduythz4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbz9dyyvspm6pduythz4.gif" alt="Animated diagram showing two side-by-side AWS Region panels, Europe Ireland and US East Ohio. A cursor switches the Region dropdown from Ireland to Ohio, the S3 bucket disappears because Ohio is empty, then switches back to Ireland where the bucket is still there. Caption reads " width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is exactly how beginners end up scattering resources without realizing it. You do one tutorial in us-east-1, a class project in us-west-2, and a weekend experiment somewhere else. Now your account has things spread across three Regions. You can't find your stuff, your bill has charges from Regions you forgot you touched, and resources look "missing" when they're just somewhere else. &lt;/p&gt;

&lt;p&gt;Future you will be grateful for picking a region and sticking to it in the beginning.&lt;/p&gt;

&lt;h3&gt;
  
  
  The exception that's worth knowing
&lt;/h3&gt;

&lt;p&gt;A handful of AWS services are global, not Region-scoped, so they look the same no matter what the dropdown says. The ones you'll meet early are IAM (users and permissions), billing (account-wide), and likely Route 53 / CloudFront. So if your IAM users don't change when you switch Regions, that's correct. They're global. Everything else, assume it's tied to a Region until you learn otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-second decision, as a flow
&lt;/h2&gt;

&lt;p&gt;When deciding on a region, run this in your head.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is there a legal rule about where this data must live? If yes, pick a compliant Region in that jurisdiction. Done. (As a student, you'll almost always skip this.)&lt;/li&gt;
&lt;li&gt;Does a human wait on this app? If yes, pick the Region closest to those people. For a personal project, that's closest to you.&lt;/li&gt;
&lt;li&gt;No humans waiting, just background number-crunching? Pick the cheapest Region that has the services you need.&lt;/li&gt;
&lt;li&gt;Following a tutorial that assumes a Region? Match it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then, the rule that ties it all together. Whatever you pick, use it for everything in this project so your resources don't scatter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5r65s5regtk66xb1juc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5r65s5regtk66xb1juc.gif" alt="Animated flowchart where the beginner path lights up through two decisions, landing on " width="486" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region decision factor&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Does it matter for your first project?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Closer Region = faster for users&lt;/td&gt;
&lt;td&gt;Yes. Pick closest to you.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Same service, slightly different price per Region&lt;/td&gt;
&lt;td&gt;Barely. Differences are small at your scale.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service availability&lt;/td&gt;
&lt;td&gt;Newer features land in bigger Regions first&lt;/td&gt;
&lt;td&gt;No. Core services are everywhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance&lt;/td&gt;
&lt;td&gt;Data legally bound to a location&lt;/td&gt;
&lt;td&gt;Almost never for students. Know it exists.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistency&lt;/td&gt;
&lt;td&gt;Keep everything in one Region&lt;/td&gt;
&lt;td&gt;Yes. This is the one that saves you pain.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gotcha&lt;/th&gt;
&lt;th&gt;Why it happens&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"My resource disappeared"&lt;/td&gt;
&lt;td&gt;Resources are Region-scoped; you switched Regions&lt;/td&gt;
&lt;td&gt;Switch the dropdown back to the Region you built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charges from a Region you forgot&lt;/td&gt;
&lt;td&gt;You scattered resources across Regions&lt;/td&gt;
&lt;td&gt;Pick one Region and stay in it; clean up the strays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM users look the same everywhere&lt;/td&gt;
&lt;td&gt;IAM is a global service&lt;/td&gt;
&lt;td&gt;That's correct, nothing to fix&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Picking a Region is step one. The next fear most beginners have is the bill. If you've heard the horror stories about surprise AWS charges, read &lt;a href="https://jvogel.me/posts/2026/aws-still-charging-you" rel="noopener noreferrer"&gt;You Deleted Everything and AWS Is Still Charging You&lt;/a&gt; next. It walks through what actually keeps costing you after you think you've cleaned up, and how to set a billing alarm so nothing sneaks past you. Pair these two and you've handled the two things that scare people off AWS on day one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The Region dropdown isn't a test you can fail. Pick the one closest to you, keep everything there, and keep building.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Access Denied: What Every AWS Beginner Gets Wrong About IAM</title>
      <dc:creator>Jonathan Vogel</dc:creator>
      <pubDate>Wed, 20 May 2026 19:45:19 +0000</pubDate>
      <link>https://dev.to/aws/access-denied-what-every-aws-beginner-gets-wrong-about-iam-9k1</link>
      <guid>https://dev.to/aws/access-denied-what-every-aws-beginner-gets-wrong-about-iam-9k1</guid>
      <description>&lt;p&gt;&lt;strong&gt;The IAM mental model I wish someone had drawn on a whiteboard for me when I was starting out with AWS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want a video to follow along with this blog, you can find it on the &lt;a href="https://youtu.be/9Pk2J_5qnlk" rel="noopener noreferrer"&gt;AWS Developers Youtube Channel&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I spun up a Lambda function and tried to have it read from an S3 bucket only to get Access Denied.&lt;/p&gt;

&lt;p&gt;This wasn't a typo or misconfiguration. I just straight up didn't understand IAM. So I did what every beginner does, I attached AdministratorAccess, the error went away, and I moved on.&lt;/p&gt;

&lt;p&gt;What I didn't think about at the time is that I'd just given that Lambda function permission to do &lt;em&gt;anything&lt;/em&gt; in my account. Delete databases. Create resources that cost money. Access data across every service it had no business touching. All because it needed to read from one S3 bucket.&lt;/p&gt;

&lt;p&gt;I talk to students and developers getting started with AWS all the time, and this is the pattern. Everything goes smooth when you follow the tutorial. When it comes to implementing your own project, you run into a permission blocker. You come up with an easy solution. You slap on AdministratorAccess and now you have a security problem you don't even know about. The Access Denied error was actually trying to help you. It was telling you exactly which permission was missing. You just didn't know how to read it yet.&lt;/p&gt;

&lt;p&gt;This post is the mental model I wish I'd had. Once you understand what I'm about to lay out, Access Denied stops being a wall and starts being a useful message.&lt;/p&gt;

&lt;h2&gt;
  
  
  What IAM Actually Is
&lt;/h2&gt;

&lt;p&gt;IAM stands for &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Identity and Access Management&lt;/a&gt;. Not sure if that name really helps you, let me put it differently.&lt;/p&gt;

&lt;p&gt;IAM is the bouncer at the door of every AWS service. Every time anything happens in your account, whether you click a button in the console, your code calls an API, or a Lambda function tries to read from a database, IAM checks two things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Who are you?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you allowed to do this?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer to either question is "no," you get Access Denied. Those two questions are the entire foundation. The rest of this post is about how they get answered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Users, Roles and Policies
&lt;/h2&gt;

&lt;p&gt;Three concepts make up the whole model when you're getting started.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Users = Employee Badge
&lt;/h3&gt;

&lt;p&gt;An &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;IAM user&lt;/a&gt; is a persistent identity. It represents a person who needs to log into the console or use the CLI. It has long-term credentials, a username and password for the console, or access keys for programmatic access.&lt;/p&gt;

&lt;p&gt;Think of it like an employee badge. It's yours, it has your name on it, and it works every day until someone revokes it.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Roles = Visitor Pass
&lt;/h3&gt;

&lt;p&gt;An &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;IAM role&lt;/a&gt; is temporary. It doesn't belong to anyone permanently. Instead, it gets &lt;em&gt;assumed&lt;/em&gt; (borrowed) by whoever needs it at that moment. AWS gives the assumer temporary credentials that expire automatically.&lt;/p&gt;

&lt;p&gt;Think of it like a visitor pass at an office. You check in, you get a badge, it works for a few hours, then it stops working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb.&lt;/strong&gt; If it's a person logging in, that's a user. If it's a service doing something, a Lambda function, an EC2 instance, another AWS account, that's a role. Roles are how Lambda functions access S3, how EC2 instances talk to DynamoDB and how one AWS account talks to another. The credentials are always short-lived, so there's nothing sitting around that could be stolen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policies = Permission Slip
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;policy&lt;/a&gt; is a JSON document that says "this identity is allowed to do these actions on these resources." You attach policies to users or roles. Without a policy attached, an IAM identity can do nothing. You have to explicitly grant every single permission.&lt;/p&gt;

&lt;p&gt;A simple policy looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says: allow reading objects from one specific S3 bucket. Nothing else.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mental Model
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;user&lt;/strong&gt; or &lt;strong&gt;role&lt;/strong&gt; is &lt;em&gt;who you are&lt;/em&gt;. A &lt;strong&gt;policy&lt;/strong&gt; is &lt;em&gt;what you're allowed to do&lt;/em&gt;. Identity plus permission. That's the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Least Privilege Principle
&lt;/h2&gt;

&lt;p&gt;This is the one concept that makes everything in IAM make sense. It applies across all of computer security, not just cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Least privilege means: give every identity only the permissions it needs to do its job. Nothing more.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You already saw the wrong version in my story. I gave a Lambda function permission to do everything because it needed to do one thing. AdministratorAccess makes the error go away, but it also means anything in your account can do anything to your account.&lt;/p&gt;

&lt;p&gt;That's like giving every employee in a company a key that opens every door because they needed to open one.&lt;/p&gt;

&lt;p&gt;The right approach is to figure out exactly which actions your identity needs, on exactly which resources, and grant only that. That Lambda function I mentioned at the start? The policy it actually needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One action. One bucket. If that function ever gets compromised, the damage is limited to reading objects from one bucket, not your entire account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr93ou0btgdyypbajc2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr93ou0btgdyypbajc2m.png" alt="Screenshot of AdministratorAccess policy vs scoped policy side by side" width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"But figuring out exact permissions sounds like a lot of work." It can take some extra minutes upfront. AWS gives you a tool called &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;IAM Access Analyzer&lt;/a&gt; that looks at what your identity actually used over a period of time and generates a scoped policy based on real activity. You let your service run, then Access Analyzer tells you what it actually needed. You don't have to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your IAM Starter Checklist
&lt;/h2&gt;

&lt;p&gt;I share this with every student setting up a new AWS account. Bookmark it and come back at the end of every project.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Lock down your root user
&lt;/h3&gt;

&lt;p&gt;When you first create an AWS account, you start as the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;root user&lt;/a&gt;. Root can do anything, including things no other identity can do, like closing the account entirely. It's the "break glass in case of emergency" identity.&lt;/p&gt;

&lt;p&gt;Enable &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;MFA&lt;/a&gt; on root immediately. That's multi-factor authentication, so even if someone gets your password, they still can't log in without your second factor. Then stop using root for daily development.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set up daily access
&lt;/h3&gt;

&lt;p&gt;For day-to-day work, create a separate identity. If you're learning on a personal account, an IAM user with MFA works fine. If you're working with a team or thinking about production, &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;IAM Identity Center&lt;/a&gt; is the current best practice. It gives you temporary credentials and scales well when you add team members.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use roles for services
&lt;/h3&gt;

&lt;p&gt;When you build things, Lambda functions, EC2 instances, anything running code, use roles. They don't need long-lived access keys. They need roles with temporary credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Start with managed policies, then tighten
&lt;/h3&gt;

&lt;p&gt;AWS has &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;pre-built managed policies&lt;/a&gt; for common use cases. They're a reasonable starting point when you're learning. As you understand what your application actually needs, narrow the permissions down. You don't have to write perfect policies on day one, but you should be moving toward least privilege over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Never put access keys in your code
&lt;/h3&gt;

&lt;p&gt;Putting keys directly in source code is a bad idea. They should never be in config files or any variable pushed to Git. Use roles instead. If you ever accidentally push AWS keys to a public repo, rotate them immediately. Bots scan for exposed keys within minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What to Do&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lock down root&lt;/td&gt;
&lt;td&gt;Enable MFA, stop using root daily&lt;/td&gt;
&lt;td&gt;Root = key that opens all doors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set up daily access&lt;/td&gt;
&lt;td&gt;IAM user (learning) or Identity Center (teams)&lt;/td&gt;
&lt;td&gt;Limits root exposure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use roles for services&lt;/td&gt;
&lt;td&gt;Lambda, EC2 get roles, not users&lt;/td&gt;
&lt;td&gt;Temporary credentials, nothing to steal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start managed, then tighten&lt;/td&gt;
&lt;td&gt;Use AWS managed policies first&lt;/td&gt;
&lt;td&gt;Don't guess permissions on day one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No keys in code&lt;/td&gt;
&lt;td&gt;Use roles, not hardcoded credentials&lt;/td&gt;
&lt;td&gt;Bots scan for exposed keys within minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I Tell Students Who Are Afraid of IAM
&lt;/h2&gt;

&lt;p&gt;Every time someone tells me "I just attach AdministratorAccess because IAM is confusing," I tell them the same thing. The confusion comes from not having the mental model. Now you have it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Users&lt;/strong&gt; are people. &lt;strong&gt;Roles&lt;/strong&gt; are services. &lt;strong&gt;Policies&lt;/strong&gt; define what any of them can do. &lt;strong&gt;Least privilege&lt;/strong&gt; means only what you need, nothing more. And &lt;strong&gt;Access Denied&lt;/strong&gt; is IAM telling you exactly which permission is missing. Read the error. It's trying to help you.&lt;/p&gt;

&lt;p&gt;Next time you get Access Denied, and you will, don't reach for AdministratorAccess. Check the policy. You've got this.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The best time to learn IAM was when you created your AWS account. The second best time is right now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/free/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Click here for more info on AWS Free Tier.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>security</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>You Deleted Everything and AWS Is Still Charging You</title>
      <dc:creator>Jonathan Vogel</dc:creator>
      <pubDate>Fri, 13 Mar 2026 18:33:50 +0000</pubDate>
      <link>https://dev.to/aws/you-deleted-everything-and-aws-is-still-charging-you-1hnd</link>
      <guid>https://dev.to/aws/you-deleted-everything-and-aws-is-still-charging-you-1hnd</guid>
      <description>&lt;p&gt;&lt;strong&gt;The AWS cleanup checklist I wish someone had given me when I was starting out with cloud.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I talk to computer science students regularly. There's one fear that comes up more than almost anything else: "I am worried about spinning up stuff in the cloud and charges getting out of control."&lt;/p&gt;

&lt;p&gt;I used to feel this. Some time ago, I set up a relational database on RDS, some virtual machines as EC2 instances, an S3 bucket to upload some data. After I was done, I deleted everything on the AWS console. Deleted my database. Terminated my instances. Then I got a bill I wasn't expecting.&lt;/p&gt;

&lt;p&gt;Didn't everything get deleted? Literally AWS told me "deleting" in the console. I didn't think anything was running. What happened?&lt;/p&gt;

&lt;p&gt;That experience stuck with me. As I work with students building on AWS, I see the exact same thing happen to them. Last semester alone, I heard some version of this story from students.&lt;/p&gt;

&lt;p&gt;I'm gonna walk us through what's actually going on and give you a checklist to eliminate this fear when building out on AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually Charging You After You "Delete Everything"
&lt;/h2&gt;

&lt;p&gt;Here's an example. You delete your RDS instance at the end of a semester project. Makes sense. Project's done. But during deletion, AWS offers to create a final snapshot of your database. It's a checkbox. You probably don't even register that it's there. You click through, the database goes away, and that snapshot sits in your account quietly costing you money.&lt;/p&gt;

&lt;p&gt;Same thing with EC2. You terminate your instances and depending on how your volumes were configured, the EBS volumes that were attached don't always get deleted with the instance. They're still there, billing. Invisible unless you know where to look.&lt;/p&gt;

&lt;p&gt;And then there's this one that gets people: &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Elastic IPs&lt;/a&gt;. When you terminate an instance, the Elastic IP doesn't get deleted with it. It just sits there, unattached, costing you a few dollars per month. Not huge, but it adds up when you forget about it. That one catches people off guard.&lt;/p&gt;

&lt;p&gt;None of this is hidden. It's all documented. But nobody tells you to look for it when you're learning, and the console doesn't wave a red flag that says "hey, you still have billable resources over here."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Frustrates Me
&lt;/h2&gt;

&lt;p&gt;Here's the part that actually gets to me as a Developer Advocate. When a student gets a surprise bill, they don't usually think "I missed a step in my cleanup." They think "AWS secretly charges you even after you delete stuff." They tell their classmates and that becomes the narrative. I've heard it in person, on discord, etc. "Be careful with AWS, they'll charge you for nothing." It's not cool because it can scare people away from learning skills that would genuinely help their careers.&lt;/p&gt;

&lt;p&gt;AWS doesn't charge you in mysterious ways. It charges you in specific, predictable ways that nobody taught you to look for. That's a knowledge gap. The purpose of this post is to shed some light on this.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's a Better Way Now
&lt;/h2&gt;

&lt;p&gt;Here's something I wish existed when I was starting out. AWS now has a &lt;a href="https://aws.amazon.com/free/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;free account plan&lt;/a&gt; where you get $100 in credits just for signing up, and you can earn up to $200 total by completing activities like launching an EC2 instance or creating a budget. The key part: you literally cannot be billed. There's no scenario where you wake up to a surprise charge. When your credits run out or six months pass, whichever comes first, your account just closes. That's it. No bill.&lt;/p&gt;

&lt;p&gt;If you want more flexibility -- say you're working on a longer project or you don't want to risk your account closing mid-semester -- you can choose the paid account plan instead. You still get the same $200 in credits, but your account stays open after they're used up. The tradeoff is that you &lt;em&gt;can&lt;/em&gt; be billed beyond your credits, which is exactly why the billing alarm later in this post matters. Set that up on day one and you're covered.&lt;/p&gt;

&lt;p&gt;I've been guiding more students toward these options lately and it keeps coming up enough that I'll probably write a dedicated post about it soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cleanup Checklist
&lt;/h2&gt;

&lt;p&gt;This is what I share with every student I work with now. I tell them to bookmark it and come back to it at the end of every project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start With Your Bill, Not Your Console
&lt;/h3&gt;

&lt;p&gt;Before you click around trying to find leftover resources, go to &lt;a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-what-is.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Billing Dashboard&lt;/a&gt; from the AWS console. Look at the current month's charges broken down by service. This tells you exactly which services are costing money right now.&lt;/p&gt;

&lt;p&gt;If you see a charge for RDS, go check RDS. If you see a charge for EC2, go check EC2. Let this be your map.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Resource Explorer
&lt;/h3&gt;

&lt;p&gt;Looking for an exhaustive list of everything going on in your AWS account across all regions?&lt;/p&gt;

&lt;p&gt;Instead of clicking through every service console in every region hoping you didn't forget something, &lt;a href="https://docs.aws.amazon.com/resource-explorer/latest/userguide/welcome.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Resource Explorer&lt;/a&gt; gives you a single search interface across your entire account. All services. All regions. One view.&lt;/p&gt;

&lt;p&gt;That last part matters more than you'd think. I've seen students create resources in &lt;strong&gt;us-east-1&lt;/strong&gt; for a tutorial and &lt;strong&gt;us-west-2&lt;/strong&gt; for a class project, then only check one region during cleanup and assume everything's gone. Resource Explorer solves that completely. Resource Explorer in the console. If your account is truly clean, you'll see very little. If it's not, you'll see exactly what's still hanging around.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the Usual Suspects
&lt;/h3&gt;

&lt;p&gt;Even with Resource Explorer, it helps to know the specific things that catch people. These are the ones I see come up a lot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snapshots (EBS and RDS)&lt;/strong&gt; Check EC2 -&amp;gt; Snapshots and RDS -&amp;gt; Snapshots. Common silent cost I see among students. They get created automatically, often during deletion workflows, and nobody thinks to look for them. &lt;a href="https://docs.aws.amazon.com/ebs/latest/userguide/ebs-snapshots.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;These snapshot costs can add up&lt;/a&gt; - something like 100 GB could be $5/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unattached EBS Volumes&lt;/strong&gt; Go to EC2 -&amp;gt; Volumes and filter by state: "available." If a volume shows "available," it's not attached to anything. It's just sitting there being billed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elastic IPs&lt;/strong&gt; Check EC2 -&amp;gt; Elastic IPs. If any are listed and not associated with a running instance, release them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAT Gateways&lt;/strong&gt; If you followed a VPC tutorial with public and private subnets, check &lt;a href="https://aws.amazon.com/vpc/pricing/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;VPC -&amp;gt; NAT Gateways&lt;/a&gt;. These run about $32/month whether you're pushing traffic through them or not. If you don't need it, delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up a Billing Alarm
&lt;/h2&gt;

&lt;p&gt;This takes two minutes and it's the single most important thing you can do on a new AWS account. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Billing -&amp;gt; Budgets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a low budget, say $5 or $10&lt;/li&gt;
&lt;li&gt;Set an alert at 80% of that threshold&lt;/li&gt;
&lt;li&gt;Add your email&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I Tell Students Who Are Afraid to Start
&lt;/h2&gt;

&lt;p&gt;Every time I meet a student who communicates to me in a way that signals "I'm scared of cloud billing getting out of control," I tell them the same thing. That fear is valid. Every dollar matters when you're a student. Once you understand where surprise charges actually come from, which is a pretty short list, the fear goes away.&lt;/p&gt;

&lt;p&gt;The students who get burned are the ones who don't know about snapshots, orphaned volumes, and unattached Elastic IPs. Now you do.&lt;/p&gt;

&lt;p&gt;I tell students these three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want zero billing risk, choose the free account plan when you sign up. You get up to $200 in credits and you cannot be charged. The tradeoff is that your account closes when credits run out or after six months. If you'd rather keep your account open longer, go with the paid plan -- you get the same credits, just set up a billing alarm so nothing sneaks past you.&lt;/li&gt;
&lt;li&gt;Set up a billing alarm no matter which plan you pick&lt;/li&gt;
&lt;li&gt;Bookmark this checklist and come back to it at the end of every project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start the account. Build the project. Learn the skills. And when the semester ends, come back to this checklist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Where to Look&lt;/th&gt;
&lt;th&gt;What to Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Billing by service&lt;/td&gt;
&lt;td&gt;Billing Dashboard -&amp;gt; Bills&lt;/td&gt;
&lt;td&gt;See which services are charging you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All resources, all regions&lt;/td&gt;
&lt;td&gt;Resource Explorer&lt;/td&gt;
&lt;td&gt;Find anything still alive in your account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshots&lt;/td&gt;
&lt;td&gt;EC2 -&amp;gt; Snapshots, RDS -&amp;gt; Snapshots&lt;/td&gt;
&lt;td&gt;Delete what you don't need&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orphaned EBS volumes&lt;/td&gt;
&lt;td&gt;EC2 -&amp;gt; Volumes -&amp;gt; filter "available"&lt;/td&gt;
&lt;td&gt;Delete unattached volumes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elastic IPs&lt;/td&gt;
&lt;td&gt;EC2 -&amp;gt; Elastic IPs&lt;/td&gt;
&lt;td&gt;Release unassociated IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateways&lt;/td&gt;
&lt;td&gt;VPC -&amp;gt; NAT Gateways&lt;/td&gt;
&lt;td&gt;Delete if project is done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing alarm&lt;/td&gt;
&lt;td&gt;Billing -&amp;gt; Budgets&lt;/td&gt;
&lt;td&gt;Set one up before you do anything else&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Or to avoid any billing entirely, choose the free account plan when signing up. You still get credits, but you are never billed. Note: when you run out of credits (or 6 months pass, whichever comes first), AWS closes your account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/free/?trk=23ae1f57-152e-4145-9aa7-04a603514f54&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Click here for more info on AWS Free Tier.&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The best time to set up a billing alert was when you created your account. The second best time is right now.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>aws</category>
      <category>tutorial</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
