<?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: AWS</title>
    <description>The latest articles on DEV Community by AWS (@aws).</description>
    <link>https://dev.to/aws</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png</url>
      <title>DEV Community: AWS</title>
      <link>https://dev.to/aws</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aws"/>
    <language>en</language>
    <item>
      <title>Bigger AI models aren't always better. Here's how to actually choose.</title>
      <dc:creator>Rohini Gaonkar</dc:creator>
      <pubDate>Fri, 15 May 2026 16:58:32 +0000</pubDate>
      <link>https://dev.to/aws/bigger-ai-models-arent-always-better-heres-how-to-actually-choose-56pc</link>
      <guid>https://dev.to/aws/bigger-ai-models-arent-always-better-heres-how-to-actually-choose-56pc</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/aws/why-does-ai-lie-hallucinations-explained-simply-1c7g"&gt;previous post&lt;/a&gt;, I showed you two models answering the same question. One hallucinated confidently. The other knew when to stop.&lt;/p&gt;

&lt;p&gt;And a bunch of you asked: okay, but which one should I actually use?&lt;/p&gt;

&lt;p&gt;Haiku, Sonnet, Opus. Micro, Lite, Pro. Mini, Small, Large. There are dozens of models and they all sound like perfume brands. How are you supposed to pick one?&lt;/p&gt;

&lt;p&gt;That's this post. I'm going to take one prompt, run it through two models (one small, one large), and show you what's different. Then I'll give you a simple framework for choosing the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo: same prompt, two models
&lt;/h2&gt;

&lt;p&gt;I went back to the recipe from the &lt;a href="https://dev.to/aws/what-even-is-ai-i-took-a-break-had-to-relearn-everything-3dpj"&gt;first post&lt;/a&gt;. Same recipe. Same question. Two different model sizes.&lt;/p&gt;

&lt;p&gt;The prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm cooking this for six people on Saturday. One is vegan, one is gluten-free. Adapt the recipe for me, give me a shopping list, and a timeline starting from 4pm."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The small model
&lt;/h3&gt;

&lt;p&gt;Quickly, it gave me a shopping list, a timeline, and basic adaptations. Nothing fancy, but everything I asked for. If I just need a quick answer and I'm going to double-check it anyway, this works.&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%2F5kh8ohnpuutmh9vxs2zh.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%2F5kh8ohnpuutmh9vxs2zh.png" alt="Claude Haiku response to the recipe prompt" width="663" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a lot of everyday tasks, this is genuinely all you need.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The large model
&lt;/h3&gt;

&lt;p&gt;Same prompt but a very different response.&lt;/p&gt;

&lt;p&gt;It added a whole "Strategy for the vegan guest" section explaining why you should make a parallel pot instead of adapting the main dish. It gave me a timeline starting from the night before. Separated prep into phases. Told me to keep the rice pots separate so nothing touches the vegan side. Scaling math for going from 4 servings to 6. It even gave me an oven method AND a stovetop method as alternatives.&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%2Fz3e5he184s3car2s0sf8.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%2Fz3e5he184s3car2s0sf8.png" alt="Claude Opus response to the same recipe prompt" width="800" height="270"&gt;&lt;/a&gt;&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%2Fojlnb9q46njv30o9ra1h.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%2Fojlnb9q46njv30o9ra1h.png" alt="Opus's over-engineered timeline starting from the night before" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More thorough and more considerate. But did I need all of that for a Saturday dinner? Maybe or maybe not.&lt;/p&gt;

&lt;p&gt;There are medium-sized models in between these two and they exist in every family. I'll tell you when to reach for them later. &lt;/p&gt;

&lt;p&gt;But the contrast between small and large is where our today's lesson lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why models come in sizes
&lt;/h2&gt;

&lt;p&gt;Let's take a simple example.&lt;/p&gt;

&lt;p&gt;My son is two. If I ask him what he wants for dinner, he says "pasta." Done in two seconds without any deliberation.&lt;/p&gt;

&lt;p&gt;If you ask me what to make for dinner, I'm thinking: what's in the fridge, what did we have yesterday, does he need more protein today, is it too late to start something that takes 40 minutes, should I batch-cook for tomorrow. Ten variables that will take me five minutes. I will give you a better answer, but slower.&lt;/p&gt;

&lt;p&gt;Models work the same way.&lt;/p&gt;

&lt;p&gt;A model's "size" is roughly how many &lt;strong&gt;parameters&lt;/strong&gt; it has. &lt;/p&gt;

&lt;p&gt;Think of parameters as the variables it can hold in its head when making a decision. More variables, more nuance, more ability to handle complex tasks. Fewer variables, faster and cheaper, but less sophisticated.&lt;/p&gt;

&lt;p&gt;My son doesn't need ten variables to pick dinner. He just needs to decide. And for a lot of tasks, that's all you need from a model too. A fast answer. Not a perfect one.&lt;/p&gt;

&lt;p&gt;Training a big model costs more. Running a big model costs more per question. And it's slower, because there are more variables to weigh for every single response.&lt;/p&gt;

&lt;p&gt;So why not just always use the biggest one? Two reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, cost.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;If you're building something that handles thousands of requests, the difference between a small model and a large model is the difference between a reasonable bill and a terrifying one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, and this is the one people miss: bigger isn't always better.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;For simple tasks, a big model can actually overthink it. Give you more than you asked for. Take longer to say something the small model said in two seconds.&lt;/p&gt;

&lt;p&gt;The model families you see (Haiku/Sonnet/Opus, Micro/Lite/Pro) are just size tiers from the same provider. Same architecture, different capacity. Like buying a car in compact, sedan, or SUV. Same manufacturer. Different trade-offs. You don't take the SUV to grab milk. You don't take the compact on a cross-country road trip with three kids.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tokens and pricing: how you actually pay
&lt;/h2&gt;

&lt;p&gt;Models don't charge by the question. They charge by the &lt;strong&gt;token&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What's a token? It's a chunk of text. Not quite a word, not quite a letter, but roughly three-quarters of a word.&lt;/p&gt;

&lt;p&gt;Take the sentence: "Adapt this recipe for a gluten-free vegan." Seven words but nine tokens. Some words get split, some punctuation becomes its own token.&lt;/p&gt;

&lt;p&gt;You don't need to memorise this. Just know: token count and word count aren't the same thing. A full page of text is around 400 tokens. A million tokens is roughly a 750,000-word book.&lt;/p&gt;

&lt;p&gt;There's a free tool called &lt;a href="https://tiktokenizer.vercel.app/" rel="noopener noreferrer"&gt;Tiktokenizer&lt;/a&gt; where you can paste text and see exactly how a model breaks it into tokens. It's weirdly satisfying. Try 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%2F5bvqk51g12l1vncs0swu.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%2F5bvqk51g12l1vncs0swu.png" alt="Tiktokenizer showing how a sentence gets broken into tokens" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing that surprised me: different models tokenize the same text differently. I sent the exact same prompt and recipe to both models. The small one counted 6,548 input tokens. The large one counted 16,685. Same words, different tokenizers under the hood.&lt;/p&gt;

&lt;p&gt;And here's the thing: &lt;strong&gt;you get charged twice.&lt;/strong&gt; Once for the tokens you send in (your question). And once for the tokens the model sends back (its answer). Input tokens and output tokens. They're priced separately, and output is always more expensive, because that's where the model is doing the work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real numbers
&lt;/h3&gt;

&lt;p&gt;On &lt;a href="https://aws.amazon.com/bedrock?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt;, for the Claude family (pricing as of May 2025, &lt;a href="https://aws.amazon.com/bedrock/pricing?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;check current prices here&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Input (per 1M tokens)&lt;/th&gt;
&lt;th&gt;Output (per 1M tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Haiku&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;~$1&lt;/td&gt;
&lt;td&gt;~$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;~$3&lt;/td&gt;
&lt;td&gt;~$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opus&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;~$5&lt;/td&gt;
&lt;td&gt;~$25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's &lt;strong&gt;5x more expensive&lt;/strong&gt; from small to large. Same question, same answer, but 5x the price on both sides.&lt;/p&gt;

&lt;p&gt;If you're asking one question yourself, who cares. The difference is fractions of a cent. But if you're building an app that handles ten thousand requests a day, each one generating a few hundred output tokens, that 5x multiplier turns into real money fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best model is the model you can afford to run at the scale you need.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks: when bigger is worse
&lt;/h2&gt;

&lt;p&gt;Let's go back to the large model's response and look at the over-engineered parts. The timeline starting from the night before. "Marinate chicken in yogurt and spices, overnight is best." Fry the vegan portion first in clean oil, then fry the chicken onions in separate oil. Keep the rice pots separate. An oven method AND a stovetop method as alternatives.&lt;/p&gt;

&lt;p&gt;The small model? A simple table. 4:00pm, start marinating. 4:05, fry onions. 5:15, into the oven. 7:00, serve. Done.&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%2Fzn2rwsgf9ryhzymiz7ys.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%2Fzn2rwsgf9ryhzymiz7ys.png" alt="Haiku's simple timeline table" width="742" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Opus is doing project management for my Saturday dinner. And here's the real cost of that overthinking.&lt;/p&gt;

&lt;p&gt;The small model: 18 seconds, about 1,900 output tokens. &lt;br&gt;
The large model: 44 seconds, 2,700 output tokens. &lt;/p&gt;

&lt;p&gt;40% more output. 2.4x slower. And about 10x more expensive for that single request.&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%2Fw81n9w5pwuk0nuy0a1xp.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%2Fw81n9w5pwuk0nuy0a1xp.png" alt="Haiku vs Opus stats comparison: tokens, time" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a Saturday dinner, this is overkill. And if I were building an app that answers recipe questions for thousands of users, I'd be paying for all that extra thinking on every single request.&lt;/p&gt;

&lt;p&gt;This is the trade-off. Bigger models are smarter, but "smarter" isn't always what you need. &lt;strong&gt;Sometimes you need fast, cheap, and good enough.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to actually choose
&lt;/h2&gt;

&lt;p&gt;Here's how I think about it.&lt;/p&gt;

&lt;p&gt;First, the biggest factor: &lt;strong&gt;cost.&lt;/strong&gt; We just saw a 5x difference between small and large. And that's per token. When the big model also generates 40% more tokens per response, it compounds fast. That alone narrows the field for most people. If you're building something, cost is the thing that decides what's even on the table. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you can't afford to run it at the scale you need, it doesn't matter how good it is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start there. What can you actually sustain?&lt;/p&gt;

&lt;p&gt;Then, once cost has set your boundaries, three questions help you pick within them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. How complex is the task?&lt;/strong&gt;&lt;br&gt;
Summarising an email? Small model. Writing a legal brief? Big model. Adapting a recipe? Probably medium.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. How many times will you run it?&lt;/strong&gt;&lt;br&gt;
If it's one question from you personally, use whatever you want. If it's an app serving thousands of users, speed matters just as much as quality. Start small, upgrade only when the quality isn't good enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What are the stakes?&lt;/strong&gt;&lt;br&gt;
If a wrong answer ruins dinner, that's low stakes. If a wrong answer means bad financial processing logic that costs you millions, that's high stakes. Higher stakes, bigger model, plus verification on top.&lt;/p&gt;

&lt;p&gt;That's it. &lt;strong&gt;Cost sets the ceiling. Complexity, volume, and stakes help you pick the floor.&lt;/strong&gt; You don't need to memorise model names. You need to know what you're optimising for.&lt;/p&gt;
&lt;h2&gt;
  
  
  What about picking a provider?
&lt;/h2&gt;

&lt;p&gt;I've been showing models from different providers. Claude, Nova, Llama. How do you pick a family?&lt;/p&gt;

&lt;p&gt;Honestly? Pick the one that's available where you already work. If you're on AWS, you have access to all of them through &lt;a href="https://aws.amazon.com/bedrock?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock&lt;/a&gt;. If you're somewhere else, use what's there. The concepts are the same. &lt;strong&gt;Don't overthink the brand. Overthink the task.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing that confuses a lot of us early on: &lt;strong&gt;models and products are not the same thing.&lt;/strong&gt; Claude is a model. But Claude inside &lt;a href="https://aws.amazon.com/kiro?trk=44b16281-e090-49b6-97d8-f1cea54d9e87&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; (a coding IDE) behaves differently from Claude in the Bedrock Playground, which behaves differently from Claude on claude.ai. Same model underneath. &lt;/p&gt;

&lt;p&gt;But each product wraps it with different instructions, tools, and context that shape how it responds. Kiro's Claude is tuned for writing code. The Playground's Claude is general-purpose. Same brain, different job description.&lt;/p&gt;

&lt;p&gt;So when you see dozens of AI "products" out there, many of them are the same few models dressed up for different use cases. &lt;strong&gt;The model decides how smart it is. The product decides what it's pointed at&lt;/strong&gt;, and priced accordingly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're just getting started:&lt;/strong&gt; models come in sizes. Bigger is smarter but slower and more expensive. For most everyday tasks, a medium model is the sweet spot. Try a few and see which one feels right for what you're doing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're more on the builder side:&lt;/strong&gt; start with the smallest model that gives acceptable quality. Only upgrade when you can point to a specific failure the bigger model fixes. &lt;strong&gt;Don't start big and optimise down. Start small and justify up.&lt;/strong&gt; And remember, you can use different models for different parts of the same system. The router doesn't need to be the same size as the reasoner. The model that decides which tool to call doesn't need to be the same one that processes the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start small. Justify up.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We are going to talk why model forgets what you told it. Ride Along.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is part of the "Learning AI Out Loud" series, a cloud architect learning AI from first principles.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/rohini_gaonkar" class="crayons-btn crayons-btn--primary"&gt;Follow along with the series&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Creating Your First Lambda Function</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Fri, 15 May 2026 06:00:00 +0000</pubDate>
      <link>https://dev.to/aws/creating-your-first-lambda-function-4a29</link>
      <guid>https://dev.to/aws/creating-your-first-lambda-function-4a29</guid>
      <description>&lt;p&gt;So you want to build your first serverless function? Let's do it. No fluff, no detours — just you, AWS SAM, and a working Lambda function running on your machine in minutes. We're not even going to deploy to AWS yet. Let's just get it working locally first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Need
&lt;/h2&gt;

&lt;p&gt;Two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AWS SAM CLI installed&lt;/strong&gt; — SAM (Serverless Application Model) is the tool we'll use to create, build, and test our function. Follow the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;install guide&lt;/a&gt; for your operating system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker or Finch installed&lt;/strong&gt; — SAM uses a container runtime to simulate Lambda locally. You have two options:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt; — The most common option. Install it and make sure it's running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/runfinch/finch" rel="noopener noreferrer"&gt;Finch&lt;/a&gt;&lt;/strong&gt; — A lightweight open source alternative from AWS. Install it with &lt;code&gt;brew install finch&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verify your setup
&lt;/h3&gt;

&lt;p&gt;Check that SAM is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you chose Finch, initialize and start the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;finch vm init
finch vm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM automatically detects Finch when Docker isn't running — no extra configuration needed. If you have both installed, SAM uses Docker by default. To make Finch the default on macOS, run:&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;sudo&lt;/span&gt; /usr/libexec/PlistBuddy &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"Add :DefaultContainerRuntime string finch"&lt;/span&gt; /Library/Preferences/com.amazon.samcli.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify your container runtime is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# or: finch --version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No AWS account needed yet. We're staying local for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Initialize Your Project
&lt;/h2&gt;

&lt;p&gt;Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM is going to walk you through a few questions. For this blog, I'm using the TypeScript template, but you can choose any runtime and template you'd like — Python, Java, Go, whatever you're comfortable with. The prompts, project structure, and file names will vary depending on what you pick. Here's what I chose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Template source&lt;/strong&gt; — Choose &lt;code&gt;1 - AWS Quick Start Templates&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick start template&lt;/strong&gt; — Choose &lt;code&gt;1 - Hello World Example&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the most popular runtime and package type?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt; — Choose &lt;code&gt;12 - nodejs24.x&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package type&lt;/strong&gt; — Choose &lt;code&gt;Zip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starter template&lt;/strong&gt; — Choose &lt;code&gt;2 — Hello World Example TypeScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable X-Ray tracing?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt; (we'll keep it simple for now)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable CloudWatch Application Insights?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Lambda Insights?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project name&lt;/strong&gt; — Hit Enter to accept the default, or name it whatever you like&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SAM just scaffolded an entire serverless project for you. Let's look at what we got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sam-app/
├── template.yaml          # The blueprint for your infrastructure
├── samconfig.toml         # SAM deployment configuration
├── hello-world/           # Your function code lives here
│   ├── app.ts             # Your actual Lambda function
│   ├── package.json       # Dependencies
│   └── tests/             # Unit tests
│       └── unit/
│           └── test-handler.test.ts
└── events/
    └── event.json         # A sample test event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two files that matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;template.yaml&lt;/strong&gt; — This describes your Lambda function and any AWS resources it needs. Think of it as the blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;app.ts&lt;/strong&gt; — This is your actual function code. The thing that runs when your Lambda gets invoked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Understanding the template
&lt;/h3&gt;

&lt;p&gt;Let's look at the key part of &lt;code&gt;template.yaml&lt;/code&gt; — the function resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;HelloWorldFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.lambdaHandler&lt;/span&gt;
      &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs24.x&lt;/span&gt;
      &lt;span class="na"&gt;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;x86_64&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;HelloWorld&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/hello&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
    &lt;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BuildMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;esbuild&lt;/span&gt;
      &lt;span class="na"&gt;BuildProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Minify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;Target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;es2020"&lt;/span&gt;
        &lt;span class="na"&gt;Sourcemap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;EntryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what's going on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type: AWS::Serverless::Function&lt;/strong&gt; — This tells SAM to create a Lambda function. SAM handles all the underlying CloudFormation resources for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeUri: hello-world/&lt;/strong&gt; — Points to the folder where your function code lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handler: app.lambdaHandler&lt;/strong&gt; — Tells Lambda which file and function to run. In this case, the &lt;code&gt;lambdaHandler&lt;/code&gt; export in &lt;code&gt;app.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime: nodejs24.x&lt;/strong&gt; — The Node.js version your function runs on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The magic is in the &lt;strong&gt;Events&lt;/strong&gt; section. By adding an event with &lt;code&gt;Type: Api&lt;/code&gt;, SAM automatically creates an API Gateway for you — no extra configuration needed. You didn't define an API Gateway resource anywhere, but SAM sees that your function wants to respond to HTTP requests at &lt;code&gt;GET /hello&lt;/code&gt; and wires it all up behind the scenes. One line, and you've got a fully managed API endpoint in front of your Lambda.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Metadata&lt;/strong&gt; section tells SAM how to build your TypeScript code. Instead of running &lt;code&gt;tsc&lt;/code&gt; and bundling manually, SAM uses &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; — a JavaScript/TypeScript bundler. It compiles your TypeScript, minifies the output, generates sourcemaps for debugging, and packages it all up. You don't need to install esbuild yourself — SAM handles it during &lt;code&gt;sam build&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build It
&lt;/h2&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;sam-app
sam build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM grabs your code, installs any dependencies, and packages everything up. You'll see a &lt;code&gt;.aws-sam&lt;/code&gt; folder appear — that's your build output. You don't need to touch it.&lt;/p&gt;

&lt;p&gt;Don't have the runtime installed on your machine (e.g., no Node.js)? No problem — just add &lt;code&gt;--use-container&lt;/code&gt; and SAM will build inside a Docker/Finch container instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--use-container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM will automatically pull down a container image that matches your function's runtime, build your code inside it, and output the packaged result to &lt;code&gt;.aws-sam&lt;/code&gt; just like a normal build. You get the exact same Lambda environment without installing anything on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Test It Locally
&lt;/h2&gt;

&lt;p&gt;Here's the fun part. Run your Lambda function right on your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a response like:&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="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;hello world&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;That's your Lambda function running locally. No AWS account, no deployment, no charges. Just your code doing its thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make It Yours
&lt;/h3&gt;

&lt;p&gt;Let's make a quick change so you can see the full loop — edit, build, test. Open &lt;code&gt;hello-world/app.ts&lt;/code&gt; and find this 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="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&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;Change it to:&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello Lambda&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;Now rebuild and invoke again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;hello Lambda&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;That's the workflow. Change your code, build, test — all local, all instant.&lt;/p&gt;

&lt;p&gt;Want to test it as an API instead? Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This spins up a local API Gateway. Open your browser and hit &lt;code&gt;http://127.0.0.1:3000/hello&lt;/code&gt; and you'll see:&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="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hello Lambda"&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;You now have a fully functional serverless API running on your laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Just Happened?
&lt;/h2&gt;

&lt;p&gt;In about 5 minutes, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffolded a serverless project with a single command&lt;/li&gt;
&lt;li&gt;Learned how the SAM template automatically wires up a Lambda function and API Gateway&lt;/li&gt;
&lt;li&gt;Built your TypeScript code with esbuild — no manual setup needed&lt;/li&gt;
&lt;li&gt;Ran a Lambda function locally&lt;/li&gt;
&lt;li&gt;Made a code change and saw it reflected instantly&lt;/li&gt;
&lt;li&gt;Spun up a local API endpoint and hit it from the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without touching AWS. All free. All on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready to Deploy? Let's Go Live.
&lt;/h2&gt;

&lt;p&gt;You've got it working locally. Now let's put it in the cloud so the rest of the world can use it. For this part, you'll need a couple more things set up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Requirements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An AWS account&lt;/strong&gt; — Head to &lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;aws.amazon.com/free&lt;/a&gt; and sign up. New customers get up to $200 in credits and a Free Plan that lets you explore AWS for up to 6 months at no cost. On top of that, Lambda has its own always-free tier — 1 million requests and 400,000 GB-seconds of compute per month.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS CLI installed and configured&lt;/strong&gt; — This is how your machine talks to your AWS account. First, install it from the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI install guide&lt;/a&gt;. Then follow the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html" rel="noopener noreferrer"&gt;Setting up the AWS CLI&lt;/a&gt; guide to configure your credentials. For most new users, you'll want the &lt;strong&gt;IAM user with short-term credentials&lt;/strong&gt; option — it's the recommended approach for getting started securely.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you're set up, verify it's working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see your account ID, you're connected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Deploy It
&lt;/h3&gt;

&lt;p&gt;From your project directory, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--guided&lt;/code&gt; flag walks you through the deployment settings the first time. Here's what to expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack Name&lt;/strong&gt; — Give it a name (like &lt;code&gt;my-sam-app&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Region&lt;/strong&gt; — Pick your closest region (e.g., &lt;code&gt;us-east-1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm changes before deploy&lt;/strong&gt; — &lt;code&gt;Y&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow SAM CLI IAM role creation&lt;/strong&gt; — &lt;code&gt;Y&lt;/code&gt; (SAM needs to create a role for your function)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable rollback&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save arguments to config file&lt;/strong&gt; — &lt;code&gt;Y&lt;/code&gt; (so you don't have to answer these again)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAM will show you a changeset — a preview of what it's about to create. Type &lt;code&gt;Y&lt;/code&gt; to confirm, and watch it go.&lt;/p&gt;

&lt;p&gt;When it's done, you'll see an &lt;strong&gt;Outputs&lt;/strong&gt; section with a URL. That's your live API endpoint. Copy it, paste it in your browser, and...&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="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hello Lambda"&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;🎉 Your Lambda function is live on AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean Up
&lt;/h3&gt;

&lt;p&gt;Don't want to leave resources hanging around? Tear it all down with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM removes everything it created. Clean slate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Just Happened?
&lt;/h2&gt;

&lt;p&gt;In about 10 minutes, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffolded a serverless project with a single command&lt;/li&gt;
&lt;li&gt;Learned how the SAM template automatically wires up a Lambda function and API Gateway&lt;/li&gt;
&lt;li&gt;Built your TypeScript code with esbuild — no manual setup needed&lt;/li&gt;
&lt;li&gt;Tested your function locally and made a code change&lt;/li&gt;
&lt;li&gt;Spun up a local API and hit it from the browser&lt;/li&gt;
&lt;li&gt;Deployed it to AWS with a real, live URL&lt;/li&gt;
&lt;li&gt;Cleaned it all up with one command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAM handled all the heavy lifting — the CloudFormation stack, the IAM roles, the API Gateway, the packaging. You just answered a few questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;You've got the basics down. From here you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more functions to your &lt;code&gt;template.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Connect your Lambda to DynamoDB, S3, or other AWS services&lt;/li&gt;
&lt;li&gt;Set up environment variables and custom permissions&lt;/li&gt;
&lt;li&gt;Explore event-driven architectures with SNS, SQS, and EventBridge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to keep learning? Check out &lt;a href="https://serverlessland.com" rel="noopener noreferrer"&gt;Serverless Land&lt;/a&gt; — the go-to hub for all things Lambda and serverless with patterns, tutorials, and best practices. Or dive into the &lt;a href="https://youtube.com/playlist?list=PLJo-rJlep0ED198FJnTzhIB5Aut_1vDAd&amp;amp;si=vsLU45O1uH6jKyGo" rel="noopener noreferrer"&gt;Sessions with SAM&lt;/a&gt; YouTube playlist that goes deeper into everything AWS SAM can do.&lt;/p&gt;

&lt;p&gt;But that's for next time. For now, celebrate — you just went serverless. 🚀&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>beginners</category>
      <category>aws</category>
    </item>
    <item>
      <title>The new Agent Toolkit for AWS includes 20+ agent skills, but your agent might never load them without this one file</title>
      <dc:creator>Esin Saribudak</dc:creator>
      <pubDate>Thu, 14 May 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aws/the-new-agent-toolkit-for-aws-includes-20-agent-skills-but-your-agent-might-never-load-them-1p6d</link>
      <guid>https://dev.to/aws/the-new-agent-toolkit-for-aws-includes-20-agent-skills-but-your-agent-might-never-load-them-1p6d</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;The Agent Toolkit for AWS gives your coding agent access to the AWS MCP and curated skills, but without updating the rules file, your agent might answer from model training data instead of using its new tools.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last updated: May 15, 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you're like me, sometimes you get so excited to try something new that you don't read all the way through the docs before you start using it. Last week, the &lt;a href="https://github.com/aws/agent-toolkit-for-aws" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt; had just been released, I had the &lt;a href="https://github.com/aws/agent-toolkit-for-aws" rel="noopener noreferrer"&gt;README&lt;/a&gt; open, and two minutes later I was asking my agent to design a serverless backend in my Kiro IDE.&lt;/p&gt;

&lt;p&gt;But the agent didn't touch any of the tools I had just configured. It answered from training data and gave me a reasonable API Gateway + Lambda + DynamoDB architecture, but never reached for the MCP documentation search or the &lt;code&gt;aws-core&lt;/code&gt; skills I had installed. I had to prompt it to use the MCP server and skills before it gave them a go.&lt;/p&gt;

&lt;p&gt;I had skipped one important file in my rush to try out this new toolkit, and it turned out to be the file that made my agent reach for these tools predictably.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the toolkit
&lt;/h2&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%2Flo8csqu3rgsmrnkktewk.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%2Flo8csqu3rgsmrnkktewk.png" alt="Three-layer stack: MCP Server (bottom), Skills (middle), Rules File (top). Without the rules file, the agent might not use the rest." width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aws.amazon.com/products/developer-tools/agent-toolkit-for-aws/?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt; was released on May 6, 2026. It works with Claude Code, Codex, Kiro, and any agent that supports MCP, and it has three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The MCP Server&lt;/strong&gt; gives your agent access to 300+ AWS APIs through one endpoint, plus sandboxed Python execution and real-time doc search (no AWS credentials needed for searching docs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; are packaged domain expertise, including architecture decision tables, service comparison matrices, deployment workflows, and troubleshooting guides (20+ available today).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rules file&lt;/strong&gt; tells the agent to use layers 1 and 2 before answering from memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I had layers 1 and 2 set up but skipped layer 3, so the agent had all the tools and none of the instructions to use them.&lt;/p&gt;

&lt;p&gt;Setup takes two minutes — follow the &lt;a href="https://github.com/aws/agent-toolkit-for-aws#quick-start" rel="noopener noreferrer"&gt;README's quick start&lt;/a&gt; for your agent (Kiro, Claude Code, Codex, or other MCP-compatible agents).&lt;/p&gt;

&lt;h2&gt;
  
  
  The file I skipped
&lt;/h2&gt;

&lt;p&gt;There's a &lt;a href="https://github.com/aws/agent-toolkit-for-aws/blob/main/rules/aws-agent-rules.md" rel="noopener noreferrer"&gt;rules file&lt;/a&gt; in the toolkit repo's &lt;code&gt;rules/&lt;/code&gt; directory, which I missed when setting it up. The README's quick start section doesn't mention it, and I was already typing prompts by the time I could have noticed. But there's a difference between "can discover skills" and "will proactively load them before answering." The rules file bridges that gap. It's 17 lines and tells the agent to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer the AWS MCP Server for all AWS interactions&lt;/li&gt;
&lt;li&gt;Before starting a task, check whether a relevant AWS skill is available&lt;/li&gt;
&lt;li&gt;Load the skill with &lt;code&gt;retrieve_skill&lt;/code&gt; and prefer its guidance over general knowledge&lt;/li&gt;
&lt;li&gt;When uncertain about API parameters, permissions, or limits, verify against documentation rather than guessing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information on where to put the rules file for different agents, check out the docs &lt;a href="https://docs.aws.amazon.com/agent-toolkit/latest/userguide/rules-files.html?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Before I added this file, the agent had passive access to skills, but after I dropped it in, skill loading became the agent's first move on any AWS question and it started pulling architecture decision tables before writing code.&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%2Fja1m3n674bgzpapi8pp8.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%2Fja1m3n674bgzpapi8pp8.png" alt="Side-by-side: without rules file the agent answers from memory; with it, the agent loads skills and searches docs first." width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a simple CRUD app like the one I was building, the AWS MCP skills refined the implementation rather than redirected it. The LLMs have gotten so good that their general knowledge already gets you to the right architecture much of the time (in my case, API Gateway + Lambda + DynamoDB + Cognito). Where the skills added value was in specificity and confidence. Here's what I got before and after: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before (no rules file)&lt;/th&gt;
&lt;th&gt;After (rules file added)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture advice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Three options presented (serverless, containers, Amplify) with a general "Option 1 is the sweet spot" recommendation&lt;/td&gt;
&lt;td&gt;One specific architecture with a decision table explaining why each component was chosen over its alternatives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Gateway type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"API Gateway" (unspecified which type)&lt;/td&gt;
&lt;td&gt;"HTTP API specifically, because REST API is overkill unless you need WAF or caching"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth approach&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Cognito or roll your own with Lambda + bcrypt + JWT"&lt;/td&gt;
&lt;td&gt;"JWT authorizer with Cognito because HTTP API has native JWT support, no Lambda authorizer needed"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Function pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not mentioned&lt;/td&gt;
&lt;td&gt;"One function per route" (skill best practice)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Constraints flagged&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;30s hard timeout, 10 MB payload limit, no WAF on HTTP API, silent Forbidden on JWT scope mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Source of guidance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Model training data&lt;/td&gt;
&lt;td&gt;AWS documentation + &lt;code&gt;aws-serverless&lt;/code&gt; skill's service selection tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Level of specificity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Told me &lt;em&gt;what&lt;/em&gt; to build&lt;/td&gt;
&lt;td&gt;Told me &lt;em&gt;which variant&lt;/em&gt; to build and &lt;em&gt;why that variant over the alternatives&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The skills would be even more useful than general LLM knowledge for more complex architectures involving things like event processing or multi-pattern designs, where the pattern selection flowcharts and service comparison tables would change the agent's choices rather than validate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this taught me about coding agents and skills
&lt;/h2&gt;

&lt;p&gt;Giving a coding agent access to tools is not the same as telling it when to use them. Skills are designed to be loaded on demand, which means something has to tell the agent &lt;em&gt;when&lt;/em&gt; to demand them. Without a rule, the agent treats skills like reference books on a shelf: available if it decides to look, but not part of its default workflow. The rules file is what changes "available on request" into "check this before you start." &lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails
&lt;/h2&gt;

&lt;p&gt;If you're wondering, "Is it safe to let an agent call AWS APIs?", that's a great question to be asking. The good news is that you can scope down what the agent is allowed to do separately from your own permissions, so even if your IAM role can create and delete resources, you can restrict the agent to read-only. Every request the agent makes through the MCP server gets logged, so you can trace which agent action caused it. And the skills have been tested as full end-to-end workflows before shipping, so when your agent follows a skill's steps, those steps are verified to produce the expected result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;If you made it this far, thanks for spending the time. Let me know if you've tried Agent Toolkit for AWS and what you think!  &lt;/p&gt;

</description>
      <category>agentskills</category>
      <category>aws</category>
      <category>mcp</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Lambda Just Got a File System. I Put AI Agents on It.</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 13 May 2026 15:50:28 +0000</pubDate>
      <link>https://dev.to/aws/lambda-just-got-a-file-system-i-put-ai-agents-on-it-1ej8</link>
      <guid>https://dev.to/aws/lambda-just-got-a-file-system-i-put-ai-agents-on-it-1ej8</guid>
      <description>&lt;p&gt;You've written this code before. An S3 event fires, your Lambda function wakes up, and the first thing it does is download a file to &lt;code&gt;/tmp&lt;/code&gt;. Process it. Upload the result. Clean up &lt;code&gt;/tmp&lt;/code&gt; so you don't run out of space. Repeat for every file, every invocation, every function in your pipeline.&lt;/p&gt;

&lt;p&gt;S3 Files changes that. You mount your S3 bucket as a local file system, and your Lambda code just uses &lt;code&gt;open()&lt;/code&gt;. I built a set of AI code review agents that share a workspace through a mounted S3 bucket, orchestrated by a durable function, and the file access code is the most boring part of the whole project. That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The /tmp Tax
&lt;/h2&gt;

&lt;p&gt;If you've built anything on Lambda that touches S3 data, you know the pattern. You need a file. S3 doesn't give you files. It gives you objects. So you download the object to &lt;code&gt;/tmp&lt;/code&gt;, do your work, and upload the result back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The old way: every Lambda developer has written this
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Download to /tmp
&lt;/span&gt;    &lt;span class="n"&gt;local_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Do your actual work
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&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="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Upload the result
&lt;/span&gt;    &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Clean up so you don't fill /tmp
&lt;/span&gt;    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_path&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 lot of ceremony for "read a file and write a file." And it gets worse when you have multiple functions that need to work with the same data. Each one downloads its own copy. Each one manages its own &lt;code&gt;/tmp&lt;/code&gt;. If you're processing a large repo or a dataset, you're burning through the 10GB &lt;code&gt;/tmp&lt;/code&gt; limit fast.&lt;/p&gt;

&lt;p&gt;I'd be doing you a disservice if I didn't mention the libraries that make this less painful. Tools like &lt;code&gt;s3fs&lt;/code&gt; and &lt;code&gt;smart_open&lt;/code&gt; abstract some of this away. But they're still making API calls under the hood. Your code is still talking to S3 through an SDK, not through a file system.&lt;/p&gt;

&lt;h2&gt;
  
  
  S3 Files for Lambda
&lt;/h2&gt;

&lt;p&gt;S3 Files is a new feature that mounts your S3 bucket as a local file system on your Lambda function. Your code reads and writes files at a mount path like &lt;code&gt;/mnt/workspace&lt;/code&gt;, and S3 Files handles the synchronization back to the bucket. Changes you write show up in S3 within minutes. Changes made to S3 objects appear on the file system within seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The new way: just file paths
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;WORKSPACE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Read directly from the mount
&lt;/span&gt;    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&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="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Write directly to the mount
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No boto3 for file access. No &lt;code&gt;/tmp&lt;/code&gt; management. No upload step. The file system IS the interface.&lt;/p&gt;

&lt;p&gt;Under the hood, S3 Files is built on Amazon EFS. It delivers sub-millisecond latency for actively used data by caching your working set on high-performance storage. For large sequential reads, it streams directly from S3. You get file system semantics with S3 durability and economics.&lt;/p&gt;

&lt;p&gt;Here's the thing, though. S3 Files requires a VPC. Your Lambda function needs to be in the same VPC as the mount targets, and you need a NAT gateway for outbound internet access.&lt;/p&gt;

&lt;p&gt;I'll be honest: as a serverless guy, I generally avoid VPCs. But AWS has removed most of the hurdles over the years. VPC-attached Lambda functions no longer have the cold start penalty they used to. The networking setup is boilerplate you write once. And for what S3 Files gives you, the tradeoff is worth it. Get yourself a reusable network template and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;I wanted to test S3 Files with something more interesting than "read a CSV." So I built a serverless code review system. You point it at a public GitHub repo, and three things happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A durable orchestrator function clones the repo to a shared S3 Files workspace&lt;/li&gt;
&lt;li&gt;A security review agent and a style review agent analyze the code in parallel&lt;/li&gt;
&lt;li&gt;The results land in the same workspace as JSON files, synced back to S3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three Lambda functions mount the same S3 bucket. The orchestrator writes files. The agents read them. No S3 keys passed between functions. No downloading to &lt;code&gt;/tmp&lt;/code&gt;. The file system is the coordination layer.&lt;/p&gt;

&lt;p&gt;The agents use the Strands Agents SDK with Amazon Bedrock. Each agent gets custom file tools that operate on the mount path, and Claude decides which files to read, what to analyze, and what to write. The orchestrator uses Lambda durable functions to coordinate the workflow with automatic checkpointing.&lt;/p&gt;

&lt;p&gt;The full source is on GitHub: &lt;a href="https://github.com/singledigit/lambda-s3-files-example" rel="noopener noreferrer"&gt;singledigit/lambda-s3-files-example&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The SAM Template
&lt;/h2&gt;

&lt;p&gt;The IaC is the part that took the most iteration. S3 Files is brand new, and the CloudFormation resource types aren't in the linter yet. Here's what I learned.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Resource Chain
&lt;/h3&gt;

&lt;p&gt;You need five resources to get S3 Files working with Lambda:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;S3 Bucket&lt;/strong&gt; with versioning enabled (required)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Role&lt;/strong&gt; for S3 Files to access the bucket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Files FileSystem&lt;/strong&gt; that bridges the bucket to NFS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount Targets&lt;/strong&gt; in each AZ (network endpoints)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Point&lt;/strong&gt; that controls POSIX identity for Lambda&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resource types are &lt;code&gt;AWS::S3Files::FileSystem&lt;/code&gt;, &lt;code&gt;AWS::S3Files::MountTarget&lt;/code&gt;, and &lt;code&gt;AWS::S3Files::AccessPoint&lt;/code&gt;. Your IDE's CloudFormation linter won't recognize them yet. Ignore the red squiggles.&lt;/p&gt;

&lt;h3&gt;
  
  
  The IAM Role Gotcha
&lt;/h3&gt;

&lt;p&gt;The S3 Files IAM role trusts &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;, not &lt;code&gt;s3files.amazonaws.com&lt;/code&gt;. This tripped me up. S3 Files is built on EFS, so the trust relationship goes through the EFS service principal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;S3FilesRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Role&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/service-role/&lt;/span&gt;
    &lt;span class="na"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
      &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AllowS3FilesAssumeRole&lt;/span&gt;
          &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
          &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticfilesystem.amazonaws.com&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sts:AssumeRole&lt;/span&gt;
          &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;StringEquals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;aws:SourceAccount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;AWS::AccountId&lt;/span&gt;
            &lt;span class="na"&gt;ArnLike&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;aws:SourceArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3files:${AWS::Region}:${AWS::AccountId}:file-system/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The role needs S3 permissions to read and write the bucket. Scope it to your specific bucket ARN with &lt;code&gt;aws:ResourceAccount&lt;/code&gt; conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Access Point
&lt;/h3&gt;

&lt;p&gt;This is the important part for Lambda. The access point controls the POSIX identity your function runs as and creates a writable root directory. Without it, Lambda can mount the file system but can't write to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;S3FilesAccessPoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::S3Files::AccessPoint&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FileSystemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FileSystem.FileSystemId&lt;/span&gt;
    &lt;span class="na"&gt;PosixUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
      &lt;span class="na"&gt;Gid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
    &lt;span class="na"&gt;RootDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/lambda&lt;/span&gt;
      &lt;span class="na"&gt;CreationPermissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;OwnerUid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
        &lt;span class="na"&gt;OwnerGid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
        &lt;span class="na"&gt;Permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;755'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CreationPermissions&lt;/code&gt; property is crucial. It auto-creates the &lt;code&gt;/lambda&lt;/code&gt; directory with the right ownership when a client first connects. Without it, the root directory is owned by root (UID 0), and Lambda (running as UID 1000 through the access point) can't create subdirectories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Configuration
&lt;/h3&gt;

&lt;p&gt;On the Lambda side, &lt;code&gt;FileSystemConfigs&lt;/code&gt; takes the access point ARN (not the file system ARN) and a local mount path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;OrchestratorFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;DependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MountTargetA&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MountTargetB&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FileSystemConfigs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FilesAccessPoint.AccessPointArn&lt;/span&gt;
        &lt;span class="na"&gt;LocalMountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;
    &lt;span class="na"&gt;VpcConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SecurityGroupIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.LambdaSGId&lt;/span&gt;
      &lt;span class="na"&gt;SubnetIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetAId&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetBId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;DependsOn&lt;/code&gt; on the mount targets is important. Lambda can't mount the file system until the mount targets are available, and they take about five minutes to create.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;S3 Files is genuinely good for this use case. Shared file access between Lambda functions without the ceremony of S3 API calls. But a few things to know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency model matters.&lt;/strong&gt; S3 Files provides close-to-open consistency. If Function A writes a file and Function B reads it immediately, B might not see the latest version. For my use case, the orchestrator writes first and the agents run after, so ordering is natural. If you need real-time coordination between concurrent writers, you'll want a different pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VPC adds complexity.&lt;/strong&gt; Not much, but some. You need subnets, security groups, NAT gateway for internet access. Template it once and reuse it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold starts are fine.&lt;/strong&gt; VPC-attached Lambda functions used to add 10+ seconds of cold start. That's been fixed for years. My functions cold-start in under 2 seconds with the file system mount.&lt;/p&gt;

&lt;p&gt;The full code is at &lt;a href="https://github.com/singledigit/lambda-s3-files-example" rel="noopener noreferrer"&gt;github.com/singledigit/lambda-s3-files-example&lt;/a&gt;. Clone it, deploy it, point it at a repo. The agents will tell you what they think.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>lambda</category>
      <category>ai</category>
    </item>
    <item>
      <title>Built-in Token Counting: Telemetry for Production AI Agents</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Wed, 13 May 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/aws/built-in-token-counting-telemetry-for-production-ai-agents-26ic</link>
      <guid>https://dev.to/aws/built-in-token-counting-telemetry-for-production-ai-agents-26ic</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Strands Agents provides native telemetry and cost tracking out of the box. Stop writing custom token counters. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Building AI agents is easy. &lt;strong&gt;Deploying them to production&lt;/strong&gt; is where most teams hit a wall.&lt;/p&gt;

&lt;p&gt;One of the first questions from finance: &lt;em&gt;"How much will this cost per request?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most agent frameworks make you build your own token counter. Strands Agents gives you one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Custom Token Counting
&lt;/h2&gt;

&lt;p&gt;Every AI application needs cost monitoring. But tracking tokens across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple model calls&lt;/li&gt;
&lt;li&gt;Tool invocations&lt;/li&gt;
&lt;li&gt;Prompt caching&lt;/li&gt;
&lt;li&gt;Multi-agent workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...requires custom infrastructure most teams rebuild from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Telemetry in &lt;a href="https://strandsagents.com/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Strands Agents includes &lt;a href="https://strandsagents.com/docs/user-guide/observability-evaluation/metrics/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;production-grade telemetry&lt;/a&gt; by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;

&lt;span class="c1"&gt;# Create an agent with tools
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Invoke the agent with a prompt and get an AgentResult
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the square root of 144?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Access metrics through the AgentResult
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accumulated_usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;totalTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Execution time: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cycle_durations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tools used: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Cache metrics (when available)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cacheReadInputTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accumulated_usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache read tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accumulated_usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cacheReadInputTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cacheWriteInputTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accumulated_usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache write tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accumulated_usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cacheWriteInputTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;strong&gt;No configuration. No custom code. It just works.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;AgentResult&lt;/code&gt; includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens sent to the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens generated by the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Total cost (input + output)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cacheReadInputTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens read from cache (Bedrock prompt caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cacheWriteInputTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens written to cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Multi-Agent Token Tracking
&lt;/h2&gt;

&lt;p&gt;For multi-agent systems (executor → validator → critic), aggregate metrics across all agents:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.multiagent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Swarm&lt;/span&gt;

&lt;span class="n"&gt;swarm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Swarm&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;critic&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;swarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node_result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accumulated_usage&lt;/span&gt;
    &lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;totalTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total cost across all agents: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Per-Cycle Tracking
&lt;/h2&gt;

&lt;p&gt;For agents that run multiple reasoning cycles, track tokens per cycle:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# First invocation
&lt;/span&gt;&lt;span class="n"&gt;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is 5 + 3?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Second invocation
&lt;/span&gt;&lt;span class="n"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the square root of 144?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Access metrics for the latest invocation
&lt;/span&gt;&lt;span class="n"&gt;latest_invocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latest_agent_invocation&lt;/span&gt;
&lt;span class="n"&gt;cycles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latest_invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cycles&lt;/span&gt;
&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latest_invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;

&lt;span class="c1"&gt;# Or access all invocations
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_invocations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invocation usage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cycle&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cycles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Cycle &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_loop_cycle_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Or print the summary (includes all invocations)
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For a complete list of attributes and their types, see the &lt;a href="https://strandsagents.com/docs/api/python/strands.telemetry.metrics/#EventLoopMetrics/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;EventLoopMetrics&lt;/a&gt; API reference.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cost visibility&lt;/strong&gt; is the difference between a prototype and production AI.&lt;/p&gt;

&lt;p&gt;With Strands telemetry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Budget AI workloads before deployment&lt;/li&gt;
&lt;li&gt;✅ Identify expensive queries in production&lt;/li&gt;
&lt;li&gt;✅ Optimize prompts with real token data&lt;/li&gt;
&lt;li&gt;✅ Track prompt caching savings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without writing a single line of telemetry code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Works with All Model Providers
&lt;/h2&gt;

&lt;p&gt;Token tracking works regardless of your model provider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Bedrock (Claude, Llama, Mistral)&lt;/li&gt;
&lt;li&gt;OpenAI (GPT-4, GPT-3.5)&lt;/li&gt;
&lt;li&gt;Anthropic API&lt;/li&gt;
&lt;li&gt;Ollama (local models)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same API, same metrics, zero config changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;strands-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Full documentation: &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;strandsagents.com/docs/user-guide/concepts/agents/&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Gracias!&lt;/p&gt;

&lt;p&gt;🇻🇪🇨🇱 &lt;a href="https://dev.to/elizabethfuentes12"&gt;Dev.to&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/lizfue/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; &lt;a href="https://github.com/elizabethfuentes12/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://twitter.com/elizabethfue12" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;a href="https://www.instagram.com/elifue.tech" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; &lt;a href="https://www.youtube.com/channel/UCr0Gnc-t30m4xyrvsQpNp2Q" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__717518"&gt;
    &lt;a href="/elizabethfuentes12" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F717518%2Fb550b165-b8b9-405d-acfb-e5dc846765b0.png" alt="elizabethfuentes12 image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;Elizabeth Fuentes L&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;I help developers build production-ready AI applications through hands-on tutorials and open-source projects.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>S3 Account Regional Namespaces with CDK</title>
      <dc:creator>Sean Boult</dc:creator>
      <pubDate>Tue, 12 May 2026 19:07:20 +0000</pubDate>
      <link>https://dev.to/aws/s3-bucket-regional-namespaces-with-cdk-3j86</link>
      <guid>https://dev.to/aws/s3-bucket-regional-namespaces-with-cdk-3j86</guid>
      <description>&lt;p&gt;So you learned about the new &lt;a href="https://aws.amazon.com/blogs/aws/introducing-account-regional-namespaces-for-amazon-s3-general-purpose-buckets/" rel="noopener noreferrer"&gt;S3 bucket account regional namespace&lt;/a&gt; feature and you're thinking "this is amazing, let me try in CDK."&lt;/p&gt;

&lt;p&gt;Well... This is where you'll learn that the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html" rel="noopener noreferrer"&gt;L2 Bucket construct&lt;/a&gt; has not yet been updated to allow for this. &lt;/p&gt;

&lt;p&gt;Currently there are the following items which are tracking the long term fix.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-cdk/issues/37760" rel="noopener noreferrer"&gt;Issue aws/aws-cdk#37760&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-cdk/pull/37386" rel="noopener noreferrer"&gt;PR aws/aws-cdk#37386&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should give both of these a 👍 to help them get prioritized and shipped. &lt;/p&gt;

&lt;h2&gt;
  
  
  Workaround
&lt;/h2&gt;

&lt;p&gt;Good news there is a workaround! By using the L1 construct as an escape hatch we can then pass in &lt;code&gt;bucketNamePrefix&lt;/code&gt; and &lt;code&gt;bucketNamespace&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&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-cdk-lib&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;Construct&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;constructs&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3&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-cdk-lib/aws-s3&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;class&lt;/span&gt; &lt;span class="nc"&gt;Cdk101Stack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// WORKAROUND: use the L1 bucket construct&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnBucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ConfigBucket&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="na"&gt;bucketNamePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bucketNamespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;account-regional&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;After deploying this you'll see your new bucket with &lt;code&gt;{name}-{account}-{region}-an&lt;/code&gt; (the an suffix indicates account namespace).&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%2Ff6lb1dlx5m0o1pm2rod9.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%2Ff6lb1dlx5m0o1pm2rod9.png" alt=" " width="800" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That will work if you only need to deploy the bucket. But if you need an L2 reference (&lt;code&gt;IBucket&lt;/code&gt;), use &lt;code&gt;s3.Bucket.fromCfnBucket&lt;/code&gt; to convert it. &lt;/p&gt;

&lt;p&gt;Full code demo:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&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-cdk-lib&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;Construct&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;constructs&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3&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-cdk-lib/aws-s3&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3deploy&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-cdk-lib/aws-s3-deployment&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&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;class&lt;/span&gt; &lt;span class="nc"&gt;Cdk101Stack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// NOTE: workaround for the following CDK issues&lt;/span&gt;
    &lt;span class="c1"&gt;// https://github.com/aws/aws-cdk/issues/37760&lt;/span&gt;
    &lt;span class="c1"&gt;// https://github.com/aws/aws-cdk/pull/37386&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cfnBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnBucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ConfigBucket&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="na"&gt;bucketNamePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bucketNamespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;account-regional&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;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCfnBucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfnBucket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Upload a config file to the bucket on deploy&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3deploy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DeployConfig&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="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;s3deploy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
      &lt;span class="na"&gt;destinationBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&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;Hope this blog helped you get a workaround for now and once &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib-readme.html" rel="noopener noreferrer"&gt;aws-cdk-lib&lt;/a&gt; is patched I'll update this blog.&lt;/p&gt;

&lt;p&gt;Happy coding 😃!&lt;/p&gt;

&lt;p&gt;Follow AWS for more articles like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__1726"&gt;
  &lt;a href="/aws" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png" alt="aws image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/aws" class="ltag__user__link"&gt;AWS&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/aws" class="ltag__user__link"&gt;
        Articles written by current and past AWS Developer Advocates to help people interested in building on AWS. Opinions are each author's own.
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>aws</category>
      <category>cdk</category>
      <category>devops</category>
    </item>
    <item>
      <title>Prompt AI Coding Assistants to Build Production-Ready Agents: 8 Essential Patterns</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Mon, 11 May 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/aws/prompt-ai-coding-assistants-to-build-production-ready-agents-8-essential-patterns-fm5</link>
      <guid>https://dev.to/aws/prompt-ai-coding-assistants-to-build-production-ready-agents-8-essential-patterns-fm5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;When you ask an AI assistant like &lt;a href="https://kiro.dev?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; (AWS's AI coding assistant), Claude Code, or ChatGPT to "build me an agent," you get working code. But you don't see the architecture decisions happening behind the scenes. The agent responds to queries, but it might waste tokens in reasoning loops, hallucinate answers from incomplete data, or freeze on slow APIs. These failures are silent until production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you prompt AI coding assistants to build agents, they make architecture decisions silently—choosing retrieval strategies, validation approaches, and error handling patterns. These 8 patterns give you the vocabulary to specify production-grade decisions in your prompts, preventing hallucinations and token waste before code is generated.&lt;/p&gt;

&lt;p&gt;This post closes two series I wrote documenting the most expensive agent failures in production: &lt;a href="https://dev.to/aws/stop-ai-agent-hallucinations-4-essential-techniques-2i94?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Stop AI Agent Hallucinations (5 techniques)&lt;/a&gt; and &lt;a href="https://dev.to/aws/why-ai-agents-fail-3-failure-modes-that-cost-you-tokens-and-time-1flb?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Why AI Agents Fail (3 failure modes)&lt;/a&gt;. &lt;strong&gt;If you know these 8 patterns, you can guide AI assistants to avoid them from the start.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't a step-by-step implementation guide. It's a reference for knowing what exists so you can recognize when to use each pattern based on your use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working code for all 8 techniques:&lt;/strong&gt; Linked in each section&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;AI coding assistants generate agent code in seconds. &lt;a href="https://kiro.dev?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, Claude Code, Cursor, and ChatGPT can scaffold tools, configure LLM calls, and wire up retrieval systems faster than manual coding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But speed creates a problem: you get working code without seeing the tradeoffs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you prompt "build a booking agent with RAG," the assistant makes decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which retrieval strategy? (vector similarity, graph queries, hybrid)&lt;/li&gt;
&lt;li&gt;How to handle large outputs? (truncate, summarize, external storage)&lt;/li&gt;
&lt;li&gt;What validation runs before tool execution? (none, prompts, framework hooks)&lt;/li&gt;
&lt;li&gt;How to handle slow APIs? (block, timeout, async patterns)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Your prompt doesn't specify these. The assistant picks defaults. Those defaults create the failure modes this post documents.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 8 Failure Patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hallucination Failures (5 patterns):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GraphRAG&lt;/strong&gt; - Vector RAG fabricates statistics from incomplete chunks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Tool Selection&lt;/strong&gt; - Too many tools, agent picks wrong ones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neurosymbolic Guardrails&lt;/strong&gt; - Agent ignores business rules in prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Guardrails (Steering)&lt;/strong&gt; - Agent violates rules, needs correction not blocking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Agent Validation&lt;/strong&gt; - Single agent claims success when operations fail&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Silent Token Waste (3 patterns):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory Pointer Pattern&lt;/strong&gt; - Large data overflows context, causes truncation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async HandleId Pattern&lt;/strong&gt; - Slow APIs block agent indefinitely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DebounceHook + Explicit States&lt;/strong&gt; - Agent loops same tool call without progress&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't implement all 8. You learn what they solve, then specify the ones your use case needs when prompting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are These 8 Patterns?
&lt;/h2&gt;

&lt;p&gt;These patterns solve the most expensive production failures: hallucinations from incomplete data (GraphRAG, Semantic Tool Selection, Guardrails, Steering, Multi-Agent), and silent token waste (Memory Pointers, Async HandleId, DebounceHook). You learn what each solves, then specify the ones your use case needs when prompting AI assistants. This prevents debugging black-box code in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measured Impact from Production
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;Exact counts vs fabricated approximations&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws/rag-vs-graphrag-when-agents-hallucinate-answers-2mcb?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;RAG vs GraphRAG&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic Tool Selection&lt;/td&gt;
&lt;td&gt;86.4% fewer errors, 89% lower token costs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws/reduce-agent-errors-and-token-costs-with-semantic-tool-selection-7mf?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Tool Selection&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory Pointers&lt;/td&gt;
&lt;td&gt;20M tokens reduced to 1,234 tokens&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws/ai-context-window-overflow-memory-pointer-fix-22bk?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;IBM Materials Science study&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async HandleId&lt;/td&gt;
&lt;td&gt;18-second block eliminated, no 424 timeouts&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws/fix-mcp-timeouts-async-handleid-pattern-8ek?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;MCP Timeouts&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit States&lt;/td&gt;
&lt;td&gt;14 calls reduced to 2 (7x improvement)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws/how-to-prevent-ai-agent-reasoning-loops-from-wasting-tokens-2652?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Reasoning Loops&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Pattern 1: GraphRAG for Precise Queries
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is GraphRAG?
&lt;/h3&gt;

&lt;p&gt;GraphRAG replaces vector similarity with graph database queries for structured data. When your agent needs exact counts, aggregations, or relationship traversal, GraphRAG translates natural language to Cypher queries that return precise results from structured data instead of hallucinated statistics from incomplete text chunks. Use it for structured queries, keep vector RAG for semantic search.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;Vector RAG fabricates statistics. Ask "How many hotels in Miami have pools and breakfast?" and vector similarity retrieves 3 text chunks mentioning Miami, pools and breakfast. The LLM sees incomplete data, calculates from samples, and returns "approximately 120 hotels" (fabricated from 3 chunks out of 200 hotels).&lt;/p&gt;

&lt;p&gt;Out-of-domain queries return hallucinated answers instead of admitting no data exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Replace vector retrieval with graph queries for structured data. Store hotels, amenities, and relationships in Neo4j. The LLM translates "hotels with pools and breakfast" into Cypher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;h:&lt;/span&gt;&lt;span class="n"&gt;Hotel&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAS_AMENITY&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Amenity&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a.name&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pool'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'breakfast'&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Result: 133 hotels (exact count from database).&lt;/p&gt;

&lt;p&gt;Out-of-domain query: "No hotels found in Antarctica" instead of fabricating results.&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%2Fcstm4df3sgeyh6d9zbkb.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%2Fcstm4df3sgeyh6d9zbkb.png" alt="Vector RAG fabricates statistics from text chunks. GraphRAG returns exact counts from structured database queries" width="800" height="990"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a travel agent using GraphRAG. For structured 
queries (hotels, amenities, availability), translate to Cypher 
and execute against the graph. Only use vector RAG for unstructured descriptions. Return exact counts from graph traversal."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Structured data with relationships (products, inventory, locations)&lt;/li&gt;
&lt;li&gt;Queries requiring counts, aggregations, or multi-hop traversal&lt;/li&gt;
&lt;li&gt;Domains where fabricating statistics creates legal/financial risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/rag-vs-graphrag-when-agents-hallucinate-answers-2mcb?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;RAG vs GraphRAG: When Agents Hallucinate Answers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn more:&lt;/strong&gt; &lt;a href="https://neo4j.com/docs/cypher-manual/current/" rel="noopener noreferrer"&gt;Neo4j Cypher Documentation&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 2: Semantic Tool Selection
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Is Semantic Tool Selection?
&lt;/h3&gt;

&lt;p&gt;Semantic tool selection uses vector embeddings to filter tools before the LLM sees them. When your agent has 10+ tools, sending all descriptions on every call increases error rates (agent picks wrong tools) and token costs (paying for unused descriptions). Semantic filtering embeds tool descriptions offline, then at runtime matches the query to top-5 relevant tools, reducing errors by 86.4% and costs by 89%.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;With 50 tools, two failures occur: (1) agent picks wrong tools because descriptions overlap, and (2) token costs explode from sending all 50 tool descriptions on every LLM call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured impact:&lt;/strong&gt; Error rates increase with tool count, token costs scale linearly.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Use vector embeddings to filter tools before the LLM sees them. Embed tool descriptions offline. At runtime, embed the user query, compute similarity, pass only top-5 relevant tools to the agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results from production:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Errors reduced: 86.4%&lt;/li&gt;
&lt;li&gt;Token costs reduced: 89%&lt;/li&gt;
&lt;li&gt;Latency: &amp;lt;10ms for tool filtering&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a multi-tool agent with semantic tool selection At 
runtime, embed the query, retrieve top-5 similar tools, pass only 
those to the agent. Keep conversation memory, dynamically swap tools."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agents with 10+ tools&lt;/li&gt;
&lt;li&gt;Tools with overlapping descriptions&lt;/li&gt;
&lt;li&gt;Cost-sensitive applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/reduce-agent-errors-and-token-costs-with-semantic-tool-selection-7mf?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Reduce Agent Errors and Token Costs with Semantic Tool Selection&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 3: Neurosymbolic Guardrails (Block)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Are Neurosymbolic Guardrails?
&lt;/h3&gt;

&lt;p&gt;Neurosymbolic guardrails enforce business rules at the framework level, below the LLM's control. When prompts alone cannot enforce constraints (max guests, valid dates, budget limits), guardrails use pre-execution hooks to validate parameters and cancel invalid operations. Rules live in code, not prompts, so the LLM cannot bypass them. Use blocking guardrails for hard constraints that cannot be violated.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;Prompts cannot enforce business rules. Even with clear docstrings ("max_guests must be ≤10"), the LLM passes &lt;code&gt;max_guests=15&lt;/code&gt; under pressure because prompts are suggestions, not constraints. The agent violates rules silently.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Use framework hooks to validate parameters before tool execution. If validation fails, cancel the tool call and return corrective guidance. Rules live in code at the framework level, below the LLM's control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured impact:&lt;/strong&gt; Zero violations in 100-query test (vs. 12 violations with prompts alone).&lt;/p&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a booking agent with guardrails using Strands Agents hooks. 
Create a BeforeToolCallEvent hook that validates:
- max_guests ≤ 10
- check_in_date &amp;gt; today
- budget &amp;gt; 0

If validation fails, cancel the tool call with event.cancel_tool() 
and return error message. Do not rely on prompts for validation."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F1jlee9gupk90w1xg00hx.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%2F1jlee9gupk90w1xg00hx.png" alt="Prompts can be ignored by LLM (top layer). Framework hooks enforce rules at code level (bottom layer, unbypassable)" width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Business rules that cannot be violated (compliance, legal, financial)&lt;/li&gt;
&lt;li&gt;Validation requiring computation (date math, inventory checks)&lt;/li&gt;
&lt;li&gt;Rules that change frequently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/ai-agent-guardrails-rules-that-llms-cannot-bypass-596d?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;AI Agent Guardrails: Rules That LLMs Cannot Bypass&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 4: Runtime Guardrails (Steer, Don't Block)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Is Steering vs Blocking?
&lt;/h3&gt;

&lt;p&gt;Steering guardrails return corrective guidance instead of blocking operations. When the agent violates a soft rule (format issues, parameter adjustments, data redaction), steering returns instructions via Guide() so the agent self-corrects and retries. This differs from blocking guardrails (Pattern 3) which stop workflows entirely. Use steering for rules where the agent can fix itself, blocking for hard constraints.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;Hard guardrails (Pattern 3) block operations and stop workflows. For soft rules where the agent can self-correct (format issues, parameter adjustments, redacting sensitive data), blocking creates friction. The agent could fix the problem itself if given guidance.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://github.com/agentcontrol/agent-control" rel="noopener noreferrer"&gt;Agent Control&lt;/a&gt; to return corrective guidance via &lt;code&gt;Guide()&lt;/code&gt; instead of blocking. When the agent violates a soft rule, the control plane returns instructions: "Adjust parameter X to Y and retry." The agent self-corrects and completes the task without human intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difference from Pattern 3:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Block (Pattern 3):&lt;/strong&gt; Hard constraints, workflow stops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steer (Pattern 4):&lt;/strong&gt; Soft rules, agent self-corrects&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a booking agent with Agent Control for soft rules. Connect 
to Agent Control server. For soft rules (parameter formatting, 
date adjustments, data redaction), return Guide() with correction 
instructions instead of blocking. Agent should retry with fix applied.

Use hard blocks (Pattern 3) only for compliance rules that cannot 
be violated under any circumstance."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Rules where agent can self-correct (format, adjust parameters)&lt;/li&gt;
&lt;li&gt;Workflows where blocking creates poor UX&lt;/li&gt;
&lt;li&gt;Rules managed centrally via API/dashboard (update without redeploying)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/runtime-guardrails-for-ai-agents-steer-dont-block-278n?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Runtime Guardrails for AI Agents: Steer, Don't Block&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 5: Multi-Agent Validation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Is Multi-Agent Validation?
&lt;/h3&gt;

&lt;p&gt;Multi-agent validation deploys specialized agents with different roles (Executor, Validator, Critic) that cross-check each other's work. Single agents optimize for appearing successful, not verifying outcomes. Multiple agents with different optimization functions catch errors the others miss. Executor performs tasks, Validator cross-checks against ground truth, Critic provides final review before returning to the user.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;Single agents cannot self-validate. When an agent books a hotel, it claims "Success: Booked Grand Plaza Hotel" even if the API returned an error or the hotel doesn't exist in the database. The agent optimizes for appearing successful, not verifying outcomes.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Deploy multiple agents with different roles: Executor performs tasks, Validator cross-checks against ground truth, Critic provides final review. Agents share context and hand off control autonomously when their role completes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured impact:&lt;/strong&gt; Multi-agent catches errors single agent misses (e.g., booking non-existent hotels).&lt;/p&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a multi-agent system using Strands Swarm with 3 agents:
1. Executor: Books hotels, searches flights
2. Validator: Cross-checks operations against database
3. Critic: Final review before returning to user

Agents share context via swarm.context. Use autonomous handoffs. 
Agents decide when to hand off based on task completion."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;High-stakes operations (financial, medical, legal)&lt;/li&gt;
&lt;li&gt;Tasks where "appears successful" differs from "actually successful"&lt;/li&gt;
&lt;li&gt;Complex workflows with multiple verification points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/how-to-stop-ai-agents-from-hallucinating-silently-with-multi-agent-validation-3f7e?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;How to Stop AI Agents from Hallucinating Silently with Multi-Agent Validation&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 6: Memory Pointer Pattern
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Is the Memory Pointer Pattern?
&lt;/h3&gt;

&lt;p&gt;The Memory Pointer Pattern stores large data outside the LLM context and passes short references instead. When tools return 200KB+ logs or 1000-row database results, passing them directly causes silent truncation. Memory pointers store data in agent.state, return a pointer to the LLM, and provide separate tools that resolve pointers to access full data. IBM reduced 20M tokens to 1,234 tokens using this pattern.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;Context window overflow occurs when tools return more data than the LLM can process (200KB+ logs, 1000-row database results). The agent doesn't crash. It silently truncates data, loses context, produces incomplete answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real production case (IBM Materials Science):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: 20 million tokens, workflow failed&lt;/li&gt;
&lt;li&gt;After: 1,234 tokens, workflow succeeded&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Store large data in &lt;code&gt;agent.state&lt;/code&gt;, pass short references to the LLM. Tools return pointers like &lt;code&gt;"logs-app-server"&lt;/code&gt;. Subsequent tools resolve pointers to access full data. LLM only sees: "Data stored as logs-app-server. Use analyze_errors(pointer)."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data in context reduced:&lt;/strong&gt; 214KB → 52 bytes&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%2Fsbzqsm6ml5qo4e5lkkjs.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%2Fsbzqsm6ml5qo4e5lkkjs.png" alt="Before: 20M tokens overflow context. After: Memory pointer reduces to 1,234 tokens, full data stored externally" width="800" height="1022"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a log analysis agent using Memory Pointer Pattern. When 
fetch_logs returns &amp;gt;20KB:
1. Store in agent.state with unique pointer ID
2. Return to LLM: 'Data stored as logs-{app}. Use analyze_logs(pointer).'
3. Implement analyze_logs(pointer) that resolves from agent.state

Never pass large data directly to LLM context."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tools returning large outputs (logs, database queries, files)&lt;/li&gt;
&lt;li&gt;Workflows with multiple processing steps on same large data&lt;/li&gt;
&lt;li&gt;Cost-sensitive applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/ai-context-window-overflow-memory-pointer-fix-22bk?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;AI Context Window Overflow: Memory Pointer Fix&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 7: Async HandleId Pattern
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Is the Async HandleId Pattern?
&lt;/h3&gt;

&lt;p&gt;The async handleId pattern prevents slow external APIs from blocking your agent. When an API takes 30+ seconds, synchronous calls freeze the entire agent. Async handleId returns a job ID immediately, letting the agent continue with other tasks. A separate check_status tool polls for results when ready. This eliminates 424 timeout errors and keeps agents responsive.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;External APIs that take 30+ seconds block the agent indefinitely. No other tools can run. After ~7 seconds, many implementations return 424 timeout errors, freezing the workflow.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Tools return immediately with a job ID instead of waiting. Agent stores handleId and continues. Separate &lt;code&gt;check_status(job_id)&lt;/code&gt; tool polls for results asynchronously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured impact:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: 18-second API blocks agent, 424 timeout&lt;/li&gt;
&lt;li&gt;After: Tool returns &amp;lt;1 second, agent polls when ready&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build an agent with async handleId pattern for slow APIs:

1. start_analysis(data): Submit job, return job_id immediately
2. check_status(job_id): Poll for results

Agent calls start_analysis, stores job_id, continues with other 
tasks, calls check_status when ready. Do not implement blocking calls."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;External APIs with &amp;gt;5 second response times&lt;/li&gt;
&lt;li&gt;Batch processing (video analysis, large transforms)&lt;/li&gt;
&lt;li&gt;Any system outside your control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/fix-mcp-timeouts-async-handleid-pattern-8ek?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Fix MCP Timeouts: Async HandleId Pattern&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Pattern 8: DebounceHook + Explicit States
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What Prevents Reasoning Loops?
&lt;/h3&gt;

&lt;p&gt;Reasoning loops occur when ambiguous tool feedback ("more may be available") signals that retrying might help. Two fixes work together: explicit terminal states (return SUCCESS/FAILED so the LLM knows when to stop) and DebounceHook (framework hook that blocks duplicate calls). Production tests showed explicit states reduced calls from 14 to 2, while DebounceHook provides a safety net for edge cases.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Breaks
&lt;/h3&gt;

&lt;p&gt;Agents loop calling the same tool repeatedly without progress. Ambiguous feedback like "Found 3 results. More may be available" signals that retrying might help. The agent loops indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real production case:&lt;/strong&gt; 847 reasoning steps at $47/minute, no answer delivered.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix (Two Parts)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Part A: Explicit Terminal States&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Return clear SUCCESS or FAILED states. Change "More may be available" to "SUCCESS: Found all 3 matching flights."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part B: DebounceHook Safety Net&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Framework hook tracks recent tool calls. When same (tool_name, input) appears twice, block third attempt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured impact (travel booking demo):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ambiguous feedback: 14 calls&lt;/li&gt;
&lt;li&gt;Explicit SUCCESS: 2 calls (7x reduction)&lt;/li&gt;
&lt;li&gt;DebounceHook: 12 calls (2 blocked)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What to Tell Your AI Assistant
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a travel agent with anti-loop protection:

1. All tools return explicit states:
   - SUCCESS: [clear completion]
   - FAILED: [clear error]
   Never return 'more may be available'

2. Implement DebounceHook:
   - Track last 3 tool calls as (tool_name, input)
   - If same pair appears twice, block third attempt
   - Return 'BLOCKED: Duplicate detected'

This prevents loops without manual retry limits."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agents prone to retry loops (search, API aggregators)&lt;/li&gt;
&lt;li&gt;Cost-sensitive applications where unbounded retries are expensive&lt;/li&gt;
&lt;li&gt;Production systems where infinite loops create availability risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full details:&lt;/strong&gt; &lt;a href="https://dev.to/aws/how-to-prevent-ai-agent-reasoning-loops-from-wasting-tokens-2652?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;How to Prevent AI Agent Reasoning Loops from Wasting Tokens&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Example: Generic vs Informed Prompting
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ❌ Generic Prompt
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a customer support agent that searches our knowledge base 
and books appointments"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vector RAG (may hallucinate on structured queries)&lt;/li&gt;
&lt;li&gt;Synchronous booking API (may timeout)&lt;/li&gt;
&lt;li&gt;No validation (can book invalid times)&lt;/li&gt;
&lt;li&gt;Single agent (claims success even when booking fails)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Works in demo, fails in production.&lt;/p&gt;


&lt;h3&gt;
  
  
  ✅ Informed Prompt
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Build a customer support agent:

Knowledge Base:
- Use Neo4j GraphRAG for structured queries (pricing, features)
- Use vector RAG only for semantic search (descriptions)

Booking:
- Validate appointment_time &amp;gt; now() before booking
- Use async handleId for booking API (10+ seconds)
- Return explicit states: SUCCESS / FAILED

Validation:
- Multi-agent: Executor (search/book), Validator (cross-check), 
  Critic (final review)
- Use Strands Swarm for autonomous handoffs

Loop Prevention:
- DebounceHook blocks duplicate calls
- All tools return terminal states"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GraphRAG prevents hallucinations&lt;/li&gt;
&lt;li&gt;Async prevents timeouts&lt;/li&gt;
&lt;li&gt;Guardrails prevent invalid bookings&lt;/li&gt;
&lt;li&gt;Multi-agent catches false successes&lt;/li&gt;
&lt;li&gt;DebounceHook prevents loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Production-ready agent.&lt;/p&gt;


&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Mistake 1: Assuming Defaults Are Best Practices
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; "Build a production agent" assumes the assistant knows what production means.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Specify patterns: "Use GraphRAG, guardrails, async patterns."&lt;/p&gt;
&lt;h3&gt;
  
  
  Mistake 2: Relying Only on Prompts for Validation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; "Make sure max_guests &amp;lt; 10" in system prompt gets ignored under pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; "Implement BeforeToolCallEvent hook that validates and cancels invalid calls."&lt;/p&gt;
&lt;h3&gt;
  
  
  Mistake 3: Not Recognizing When Patterns Apply
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Agent works in demo, breaks on edge cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Know the 8 patterns. When you see hallucinations, timeouts, or loops, you'll recognize which pattern solves it.&lt;/p&gt;


&lt;h2&gt;
  
  
  My Thoughts
&lt;/h2&gt;

&lt;p&gt;AI coding assistants will keep improving at generating working code. But working code and production-ready architecture remain different targets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gap isn't the assistant's capability. It's the prompt's specificity.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;
&lt;h3&gt;
  
  
  If You're Building a New Agent
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Identify which patterns apply (use symptom checklist)&lt;/li&gt;
&lt;li&gt;Specify patterns in your prompt&lt;/li&gt;
&lt;li&gt;Verify generated code implements them&lt;/li&gt;
&lt;li&gt;Test failure modes (timeouts, invalid inputs, non-existent data)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  If You're Debugging an Existing Agent
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Identify the symptom (hallucinations, loops, timeouts, rule violations)&lt;/li&gt;
&lt;li&gt;Map symptom to pattern (see Step 1: Recognize the Symptom)&lt;/li&gt;
&lt;li&gt;Prompt your assistant to add the pattern: "Add DebounceHook to prevent loops"&lt;/li&gt;
&lt;li&gt;Verify fix with targeted tests&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Learn More (Full Implementation Guides)
&lt;/h3&gt;

&lt;p&gt;Each pattern has a complete guide with working code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GraphRAG:&lt;/strong&gt; &lt;a href="https://dev.to/aws/rag-vs-graphrag-when-agents-hallucinate-answers-2mcb?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;RAG vs GraphRAG: When Agents Hallucinate Answers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Tool Selection:&lt;/strong&gt; &lt;a href="https://dev.to/aws/reduce-agent-errors-and-token-costs-with-semantic-tool-selection-7mf?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Reduce Agent Errors and Token Costs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neurosymbolic Guardrails:&lt;/strong&gt; &lt;a href="https://dev.to/aws/ai-agent-guardrails-rules-that-llms-cannot-bypass-596d?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;AI Agent Guardrails: Rules That LLMs Cannot Bypass&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Guardrails (Steering):&lt;/strong&gt; &lt;a href="https://dev.to/aws/runtime-guardrails-for-ai-agents-steer-dont-block-278n?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Runtime Guardrails for AI Agents: Steer, Don't Block&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Agent Validation:&lt;/strong&gt; &lt;a href="https://dev.to/aws/how-to-stop-ai-agents-from-hallucinating-silently-with-multi-agent-validation-3f7e?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Stop AI Agents from Hallucinating Silently&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Pointers:&lt;/strong&gt; &lt;a href="https://dev.to/aws/ai-context-window-overflow-memory-pointer-fix-22bk?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;AI Context Window Overflow: Memory Pointer Fix&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async HandleId:&lt;/strong&gt; &lt;a href="https://dev.to/aws/fix-mcp-timeouts-async-handleid-pattern-8ek?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Fix MCP Timeouts: Async HandleId Pattern&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DebounceHook:&lt;/strong&gt; &lt;a href="https://dev.to/aws/how-to-prevent-ai-agent-reasoning-loops-from-wasting-tokens-2652?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Prevent AI Agent Reasoning Loops&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Complete series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws/stop-ai-agent-hallucinations-4-essential-techniques-2i94?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Stop AI Agent Hallucinations: 5 Essential Techniques&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws/why-ai-agents-fail-3-failure-modes-that-cost-you-tokens-and-time-1flb?trk=87c4c426-cddf-4799-a299-273337552ad8&amp;amp;sc_channel=el"&gt;Why AI Agents Fail: 3 Failure Modes That Cost You Tokens and Time&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Gracias!&lt;/p&gt;

&lt;p&gt;🇻🇪🇨🇱 &lt;a href="https://dev.to/elizabethfuentes12"&gt;Dev.to&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/lizfue/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; &lt;a href="https://github.com/elizabethfuentes12/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://twitter.com/elizabethfue12" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;a href="https://www.instagram.com/elifue.tech" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; &lt;a href="https://www.youtube.com/channel/UCr0Gnc-t30m4xyrvsQpNp2Q" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__717518"&gt;
    &lt;a href="/elizabethfuentes12" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F717518%2Fb550b165-b8b9-405d-acfb-e5dc846765b0.png" alt="elizabethfuentes12 image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;Elizabeth Fuentes L&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/elizabethfuentes12"&gt;I help developers build production-ready AI applications through hands-on tutorials and open-source projects.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Estructuras de datos: ¿Qué son los Stacks (LIFO)?</title>
      <dc:creator>Axel Espinosa</dc:creator>
      <pubDate>Fri, 08 May 2026 21:22:09 +0000</pubDate>
      <link>https://dev.to/aws/estructuras-de-datos-que-son-los-stacks-lifo-1d0n</link>
      <guid>https://dev.to/aws/estructuras-de-datos-que-son-los-stacks-lifo-1d0n</guid>
      <description>&lt;p&gt;¿Alguna vez te has preguntado qué pasa cuando presionas el botón "atrás" en tu navegador? ¿O cómo &lt;code&gt;Ctrl + Z&lt;/code&gt; sabe exactamente qué acción deshacer? Detrás de esas funciones hay una estructura de datos que llevas años usando sin darte cuenta: el stack.&lt;/p&gt;

&lt;p&gt;En los artículos anteriores vimos los &lt;a href="https://dev.to/aws/arrays-los-bloques-fundamentales-de-la-programacion-3jmf"&gt;arrays&lt;/a&gt; y los &lt;a href="https://dev.to/aws/strings-en-programacion-mas-que-un-simple-array-de-caracteres-2lgn"&gt;strings&lt;/a&gt;. Hoy toca hablar de la siguiente pieza: los stacks. Si los arrays son el almacén general, el stack es una pila de platos. Ya lo vas a ver.&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%2Fo44qgfbopv1qjuo5c1pe.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%2Fo44qgfbopv1qjuo5c1pe.png" alt="Todo esto que ya usas es un stack: botón atrás, Ctrl+Z y stack traces" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lo que encontrarás en este artículo:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qué es un stack y qué significa LIFO&lt;/li&gt;
&lt;li&gt;Las operaciones básicas: push, pop, peek, size, isEmpty&lt;/li&gt;
&lt;li&gt;Cómo implementar tu propio stack en JavaScript&lt;/li&gt;
&lt;li&gt;La complejidad Big O de cada operación&lt;/li&gt;
&lt;li&gt;Dónde se usan los stacks en la vida real&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ¿Qué es un stack?
&lt;/h2&gt;

&lt;p&gt;Un stack (o pila) es una estructura de datos que sigue el principio &lt;strong&gt;LIFO: Last In, First Out&lt;/strong&gt;. En español: el último que entra es el primero que sale.&lt;/p&gt;

&lt;p&gt;Imagina una pila de libros sobre tu escritorio. Vas apilándolos uno sobre otro. Cuando quieres leer uno, ¿por cuál empiezas? Por el de arriba, no por el de abajo. El último libro que pusiste es el primero que vas a tomar.&lt;/p&gt;

&lt;p&gt;Esa es la esencia de un stack. No puedes sacar un elemento del medio o del fondo. Solo trabajas con el que está hasta arriba.&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%2Fcku5pmn7qj6isjnc8amj.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%2Fcku5pmn7qj6isjnc8amj.png" alt="LIFO: Last In, First Out" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pero aquí viene el detalle interesante: aunque suene restrictivo, esta limitación es justo lo que hace al stack útil. Cuando solo te importa el último elemento agregado, un stack es la herramienta perfecta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operaciones básicas de un stack
&lt;/h2&gt;

&lt;p&gt;Un stack tiene cinco operaciones fundamentales. Estas son las que aparecen en el ADT (Abstract Data Type) de un stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;push(item)&lt;/code&gt;: agrega un elemento a la parte superior&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pop()&lt;/code&gt;: elimina el elemento de arriba y lo devuelve&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peek()&lt;/code&gt;: mira el elemento de arriba sin eliminarlo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size()&lt;/code&gt;: devuelve cuántos elementos hay en el stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isEmpty()&lt;/code&gt;: dice si el stack está vacío o no&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Las dos estrellas son &lt;code&gt;push&lt;/code&gt; y &lt;code&gt;pop&lt;/code&gt;. Con ellas agregas y quitas elementos, siempre desde la misma puerta: la de arriba.&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%2Fc9gqqyr24femkwdeopc6.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%2Fc9gqqyr24femkwdeopc6.png" alt="push y pop paso a paso" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fíjate en cómo &lt;code&gt;pop()&lt;/code&gt; siempre saca el último que entró. En el paso 3 agregamos 🐱, y en el paso 4 &lt;code&gt;pop()&lt;/code&gt; nos devuelve 🐱. Si hubiéramos hecho &lt;code&gt;pop()&lt;/code&gt; otra vez, nos devolvería 🐶.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo implementar un stack en JavaScript
&lt;/h2&gt;

&lt;p&gt;Dato curioso antes de empezar: los arrays en JavaScript ya se comportan como un stack. Si haces &lt;code&gt;miArray.push(x)&lt;/code&gt; y &lt;code&gt;miArray.pop()&lt;/code&gt;, estás trabajando como si fuera un stack..&lt;/p&gt;

&lt;p&gt;Pero un array te deja hacer cosas que un stack no debería permitir: acceder por cualquier índice, &lt;code&gt;unshift&lt;/code&gt;, &lt;code&gt;splice&lt;/code&gt;, lo que quieras. Y esa libertad de más es justo lo que queremos esconder.&lt;/p&gt;

&lt;p&gt;Así que vamos a implementarlo desde cero. Sin usar &lt;code&gt;.push()&lt;/code&gt; ni &lt;code&gt;.pop()&lt;/code&gt; del array. Solo lo vamos a usar como almacenamiento indexado y el control lo lleva nosotros. Así se ve qué pasa por debajo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// posición donde iría el próximo elemento&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;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;pop&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;El stack está vacío&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&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;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// recortamos el array&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;peek&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;El stack está vacío&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;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;size&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;isEmpty&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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;Probemos cómo se usa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;miStack&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;Stack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;miStack&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🐶&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;miStack&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🐱&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;miStack&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👻&lt;/span&gt;&lt;span class="dl"&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;miStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 3&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;miStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// "👻" (solo mira, no elimina)&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;miStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// "👻" (lo elimina y devuelve)&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;miStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 2&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;miStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Así se ve el stack en cada paso:&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%2Fifm64jmuvcn9tj2aqnzt.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%2Fifm64jmuvcn9tj2aqnzt.png" alt="Paso a paso de push, peek y pop sobre miStack" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puedes probar este código en &lt;a href="https://runjs.app/play" rel="noopener noreferrer"&gt;RunJS&lt;/a&gt; o verlo paso a paso en &lt;a href="https://pythontutor.com/visualize.html#mode=edit" rel="noopener noreferrer"&gt;PythonTutor&lt;/a&gt;, que también soporta JavaScript.&lt;/p&gt;

&lt;p&gt;Vamos a desarmar lo que está pasando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; lleva la cuenta de cuántos elementos hay. También indica la siguiente posición libre del array. Recuerda que los arrays empiezan en el índice 0, así que cuando el stack está vacío &lt;code&gt;count&lt;/code&gt; vale 0 y ese es justo el lugar donde va a caer el primer elemento.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;push()&lt;/code&gt; coloca el nuevo elemento en la posición &lt;code&gt;count&lt;/code&gt; y luego incrementa el contador.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pop()&lt;/code&gt; hace lo contrario: decrementa &lt;code&gt;count&lt;/code&gt; primero (ahora apunta al último elemento), lee ese valor y recorta el array.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peek()&lt;/code&gt; lee la posición &lt;code&gt;count - 1&lt;/code&gt; (el último elemento) sin mover nada.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por ejemplo, si hacemos &lt;code&gt;push("🐶")&lt;/code&gt;, &lt;code&gt;push("🐱")&lt;/code&gt;, &lt;code&gt;push("👻")&lt;/code&gt;, el array queda así:&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%2Fxl2g84clcfs1afg5taz6.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%2Fxl2g84clcfs1afg5taz6.png" alt="Estado interno del stack" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cuando hacemos &lt;code&gt;pop()&lt;/code&gt;, el contador baja de 3 a 2 y leemos &lt;code&gt;items[2]&lt;/code&gt; que es 👻. Luego hacemos &lt;code&gt;this.items.length = 2&lt;/code&gt; para recortar el array, y listo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un truco de los arrays en JavaScript
&lt;/h3&gt;

&lt;p&gt;Fíjate en esta línea: &lt;code&gt;this.items.length = this.count&lt;/code&gt;. Podría parecer que solo estamos "cambiando un número", pero en realidad estamos &lt;strong&gt;modificando el array&lt;/strong&gt;. En JavaScript, asignarle un valor menor a &lt;code&gt;.length&lt;/code&gt; recorta el array y elimina los elementos que sobran.&lt;/p&gt;

&lt;p&gt;Míralo por fuera de la clase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 5&lt;/span&gt;

&lt;span class="c1"&gt;// Definir la longitud&lt;/span&gt;
&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;numbers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [1, 2, 3]&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;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3&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;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// undefined (los elementos extra se eliminan)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este es un detalle que muchos desarrolladores desconocen y te abre posibilidades interesantes. En nuestra clase lo usamos para asegurarnos de que el array y el contador &lt;code&gt;count&lt;/code&gt; siempre estén sincronizados.&lt;/p&gt;

&lt;p&gt;Si te fijas, no usamos los métodos &lt;code&gt;push&lt;/code&gt; y &lt;code&gt;pop&lt;/code&gt; del array nativo de JavaScript. Solo lo usamos como almacenamiento indexado. Todo el control lo lleva nuestra variable &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complejidad Big O de las operaciones
&lt;/h2&gt;

&lt;p&gt;Ya vimos qué hace cada operación. Ahora, ¿cuánto cuestan? Aquí es donde entra &lt;a href="https://www.freecodecamp.org/espanol/news/explicacion-de-la-notacion-big-o-con-ejemplo/" rel="noopener noreferrer"&gt;Big O&lt;/a&gt;, la notación que nos dice cómo crece el tiempo de una operación conforme crece el stack.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operación&lt;/th&gt;
&lt;th&gt;Complejidad en tiempo&lt;/th&gt;
&lt;th&gt;Complejidad en espacio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;push()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O(1)*&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pop()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;peek()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;size()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEmpty()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* Amortizado. Ocasionalmente O(n) cuando el array dinámico necesita crecer por debajo.&lt;/p&gt;

&lt;p&gt;Todas las operaciones de un stack son O(1). ¿Por qué? Porque siempre trabajamos con un solo elemento: el de arriba. No importa si el stack tiene 10 o 10 millones de elementos, &lt;code&gt;push&lt;/code&gt; y &lt;code&gt;pop&lt;/code&gt; tardan lo mismo.&lt;/p&gt;

&lt;p&gt;Compara esto con un array, donde insertar al inicio es O(n) porque hay que mover todos los elementos. En un stack no tenemos ese problema porque solo tocamos la punta.&lt;/p&gt;

&lt;p&gt;Esta es la razón principal por la que los stacks se usan tanto: son rápidos y predecibles. Si tu problema solo necesita trabajar con el último elemento agregado, un stack te da esa operación en tiempo constante.&lt;/p&gt;

&lt;p&gt;Eso sí, rápido no quiere decir infinito. Un stack vive en memoria, así que si crece demasiado va a depender por completo de los recursos del dispositivo donde corra. En celulares, tablets y computadoras con poca RAM esto importa más de lo que crees: un stack que crece sin control puede tronar la app. Los lenguajes hasta tienen un límite específico para el call stack, y si lo rebasas te topas con el famoso &lt;em&gt;stack overflow&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Dónde se usan los stacks en la vida real?
&lt;/h2&gt;

&lt;p&gt;Antes de ver ejemplos, quédate con esto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Regla de oro:&lt;/strong&gt; cuando lo único que te importa es el último elemento, un stack es la respuesta.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Todo lo que viene a continuación es una variante del mismo patrón. Una vez que lo ves, empiezas a reconocer stacks en todos lados.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navegador web: botón de "atrás"
&lt;/h3&gt;

&lt;p&gt;Cada vez que visitas una página nueva, el navegador hace &lt;code&gt;push()&lt;/code&gt; con la URL. Cuando presionas "atrás", hace &lt;code&gt;pop()&lt;/code&gt; y te lleva a la página anterior.&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%2F6g3uc6zt7do2joho3d27.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%2F6g3uc6zt7do2joho3d27.png" alt="Historial del navegador como stack" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Funciona perfecto porque siempre quieres volver a la última página que visitaste, no a una del medio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ctrl + Z: deshacer acciones
&lt;/h3&gt;

&lt;p&gt;Cuando escribes en un editor de texto, cada acción (escribir una letra, borrar, pegar) se agrega a un stack. Cuando presionas &lt;code&gt;Ctrl + Z&lt;/code&gt;, la última acción se hace &lt;code&gt;pop()&lt;/code&gt; y se deshace. Presionas &lt;code&gt;Ctrl + Z&lt;/code&gt; otra vez y la penúltima se deshace.&lt;/p&gt;

&lt;p&gt;Mismo patrón: siempre deshaces la acción más reciente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Call stack de tu programa
&lt;/h3&gt;

&lt;p&gt;Cuando una función llama a otra, que a su vez llama a otra, tu programa va apilando cada llamada en un "call stack". Cuando una función termina, se hace &lt;code&gt;pop()&lt;/code&gt; y el control vuelve a la función anterior.&lt;/p&gt;

&lt;p&gt;Por eso cuando ves un error con "stack trace" en la consola, estás viendo exactamente eso: el estado del stack de llamadas en el momento del error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validación de paréntesis balanceados
&lt;/h3&gt;

&lt;p&gt;Un problema clásico de entrevistas. ¿Cómo verificas que en &lt;code&gt;((a + b) * (c - d))&lt;/code&gt; todos los paréntesis estén correctamente balanceados? Con un stack. Cada &lt;code&gt;(&lt;/code&gt; hace push, cada &lt;code&gt;)&lt;/code&gt; hace pop. Si al final el stack está vacío, los paréntesis están balanceados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;Los stacks son simples: cinco métodos, todos O(1), un solo principio (LIFO). Y justo por eso los encuentras en tantos lugares, desde el botón de atrás hasta el call stack de tu lenguaje de programación.&lt;/p&gt;

&lt;p&gt;Si te sirvió el artículo, dale ❤️ y sígueme para no perderte el siguiente. Nos leemos pronto. 🙌🏻&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>beginners</category>
      <category>computerscience</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Why does AI lie? Hallucinations explained simply</title>
      <dc:creator>Rohini Gaonkar</dc:creator>
      <pubDate>Fri, 08 May 2026 16:37:02 +0000</pubDate>
      <link>https://dev.to/aws/why-does-ai-lie-hallucinations-explained-simply-1c7g</link>
      <guid>https://dev.to/aws/why-does-ai-lie-hallucinations-explained-simply-1c7g</guid>
      <description>&lt;p&gt;In the &lt;a href=""&gt;previous post&lt;/a&gt;, I showed you an AI doing something genuinely useful, helping me adapt a recipe for a dinner party. We talked about the basic loop: send a prompt to a foundation model, get a response.&lt;/p&gt;

&lt;p&gt;Today we're talking about why AI lies to you.&lt;/p&gt;

&lt;p&gt;You know how AI sounds confident when it's completely wrong? It's called &lt;strong&gt;hallucination&lt;/strong&gt;, and it's the thing that'll either make you trust AI long-term, or burn you badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo: same question, two models
&lt;/h2&gt;

&lt;p&gt;I asked two different models the same question in Amazon Bedrock Playground:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What happened at the recent Lyrids meteor shower?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Model 1: Amazon Nova Micro 1.0
&lt;/h3&gt;

&lt;p&gt;Nova Micro gave me details. Dates, locations, numbers, all delivered with complete confidence. It didn't hesitate. It didn't caveat. It just answered as if it knew.&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%2Fafnndmohtedvtg66qimo.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%2Fafnndmohtedvtg66qimo.png" alt="Nova Micro confidently answering about the 2023 Lyrids meteor shower with invented details" width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it doesn't know. Its training data ends in &lt;strong&gt;2023&lt;/strong&gt;. Anything after that is a gap it can't see. It didn't flag that. It just filled the gap with something plausible.&lt;/p&gt;

&lt;p&gt;This is hallucination. The model invents something plausible to fill a gap it doesn't know how to admit. It's not lying on purpose. &lt;strong&gt;It's doing exactly what it's designed to do: predict what a useful-sounding answer looks like.&lt;/strong&gt; It has no idea whether the answer is actually &lt;em&gt;true&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model 2: Claude Haiku 4.5
&lt;/h3&gt;

&lt;p&gt;Same question, newer model, much more recent training.&lt;/p&gt;

&lt;p&gt;Haiku told me straight: "I don't have access to current information. My knowledge was last updated in April 2024." Then it offered general facts about the Lyrids and suggested I check recent astronomy websites.&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%2F3716q7c2lpgl348asyrc.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%2F3716q7c2lpgl348asyrc.png" alt="Claude Haiku 4.5 refusing to answer about recent Lyrids, stating its April 2024 knowledge cutoff" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Progress. Newer models are better at recognising the edges of what they know.&lt;/p&gt;

&lt;p&gt;I gave it a link to a Space.com article. It told me it can't browse the internet. &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%2F5pf8km499g7tbing8zde.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%2F5pf8km499g7tbing8zde.png" alt="Claude Haiku 4.5 refusing to access a URL" width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I uploaded the PDF of that website article. There are limits to how big the file size can be so I provided it first few pages only. Then it answered accurately, pulling real details from the source.&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%2Fcasl1p4vb4cp6o9owb16.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%2Fcasl1p4vb4cp6o9owb16.png" alt="Claude Haiku 4.5 accurately summarising the uploaded PDF" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, in this case, we provided some context to the model and it gave me an answer based on that context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biography test
&lt;/h2&gt;

&lt;p&gt;I asked Nova Micro:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Tell me about Rohini Gaonkar."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It didn't hesitate. It told me I'm a "well-known Indian writer, scholar, and cultural critic." That I got my PhD in Comparative Literature from Duke. That I'm a professor at the University of Minnesota. That I've edited influential anthologies on postcolonial theory.&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%2Fbd7byjw9ugpddfva0p67.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%2Fbd7byjw9ugpddfva0p67.png" alt="Nova Micro inventing an entire academic biography for Rohini Gaonkar" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of this is true. Not one detail.&lt;/p&gt;

&lt;p&gt;The model doesn't know who I am. But it knows what an academic biography &lt;em&gt;looks like&lt;/em&gt;. So it generated one. Complete with research interests, notable works, and recognition. All fabricated. All confident.&lt;/p&gt;

&lt;p&gt;So Haiku knew when to stop. Nova Micro didn't. &lt;/p&gt;

&lt;p&gt;But the underlying mechanism is the same in both models: &lt;strong&gt;prediction&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;One has better guardrails. The other just fills every gap it finds. &lt;/p&gt;

&lt;p&gt;Hallucination isn't just about training cutoffs. It's about the model filling gaps &lt;em&gt;anywhere&lt;/em&gt; in what it knows. Names it hasn't seen. Niche topics. Combinations it was never taught. Better guardrails help. They don't make the problem disappear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on the name test:&lt;/strong&gt; I used my own name on purpose. If the model invents something weird about me, the only person affected is me. Be thoughtful if you try this with other people's names, especially private ones, or anyone who hasn't agreed to be part of your experiment. Whatever the model says about them, you've just generated and potentially broadcasted it. So, be cautious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens: the architecture
&lt;/h2&gt;

&lt;p&gt;Remember the loop from the last post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input (prompt) → Foundation Model → Output (response)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model predicts what a useful answer looks like, based on everything it learned &lt;em&gt;during training&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;During training&lt;/strong&gt; is the key phrase.&lt;/p&gt;

&lt;p&gt;Training ends on a specific date, called the &lt;strong&gt;training cutoff&lt;/strong&gt;. After that, the model is frozen. When you ask it about anything past that date, or anything it never quite learned, it has two options: say "I don't know", or do the thing it's designed to do i.e. &lt;strong&gt;predict&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And for a long time, these models weren't great at saying "I don't know". That's not what they were rewarded for in training. They were rewarded for producing fluent, useful-sounding answers. So that's what they produce. Even when the answer is made up.&lt;/p&gt;

&lt;p&gt;Hallucination shows up in different flavors: fabricated facts (the biography), outdated information stated as current (the meteor shower), inconsistent reproduction even with the source right there (the quote test). There are others too, wrong attributions, sycophantic agreement (going along with something you said even when it's wrong), confident extrapolation (extending a pattern beyond where the data supports it). &lt;/p&gt;

&lt;p&gt;The mechanism is always the same, prediction filling a gap, but knowing the flavor helps you design the right mitigation. We'll get into those mitigations in later posts when we talk about grounding, evaluation, and guardrails.&lt;/p&gt;

&lt;p&gt;If you're a builder, this'll feel familiar. Think of a DNS cache. You move your app to a new server, update the DNS record, but for the next hour some users still get routed to the old IP. The cache doesn't know the record changed. It just serves what it has, confidently, because it was designed to always give you an answer fast.&lt;/p&gt;

&lt;p&gt;Or autoscaling on the wrong metric. You scale on CPU. CPU is low, so the system thinks everything's fine. Meanwhile your queue is backed up with 10,000 unprocessed messages. The system is optimized to respond to one signal, so it confidently does nothing while things pile up.&lt;/p&gt;

&lt;p&gt;An AI model works the same way. It was trained to always produce a helpful-sounding answer. So when it doesn't know something, it still produces a helpful-sounding answer. It doesn't have a "say nothing" instinct. It has a "say something useful-looking" instinct.&lt;/p&gt;

&lt;p&gt;Modern models are &lt;em&gt;much&lt;/em&gt; better at refusing. But the underlying shape of the problem doesn't go away. The model doesn't know what it knows. It just predicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  "But ChatGPT can search the web?"
&lt;/h3&gt;

&lt;p&gt;Yes, most chat tools today can look things up online. That's not the model itself doing the searching. It is a tool plugged into the model. &lt;/p&gt;

&lt;p&gt;We'll get to how that works in a later post. For today, we're looking at the model on its own. No internet, no tools. Just what it learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, and where the fix breaks
&lt;/h2&gt;

&lt;p&gt;I gave Nova Lite the actual article as a PDF and asked it to quote the second paragraph.&lt;/p&gt;

&lt;p&gt;It gave me a response. Then I asked the same thing again. Different answer. Same source, same conversation, two different versions of the same paragraph.&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%2Fry1p2ijeyzayjr886nq0.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%2Fry1p2ijeyzayjr886nq0.png" alt="Nova Lite giving two different versions of the same paragraph when asked to quote it exactly" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even with the source right there, it didn't pull the paragraph verbatim. I asked the same question twice, same conversation, same document, and got two different versions. It's not retrieving. It's still predicting what that paragraph probably looks like. And prediction isn't deterministic.&lt;/p&gt;

&lt;p&gt;This matters because a lot of people think &lt;em&gt;"just give the AI the document and it'll be fine."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;It's better but it's not perfect. Things can get complex and messy, especially for anything that depends on exact wording, like legal text, medical dosages, or contract clauses. You still need to verify the responses. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context reduces hallucination. It doesn't eliminate it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Three signs you should double-check
&lt;/h2&gt;

&lt;p&gt;If you're using AI day-to-day, here are the tells:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Specific details you can't verify.&lt;/strong&gt; Names, dates, numbers, URLs in an area you can't check. Assume 50/50.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fluency on topics that should be fuzzy.&lt;/strong&gt; Ask about something niche or recent, get a confident detailed answer, and be suspicious. Real expertise has hedges, hallucination doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Citations. Especially URLs.&lt;/strong&gt; Models invent sources that look real. If you get a URL, open it. Nine times out of ten it's fine. The tenth time it's a made-up paper.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're more on the builder side:&lt;/strong&gt; &lt;br&gt;
Remember, hallucinations aren't a bug you patch. They're a property of the system. You mitigate them with grounding (give the model real context), with instructions (tell the model to refuse when unsure), and later, with evaluation. &lt;strong&gt;Designing around them &lt;em&gt;is&lt;/em&gt; the job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're just getting started:&lt;/strong&gt; &lt;br&gt;
Remember, AI is NOT a search engine. It's a prediction engine that's really good at sounding right. Treat specific claims the way you'd treat a confident stranger at a party. Friendly, but verify before you repeat them.&lt;/p&gt;

&lt;p&gt;Some examples I found on internet, for fun and educational purposes only: (Answers may change as models are catching up)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How many 'r's are in the word strawberry?&lt;/li&gt;
&lt;li&gt;If I have to take my car to car wash, and the car wash is 100ft away. Should I drive or go walking?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Why are there so many of these things? Haiku, Sonnet, Opus. Mini, large, pro. And honestly, which one should you actually pick?&lt;/p&gt;

&lt;p&gt;That's the next post. Ride along.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is part of the "Learning AI Out Loud" series, a cloud architect learning AI from first principles.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/rohini_gaonkar" class="crayons-btn crayons-btn--primary"&gt;Follow along with the series&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built My Mom an AI Recipe Helper for Mother's Day</title>
      <dc:creator>Ifeanyi O.</dc:creator>
      <pubDate>Fri, 08 May 2026 07:53:25 +0000</pubDate>
      <link>https://dev.to/aws/i-built-my-mom-an-ai-recipe-helper-for-mothers-day-2hc5</link>
      <guid>https://dev.to/aws/i-built-my-mom-an-ai-recipe-helper-for-mothers-day-2hc5</guid>
      <description>&lt;p&gt;&lt;em&gt;A project about a fridge, Strands agents &amp;amp; trying to keep up with Mom's seasons.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Mom's Problem
&lt;/h2&gt;

&lt;p&gt;My mom has a drawer full of recipes. Yes! Not a folder.... an actual drawer. Torn pages from magazines, things written on the back of envelopes and binders that've been around longer than I have. Whenever she's looking for dinner ideas she pulls something out, sighs and puts it back.&lt;/p&gt;

&lt;p&gt;The recipe is not the issue here, that's always fine. It's that she's missing one ingredient or four!&lt;/p&gt;

&lt;p&gt;And even if she has everything, the recipe might not fit whatever season she's in at the moment because the other thing is, her eating is a moving target.&lt;/p&gt;

&lt;p&gt;One month she's a vegetarian because a friend got her into it, a few months later she's watching her sugar because the doctor mentioned it during a checkup, then it's low sodium or she's "just trying to be a little better with dinners." You just never really know which version is going to be at the stove on a given Tuesday.&lt;br&gt;
But the real question she's always asking, underneath the recipe hunt, is...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What could I make with what I already have, that fits how I'm eating right now, that I won't get bored with?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that sounds like problem for an agent! So for Mother's Day this year, I built her one and called it &lt;strong&gt;AskMom Recipes&lt;/strong&gt;. You can try it out &lt;a href="https://d12rcxds4c00cm.cloudfront.net?trk=ddc7640f-4b0f-4289-896a-4c93114ffd04&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &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%2Fm6apyp315q3uawdotk4h.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%2Fm6apyp315q3uawdotk4h.png" alt=" " width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does in 30 seconds
&lt;/h2&gt;

&lt;p&gt;You open a pink website and tell it what's in your kitchen. You can type it, or you can just take a photo of your groceries on the counter and drop it in.&lt;/p&gt;

&lt;p&gt;You also have the option to pick how you're eating this week, for eg. no restriction, vegetarian, low sodium, diabetic-friendly or gluten-free. Then hit the "&lt;strong&gt;Suggest recipes&lt;/strong&gt;" button.&lt;br&gt;
A couple seconds later, you'll get three recipes and each one tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What you already have vs. what you'd need to grab&lt;/li&gt;
&lt;li&gt;Simple, unfussy steps (the kind of thing my mom actually cooks)&lt;/li&gt;
&lt;li&gt;Why it's good for you, with real nutrition numbers from the USDA&lt;/li&gt;
&lt;li&gt;One sentence about where the dish possibly comes from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't love the recipes, you can tap "&lt;strong&gt;make it healthier&lt;/strong&gt;" or "&lt;strong&gt;something quicker&lt;/strong&gt;" or "&lt;strong&gt;fewer ingredients&lt;/strong&gt;" and it adjusts on the fly.&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%2Fxxod5fswq7pcfvwvt13m.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%2Fxxod5fswq7pcfvwvt13m.png" alt=" " width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! The rest of this blog is how I built it and the design decisions that made the difference between "&lt;strong&gt;okay, cool demo that kinda works&lt;/strong&gt;" and "&lt;strong&gt;something my mom would actually use.&lt;/strong&gt;"&lt;/p&gt;
&lt;h2&gt;
  
  
  Why an agent and not a prompt
&lt;/h2&gt;

&lt;p&gt;An easy version of this would just be to throw the ingredients into a single prompt, ask for three recipes, done. I tried it, it's fine but there's problems that show up the moment you try to use it for real.&lt;br&gt;
First, the model will happily make up nutrition numbers. Ask it how much protein is in chicken and it'll give you a number, confidently and you'll have no idea if it's right.&lt;/p&gt;

&lt;p&gt;Second, it'll cheerfully tell you that tomatoes come from Italy or that pasta is a Chinese invention. Third, if you want to say "&lt;strong&gt;actually, make that healthier&lt;/strong&gt;" the prompt has to start over from scratch because there's no memory.&lt;/p&gt;

&lt;p&gt;An agent solves two of those three. As for the made up facts we needed something else which we'll get.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;, which is a refreshingly simple Python SDK for building agents on Amazon Bedrock. You define your tools as functions, write a system prompt and Strands handles the loop of "&lt;strong&gt;model decides what tool to call, tool runs, result goes back to model, repeat.&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;It's the first agent framework I've used that didn't make me want to throw my laptop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BEDROCK_MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic.claude-3-haiku-20240307-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_REGION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.4&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="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;extract_ingredients_from_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;extract_ingredients_from_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;suggest_recipes&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;With Strands, setting up the agent is almost anticlimactic. There's a model, a prompt and a list of tools. That's the whole thing.&lt;/p&gt;

&lt;p&gt;I wanted to flag this, because it took me a few iterations to get right, the tools I gave the agent are not "&lt;strong&gt;one tool for everything.&lt;/strong&gt;" They're small, single-purpose and named like verbs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools and why there aren't very many
&lt;/h2&gt;

&lt;p&gt;Everyone always goes tool crazy, when they first build an agent. Each tool feels free so why not just add them all, right? Wrong!&lt;/p&gt;

&lt;p&gt;The problem is every tool call is another round trip through the model. The model says "&lt;strong&gt;I'd like to call X,&lt;/strong&gt;" you run X, hand the result back, it thinks for a second and says "&lt;strong&gt;now I'd like to call Y.&lt;/strong&gt;" Each hop is a few seconds but when you add six tool calls, then your recipe helper takes forty seconds and your API times out.&lt;/p&gt;

&lt;p&gt;So I decided to split the work in two:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things the agent handles&lt;/strong&gt; (because they genuinely need a model's judgment):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading a photo and identifying what's in it&lt;/li&gt;
&lt;li&gt;Normalizing messy text like "some chicken, rice, maybe garlic" into a clean list&lt;/li&gt;
&lt;li&gt;Generating three healthy recipes that fit the user's preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Things Python handles&lt;/strong&gt; (because they just need a lookup):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Looking up real nutrition numbers&lt;/li&gt;
&lt;li&gt;Formatting a recipe card for the frontend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since Python can fetch faster than the model can ask, why would I let a language model guess when there's a real source? USDA has an API with the actual nutrition numbers.&lt;/p&gt;

&lt;p&gt;This one design decision cut request latency from ~34 seconds to ~17 seconds. You get the same quality in half the time with way less money spent on Bedrock tokens and it made the agent more reliable with fewer retries and self corrections that eat tokens for no reason.&lt;/p&gt;

&lt;p&gt;The lesson is to use the LLM for the things that need reasoning and use code for the things that need answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preference handling
&lt;/h2&gt;

&lt;p&gt;Alright, back to my mom for a second. The seasons thing, vegetarian one month and low sodium the next taught me more about preference handling that I didn't expect.&lt;/p&gt;

&lt;p&gt;The naive approach is a freeform text field that ask you to type your dietary preferences. The problem is the model has to figure out what "&lt;strong&gt;watching my sugar a bit&lt;/strong&gt;" means versus "&lt;strong&gt;diabetic-friendly&lt;/strong&gt;" versus "low-carb." Sometimes it guesses wrong and sometimes the recipes it produces have way too much added.&lt;/p&gt;

&lt;p&gt;So I made it a dropdown. Five options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No restriction&lt;/li&gt;
&lt;li&gt;Vegetarian&lt;/li&gt;
&lt;li&gt;Low sodium&lt;/li&gt;
&lt;li&gt;Diabetic-friendly&lt;/li&gt;
&lt;li&gt;Gluten-free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one maps to a crisp, one sentence guidance block that gets injected into the recipe prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_PREFERENCE_GUIDANCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No dietary restrictions. Focus on healthy, balanced meals.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vegetarian&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Strictly vegetarian: no meat, poultry, or fish.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low_sodium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Low sodium: minimize salt and high-sodium ingredients like soy sauce or canned broths.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diabetic_friendly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Diabetic-friendly: low glycemic index, limit added sugars and refined carbs.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gluten_free&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Strictly gluten-free: no wheat, barley, rye, or standard soy sauce.&lt;/span&gt;&lt;span class="sh"&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;It's five lines of Python. &lt;/p&gt;

&lt;p&gt;A dropdown sounds less impressive than "&lt;strong&gt;the AI understands your preferences,&lt;/strong&gt;" but the dropdown is more reliable and trust me, reliable is what my mom needs.&lt;/p&gt;

&lt;p&gt;When it comes to agent patterns, the more you can constrain the inputs, the better the outputs. Freeform is cool in demos but more structure is better in real life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "send a photo of your groceries" part
&lt;/h2&gt;

&lt;p&gt;I wanted to keep the typed-ingredients path simple, but the photo path is where it gets fun. You take a picture of whatever's on your counter and the agent reads the photo, identifies the food and treats it the same as if you'd typed it in.&lt;/p&gt;

&lt;p&gt;Under the hood this is one of Strands' best party tricks. Claude 3 Haiku on Bedrock handles vision natively, so the whole thing is one tool call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_ingredients_from_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Look at a photo of groceries and return the ingredients visible in it.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPLOADS_BUCKET_NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_REGION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 1. Fetch image bytes from S3
&lt;/span&gt;    &lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s3_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;media_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ContentType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Ask Bedrock Claude Haiku what's in the photo
&lt;/span&gt;    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;List only the food ingredients you can clearly see in this photo. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return a JSON array of short lowercase names, nothing else. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Example: [&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tomato&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;onion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chicken breast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If you can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t identify any food, return [].&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bedrock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-runtime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BEDROCK_MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-2023-05-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;standard_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&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="c1"&gt;# 3. Pull the JSON array back out (defensively)
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\[.*?\]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DOTALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt is boring on purpose. All is says is "&lt;strong&gt;List only the food ingredients you can clearly see in this photo. Return a JSON array of short lowercase names, nothing else.&lt;/strong&gt;" I did not give it a personality or tell it to "&lt;strong&gt;please think carefully&lt;/strong&gt;". I also didn't give it examples of how the model should "&lt;strong&gt;reason.&lt;/strong&gt;" I just told it what I want and what format I want it in.&lt;/p&gt;

&lt;p&gt;The model is very good at ambitious prompts and very good at literal ones. Ambitious prompts return ambitious, chatty and often wrong answers but literal prompts more closely return what you asked for. For this tool, where the output feeds directly into the next step, literal wins every time.&lt;/p&gt;

&lt;p&gt;Also, note that the photo gets uploaded straight from the browser to S3 via a pre-signed URL, so my Lambda never has to deal with multipart file uploads. The agent just gets an S3 key and fetches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Here's the whole thing, in the order a request flows:&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%2Fsz0yeovkw1qrk97nvtbl.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%2Fsz0yeovkw1qrk97nvtbl.png" alt=" " width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser loads static pink site from S3, served through CloudFront.&lt;/li&gt;
&lt;li&gt;User submits text and/or photo. If photo, goes straight to S3 as pre-signed URL.&lt;/li&gt;
&lt;li&gt;Browser calls &lt;code&gt;POST /ingredients&lt;/code&gt; on API Gateway, which hits a Lambda.&lt;/li&gt;
&lt;li&gt;Lambda builds a Strands agent with three tools, runs the agent loop.&lt;/li&gt;
&lt;li&gt;Agent extracts ingredients (text + vision), then calls &lt;code&gt;suggest_recipes&lt;/code&gt; once.&lt;/li&gt;
&lt;li&gt;Lambda enriches each recipe in Python, origin lookup, USDA lookup and format.&lt;/li&gt;
&lt;li&gt;Session is saved to DynamoDB with a 24-hour TTL for the "refine" follow up.&lt;/li&gt;
&lt;li&gt;Recipes come back to the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing is deployed with AWS CDK in Python, one stack, one &lt;code&gt;make deploy&lt;/code&gt;. No Docker needed, no hand-wiring and definitely no clicking around in the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs me to keep it running
&lt;/h2&gt;

&lt;p&gt;For my mom's personal deployment, answering a few requests a week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock&lt;/strong&gt;: Tenth of a penny per recipe request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt;: Free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: Free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 + CloudFront&lt;/strong&gt;: Couple cents a month for the static site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USDA API&lt;/strong&gt;: Free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I pay less for AskMom per month than for a single coffee. If I published it and a hundred people used it regularly, I'd still be under a few dollars a month!&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;If you want to build one for your mom (or your dad, or anyone whose fridge is a mystery), the whole thing is open source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/rextheking/askmom-recipes-sample-for-aws-strands" rel="noopener noreferrer"&gt;github.com/rextheking/askmom-recipes-sample-for-aws-strands&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The README walks through the deploy in six steps. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account with Bedrock access enabled for Claude 3 Haiku in &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Python 3.12, Node 20/22/24, AWS CDK CLI and the AWS CLI&lt;/li&gt;
&lt;li&gt;A free USDA FoodData Central API key (takes 30 seconds to get)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it's:&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;infra
make &lt;span class="nb"&gt;install
&lt;/span&gt;make bootstrap     &lt;span class="c"&gt;# one time per AWS account&lt;/span&gt;
make deploy        &lt;span class="c"&gt;# ~5-7 minutes, most of it CloudFront&lt;/span&gt;
&lt;span class="c"&gt;# paste the ApiUrl into web/config.js&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../web
make deploy
&lt;span class="c"&gt;# open the CloudFront URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note, the first deploy takes a few minutes because CloudFront is slow to propagate. If you're just kicking the tires, there's a &lt;code&gt;-c with_cloudfront=false&lt;/code&gt; flag that skips CloudFront and gets you a working API in about 90 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hug
&lt;/h2&gt;

&lt;p&gt;If you build something for your mom this year, I'd love to see it. Tag me or share the repo of whatever weird thing you shipped.&lt;/p&gt;

&lt;p&gt;Happy Mother's Day 💖&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%2Fmbox7zkb7p2i5ekulh7p.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%2Fmbox7zkb7p2i5ekulh7p.png" alt=" " width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; for making agent code actually pleasant to write&lt;/li&gt;
&lt;li&gt;Amazon Bedrock + Claude 3 Haiku for the model&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fdc.nal.usda.gov/api-guide.html" rel="noopener noreferrer"&gt;USDA FoodData Central&lt;/a&gt; for free, honest nutrition data&lt;/li&gt;
&lt;li&gt;My mom, for the original problem&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>agents</category>
      <category>mothersday</category>
    </item>
    <item>
      <title>Build your own daily notification bot on AWS Free Tier</title>
      <dc:creator>Esin Saribudak</dc:creator>
      <pubDate>Fri, 08 May 2026 02:04:13 +0000</pubDate>
      <link>https://dev.to/aws/build-your-own-daily-notification-bot-on-aws-free-tier-kbk</link>
      <guid>https://dev.to/aws/build-your-own-daily-notification-bot-on-aws-free-tier-kbk</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;A Lambda function checks the forecast, Bedrock tells you what to wear, and ntfy.sh pushes it to your phone before you get ready for the day, and it's all Free Tier eligible.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last updated: May 7, 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every morning before work, you open your weather app. It takes a few seconds to refresh. Then you scroll past the hourly breakdown, the radar map, the air quality index, the pollen count. All you need to know is: what's the high, what's the low, is it going to rain, and what should you wear?&lt;/p&gt;

&lt;p&gt;If you're leaving your home for the office every day like I am, this time spent scrolling and thinking every morning adds up. Especially where I live, where it might be 55°F when I leave the house and 88°F by lunch. You need a jacket at 7 AM and regret wearing it by noon. The weather app gives you all the data but none of the interpretation.&lt;/p&gt;

&lt;p&gt;This tutorial walks you through building a service that sends you a push notification every morning with the forecast and a clothing recommendation. You deploy it once, and it runs on a schedule. Instead of opening an app and parsing data yourself, the notification just shows up with what you need. It looks like this on your phone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Austin, TX — Tuesday, May 6
Today: 88°F, Partly Sunny
Tonight: 73°F, Mostly Cloudy
Wind: 10 mph S | Rain: 20%

Light layers today. Short sleeves with a light
jacket for the morning, ditch it by lunch.
No umbrella needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Along the way, you'll wire together EventBridge, Lambda, and Bedrock into an app that streamlines a daily task. By the end, you'll have a working notification on your phone and hands-on experience with the same infrastructure that powers production applications. All the AWS services it uses are eligible for the &lt;a href="https://aws.amazon.com/free/?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Free Tier&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you followed the &lt;a href="https://dev.to/aws/build-your-own-blog-post-view-counter-on-aws-free-tier-306f"&gt;blog post view counter tutorial&lt;/a&gt;, this is a good next project. Same tools, different services, and you end up with something you'll use every day. If you haven't built that one, that's fine too — the prerequisites section covers what you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're building
&lt;/h2&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%2Fcroj9ogeycxsrz1iaqde.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%2Fcroj9ogeycxsrz1iaqde.png" alt="Architecture diagram showing EventBridge triggering Lambda, which calls NWS API, Bedrock, and ntfy.sh" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the application flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;EventBridge triggers a Lambda function every morning on a cron schedule&lt;/li&gt;
&lt;li&gt;Lambda calls the National Weather Service API with your coordinates to get the local forecast&lt;/li&gt;
&lt;li&gt;Lambda sends that weather data to Amazon Bedrock (Nova Lite) and asks for a clothing recommendation&lt;/li&gt;
&lt;li&gt;Lambda formats the forecast + recommendation into a short message&lt;/li&gt;
&lt;li&gt;Lambda sends the message as a push notification to your phone via ntfy.sh, an open source pub/sub service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three AWS services, two free external APIs, about 230 lines of TypeScript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Outside the US?&lt;/strong&gt; The NWS API only covers US locations. If you're elsewhere, swap in &lt;a href="https://open-meteo.com/" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; as a drop-in replacement. It's free for non-commercial use, requires no API key, and covers the entire world.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://aws.amazon.com/free/?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt;. If you don't have one yet, the &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Creating an AWS account guide&lt;/a&gt; walks you through it. You'll need a credit card on file, but this project stays within Free Tier limits.&lt;/li&gt;
&lt;li&gt;Node.js 24 or later&lt;/li&gt;
&lt;li&gt;AWS CLI installed and configured&lt;/li&gt;
&lt;li&gt;AWS CDK bootstrapped in your account. If you haven't used CDK before, it's an infrastructure-as-code tool. You write TypeScript that describes your AWS resources, and CDK turns it into CloudFormation and deploys it. The bootstrap command creates a staging bucket CDK needs to upload your code, and needs to be run the first time you use it.
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ntfy app installed on your phone.&lt;/strong&gt; Download &lt;a href="https://ntfy.sh" rel="noopener noreferrer"&gt;ntfy&lt;/a&gt; from the App Store or Google Play. Open it and add a topic name you'll remember (this acts as your private channel, so pick something not easily guessable, like &lt;code&gt;weather-forecast-abc123&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&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%2Fdw52j54u6k9q1aj2bifh.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%2Fdw52j54u6k9q1aj2bifh.png" alt="ntfy mobile app Add subscription screen with a topic name entered in the text field" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is ntfy.sh and why use it for notifications
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ntfy.sh" rel="noopener noreferrer"&gt;ntfy.sh&lt;/a&gt; (pronounced "notify") is an open-source push notification service built on a simple pub/sub model. You subscribe to a topic in the app, and any HTTP POST to that topic's URL shows up as a push notification on your phone. No account required, no API key, no app store approval process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;Create a new directory and initialize the project:&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;mkdir &lt;/span&gt;daily-notification-bot &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;daily-notification-bot
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;aws-cdk-lib constructs @aws-sdk/client-bedrock-runtime
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; aws-cdk tsx typescript @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;cdk.json&lt;/code&gt; file in the project root:&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;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx tsx cdk/app.ts"&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;And a &lt;code&gt;tsconfig.json&lt;/code&gt;:&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;"compilerOptions"&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;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&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="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&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="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"include"&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="s2"&gt;"lambda/**/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cdk/**/*"&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;Your project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daily-notification-bot/
├── cdk/
│   ├── app.ts          # CDK entry point
│   └── stack.ts        # Infrastructure definition
├── lambda/
│   └── index.ts        # Lambda function code
├── cdk.json
├── package.json
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Define the infrastructure with CDK
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;cdk/stack.ts&lt;/code&gt;. This defines all the infrastructure:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&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-cdk-lib&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;events&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-cdk-lib/aws-events&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;targets&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-cdk-lib/aws-events-targets&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;iam&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-cdk-lib/aws-iam&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;NodejsFunction&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-cdk-lib/aws-lambda-nodejs&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;Runtime&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-cdk-lib/aws-lambda&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&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;fileURLToPath&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;url&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;Construct&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;constructs&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;__dirname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fileURLToPath&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;class&lt;/span&gt; &lt;span class="nc"&gt;MorningForecastStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Configuration — pass these at deploy time:&lt;/span&gt;
    &lt;span class="c1"&gt;//   npx cdk deploy -c latitude=30.27 -c longitude=-97.74 -c ntfyTopic=my-secret-weather-topic&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryGetContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latitude&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;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryGetContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;longitude&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;ntfyTopic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryGetContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ntfyTopic&lt;/span&gt;&lt;span class="dl"&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;latitude&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ntfyTopic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing required context. Deploy with: npx cdk deploy &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-c latitude=30.27 -c longitude=-97.74 -c ntfyTopic=my-secret-weather-topic&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="c1"&gt;// Lambda function — fetches weather, calls Bedrock, sends notification via ntfy&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&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;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ForecastFunction&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="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_24_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda/index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;LATITUDE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LONGITUDE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;NTFY_TOPIC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ntfyTopic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Grant Lambda permission to invoke Bedrock&lt;/span&gt;
    &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&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;bedrock:InvokeModel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&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;arn:aws:bedrock:*::foundation-model/amazon.nova-lite-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;span class="c1"&gt;// EventBridge rule — triggers every morning at 6:30 AM CT (11:30 UTC)&lt;/span&gt;
    &lt;span class="c1"&gt;// Adjust the cron to match your timezone and preferred time&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MorningSchedule&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="na"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// 11:30 UTC = 6:30 AM CT&lt;/span&gt;
        &lt;span class="na"&gt;weekDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MON-FRI&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="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Output the function name for manual testing&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FunctionName&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionName&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;Lambda function name (for manual invocation)&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;h3&gt;
  
  
  How to schedule a Lambda function with EventBridge
&lt;/h3&gt;

&lt;p&gt;The EventBridge rule above is what makes this run every morning. A few things to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration via context.&lt;/strong&gt; Your coordinates and ntfy topic are passed at deploy time with &lt;code&gt;-c&lt;/code&gt; flags. This keeps configuration out of your source code and makes the project easy to share. To find your coordinates, right-click any location in Google Maps and the lat/long will appear at the top of the context menu.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notifications go through ntfy.sh&lt;/strong&gt;, which is just an HTTP POST from Lambda. There are no AWS messaging services to configure, no phone number verification steps, and no sandbox restrictions to deal with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekdays only.&lt;/strong&gt; The cron schedule runs Monday through Friday. If you want weekends too, change &lt;code&gt;weekDay: 'MON-FRI'&lt;/code&gt; to remove that constraint, or set it to &lt;code&gt;'*'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UTC time.&lt;/strong&gt; EventBridge cron expressions use UTC. Central Time is UTC-5 (or UTC-6 during standard time), so 6:30 AM CT is 11:30 UTC during CDT. You'll need to adjust this for your timezone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30-second timeout.&lt;/strong&gt; The Lambda makes three external calls (NWS points, NWS forecast, and Bedrock), so we give it more time than the default 3 seconds. In practice it finishes in 2-4 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now create the entry point at &lt;code&gt;cdk/app.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="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&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-cdk-lib&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;MorningForecastStack&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;./stack.js&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;App&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;MorningForecastStack&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MorningForecastNotificationApp&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;h2&gt;
  
  
  Step 2: Fetch the forecast and generate a notification
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;lambda/index.ts&lt;/code&gt;. This is the code that runs every morning:&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;InvokeModelCommand&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="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bedrock&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LATITUDE&lt;/span&gt; &lt;span class="o"&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;LATITUDE&lt;/span&gt;&lt;span class="o"&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;LONGITUDE&lt;/span&gt; &lt;span class="o"&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;LONGITUDE&lt;/span&gt;&lt;span class="o"&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;NTFY_TOPIC&lt;/span&gt; &lt;span class="o"&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;NTFY_TOPIC&lt;/span&gt;&lt;span class="o"&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;NWS_USER_AGENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily-notification-bot (github.com/your-repo)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Type definitions&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NwsPoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;forecastUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ForecastPeriod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;windSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;windDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;probabilityOfPrecipitation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isDaytime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WeatherData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ForecastPeriod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ForecastPeriod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Step 1: Get the NWS grid point for our coordinates&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;getNwsPoint&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;NwsPoint&lt;/span&gt;&lt;span class="o"&gt;&amp;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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.weather.gov/points/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LATITUDE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LONGITUDE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NWS_USER_AGENT&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="o"&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;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`NWS points API error: &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;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;forecastUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relativeLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relativeLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&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="c1"&gt;// Step 2: Get the forecast from the NWS grid point&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;getForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NwsPoint&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;WeatherData&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forecastUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NWS_USER_AGENT&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="o"&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;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`NWS forecast API error: &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;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;periods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;periods&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;daytime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;p&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDaytime&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;nighttime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;p&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDaytime&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;toPeriod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;p&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="nx"&gt;ForecastPeriod&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;windSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windSpeed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;windDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windDirection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;probabilityOfPrecipitation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probabilityOfPrecipitation&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isDaytime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDaytime&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toPeriod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toPeriod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nighttime&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="c1"&gt;// Step 3: Ask Bedrock for a clothing recommendation&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;getClothingAdvice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WeatherData&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="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Today's forecast for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:
- Daytime (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
- Wind: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windSpeed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windDirection&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
- Rain chance: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probabilityOfPrecipitation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%
- Tonight: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

What should I wear today? Give me a practical recommendation in one to two sentences. Be direct and specific. No preamble.`&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="nx"&gt;prompt&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;inferenceConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&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;InvokeModelCommand&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;amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&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;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;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;bedrock&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="nx"&gt;command&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&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;body&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;result&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Step 4: Format the notification message&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WeatherData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;advice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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;today&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;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&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="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/Chicago&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Adjust to your timezone&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Tonight: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nighttime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortForecast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Wind: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windSpeed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;windDirection&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | Rain: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daytime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probabilityOfPrecipitation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;advice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="c1"&gt;// Step 5: Send push notification via ntfy.sh&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;sendNotification&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="kr"&gt;string&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="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://ntfy.sh/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;NTFY_TOPIC&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Title&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;Morning Forecast&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="na"&gt;body&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="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ntfy.sh error: &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;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="c1"&gt;// Main handler — EventBridge triggers this every morning&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;point&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;getNwsPoint&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;weather&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;getForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&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;advice&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;getClothingAdvice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weather&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;advice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendNotification&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Forecast sent:&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function does four things in sequence:&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling the NWS weather API from Lambda
&lt;/h3&gt;

&lt;p&gt;The NWS API works in two steps. First you send your coordinates to &lt;code&gt;/points/{lat},{lon}&lt;/code&gt;, which returns metadata about your location including the forecast URL and your city/state name. The NWS requires a &lt;code&gt;User-Agent&lt;/code&gt; header for identification, but no API key.&lt;/p&gt;

&lt;p&gt;The second call hits the forecast URL from step one. It returns forecast periods (today, tonight, tomorrow, etc.) with temperature, wind, precipitation probability, and a plain-English summary like "Partly Cloudy" or "Chance Showers."&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Bedrock to generate clothing advice
&lt;/h3&gt;

&lt;p&gt;We send the weather data to Amazon Nova Lite with a prompt that asks for practical clothing advice. The &lt;code&gt;maxTokens: 80&lt;/code&gt; keeps the response tight — one to two sentences, no filler. Nova Lite is Amazon's cheapest model and responds in under a second.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending push notifications with ntfy.sh
&lt;/h3&gt;

&lt;p&gt;The city name, forecast summary, and clothing advice get combined into a notification message and sent to ntfy.sh with a single HTTP POST. The ntfy app on your phone picks it up instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Deploy and test your notification
&lt;/h2&gt;

&lt;p&gt;Deploy with your coordinates and ntfy topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cdk deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;latitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_LATITUDE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;longitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_LONGITUDE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;ntfyTopic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_NTFY_TOPIC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the values with your own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latitude and longitude&lt;/strong&gt; of your location. Right-click any spot in &lt;a href="https://www.google.com/maps" rel="noopener noreferrer"&gt;Google Maps&lt;/a&gt; and the coordinates appear at the top of the menu. For example, Austin, TX is &lt;code&gt;30.27,-97.74&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ntfy topic&lt;/strong&gt; — the same topic name you subscribed to in the ntfy app. This is like a private channel, so pick something not easily guessable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CDK will show you the resources it's creating and ask for confirmation. Type &lt;code&gt;y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;CDK asks because the stack creates IAM permissions (Lambda's execution role, Bedrock invoke access). This is standard for any stack that includes a Lambda function. Review the permissions if you want, then approve.&lt;/p&gt;

&lt;p&gt;After about two minutes, you'll see output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✅  MorningForecastNotificationApp

✨  Deployment time: 107.44s

Outputs:
MorningForecastNotificationApp.FunctionName = MorningForecastNotificationApp-ForecastFunctionBE622553-a1B2c3D4e5Fg

Stack ARN:
arn:aws:cloudformation:us-east-1:123456789012:stack/MorningForecastNotificationApp/67f1ff60-1234-5678-abcd-0affd8af3937

✨  Total time: 110s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't wait until tomorrow morning to test it. Invoke the function manually using the function name from the deploy output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; MorningForecastNotificationApp-ForecastFunctionBE622553-a1B2c3D4e5Fg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  output.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your phone. You should have a push notification from the ntfy app with today's forecast and a clothing recommendation. 😎&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%2Ftr7kogdgnxw64d4ny4fa.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%2Ftr7kogdgnxw64d4ny4fa.png" alt="ntfy app showingmorning forecast notifications with weather data and clothing recommendations" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Customize the schedule, location, and AI prompt
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Change your location
&lt;/h3&gt;

&lt;p&gt;Update the coordinates in your deploy command. Right-click any location in Google Maps to get the&lt;br&gt;
lat/long:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cdk deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;latitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_LATITUDE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;longitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_LONGITUDE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;ntfyTopic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_NTFY_TOPIC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to change the EventBridge cron schedule
&lt;/h3&gt;

&lt;p&gt;The cron expression in &lt;code&gt;stack.ts&lt;/code&gt; controls when you get your notification. A few examples:&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="c1"&gt;// Every day at 7:00 AM ET (12:00 UTC during EDT)&lt;/span&gt;
&lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Weekdays at 6:00 AM CT (11:00 UTC during CDT)&lt;/span&gt;
&lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weekDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MON-FRI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Every day at 6:30 AM PT (13:30 UTC during PDT)&lt;/span&gt;
&lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;13&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;Remember: EventBridge uses UTC. Convert your local time accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tweak the AI prompt
&lt;/h3&gt;

&lt;p&gt;The Bedrock prompt in &lt;code&gt;getClothingAdvice()&lt;/code&gt; is where you make this yours. The default asks for one to two direct sentences. Some ways to customize:&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="c1"&gt;// If you bike to work:&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Assume I'm biking 3 miles to the office.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// If you run hot:&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I tend to run warm, so err on the side of lighter clothing.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// If you want outfit specifics:&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Suggest specific items: jacket type, shirt type, pants vs shorts, shoe type.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// If you want even less:&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer in one sentence only.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt is just a string. Change it, redeploy, and your morning notification changes with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the full flow works
&lt;/h2&gt;

&lt;p&gt;Now that you've seen all the code, here's the complete flow, complete with approximate timestamps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;6:30 AM&lt;/strong&gt; — EventBridge fires a scheduled event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6:30:01&lt;/strong&gt; — Lambda calls the NWS API with your coordinates, gets the forecast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6:30:02&lt;/strong&gt; — Lambda sends the weather data to Bedrock, gets back clothing advice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6:30:03&lt;/strong&gt; — Lambda formats the message and POSTs it to ntfy.sh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6:30:04&lt;/strong&gt; — Your phone buzzes with a push notification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total execution time: 2-4 seconds. Total cost: covered by Free Tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much this costs on AWS Free Tier
&lt;/h2&gt;

&lt;p&gt;This project uses services eligible for the &lt;a href="https://aws.amazon.com/free/?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Free Tier&lt;/a&gt;. Here's what each one costs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Free Tier allowance&lt;/th&gt;
&lt;th&gt;Your usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EventBridge&lt;/td&gt;
&lt;td&gt;14M scheduled invocations/month&lt;/td&gt;
&lt;td&gt;~30/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda&lt;/td&gt;
&lt;td&gt;1M requests/month, 400K GB-seconds&lt;/td&gt;
&lt;td&gt;~30 invocations/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock (Nova Lite)&lt;/td&gt;
&lt;td&gt;Free Tier credits&lt;/td&gt;
&lt;td&gt;~$0.001/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ntfy.sh&lt;/td&gt;
&lt;td&gt;Free (open source)&lt;/td&gt;
&lt;td&gt;~30 notifications/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NWS API&lt;/td&gt;
&lt;td&gt;Free (US government data)&lt;/td&gt;
&lt;td&gt;~60 calls/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;EventBridge&lt;/strong&gt; — 14 million scheduled invocations per month free. You're using around 30.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda&lt;/strong&gt; — You'll probably use around 30 invocations per month, each running for 2-4 seconds at 256 MB. That's well under the 1 million requests and 400,000 GB-seconds you get free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bedrock&lt;/strong&gt; — Nova Lite costs fractions of a cent per invocation at this token volume. At 30 calls per month, it comes out of your Free Tier credits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ntfy.sh&lt;/strong&gt; — free, and doesn't require an account or API key for personal use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NWS API&lt;/strong&gt; — free US government public data, no key required.&lt;/p&gt;

&lt;p&gt;I'd still recommend &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/monitor_estimated_charges_with_cloudwatch.html?trk=5ccc714b-b822-49c5-920c-aaaeda832ce7&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;setting up a billing alarm&lt;/a&gt; at $5 as a safety net.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you just learned
&lt;/h2&gt;

&lt;p&gt;If you followed along, you now have hands-on experience with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge&lt;/strong&gt;: cron scheduling in UTC, weekday-only rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt;: calling external APIs, environment variables, working with the Bedrock SDK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock&lt;/strong&gt;: invoking a foundation model, prompt design, controlling output length&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDK&lt;/strong&gt;: defining infrastructure in TypeScript, passing configuration via context, deploying with a single command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NWS API&lt;/strong&gt;: the two-step points → forecast pattern for US weather data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ntfy.sh&lt;/strong&gt;: push notifications via a simple HTTP POST&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you have something running that you'll actually use every morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on security
&lt;/h2&gt;

&lt;p&gt;This project has a small attack surface. The Lambda has no public URL and can only be triggered by EventBridge within your account. There's no user input, no stored data, and the IAM permissions are scoped to a single Bedrock model.&lt;/p&gt;

&lt;p&gt;The one thing to be aware of: your ntfy topic name is the only access control on your notifications. Anyone who knows the topic name can subscribe and read your messages. Since the content is just weather data, the risk is low, but pick a topic name that isn't easily guessable. If you want tighter control, ntfy supports token-based auth on their paid plan, or you can self-host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;If you want to tear everything down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cdk destroy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;latitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_LATITUDE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;longitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_LONGITUDE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;ntfyTopic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_NTFY_TOPIC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deletes the Lambda, the EventBridge rule, and the IAM role. Your morning notifications will stop. Unsubscribe from the topic in the ntfy app if you want to clean that up too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to try next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add a weekend mode with a later send time and more casual tone&lt;/li&gt;
&lt;li&gt;Send a mid-day alert if rain probability spikes above 70%&lt;/li&gt;
&lt;li&gt;Share the topic with your household so everyone gets the notification&lt;/li&gt;
&lt;li&gt;Add a weekly summary on Sunday evenings for the week ahead&lt;/li&gt;
&lt;li&gt;Swap the NWS API for &lt;a href="https://open-meteo.com/" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; if you're outside the US (free for non-commercial use, covers the entire world)&lt;/li&gt;
&lt;li&gt;Replace the weather API with something else you care about: stock prices, package tracking, your team's on-call schedule, a daily quote. The architecture will likely be similar regardless of what data you're fetching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you built the &lt;a href="https://dev.to/aws/build-your-own-blog-post-view-counter-on-aws-free-tier-306f"&gt;blog post view counter&lt;/a&gt;, you now have two working projects on AWS Free Tier: one that reacts to events (page views) and one that runs on a schedule (morning forecast). Between the two, you've used Lambda, DynamoDB, API Gateway, EventBridge, and Bedrock.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/esin87/daily-notification-bot-sample-for-aws" rel="noopener noreferrer"&gt;source code for this project&lt;/a&gt; is on GitHub if you want to fork it and make it your own.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>lambda</category>
      <category>eventbridge</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Embed These Websites In Your Blog Today</title>
      <dc:creator>Sean Boult</dc:creator>
      <pubDate>Thu, 07 May 2026 18:34:41 +0000</pubDate>
      <link>https://dev.to/aws/embed-these-websites-in-your-blog-today-286p</link>
      <guid>https://dev.to/aws/embed-these-websites-in-your-blog-today-286p</guid>
      <description>&lt;p&gt;I stumbled onto the DEV &lt;a href="https://dev.to/p/editor_guide#supported-url-embeds-list"&gt;supported URL embeds list&lt;/a&gt;, which revealed some interesting supported platforms.&lt;/p&gt;

&lt;p&gt;You'll want to refer to the link above to figure out the syntax for the DEV specific ones but most are just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;embed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;url&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you would like to jump around.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asciinema&lt;/li&gt;
&lt;li&gt;StackBlitz&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;X/Twitter&lt;/li&gt;
&lt;li&gt;YouTube&lt;/li&gt;
&lt;li&gt;DEV specific&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://asciinema.org" rel="noopener noreferrer"&gt;asciinema&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A video-like player which is so cool because you can actually pause it and copy the text from it.&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a href="https://stackblitz.com" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Interactive code editor right in a blog post 😍. Seems like the embedded terminal and preview will fail due to some &lt;code&gt;postMessage&lt;/code&gt; shenanigans, but perhaps it can be fixed.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/vitejs-vite-48u9w2no?file=src%2FApp.tsx&amp;amp;view=editor" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;It supports rendering the README to give some brief context with a CTA to open the repo.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/strands-agents" rel="noopener noreferrer"&gt;
        strands-agents
      &lt;/a&gt; / &lt;a href="https://github.com/strands-agents/sdk-typescript" rel="noopener noreferrer"&gt;
        sdk-typescript
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A model-driven approach to building AI agents in just a few lines of code. 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
  &lt;div&gt;
    &lt;a href="https://strandsagents.com" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/1cf2d94f5ad881d696cc58b3ffad81acf923846f6c5132f56d6a355ebbb9d6a5/68747470733a2f2f737472616e64736167656e74732e636f6d2f6c61746573742f6173736574732f6c6f676f2d6769746875622e737667" alt="Strands Agents" width="55px" height="105px"&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
    Strands Agents - TypeScript SDK
  &lt;/h1&gt;
&lt;/div&gt;
  &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;
    A model-driven approach to building AI agents in TypeScript/JavaScript
  &lt;/h2&gt;
&lt;/div&gt;
  &lt;div&gt;
    &lt;a href="https://github.com/strands-agents/sdk-typescript/graphs/commit-activity" rel="noopener noreferrer"&gt;&lt;img alt="GitHub commit activity" src="https://camo.githubusercontent.com/8821bd0b3888f85c4a98c53352d041c4f739503a337ee8c8a4875cd3188b998f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f6d2f737472616e64732d6167656e74732f73646b2d74797065736372697074"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/strands-agents/sdk-typescript/issues" rel="noopener noreferrer"&gt;&lt;img alt="GitHub open issues" src="https://camo.githubusercontent.com/cda88797d49d9b5fbdc6adf4b2653ab119f098575d4cd890075e2ede90d39a76/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f737472616e64732d6167656e74732f73646b2d74797065736372697074"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/strands-agents/sdk-typescript/pulls" rel="noopener noreferrer"&gt;&lt;img alt="GitHub open pull requests" src="https://camo.githubusercontent.com/b97af830b461ced88f0c8d3a1131e8e97ef484357fc50cc9207bb80b40661893/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d70722f737472616e64732d6167656e74732f73646b2d74797065736372697074"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/strands-agents/sdk-typescript/blob/main/LICENSE.APACHE" rel="noopener noreferrer"&gt;&lt;img alt="License" src="https://camo.githubusercontent.com/0c123f3b067b77f5b9daffb5702a6465f7b31a8592c15b115f0ff751dfb21300/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f737472616e64732d6167656e74732f73646b2d74797065736372697074"&gt;&lt;/a&gt;
    &lt;a href="https://www.npmjs.com/package/@strands-agents/sdk" rel="nofollow noopener noreferrer"&gt;&lt;img alt="NPM Version" src="https://camo.githubusercontent.com/f5536192be00821a49a2cef74506f8a603018b1ff32f3bd3fc1c257d64b74e64/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f40737472616e64732d6167656e74732f73646b"&gt;&lt;/a&gt;
  &lt;/div&gt;
  &lt;p&gt;
    &lt;a href="https://strandsagents.com/" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;
    ◆ &lt;a href="https://github.com/strands-agents/samples" rel="noopener noreferrer"&gt;Samples&lt;/a&gt;
    ◆ &lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;
    ◆ &lt;a href="https://github.com/strands-agents/tools" rel="noopener noreferrer"&gt;Tools&lt;/a&gt;
    ◆ &lt;a href="https://github.com/strands-agents/agent-builder" rel="noopener noreferrer"&gt;Agent Builder&lt;/a&gt;
    ◆ &lt;a href="https://github.com/strands-agents/mcp-server" rel="noopener noreferrer"&gt;MCP Server&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Strands Agents is a simple yet powerful SDK that takes a model-driven approach to building and running AI agents. The TypeScript SDK brings key features from the Python Strands framework to Node.js environments, enabling type-safe agent development for everything from simple assistants to complex workflows.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Key Features&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🪶 Lightweight &amp;amp; Flexible&lt;/strong&gt;: Simple agent loop that works seamlessly in Node.js and browser environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔒 Type-Safe Tools&lt;/strong&gt;: Define tools easily using Zod schemas for robust input validation and type inference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📋 Structured Output&lt;/strong&gt;: Get type-safe, validated responses from LLMs using Zod schemas with automatic retry on validation errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔌 Model Agnostic&lt;/strong&gt;: First-class support for Amazon Bedrock and OpenAI, with extensible architecture for custom providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔗 Built-in MCP&lt;/strong&gt;: Native…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/strands-agents/sdk-typescript" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://x.com" rel="noopener noreferrer"&gt;X/Twitter&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Useful when a tweet has some context you want to reference.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2052162536301072643-248" src="https://platform.twitter.com/embed/Tweet.html?id=2052162536301072643"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2052162536301072643-248');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2052162536301072643&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Video is king 👑. However, I'd probably reach for the "Cover Video Link" feature, where you can have the video be your banner instead of an image.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/NJ6F0atWKpM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  DEV specific
&lt;/h2&gt;

&lt;p&gt;These are some of the custom things that DEV supports outside of third party site embeds.&lt;/p&gt;

&lt;p&gt;Article Link:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6" class="crayons-story__hidden-navigation-link"&gt;Stop Reaching for Python: Strands Agents TypeScript SDK Just Hit 1.0&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws"&gt;
            &lt;img alt="AWS logo" 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%2Forganization%2Fprofile_image%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png" class="crayons-logo__image" width="320" height="320"&gt;
          &lt;/a&gt;

          &lt;a href="/erikch" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2Fuser%2Fprofile_image%2F1994%2F0j84YwMs.jpeg" alt="erikch profile" class="crayons-avatar__image" width="208" height="208"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/erikch" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Erik Hanchett
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Erik Hanchett
                
              
              &lt;div id="story-author-preview-content-3610948" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/erikch" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1994%2F0j84YwMs.jpeg" class="crayons-avatar__image" alt="" width="208" height="208"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Erik Hanchett&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws" class="crayons-story__secondary fw-medium"&gt;AWS&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 4&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6" id="article-link-3610948"&gt;
          Stop Reaching for Python: Strands Agents TypeScript SDK Just Hit 1.0
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aws"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aws&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/agents"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;agents&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;19&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;User Profile:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__735825"&gt;
    &lt;a href="/brooke_jamieson" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F735825%2F6ede452c-440e-4a66-af2f-7ef5ab91eaa2.jpeg" alt="brooke_jamieson image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/brooke_jamieson"&gt;Brooke Jamieson&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/brooke_jamieson"&gt;🌥️ I’ll teach you tech tips in bite sized videos
📣 Keynote Speaker
👩🏻‍💻 AI/ML Specialist
💼 AWS Sr Developer Advocate (views are my own)
🇦🇺AU ➡️ NYC🗽&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Tag Profile:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__tag ltag__tag__id__397"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/aws" class="ltag__tag__link"&gt;aws&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        Amazon Web Services (AWS) is a collection of web services for computing, storage, machine learning, security, and more

There are over 200+ AWS services as of 2023.
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Comment:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__comment crayons-card my-2 p-0 overflow-hidden"&gt;
    &lt;a href="https://dev.to/aws/what-even-is-ai-i-took-a-break-had-to-relearn-everything-3dpj" class="flex items-center gap-2 p-3 fs-s color-base-60 hover:color-base-90"&gt;
      

      &lt;span&gt;Comment on &lt;strong class="fw-medium color-base-90"&gt;What Even Is AI? (I Took a Break &amp;amp; Had to Relearn Everything)&lt;/strong&gt;&lt;/span&gt;
    &lt;/a&gt;
  &lt;div class="p-4"&gt;
    &lt;div class="flex items-center gap-2 mb-3"&gt;
      &lt;a href="/jess" class="crayons-avatar crayons-avatar--l"&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%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" alt="jess" class="crayons-avatar__image" width="400" height="400"&gt;
      &lt;/a&gt;
      &lt;div&gt;
        &lt;a href="/jess" class="crayons-link fw-medium"&gt;Jess Lee&lt;/a&gt;
        &lt;span class="fs-xs color-base-60 ml-1"&gt;May 5&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="text-styles"&gt;
      &lt;blockquote&gt;
&lt;p&gt;I just came back from maternity leave. And honestly? I felt like I'd missed a decade in six months.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I feel this so hard.&lt;/p&gt;

&lt;p&gt;Looking forward to your series!&lt;/p&gt;


    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Podcast Episode:&lt;br&gt;
&lt;/p&gt;
&lt;div class="podcastliquidtag"&gt;
  &lt;div class="podcastliquidtag__info"&gt;
    &lt;a href="/awsbites/120-lambda-best-practices"&gt;
      &lt;h1 class="podcastliquidtag__info__episodetitle"&gt;120. Lambda Best Practices&lt;/h1&gt;
    &lt;/a&gt;
    &lt;a href="/awsbites"&gt;
      &lt;h2 class="podcastliquidtag__info__podcasttitle"&gt;
        AWS Bites
      &lt;/h2&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;div id="record-120-lambda-best-practices" class="podcastliquidtag__record"&gt;
    &lt;img class="button play-butt" id="play-butt-120-lambda-best-practices" src="https://assets.dev.to/assets/playbutt-5e444a2eae28832efea0dec3342ccf28a228b326c47f46700d771801f75d6b88.png" alt="play"&gt;
    &lt;img class="button pause-butt" id="pause-butt-120-lambda-best-practices" src="https://assets.dev.to/assets/pausebutt-bba7cb5f432cfb16510e78835378fa22f45fa6ae52a624f7c9794fefa765c384.png" alt="pause"&gt;
    &lt;img class="podcastliquidtag__podcastimage" id="podcastimage-120-lambda-best-practices" alt="AWS Bites" 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%2Fpodcast%2Fimage%2F674%2F056236bd-d6bf-4665-9ef1-ec6e3009420d.png"&gt;
  &lt;/div&gt;

  &lt;div class="hidden-audio" id="hidden-audio-120-lambda-best-practices"&gt;
  
    
    Your browser does not support the audio element.
  
  &lt;div id="progressBar" class="audio-player-display"&gt;
    &lt;a href="/awsbites/120-lambda-best-practices"&gt;
      &lt;img id="episode-profile-image" alt="120. Lambda Best Practices" width="420" height="420" 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%2Fpodcast%2Fimage%2F674%2F056236bd-d6bf-4665-9ef1-ec6e3009420d.png"&gt;
      &lt;img id="animated-bars" src="https://assets.dev.to/assets/animated-bars-4e8c57c8b58285fcf7d123680ad8af034cd5cd43b4d9209fe3aab49d1e9d77b3.gif" alt="animated volume bars"&gt;
    &lt;/a&gt;
    &lt;span id="barPlayPause"&gt;
      &lt;img class="butt play-butt" alt="play" src="https://assets.dev.to/assets/playbutt-5e444a2eae28832efea0dec3342ccf28a228b326c47f46700d771801f75d6b88.png"&gt;
      &lt;img class="butt pause-butt" alt="pause" src="https://assets.dev.to/assets/pausebutt-bba7cb5f432cfb16510e78835378fa22f45fa6ae52a624f7c9794fefa765c384.png"&gt;
    &lt;/span&gt;
    &lt;span id="volume"&gt;
      &lt;span id="volumeindicator" class="volume-icon-wrapper showing"&gt;
        &lt;span id="volbutt"&gt;
          &lt;img alt="volume" class="icon-img" height="16" width="16" src="https://assets.dev.to/assets/volume-cd20707230ae3fc117b02de53c72af742cf7d666007e16e12f7ac11ebd8130a7.png"&gt;
        &lt;/span&gt;
        &lt;span class="range-wrapper"&gt;
          
        &lt;/span&gt;
      &lt;/span&gt;
      &lt;span id="mutebutt" class="volume-icon-wrapper hidden"&gt;
        &lt;img alt="volume-mute" class="icon-img" height="16" width="16" src="https://assets.dev.to/assets/volume-mute-8f08ec668105565af8f8394eb18ab63acb386adbe0703afe3748eca8f2ecbf3b.png"&gt;
      &lt;/span&gt;
      &lt;span class="speed" id="speed"&gt;1x&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class="buffer-wrapper" id="bufferwrapper"&gt;
      &lt;span id="buffer"&gt;&lt;/span&gt;
      &lt;span id="progress"&gt;&lt;/span&gt;
      &lt;span id="time"&gt;initializing...&lt;/span&gt;
      &lt;span id="closebutt"&gt;×&lt;/span&gt;
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Organization:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__1726"&gt;
  &lt;a href="/aws" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png" alt="aws image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/aws" class="ltag__user__link"&gt;AWS&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/aws" class="ltag__user__link"&gt;
        Articles written by current and past AWS Developer Advocates to help people interested in building on AWS. Opinions are each author's own.
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Well, this one is not a website, but it's useful when you want to add some CTA at the end of a blog.&lt;/p&gt;

&lt;p&gt;Buttons:&lt;br&gt;
&lt;a href="https://x.com/AWSDevelopers" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;View @AWSDevelopers on X/Twitter 💻&lt;/a&gt;
&lt;br&gt;
—&lt;br&gt;
&lt;a href="https://builder.aws.com/?trk=02c7b25c-78f4-4968-8fa2-241bdf0bcf97&amp;amp;sc_channel=el" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Visit AWS Builder Center&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;Let me know what your favorite embed is or if I'm missing any amazing ones in the comments. &lt;/p&gt;

&lt;p&gt;Happy coding 👨‍💻!&lt;/p&gt;

</description>
      <category>devto</category>
      <category>todayilearned</category>
      <category>howtodev</category>
    </item>
  </channel>
</rss>
