<?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>Forem: AWS</title>
    <description>The latest articles on Forem by AWS (@aws).</description>
    <link>https://forem.com/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>Forem: AWS</title>
      <link>https://forem.com/aws</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aws"/>
    <language>en</language>
    <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://forem.com/aws/estructuras-de-datos-que-son-los-stacks-lifo-5e6b</link>
      <guid>https://forem.com/aws/estructuras-de-datos-que-son-los-stacks-lifo-5e6b</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>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://forem.com/aws/estructuras-de-datos-que-son-los-stacks-lifo-1d0n</link>
      <guid>https://forem.com/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://forem.com/aws/why-does-ai-lie-hallucinations-explained-simply-1c7g</link>
      <guid>https://forem.com/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://forem.com/aws/i-built-my-mom-an-ai-recipe-helper-for-mothers-day-2hc5</link>
      <guid>https://forem.com/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://forem.com/aws/build-your-own-daily-notification-bot-on-aws-free-tier-kbk</link>
      <guid>https://forem.com/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://forem.com/aws/embed-these-websites-in-your-blog-today-286p</link>
      <guid>https://forem.com/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-10" src="https://platform.twitter.com/embed/Tweet.html?id=2052162536301072643"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2052162536301072643-10');
  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;18&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>
    <item>
      <title>¿Qué es un Agente de IA? Explicado para devs</title>
      <dc:creator>Ramses Mata</dc:creator>
      <pubDate>Wed, 06 May 2026 20:51:06 +0000</pubDate>
      <link>https://forem.com/aws/que-es-un-agente-de-ia-explicado-para-devs-1p8d</link>
      <guid>https://forem.com/aws/que-es-un-agente-de-ia-explicado-para-devs-1p8d</guid>
      <description>&lt;p&gt;Si has usado un chat de IA, ya interactuaste con un modelo. Le haces una pregunta, te responde, y ahí termina la historia. Pero, ¿qué pasa cuando necesitas que la IA no solo responda, sino que &lt;strong&gt;haga cosas&lt;/strong&gt;? Que busque información, ejecute código, llame APIs, y tome decisiones sobre qué hacer a continuación. Ahí es donde entran los &lt;strong&gt;agentes de IA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En mi &lt;a href="https://dev.to/ramtoearth/que-es-un-modelo-de-ia-explicado-para-devs"&gt;artículo anterior&lt;/a&gt; exploramos qué es un modelo de IA, y descubrimos que es un programa que predice texto basándose en patrones. Hoy vamos a ver qué pasa cuando le das a ese modelo la capacidad de actuar en el mundo real.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. ¿Qué es un agente de IA?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Un agente de IA es un sistema que usa un modelo de lenguaje como cerebro para razonar, tomar decisiones y ejecutar acciones de forma autónoma hasta completar una tarea.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El modelo genera texto, el agente &lt;strong&gt;hace cosas&lt;/strong&gt;. Un modelo es como un cerebro en un frasco. Muy inteligente, puede razonar, analizar y generar ideas, pero no tiene manos, no tiene ojos, no puede interactuar con nada fuera de sí mismo. Un agente es ese mismo cerebro conectado a manos, ojos y memoria. Puede buscar información, ejecutar código, leer archivos, llamar APIs, y decidir qué hacer a continuación basándose en lo que observa.&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%2Fivcuowbhyazqqgt8em5q.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%2Fivcuowbhyazqqgt8em5q.png" alt="Modelo vs Agente" width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Modelo vs Agente: ¿cuál es la diferencia?
&lt;/h2&gt;

&lt;p&gt;Veamos un ejemplo concreto para que la diferencia quede clara.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tú preguntas:&lt;/strong&gt; "¿Qué clima hace en Ciudad de México?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un modelo:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recibe tu pregunta&lt;/li&gt;
&lt;li&gt;Genera texto basándose en sus datos de entrenamiento&lt;/li&gt;
&lt;li&gt;Responde: "No tengo acceso a información en tiempo real, pero el clima en CDMX suele ser templado..."&lt;/li&gt;
&lt;li&gt;Fin. No puede hacer más.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Un agente:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recibe tu pregunta&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Piensa:&lt;/strong&gt; "Necesito datos del clima actual. Tengo una herramienta que consulta APIs de clima."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actúa:&lt;/strong&gt; Llama a la API del clima con la ubicación "Ciudad de México"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observa:&lt;/strong&gt; Recibe los datos: 22°C, soleado, humedad 45%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responde:&lt;/strong&gt; "Ahora mismo en Ciudad de México hay 22°C, está soleado con 45% de humedad."&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%2F2hkd9rz37asiqkcqgzc8.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%2F2hkd9rz37asiqkcqgzc8.png" alt="modelo solo vs agente respondiendo sobre el clima" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El modelo está limitado a lo que sabe de su entrenamiento. El agente puede &lt;strong&gt;ir a buscar&lt;/strong&gt; lo que no sabe.&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;Modelo&lt;/th&gt;
&lt;th&gt;Agente&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Genera texto&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usa herramientas&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toma decisiones&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ejecuta acciones&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recuerda contexto entre pasos&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trabaja de forma autónoma&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Si&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  3. ¿Qué NO es un agente?
&lt;/h2&gt;

&lt;p&gt;Antes de seguir, limpiemos confusiones comunes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Un chatbot con respuestas predefinidas&lt;/strong&gt; no es un agente. Si solo sigue un árbol de decisiones fijo, no hay razonamiento ni autonomía.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un modelo con un prompt muy largo&lt;/strong&gt; no es un agente. Por más contexto que le des, si no puede ejecutar acciones ni tomar decisiones sobre qué hacer, sigue siendo solo un modelo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Una automatización con if/else&lt;/strong&gt; no es un agente. Si el flujo está completamente predefinido por un humano, no hay autonomía.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un agente combina razonamiento + decisión + acción + observación en un loop que él mismo controla.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Los componentes de un agente
&lt;/h2&gt;

&lt;p&gt;Un agente no es una sola cosa mágica. Es una &lt;strong&gt;combinación de componentes&lt;/strong&gt; trabajando juntos.&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%2Fooun5aiu74m48u7k8cvp.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%2Fooun5aiu74m48u7k8cvp.png" alt="Componentes de un agente de IA" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Modelo (el cerebro)
&lt;/h3&gt;

&lt;p&gt;Es el componente que razona y decide. El modelo de lenguaje analiza la situación, decide qué hacer a continuación y genera las instrucciones para actuar. Sin el modelo, no hay razonamiento.&lt;/p&gt;

&lt;p&gt;En el &lt;a href="https://dev.to/aws/que-es-un-modelo-de-ia-explicado-de-manera-sencilla-2joo"&gt;artículo anterior&lt;/a&gt; vimos cómo elegir un modelo según sus capacidades. Para agentes, necesitas un modelo que soporte &lt;strong&gt;tool use&lt;/strong&gt; (uso de herramientas), que es la capacidad de describir cuándo necesita llamar a una función externa y con qué parámetros.&lt;/p&gt;

&lt;h3&gt;
  
  
  Herramientas (las manos)
&lt;/h3&gt;

&lt;p&gt;Las herramientas son funciones que el agente puede ejecutar para interactuar con el mundo exterior. Pueden ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;APIs externas:&lt;/strong&gt; clima, bases de datos, servicios web&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ejecución de código:&lt;/strong&gt; correr scripts, hacer cálculos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Búsqueda:&lt;/strong&gt; buscar en internet, en documentos, en bases de conocimiento&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acciones en sistemas:&lt;/strong&gt; crear archivos, enviar emails, desplegar código&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sin herramientas, el agente sería solo un modelo con pasos extra. Las herramientas son lo que le dan la capacidad de &lt;strong&gt;actuar&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memoria (el contexto)
&lt;/h3&gt;

&lt;p&gt;La memoria permite al agente recordar qué ha hecho, qué ha observado y qué ha decidido. Hay dos tipos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memoria a corto plazo:&lt;/strong&gt; la conversación actual, los pasos que ha dado, los resultados que ha obtenido. Esto vive en la ventana de contexto del modelo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memoria a largo plazo:&lt;/strong&gt; información que persiste entre conversaciones. Puede ser una base de datos, un archivo, o cualquier sistema de almacenamiento externo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sin memoria, el agente repetiría los mismos errores y no podría construir sobre sus observaciones anteriores.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrucciones (el propósito)
&lt;/h3&gt;

&lt;p&gt;Las instrucciones le dicen al agente &lt;strong&gt;qué&lt;/strong&gt; debe hacer y &lt;strong&gt;cómo&lt;/strong&gt; debe comportarse. Esto incluye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El prompt del sistema que define su rol y restricciones&lt;/li&gt;
&lt;li&gt;Los objetivos que debe cumplir&lt;/li&gt;
&lt;li&gt;Las reglas que debe seguir&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sin instrucciones claras, el agente no sabría hacia dónde dirigir su razonamiento.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. El loop: cómo funciona un agente
&lt;/h2&gt;

&lt;p&gt;Ahora que conoces los componentes, veamos cómo interactúan. Un agente funciona en un &lt;strong&gt;loop&lt;/strong&gt; (ciclo) que se repite hasta completar la tarea:&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%2Fcufqxgnsctvivbtyfq4v.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%2Fcufqxgnsctvivbtyfq4v.png" alt="Agentic loop" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pensar
&lt;/h3&gt;

&lt;p&gt;El modelo analiza la situación actual: ¿qué me pidieron? ¿qué sé hasta ahora? ¿qué me falta? Decide cuál es el siguiente paso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Actuar
&lt;/h3&gt;

&lt;p&gt;Basándose en su razonamiento, el agente ejecuta una acción. Puede ser llamar una herramienta, hacer una búsqueda, o ejecutar código.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observar
&lt;/h3&gt;

&lt;p&gt;El agente recibe el resultado de su acción y lo incorpora a su contexto. ¿Funcionó? ¿Obtuvo lo que necesitaba? ¿Necesita hacer algo más?&lt;/p&gt;

&lt;h3&gt;
  
  
  Repetir o responder
&lt;/h3&gt;

&lt;p&gt;Si la tarea está completa, el agente genera su respuesta final. Si no, vuelve a &lt;strong&gt;Pensar&lt;/strong&gt; con la nueva información y repite el ciclo.&lt;/p&gt;

&lt;p&gt;El modelo responde una vez. El agente &lt;strong&gt;itera&lt;/strong&gt; hasta resolver el problema.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Tip importante:&lt;/strong&gt; El número de iteraciones del loop depende de la complejidad de la tarea. Una pregunta simple puede resolverse en 1-2 ciclos. Una tarea compleja como "investiga este bug y propón una solución" puede tomar 5-10 ciclos donde el agente lee código, ejecuta tests, analiza errores y propone cambios.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. ¿Cómo se construye un agente?
&lt;/h2&gt;

&lt;p&gt;Si estás pensando "esto suena complejo de implementar", tienes razón. Conectar un modelo con herramientas, manejar memoria, implementar el loop, manejar errores... son muchas piezas.&lt;/p&gt;

&lt;p&gt;Por eso existen &lt;strong&gt;SDKs y frameworks&lt;/strong&gt; que se encargan de la infraestructura para que tú te enfoques en definir qué debe hacer tu agente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;:&lt;/strong&gt; Un SDK open source de AWS que simplifica la creación de agentes con un enfoque en model-driven development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.langchain.com/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;:&lt;/strong&gt; Framework para construir agentes como grafos de estados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/bedrock/agents/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock Agents&lt;/a&gt;:&lt;/strong&gt; Servicio administrado de AWS para crear agentes sin manejar infraestructura&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Estos frameworks se encargan de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementar el loop (Pensar → Actuar → Observar)&lt;/li&gt;
&lt;li&gt;Conectar el modelo con las herramientas&lt;/li&gt;
&lt;li&gt;Manejar la memoria y el contexto&lt;/li&gt;
&lt;li&gt;Gestionar errores y reintentos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tu trabajo como desarrollador es definir: ¿qué herramientas tiene disponibles mi agente? ¿qué instrucciones le doy? ¿qué modelo uso?&lt;/p&gt;

&lt;p&gt;En el próximo artículo de esta serie vamos a construir tu primer agente de IA paso a paso usando uno de estos frameworks. Vas a ver que con pocas líneas de código puedes tener un agente funcional que resuelve problemas reales.&lt;/p&gt;




&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Todos los chatbots de IA son agentes?
&lt;/h3&gt;

&lt;p&gt;No. La mayoría de chatbots que usas día a día (como ChatGPT en modo básico) son modelos con una interfaz de conversación. Se convierten en agentes cuando tienen acceso a herramientas y pueden ejecutar acciones, como cuando ChatGPT busca en internet o ejecuta código con Code Interpreter.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Un agente puede equivocarse?
&lt;/h3&gt;

&lt;p&gt;Si. Los agentes heredan las limitaciones del modelo que usan como cerebro. Pueden tomar decisiones incorrectas, usar herramientas de forma equivocada, o entrar en loops infinitos. Por eso las instrucciones claras y las restricciones son importantes.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Necesito un modelo específico para construir agentes?
&lt;/h3&gt;

&lt;p&gt;Necesitas un modelo que soporte &lt;strong&gt;tool use&lt;/strong&gt; (también llamado function calling). La mayoría de modelos modernos lo soportan: Claude, GPT-4, Llama 3, Nova, entre otros. Puedes acceder a varios de estos a través de &lt;a href="https://aws.amazon.com/bedrock/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Los agentes son seguros?
&lt;/h3&gt;

&lt;p&gt;Depende de cómo los construyas. Un agente con acceso a herramientas poderosas (como ejecutar código o modificar bases de datos) necesita restricciones claras. Es tu responsabilidad como desarrollador definir qué puede y qué no puede hacer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Recapitulemos lo que aprendimos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un &lt;strong&gt;agente de IA&lt;/strong&gt; es un sistema que usa un modelo como cerebro para razonar, decidir y ejecutar acciones de forma autónoma&lt;/li&gt;
&lt;li&gt;La diferencia con un modelo solo: el modelo genera texto, el agente &lt;strong&gt;hace cosas&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Un agente tiene 4 componentes: &lt;strong&gt;modelo&lt;/strong&gt; (cerebro), &lt;strong&gt;herramientas&lt;/strong&gt; (manos), &lt;strong&gt;memoria&lt;/strong&gt; (contexto) e &lt;strong&gt;instrucciones&lt;/strong&gt; (propósito)&lt;/li&gt;
&lt;li&gt;Funciona en un &lt;strong&gt;loop&lt;/strong&gt;: Pensar → Actuar → Observar → Repetir hasta completar la tarea&lt;/li&gt;
&lt;li&gt;Existen &lt;strong&gt;frameworks y SDKs&lt;/strong&gt; que simplifican la construcción de agentes para que no tengas que implementar todo desde cero&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En el próximo artículo vamos a pasar de la teoría a la práctica: vas a construir tu primer agente de IA funcional, con herramientas reales, en pocas líneas de código.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>beginners</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Build Your Own AI Butler - A Scheduled Agent That Runs Itself!</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Wed, 06 May 2026 16:00:47 +0000</pubDate>
      <link>https://forem.com/aws/build-your-own-ai-butler-a-scheduled-agent-that-runs-itself-3dmk</link>
      <guid>https://forem.com/aws/build-your-own-ai-butler-a-scheduled-agent-that-runs-itself-3dmk</guid>
      <description>&lt;p&gt;I want an AI agent that works for me. I want it to search up the latest news, and I want it to deliver it to me in a daily message. I want to chat with it, and get advice. I want to expand its capabilities in the future. And most importantly I want to control it. Basically, I want Jarvis.&lt;/p&gt;

&lt;p&gt;Yes Jarvis, the fictional AI character in the Marvel Cinematic Universe (MCU), serving as Tony Stark's butler. I've never had my own agentic butler, and now that I think about it who wouldn't? 😆 A few years ago I would have told you this would have never happened. But today with the rise of agents the future is here.&lt;/p&gt;

&lt;p&gt;Well sort of... we are getting closer than ever though!&lt;/p&gt;

&lt;p&gt;To take on this task one of the most obvious routes was to use something like OpenClaw. Except, OpenClaw is too new, and a little too much for what I needed, so like any good engineer I decided to roll my own. &lt;em&gt;&lt;a href="https://openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; is an open-source, autonomous AI agent that runs locally on a your machine or VPS to automate tasks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Recently &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt; released its managed harness in preview. You define the agent in a config file and a system prompt, and then deploy it. That was it! From my research it looked very straightforward to get up and running, and it could save me a lot of time in the long run. So I decided to take the journey to create my very own Jarvis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want the conceptual breakdown of what a harness actually is in more detail, &lt;a href="https://dev.to/aws/what-is-an-agent-harness-a-hands-on-guide-with-agentcore-harness-1h33"&gt;my friend Morgan's post on dev.to&lt;/a&gt; covers that well. This post is the hands-on build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After several hours of tinkering and research I created "The Pulse." It opens &lt;a href="https://news.ycombinator.com" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt; in a real browser, grabs Reddit posts via RSS, saves everything to persistent storage, and every 6 hours writes a trending stories digest. It messages me every day, or I can message it anytime and ask what's happening in AI. It reads from the data it's been collecting all day.&lt;/p&gt;

&lt;p&gt;If you're more like me and love videos, check out the full video below on how to get started. I'm also including the source code so you can try it yourself. Otherwise continue on and learn all about how to get started! Feel free to let me know how it goes.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Full code:&lt;/strong&gt;&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/aws-samples" rel="noopener noreferrer"&gt;
        aws-samples
      &lt;/a&gt; / &lt;a href="https://github.com/aws-samples/sample-AgentCore-Managed-Harness-News" rel="noopener noreferrer"&gt;
        sample-AgentCore-Managed-Harness-News
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AgentCore Managed Harness News Sample — The Pulse&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A sample AgentCore managed harness project that runs a scheduled news digest agent ("The Pulse") on Amazon Bedrock AgentCore and posts the results to Telegram.&lt;/p&gt;
&lt;p&gt;The agent is scheduled with EventBridge Scheduler, invoked via a Lambda trigger, and uses AgentCore Memory to retain context across runs. It's a realistic end-to-end example of building a recurring, long-running agent on AWS.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's in here&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;AgentCore-News-Sample/
├── agentcore/
│   ├── agentcore.json          # Project config (runtimes, memories, etc.)
│   ├── aws-targets.json        # AWS account + region to deploy into
│   ├── cdk/                    # CDK infrastructure (@aws/agentcore-cdk)
│   │   ├── bin/cdk.ts          # CDK entry — synthesizes both stacks
│   │   ├── lib/cdk-stack.ts    # AgentCore stack (agent, memory, roles)
│   │   ├── lib/scheduler-stack.ts  # Scheduler + Lambda trigger
│   │   └── lambda/pulse-trigger/   # Lambda that invokes the harness
│   └── .llm-context/           # Type definitions for AI coding assistants&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aws-samples/sample-AgentCore-Managed-Harness-News" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;AgentCore Harness is in preview. The CLI, pricing, and some APIs may change. Check the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore docs&lt;/a&gt; for the latest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt;
  Table of Contents
  &lt;ul&gt;
&lt;li&gt;What We're Building&lt;/li&gt;
&lt;li&gt;Why Not Just Use Lambda?&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Create the Harness Project&lt;/li&gt;
&lt;li&gt;Configure the Agent&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;li&gt;Test the Hourly Collection&lt;/li&gt;
&lt;li&gt;Test the Summary Digest&lt;/li&gt;
&lt;li&gt;Ask It Questions&lt;/li&gt;
&lt;li&gt;Pull Data Out Without Burning Tokens&lt;/li&gt;
&lt;li&gt;Schedule It With EventBridge and Lambda&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;Cost Estimate&lt;/li&gt;
&lt;li&gt;Cleanup&lt;/li&gt;
&lt;li&gt;What's Next&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  What We'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%2Fcpan3tm1n4vjbpmz3437.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%2Fcpan3tm1n4vjbpmz3437.png" alt="The Pulse architecture diagram" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't worry if this diagram looks intimidating! The most complicated part is just getting the EventBridge connected with the correct IAM roles to the harness and dealing with the infrastructure code if you're new to that. Let's break down the flow here.&lt;/p&gt;

&lt;p&gt;EventBridge fires every hour and triggers a Lambda. &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/what-is-amazon-eventbridge.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt; is AWS's scheduler service, think of it like a cron job in the cloud. The &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt; makes one API call to the harness. Inside the harness, the agent runs in its own microVM, which is basically a lightweight virtual machine that spins up just for your session with its own CPU, memory, and filesystem. The agent opens a browser, scrapes HN, fetches Reddit RSS, and saves a snapshot. Every 6th run it reads all the snapshots, finds trends, and writes a digest. And because the session ID stays the same across runs, all that data accumulates on &lt;code&gt;/mnt/data&lt;/code&gt;. You can also just talk to it anytime through the CLI and ask what's trending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use Lambda?
&lt;/h2&gt;

&lt;p&gt;Before we get into the details, one question you may be thinking is couldn't we do all this with just a Lambda, some &lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; code, an LLM, and a dream? For a simple one-shot task, you are probably right. But this agent needs a few things Lambda can't give you, which makes this approach well worth it.&lt;/p&gt;

&lt;p&gt;First, the agent opens a real browser to scrape Hacker News. Lambda can't spin up a managed browser session. You'd need &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Fargate&lt;/a&gt; plus Playwright, and now you're managing containers. I love containers, but let's skip it for this scenario.&lt;/p&gt;

&lt;p&gt;Second, it saves hourly snapshots to &lt;code&gt;/mnt/data/runs/&lt;/code&gt; and reads them back 6 hours later. Lambda's &lt;code&gt;/tmp&lt;/code&gt; gets wiped after every invocation. You'd need &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;S3&lt;/a&gt; plus custom read/write logic for every file operation. That's a lot of work for what should be "save a file."&lt;/p&gt;

&lt;p&gt;Third, the 6-hour summary run reads all the accumulated data, compares scores across snapshots, identifies trends, and writes a digest. That can take a while. Lambda maxes out at 15 minutes. The harness defaults to a 1-hour timeout per invocation (I set mine to 30 minutes since that's plenty), and the microVM itself can stay alive for up to 8 hours across multiple invocations.&lt;/p&gt;

&lt;p&gt;And then there's memory. AgentCore Memory is a built-in service that stores facts, preferences, and conversation summaries across sessions. So "What did I ask about yesterday?" works because the memory carries context forward. In Lambda, you'd need &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;DynamoDB&lt;/a&gt; plus a custom integration.&lt;/p&gt;

&lt;p&gt;Could you build all of this with Lambda plus S3 plus Fargate plus DynamoDB plus Step Functions? Sure. That's five services instead of one. That's four too many for this tutorial.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Node.js 20+&lt;/li&gt;
&lt;li&gt;AWS credentials configured in a preview region (&lt;code&gt;us-west-2&lt;/code&gt;, &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;eu-central-1&lt;/code&gt;, or &lt;code&gt;ap-southeast-2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CDK bootstrapped in your account (&lt;code&gt;cdk bootstrap&lt;/code&gt;). &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CDK&lt;/a&gt; is the AWS Cloud Development Kit. It lets you define infrastructure in TypeScript instead of clicking through the console. If you haven't used it before, &lt;code&gt;npm install -g aws-cdk&lt;/code&gt; and then &lt;code&gt;cdk bootstrap&lt;/code&gt; in your account.&lt;/li&gt;
&lt;li&gt;The AgentCore CLI:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @aws/agentcore@preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Bedrock model access for Claude Sonnet 4.6 in your region. As of late 2025, Bedrock gives you automatic access to all serverless models in your region without needing to enable them individually. If your organization uses IAM policies or SCPs to restrict model access, make sure Sonnet isn't blocked. You can verify by opening the &lt;a href="https://us-west-2.console.aws.amazon.com/bedrock/home?region=us-west-2&amp;amp;trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock console&lt;/a&gt; and trying the model in the playground.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create the Harness Project
&lt;/h2&gt;

&lt;p&gt;This is where the fun begins. We'll create our very first managed harness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-provider&lt;/span&gt; bedrock &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-storage-mount-path&lt;/span&gt; /mnt/data

&lt;span class="nb"&gt;cd &lt;/span&gt;thepulse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one command scaffolds the whole project. Harness config, memory, session storage, CDK app, IAM role. The &lt;code&gt;--session-storage-mount-path&lt;/code&gt; flag wires up persistent storage so the platform creates a &lt;code&gt;/mnt/data&lt;/code&gt; mount point for you. You don't have to worry about a Dockerfile, or container to manage.&lt;/p&gt;

&lt;p&gt;Now add the browser tool. AgentCore Browser is a managed headless Chrome instance that the harness can spin up on demand. Your agent can navigate pages, click elements, extract content, basically anything you'd do with Playwright but without managing the browser infrastructure yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore add tool &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="nt"&gt;--type&lt;/span&gt; agentcore_browser &lt;span class="nt"&gt;--name&lt;/span&gt; browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what you end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thepulse/
├── agentcore/
│   ├── agentcore.json          # Project-level resource config
│   ├── aws-targets.json        # Deployment targets (account, region)
│   ├── .env.local              # Local dev config (gitignored)
│   └── cdk/                    # CDK TypeScript app
├── app/
│   └── thepulse/
│       ├── harness.json        # Agent config
│       └── system-prompt.md    # Agent instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure the Agent
&lt;/h2&gt;

&lt;p&gt;Our AgentCore CLI already set up the model, memory, and session storage. You just need to tweak the execution limits and write the system prompt.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;app/thepulse/harness.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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thepulse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&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;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bedrock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"modelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"global.anthropic.claude-sonnet-4-6"&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;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agentcore_browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&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;"memory"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thepulseMemory"&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;"sessionStoragePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxIterations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeoutSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&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;A few things worth mentioning here.&lt;/p&gt;

&lt;p&gt;I set &lt;code&gt;maxIterations&lt;/code&gt; to 75 because browser interactions are chatty. That eats through iterations fast, and 75 gives room for the full HN browser scrape plus Reddit RSS plus file operations without the agent hitting the ceiling.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;timeoutSeconds&lt;/code&gt; is 1800 (30 minutes). Browser sessions take time, and the summary run reads a lot of data. I didn't hit too many timeouts when testing, but this was a just-in-case update.&lt;/p&gt;

&lt;p&gt;This next setting is really useful. The platform creates the &lt;code&gt;/mnt/data&lt;/code&gt; mount point automatically when you set &lt;code&gt;sessionStoragePath&lt;/code&gt;. This mount point will be shared per session, no matter if the session times out.&lt;/p&gt;

&lt;p&gt;Now edit &lt;code&gt;app/thepulse/system-prompt.md&lt;/code&gt;. The CLI created this file with a default prompt. Replace it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are The Pulse, an AI research agent that monitors Hacker News
and Reddit for AI/ML stories.

You have two modes:

&lt;span class="gu"&gt;### HOURLY COLLECTION&lt;/span&gt;
When the prompt says "hourly run":

&lt;span class="gs"&gt;**Hacker News (browser):**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Use the browser tool to open https://news.ycombinator.com
&lt;span class="p"&gt;2.&lt;/span&gt; Extract the top 30 stories: title, score, comment count, URL
&lt;span class="p"&gt;3.&lt;/span&gt; Close the browser session when done

&lt;span class="gs"&gt;**Reddit (RSS — do NOT use the browser):**&lt;/span&gt;
Reddit blocks automated browser access (403). Use the RSS feed instead:
&lt;span class="p"&gt;1.&lt;/span&gt; Shell: &lt;span class="sb"&gt;`curl -s -A 'ThePulse/1.0'
   'https://www.reddit.com/r/MachineLearning/.rss'`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Parse the XML to extract post titles, URLs, authors, and dates
&lt;span class="p"&gt;3.&lt;/span&gt; Note: RSS does not include upvote scores. That's expected.
&lt;span class="p"&gt;4.&lt;/span&gt; Also try r/artificial: &lt;span class="sb"&gt;`curl -s -A 'ThePulse/1.0'
   'https://www.reddit.com/r/artificial/.rss'`&lt;/span&gt;

&lt;span class="gs"&gt;**Save the snapshot:**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Combine HN and Reddit data into a single JSON file
&lt;span class="p"&gt;2.&lt;/span&gt; Save to /mnt/data/runs/YYYY-MM-DD-HH.json
&lt;span class="p"&gt;3.&lt;/span&gt; If a previous hour's snapshot exists (check /mnt/data/runs/),
   compare and flag:
&lt;span class="p"&gt;   -&lt;/span&gt; Stories that appeared in both snapshots (persistent)
&lt;span class="p"&gt;   -&lt;/span&gt; HN stories with rising scores

&lt;span class="gu"&gt;### SUMMARY DIGEST&lt;/span&gt;
When the prompt says "generate summary":
&lt;span class="p"&gt;1.&lt;/span&gt; Read all snapshots from /mnt/data/runs/ for the last 6 hours
&lt;span class="p"&gt;2.&lt;/span&gt; Identify stories that appeared in multiple snapshots
   (persistent presence = trending)
&lt;span class="p"&gt;3.&lt;/span&gt; Identify stories with rising scores across snapshots
&lt;span class="p"&gt;4.&lt;/span&gt; Deduplicate and rank by persistence, score growth, and
   comment activity
&lt;span class="p"&gt;5.&lt;/span&gt; Write a markdown digest to
   /mnt/data/digests/YYYY-MM-DD-{morning|afternoon|evening|night}.md
&lt;span class="p"&gt;6.&lt;/span&gt; Include the top 10 trending stories with: title, source
   (HN/Reddit), peak score, trend direction, URL

&lt;span class="gu"&gt;### INTERACTIVE&lt;/span&gt;
When a user asks a question:
&lt;span class="p"&gt;-&lt;/span&gt; Read from /mnt/data/runs/ and /mnt/data/digests/ to answer
&lt;span class="p"&gt;-&lt;/span&gt; Use memory for conversational context

&lt;span class="gu"&gt;## Important&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; /mnt/data is persistent session storage. Use the same session ID
  across runs so data accumulates.
&lt;span class="p"&gt;-&lt;/span&gt; Always create directories before writing:
  &lt;span class="sb"&gt;`mkdir -p /mnt/data/runs /mnt/data/digests`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I put the collection to be hourly and the snapshots to run every six hours. Retrospectively, this is a lot of data, and I could certainly have expanded that. I'd say collecting twice a day and having a snapshot created once a day would be fine as well. Feel free to tweak this prompt to your liking.&lt;/p&gt;

&lt;p&gt;One gotcha I'd like to pass on is that Reddit doesn't like agents scraping its data. The agent kept trying to open Reddit in the browser, getting 403'd, and burning 5-6 iterations before giving up. I sat there watching it retry over and over thinking "please just stop." The RSS feed at &lt;code&gt;reddit.com/r/MachineLearning/.rss&lt;/code&gt; works every time. Putting it explicitly in the system prompt saves you from that pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;One command. It provisions the harness, memory, IAM role, session storage, and the underlying microVM infrastructure. This takes about 3-5 minutes because it's also spinning up AgentCore Memory and running CDK for the IAM roles.&lt;/p&gt;

&lt;p&gt;You can verify what got created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore status &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test the Hourly Collection
&lt;/h2&gt;

&lt;p&gt;To test this out you'll need a stable session ID so &lt;code&gt;/mnt/data&lt;/code&gt; accumulates across runs, as I mentioned earlier. The session ID needs to be at least 33 characters (a UUID is 36, so that works). This is important because the same session ID means the same filesystem. That's how data builds up over time.&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="nv"&gt;SESSION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"thepulse-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;  &lt;span class="c"&gt;# Save this. You'll reuse it for every run.&lt;/span&gt;

agentcore invoke &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--actor-id&lt;/span&gt; scheduler &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Hourly run. Collect AI/ML stories from Hacker News and Reddit. Save the snapshot."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will open a managed Chrome browser, navigate to &lt;code&gt;news.ycombinator.com&lt;/code&gt;, extract stories, fetch Reddit via RSS, and save everything to &lt;code&gt;/mnt/data/runs/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Verify it worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--exec&lt;/span&gt; &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"ls -la /mnt/data/runs/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;--exec&lt;/code&gt; flag is one of my favorite things about the harness. It runs a shell command directly on the microVM. It's not connecting to any LLM so no tokens are used. You can use it to poke around the filesystem anytime without spending money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Summary Digest
&lt;/h2&gt;

&lt;p&gt;Run the collection a couple times (or wait a few hours), then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--actor-id&lt;/span&gt; scheduler &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Generate summary. Read all snapshots from the last 6 hours and write a trending stories digest."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads all the hourly snapshots, identifies stories that appeared multiple times (trending), flags rising scores, and writes a markdown digest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask It Questions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--actor-id&lt;/span&gt; demo-user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"What's trending in AI right now?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads from &lt;code&gt;/mnt/data/runs/&lt;/code&gt; and &lt;code&gt;/mnt/data/digests/&lt;/code&gt; to answer with real data. Not hallucinated summaries, actual scores and URLs from the snapshots it collected. I really liked this part. The agent isn't guessing. It's reading files it wrote earlier that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule It With EventBridge and Lambda
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;agentcore create&lt;/code&gt; command already scaffolded a CDK app at &lt;code&gt;agentcore/cdk/&lt;/code&gt;. We'll add a Lambda trigger and an EventBridge schedule to that existing project. EventBridge Scheduler is the AWS service that fires events on a cron schedule. We'll have it invoke a Lambda every hour, and that Lambda calls the harness. One &lt;code&gt;agentcore deploy&lt;/code&gt; handles the harness, one &lt;code&gt;cdk deploy&lt;/code&gt; handles the scheduler. I like keeping these separate because you iterate on the agent config way more often than the scheduling infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up the Lambda
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; agentcore/cdk/lambda/pulse-trigger
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk/lambda/pulse-trigger
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-sdk/client-bedrock-agentcore @aws-sdk/client-secrets-manager
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node esbuild
&lt;span class="nb"&gt;cd&lt;/span&gt; ../../../..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;agentcore/cdk/lambda/pulse-trigger/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BedrockAgentCoreClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InvokeHarnessCommand&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&gt;@aws-sdk/client-bedrock-agentcore&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;SecretsManagerClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GetSecretValueCommand&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&gt;@aws-sdk/client-secrets-manager&lt;/span&gt;&lt;span class="dl"&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;TelegramSecret&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;botToken&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;defaultChatId&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentCore&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;BedrockAgentCoreClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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;AWS_REGION&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;secrets&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;SecretsManagerClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Cache the secret across warm invocations&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cachedSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TelegramSecret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;expiry&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="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRET_TTL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 5 minutes&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;getSecret&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;TelegramSecret&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedSecret&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cachedSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cachedSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;secretArn&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;TELEGRAM_SECRET_ARN&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;secretArn&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;Missing TELEGRAM_SECRET_ARN env var&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;res&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;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetSecretValueCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;SecretId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;secretArn&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TelegramSecret&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretString&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cachedSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;SECRET_TTL_MS&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;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendToTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;botToken&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;chatId&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;text&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="c1"&gt;// Telegram's message limit is 4096 chars. Split if needed.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\s\S]{1,4000}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&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;res&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://api.telegram.org/bot&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sendMessage`&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="s2"&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="s2"&gt;Content-Type&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="s2"&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="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;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatId&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;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parse_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Markdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="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;res&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Telegram send failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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="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;hour&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;getUTCHours&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;sessionId&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;STABLE_SESSION_ID&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;harnessArn&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;HARNESS_ARN&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;isSummaryRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;6&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="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="nx"&gt;isSummaryRun&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generate summary. Read all snapshots from the last 6 hours and write a trending stories digest.&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="s2"&gt;Hourly run. Collect AI/ML stories from Hacker News and Reddit. Save the snapshot.&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;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;InvokeHarnessCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;harnessArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;actorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scheduler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="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="s2"&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="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;agentCore&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="c1"&gt;// Collect the full response text from the stream&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;responseText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;chunkCount&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="k"&gt;if &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;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;chunkCount&lt;/span&gt;&lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;event&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;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;as&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;contentBlockDelta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;responseText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="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="s2"&gt;`\n[pulse-trigger] Stream complete — &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chunkCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; chunks`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// On summary runs, send the digest to Telegram&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSummaryRun&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;responseText&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultChatId&lt;/span&gt; &lt;span class="p"&gt;}&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;getSecret&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;sendToTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseText&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;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isSummaryRun&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary&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="s2"&gt;collection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;digestSent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isSummaryRun&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;Let's break down what's happening here.&lt;/p&gt;

&lt;p&gt;The Lambda passes a stable session ID (from an environment variable) so every hourly run writes to the same &lt;code&gt;/mnt/data&lt;/code&gt;. It collects the full response text from the stream and logs it to &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CloudWatch&lt;/a&gt;. On summary runs, it sends the digest to Telegram using a bot token stored in &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Secrets Manager&lt;/a&gt;. Secrets Manager is the AWS service for storing sensitive values like API keys and tokens. You don't want a bot token sitting in an environment variable where anyone with console access can see it. The &lt;code&gt;sendToTelegram&lt;/code&gt; function handles Telegram's 4096 character limit by chunking the text. I found the CloudWatch logging really helpful for debugging early on when the agent would occasionally get stuck in a browser loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the Scheduler Stack
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;agentcore/cdk/lib/scheduler-stack.ts&lt;/code&gt;. This is a CDK stack, which is basically a unit of deployment. Everything in this file gets deployed together as one &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt; stack.&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;Stack&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="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&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;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="s2"&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="s2"&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Secret&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="s2"&gt;aws-cdk-lib/aws-secretsmanager&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="s2"&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-scheduler&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="s2"&gt;aws-cdk-lib/aws-scheduler-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="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="s2"&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;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PulseSchedulerStackProps&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;agentRuntimeArn&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;harnessArn&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;stableSessionId&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;telegramSecretArn&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PulseSchedulerStack&lt;/span&gt; &lt;span class="kd"&gt;extends&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="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;PulseSchedulerStackProps&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;telegramSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSecretCompleteArn&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;TelegramSecret&lt;/span&gt;&lt;span class="dl"&gt;"&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;telegramSecretArn&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;triggerFn&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="s2"&gt;PulseTrigger&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_22_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="s2"&gt;../../lambda/pulse-trigger/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="s2"&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;timeout&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;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&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="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;HARNESS_ARN&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;harnessArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;STABLE_SESSION_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="nx"&gt;stableSessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;TELEGRAM_SECRET_ARN&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;telegramSecretArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;externalModules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;triggerFn&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="s2"&gt;bedrock-agentcore:InvokeHarness&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="s2"&gt;bedrock-agentcore:InvokeAgentRuntime&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;resources&lt;/span&gt;&lt;span class="p"&gt;:&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;harnessArn&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;agentRuntimeArn&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;// Read access to the Telegram bot token secret&lt;/span&gt;
    &lt;span class="nx"&gt;telegramSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;triggerFn&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;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Schedule&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;HourlySchedule&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;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScheduleExpression&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="s2"&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="s2"&gt;*&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="nx"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC&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;target&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;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;triggerFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Pulse — hourly AI/ML news collection&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;new&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="s2"&gt;TriggerFunctionName&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;triggerFn&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="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;The &lt;code&gt;telegramSecret.grantRead(triggerFn)&lt;/code&gt; line gives the Lambda permission to read the bot token from Secrets Manager. The bot token lives there instead of in environment variables so it doesn't show up in CloudFormation outputs or the Lambda console.&lt;/p&gt;

&lt;p&gt;Wire it into &lt;code&gt;agentcore/cdk/bin/cdk.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PulseSchedulerStack&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="s2"&gt;../lib/scheduler-stack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... existing AgentCore stack code ...&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PulseSchedulerStack&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="s2"&gt;ThePulseScheduler&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;harnessArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_HARNESS_ARN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_RUNTIME_ARN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stableSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_SESSION_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;telegramSecretArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_SECRET_ARN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targets&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;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targets&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;region&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;I'm showing placeholders here to make it clear what goes where. You can get your harness ARN and runtime ARN with &lt;code&gt;agentcore status --json&lt;/code&gt;, and use the same session ID you've been testing with. The Telegram secret ARN comes from Secrets Manager (store your bot token and chat ID as a JSON object with &lt;code&gt;botToken&lt;/code&gt; and &lt;code&gt;defaultChatId&lt;/code&gt; keys).&lt;/p&gt;

&lt;p&gt;That said, you shouldn't hardcode ARNs and secrets into source files you commit to git. The &lt;a href="https://github.com/aws-samples/sample-AgentCore-Managed-Harness-News" rel="noopener noreferrer"&gt;actual repo&lt;/a&gt; resolves the harness ARN and runtime ARN automatically from the deployed state file that &lt;code&gt;agentcore deploy&lt;/code&gt; creates, and reads the Telegram secret ARN and session ID from environment variables. That way nothing sensitive ends up in version control. Check the &lt;code&gt;cdk.ts&lt;/code&gt; in the repo for the full implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy and Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install esbuild in the CDK project (needed for NodejsFunction)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install &lt;/span&gt;esbuild &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ../..

&lt;span class="c"&gt;# Deploy the harness&lt;/span&gt;
agentcore deploy

&lt;span class="c"&gt;# Deploy the scheduler stack&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx cdk deploy ThePulseScheduler &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ../..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the Lambda directly:&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; &lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--stack-name&lt;/span&gt; ThePulseScheduler &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs[?OutputKey==`TriggerFunctionName`].OutputValue'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cli-read-timeout&lt;/span&gt; 900 &lt;span class="se"&gt;\&lt;/span&gt;
  /tmp/lambda-response.json

&lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/lambda-response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--cli-read-timeout 900&lt;/code&gt; is important. The harness invocation takes a few minutes (the agent is browsing HN, fetching RSS, saving data), and the AWS CLI's default timeout is 60 seconds. Without it, you'll get a &lt;code&gt;Read timeout&lt;/code&gt; error even though the Lambda is still running fine. Don't make this mistake like I did.&lt;/p&gt;

&lt;p&gt;You should see &lt;code&gt;{"statusCode":200,"hour":...,"type":"collection"}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;A few things I hit while building this. Hopefully they save you some time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;npm ci&lt;/code&gt; fails during CDK synth.&lt;/strong&gt; If you use &lt;code&gt;nodeModules&lt;/code&gt; in the bundling config instead of &lt;code&gt;externalModules: []&lt;/code&gt;, CDK copies your &lt;code&gt;package-lock.json&lt;/code&gt; into a temp directory and runs &lt;code&gt;npm ci&lt;/code&gt;. If the lock file is even slightly out of sync with &lt;code&gt;package.json&lt;/code&gt;, it fails. I went back and forth on this for a while before landing on the &lt;code&gt;externalModules: []&lt;/code&gt; approach that bundles everything with esbuild and skips &lt;code&gt;npm ci&lt;/code&gt; entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AccessDeniedException&lt;/code&gt; on &lt;code&gt;ConverseStream&lt;/code&gt;.&lt;/strong&gt; This one got me. Bedrock now gives automatic access to all models in your region, so the old "Model Access" page is gone. But if your organization uses IAM (Identity and Access Management) policies or SCPs (Service Control Policies) to restrict which models can be invoked, you'll get this error. Check with your admin to make sure the model isn't blocked by a deny policy. I hit this when I tried to use Haiku for cheaper collection runs in an account that had an SCP limiting models to Sonnet only. The error message doesn't make it obvious that it's a permissions issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda times out but the agent is still running.&lt;/strong&gt; The Lambda timeout (15 min max) and the harness timeout (30 min) are separate. If the agent takes longer than the Lambda allows, the Lambda dies but the harness session keeps running in the background. Check CloudWatch logs for the Lambda, and use &lt;code&gt;agentcore logs --harness thepulse --since 1h&lt;/code&gt; for the harness side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reddit returns 403 in the browser.&lt;/strong&gt; Reddit blocks automated browser access. The system prompt tells the agent to use RSS instead, but if you're iterating on the prompt and forget to include that instruction, the agent will waste iterations trying to open Reddit in the browser. Ask me how I know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Estimate
&lt;/h2&gt;

&lt;p&gt;The main cost here is Bedrock model usage. Each hourly collection run uses around 10-15K tokens (browser interactions are chatty), and summary runs use more since they read all the accumulated data. Lambda and EventBridge are basically free at this scale.&lt;/p&gt;

&lt;p&gt;You can cut costs by enabling a cheaper model like Haiku in the Bedrock console and passing a model override for collection runs. Keep Sonnet for the summary runs that need more reasoning. The harness supports per-invocation model overrides so you don't need to redeploy. You can also just reduce the number of times it collects data.&lt;/p&gt;

&lt;p&gt;AgentCore Runtime and Browser have no separate charge during preview.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://aws.amazon.com/bedrock/pricing/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock pricing page&lt;/a&gt; for current model token costs and the &lt;a href="https://aws.amazon.com/bedrock/agentcore/pricing/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore pricing page&lt;/a&gt; for runtime and browser session rates.&lt;/p&gt;

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

&lt;p&gt;Make sure you always clean up your stacks after trying out tutorials!&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="c"&gt;# Delete the scheduler stack&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx cdk destroy ThePulseScheduler &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ../..

&lt;span class="c"&gt;# Delete the harness and all AgentCore resources&lt;/span&gt;
agentcore destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;agentcore destroy&lt;/code&gt; command removes the harness, memory, IAM role, and session storage. The CDK destroy removes the Lambda, EventBridge schedule, and any IAM roles CDK created.&lt;/p&gt;

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

&lt;p&gt;This is a starting point. I have thoughts on a few directions I could take my very own Jarvis 😊.&lt;/p&gt;

&lt;p&gt;Right now the Telegram integration is outbound only. I want to add two-way chat so I can message the bot and ask questions (without just using the command line). That means an &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; webhook and a second Lambda that invokes the harness in interactive mode. The agent reads from &lt;code&gt;/mnt/data&lt;/code&gt; to answer with real data.&lt;/p&gt;

&lt;p&gt;More sources would be easy to add. The system prompt is just markdown. Add instructions for other news sources that a browser can reach. The nice thing is it would just take a prompt update and a new run of &lt;code&gt;agentcore deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also want to try switching models per run. Enable Haiku in the Bedrock console and pass &lt;code&gt;--model-id&lt;/code&gt; on collection runs for cheaper hourly scrapes. Keep Sonnet for the summary runs that need more reasoning. The harness supports this per-invocation without a redeploy.&lt;/p&gt;

&lt;p&gt;And eventually, a dashboard. Have the agent generate an HTML page from the accumulated data and sync it to S3. Use &lt;code&gt;--exec&lt;/code&gt; to run &lt;code&gt;aws s3 sync&lt;/code&gt; from the microVM.&lt;/p&gt;

&lt;p&gt;After building this, I'm convinced the harness approach works really well for simpler use cases like my personal news Jarvis. If your agent needs persistent state, scheduled runs, and a browser, this is way less infrastructure than stitching it together yourself. If you just need a one-shot agent that answers a question, stick with Lambda. And of course if you need maximum flexibility, using AgentCore runtime is your best bet!&lt;/p&gt;

&lt;p&gt;Let me know in the comments what you think! Thanks!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore Harness docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore Browser docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-tools.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Connect to tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-memory.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Persist memory and filesystem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/agentcore-cli" rel="noopener noreferrer"&gt;AgentCore CLI @preview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/bedrock/agentcore/pricing/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Even Is AI? (I Took a Break &amp; Had to Relearn Everything)</title>
      <dc:creator>Rohini Gaonkar</dc:creator>
      <pubDate>Tue, 05 May 2026 18:38:06 +0000</pubDate>
      <link>https://forem.com/aws/what-even-is-ai-i-took-a-break-had-to-relearn-everything-3dpj</link>
      <guid>https://forem.com/aws/what-even-is-ai-i-took-a-break-had-to-relearn-everything-3dpj</guid>
      <description>&lt;p&gt;I just came back from maternity leave. And honestly? I felt like I'd missed a decade in six months. I talked about starting small in my other blog &lt;a href="https://dev.to/aws/lost-in-the-ai-hype-i-started-small-2a72"&gt;Lost in the AI Hype, I Started Small&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've spent the last fifteen years designing cloud systems. And even I felt behind. AI went from a thing people were experimenting with to a thing everyone's apparently &lt;em&gt;building&lt;/em&gt; with, and I had no idea where to start.&lt;/p&gt;

&lt;p&gt;So I did what any architect would do. I went back to first principles.&lt;/p&gt;

&lt;p&gt;I'm rebuilding my AI mental model from scratch in public. No math. No expert-level coding. Just real problems, the architecture underneath, and honest notes on where things might break.&lt;/p&gt;

&lt;p&gt;If you prefer video, please watch Episode 1 of my video series . If you prefer reading, you're in the right place.&lt;/p&gt;




&lt;h2&gt;
  
  
  The demo: AI adapts a recipe in under a minute
&lt;/h2&gt;

&lt;p&gt;Before any theory, let me show you what these models can actually do.&lt;/p&gt;

&lt;p&gt;I opened &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; Playground, pasted a real recipe, and asked three questions with each one pushing the model a little further:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Extract and summarise&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What are the core techniques in this recipe, strip off the fluff?"&lt;/p&gt;
&lt;/blockquote&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%2Fcygu18a807ztlemcekbc.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%2Fcygu18a807ztlemcekbc.png" alt="What are the core techniques in this recipe, stripped of the fluff" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clean, fast, useful. You might think: that's a fancy Ctrl+F (search). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Interpret and advise&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Looking at this recipe, what's the thing that's most likely to go wrong for someone cooking it for the first time?"&lt;/p&gt;
&lt;/blockquote&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%2F88ldw6zizl6gyezfju96.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%2F88ldw6zizl6gyezfju96.png" alt="Looking at this recipe, what's the thing that's most likely to go wrong for someone cooking it for the first time?" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we're somewhere a search tool genuinely can't go. The model is &lt;strong&gt;reasoning&lt;/strong&gt; about the recipe like spotting the bit where people actually mess up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Personalise&lt;/strong&gt;&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, give me a shopping list, and a timeline starting from 4pm."&lt;/p&gt;
&lt;/blockquote&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%2Fzmizw0pgi3uuo9zkks45.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%2Fzmizw0pgi3uuo9zkks45.png" alt="I'm cooking this for six people on Saturday. One is vegan, one is gluten-free. Adapt the recipe, give me a shopping list, and a timeline starting from 4pm." width="591" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the moment. I asked it something I'd normally spend twenty minutes thinking through. It gave me a starting point in ten seconds.&lt;/p&gt;

&lt;p&gt;If you're curious but not technical, that's already useful. &lt;/p&gt;

&lt;p&gt;If you're a builder, you're probably already thinking so what happened here.&lt;/p&gt;




&lt;h2&gt;
  
  
  So what actually happened?
&lt;/h2&gt;

&lt;p&gt;Here's the architecture, as simply as I can put it.&lt;/p&gt;

&lt;p&gt;I sent text called a &lt;strong&gt;prompt&lt;/strong&gt; to a &lt;strong&gt;foundation model&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;People throw around terms like AI, LLMs, and foundation models like they all mean the same thing but they don’t.&lt;/p&gt;

&lt;p&gt;AI is the broad umbrella. It includes everything from recommendation engines and fraud detection systems to generative AI tools like ChatGPT.&lt;/p&gt;

&lt;p&gt;Foundation models are a subset of AI, they are large models trained on massive datasets that can be adapted for different tasks. These aren’t just text models; they can generate images, video, speech, code, and more. Platforms like &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; give access to many of these models.&lt;/p&gt;

&lt;p&gt;LLMs (Large Language Models) are a specific type of foundation model built for language tasks like answering questions, summarizing text, writing, or coding. So in my recipe demo, I was technically interacting with an LLM.&lt;/p&gt;

&lt;p&gt;The simplest way to think about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI → Foundation Models → LLMs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, in our case it means its a big model trained on a huge mix of data for your day to day general purpose. &lt;/p&gt;

&lt;p&gt;The model is a piece of software trained on an enormous amount of text: books, articles, code, conversations. It is &lt;em&gt;not&lt;/em&gt; searching the internet. It learned patterns from all that text beforehand.&lt;/p&gt;

&lt;p&gt;When I give it my prompt, it &lt;strong&gt;predicts the most useful response&lt;/strong&gt; based on everything it learned.&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;I've been building distributed systems for years, and a foundation model call is simpler than most of the APIs I'm used to. It's an HTTP request with text in, text out. &lt;/p&gt;

&lt;p&gt;The complexity isn't in the call itself, it is in what the model learned before you or I ever showed up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And this exact loop is what the entire current wave of AI is built on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time you see a new Claude, or GPT, or Llama land, what's actually happening is someone trained a bigger or smarter version of this same idea.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same loop. More data. Better prediction.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it breaks
&lt;/h2&gt;

&lt;p&gt;The model doesn't know if it's right. It's predicting what a useful answer &lt;em&gt;looks like&lt;/em&gt;. Sometimes that prediction is brilliant. Sometimes it invents something that sounds plausible and is completely wrong.&lt;/p&gt;

&lt;p&gt;Every time you use one of these tools, ask yourself: &lt;strong&gt;what would I need to double-check before I trusted this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question is the single most useful habit you can build right now. We'll dig into &lt;em&gt;why&lt;/em&gt; this happens in the next post.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the models live: Amazon Bedrock
&lt;/h2&gt;

&lt;p&gt;You might've noticed I wasn't using ChatGPT or Claude's own website. I was using &lt;strong&gt;&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;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Bedrock is where a bunch of foundation models live on AWS. Anthropic's Claude, Meta's Llama, Mistral, Amazon's own models, they are all callable through Bedrock, no need to run or train anything yourself.&lt;/p&gt;

&lt;p&gt;The Playground is the easy door in, just type and go. Later in this series, when we start building, we'll call these same models from code. Same models, different door.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on my stack
&lt;/h2&gt;

&lt;p&gt;I work at AWS. So the tools I use in this series are AWS tools like Bedrock for the models, and later, an AI-powered IDE called Kiro for building.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;concepts&lt;/em&gt;, though, aren't AWS specific. Foundation models, tokens, context windows, RAG, agents, these work the same way on any cloud. I'm showing you my stack. And honestly, I'm still figuring out which parts of it are great and which parts are a pain. You'll know which is which.&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; open any AI chat tool (Bedrock Playground, Claude, ChatGPT, whatever you have access to), paste a recipe, a contract, a long email and ask it three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One to summarise.&lt;/li&gt;
&lt;li&gt;One to interpret.&lt;/li&gt;
&lt;li&gt;One that's personal to you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;See what happens. That's your homework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're more on the builder side:&lt;/strong&gt; the mental model is simple: text in, model, text out. Everything we build in this series is a variation on that loop.&lt;/p&gt;




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

&lt;p&gt;Next up: when AI sounds confident and is completely wrong. Why it happens, how to spot it, how to stop it.&lt;/p&gt;

&lt;p&gt;This is a series. I'm learning this in public, building as I go, and being honest when things don't work. If that sounds useful, please  follow 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;a href="https://www.youtube.com/playlist?list=PLTuh5MoXKZTwoeROV-bA4_6maw0FIoeEf" rel="noopener noreferrer"&gt;Watch the video version&lt;/a&gt; or follow the series on dev.to.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>aws</category>
      <category>learning</category>
    </item>
    <item>
      <title>Modern Container Builds and WebSocket APIs Come to AWS SAM</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Tue, 05 May 2026 18:09:06 +0000</pubDate>
      <link>https://forem.com/aws/modern-container-builds-and-websocket-apis-come-to-aws-sam-8gm</link>
      <guid>https://forem.com/aws/modern-container-builds-and-websocket-apis-come-to-aws-sam-8gm</guid>
      <description>&lt;p&gt;SAM CLI has always been good at taking the grunt work out of serverless deployments. You define your functions and APIs in a template, and SAM handles the CloudFormation, the packaging, the deployment. It works.&lt;/p&gt;

&lt;p&gt;Until recently, two things were missing. BuildKit support for container image builds. And a SAM resource type for WebSocket APIs.&lt;/p&gt;

&lt;p&gt;Both are now shipped. Neither breaks existing templates.&lt;/p&gt;

&lt;p&gt;Let's walk through it.&lt;/p&gt;

&lt;p&gt;BuildKit Support for Image-Based Lambda Functions&lt;br&gt;
The problem&lt;br&gt;
When you run sam build for an image-based Lambda function, SAM uses the docker-py Python SDK under the hood. That SDK talks to the Docker daemon directly, but it doesn't support BuildKit. At all.&lt;/p&gt;

&lt;p&gt;That means every sam build invocation uses the legacy Docker builder. You lose parallelized build stages. You lose efficient layer caching. You lose multi-stage build optimizations. You lose cross-architecture improvements that BuildKit has shipped over the past several years.&lt;/p&gt;

&lt;p&gt;For simple single-stage Dockerfiles, this probably doesn't matter. For anything with multiple stages, private dependencies, or cross-compilation targets, it's a real bottleneck.&lt;/p&gt;

&lt;p&gt;What shipped&lt;br&gt;
SAM CLI v1.156.0 introduced the --use-buildkit flag on sam build. When you pass this flag, SAM bypasses docker-py entirely and shells out to the Docker CLI (or Finch CLI) directly. That gives you access to everything BuildKit offers.&lt;/p&gt;

&lt;p&gt;sam build --use-buildkit&lt;br&gt;
That's it. One flag.&lt;/p&gt;

&lt;p&gt;SAM auto-detects which container runtime you have. It defaults to Docker, falls back to Finch if Docker isn't running, and respects any admin-configured preference. Finch supports BuildKit too, so either runtime works.&lt;/p&gt;

&lt;p&gt;What you get&lt;br&gt;
BuildKit brings real improvements over the legacy builder:&lt;/p&gt;

&lt;p&gt;Parallel stage execution. Multi-stage Dockerfiles build independent stages concurrently instead of sequentially.&lt;br&gt;
Better layer caching. BuildKit tracks dependencies at the file level, not just the layer level. Change one file, rebuild one layer.&lt;br&gt;
Multi-stage build optimizations. BuildKit skips stages that don't contribute to the final output. The legacy builder runs every stage regardless.&lt;br&gt;
Improved cross-architecture support. Building arm64 (Graviton2) images on an x86 machine is more reliable with BuildKit's QEMU integration. Lambda currently supports Graviton2 for arm64 workloads.&lt;br&gt;
Build secrets&lt;br&gt;
SAM CLI v1.159.0 added support for passing BuildKit parameters, including build-time secrets. This lets you pass credentials into a build stage without baking them into a layer. Useful when your Lambda function pulls packages from a private registry during the build.&lt;/p&gt;

&lt;h1&gt;
  
  
  syntax=docker/dockerfile:1
&lt;/h1&gt;

&lt;p&gt;FROM public.ecr.aws/lambda/python:3.12&lt;/p&gt;

&lt;p&gt;RUN --mount=type=secret,id=pip_conf,target=/etc/pip.conf \&lt;br&gt;
    pip install -r requirements.txt&lt;/p&gt;

&lt;p&gt;COPY app.py ${LAMBDA_TASK_ROOT}&lt;br&gt;
CMD ["app.handler"]&lt;br&gt;
You configure secrets through the Metadata section of your function resource, using DockerBuildExtraParams. SAM passes these parameters straight to docker buildx under the hood.&lt;/p&gt;

&lt;p&gt;Resources:&lt;br&gt;
  MyFunction:&lt;br&gt;
    Type: AWS::Serverless::Function&lt;br&gt;
    Properties:&lt;br&gt;
      PackageType: Image&lt;br&gt;
      Architectures: [x86_64]&lt;br&gt;
      Timeout: 10&lt;br&gt;
    Metadata:&lt;br&gt;
      Dockerfile: Dockerfile&lt;br&gt;
      DockerContext: ./src&lt;br&gt;
      DockerTag: latest&lt;br&gt;
      DockerBuildExtraParams:&lt;br&gt;
        - "--secret"&lt;br&gt;
        - "id=pip_conf,src=$HOME/.pip/pip.conf"&lt;br&gt;
The secret mounts into the build stage at the path you specify in the Dockerfile, gets used during pip install, and never appears in the final image layers. Different functions can have different build secrets, since the configuration lives in each function's Metadata block.&lt;/p&gt;

&lt;p&gt;Tradeoffs and limitations&lt;br&gt;
A few things to keep in mind.&lt;/p&gt;

&lt;p&gt;Opt-in only. This doesn't change existing build behavior. If you don't pass --use-buildkit, nothing changes. That's intentional. SAM doesn't break working builds.&lt;/p&gt;

&lt;p&gt;Requires Docker or Finch CLI. BuildKit support works by calling the Docker or Finch CLI directly. If your CI environment only has the Docker daemon (no CLI), this won't work. Most environments have both, but check yours.&lt;/p&gt;

&lt;p&gt;Dockerfile syntax matters. Some BuildKit features, like secret mounts, require the # syntax=docker/dockerfile:1 parser directive at the top of your Dockerfile. Without it, the build falls back to legacy parsing and you get confusing errors.&lt;/p&gt;

&lt;p&gt;Not for ZIP-based functions. This flag only applies to image-based Lambda functions (PackageType: Image). ZIP-based functions don't use Docker at all.&lt;/p&gt;

&lt;p&gt;The rule of thumb I like is this: if your Dockerfile is more than a few lines, or you're building for a different architecture, turn on BuildKit. The caching alone will save you time.&lt;/p&gt;

&lt;p&gt;WebSocket API Support&lt;br&gt;
The problem&lt;br&gt;
Before this release, SAM had no native resource type for WebSocket APIs. You had two options: write raw CloudFormation, or don't use SAM for that part of your stack.&lt;/p&gt;

&lt;p&gt;Here's what a minimal WebSocket API looks like in plain CloudFormation. Three routes ($connect, $disconnect, sendMessage), each backed by a Lambda function:&lt;/p&gt;

&lt;p&gt;Resources:&lt;br&gt;
  WebSocketApi:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Api&lt;br&gt;
    Properties:&lt;br&gt;
      Name: MyWebSocketApi&lt;br&gt;
      ProtocolType: WEBSOCKET&lt;br&gt;
      RouteSelectionExpression: $request.body.action&lt;/p&gt;

&lt;p&gt;ConnectRoute:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Route&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      RouteKey: $connect&lt;br&gt;
      Target: !Sub integrations/${ConnectIntegration}&lt;/p&gt;

&lt;p&gt;ConnectIntegration:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Integration&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      IntegrationType: AWS_PROXY&lt;br&gt;
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectFunction.Arn}/invocations&lt;/p&gt;

&lt;p&gt;ConnectPermission:&lt;br&gt;
    Type: AWS::Lambda::Permission&lt;br&gt;
    Properties:&lt;br&gt;
      FunctionName: !Ref ConnectFunction&lt;br&gt;
      Action: lambda:InvokeFunction&lt;br&gt;
      Principal: apigateway.amazonaws.com&lt;br&gt;
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*/$connect&lt;/p&gt;

&lt;p&gt;DisconnectRoute:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Route&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      RouteKey: $disconnect&lt;br&gt;
      Target: !Sub integrations/${DisconnectIntegration}&lt;/p&gt;

&lt;p&gt;DisconnectIntegration:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Integration&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      IntegrationType: AWS_PROXY&lt;br&gt;
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DisconnectFunction.Arn}/invocations&lt;/p&gt;

&lt;p&gt;DisconnectPermission:&lt;br&gt;
    Type: AWS::Lambda::Permission&lt;br&gt;
    Properties:&lt;br&gt;
      FunctionName: !Ref DisconnectFunction&lt;br&gt;
      Action: lambda:InvokeFunction&lt;br&gt;
      Principal: apigateway.amazonaws.com&lt;br&gt;
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*/$disconnect&lt;/p&gt;

&lt;p&gt;SendMessageRoute:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Route&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      RouteKey: sendMessage&lt;br&gt;
      Target: !Sub integrations/${SendMessageIntegration}&lt;/p&gt;

&lt;p&gt;SendMessageIntegration:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Integration&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      IntegrationType: AWS_PROXY&lt;br&gt;
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SendMessageFunction.Arn}/invocations&lt;/p&gt;

&lt;p&gt;SendMessagePermission:&lt;br&gt;
    Type: AWS::Lambda::Permission&lt;br&gt;
    Properties:&lt;br&gt;
      FunctionName: !Ref SendMessageFunction&lt;br&gt;
      Action: lambda:InvokeFunction&lt;br&gt;
      Principal: apigateway.amazonaws.com&lt;br&gt;
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*/sendMessage&lt;/p&gt;

&lt;p&gt;Deployment:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Deployment&lt;br&gt;
    DependsOn:&lt;br&gt;
      - ConnectRoute&lt;br&gt;
      - DisconnectRoute&lt;br&gt;
      - SendMessageRoute&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;/p&gt;

&lt;p&gt;Stage:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Stage&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      StageName: prod&lt;br&gt;
      DeploymentId: !Ref Deployment&lt;br&gt;
That's twelve resources for three routes. Around 90 lines of YAML. And I left out the Lambda function definitions.&lt;/p&gt;

&lt;p&gt;Notice the DependsOn on the Deployment resource. If you forget that, CloudFormation tries to create the deployment before the routes exist, and the stack fails. You have to manage that dependency graph yourself.&lt;/p&gt;

&lt;p&gt;That's a lot of ceremony for "connect a WebSocket to some Lambda functions."&lt;/p&gt;

&lt;p&gt;What shipped&lt;br&gt;
The new AWS::Serverless::WebSocketApi resource type collapses all of that into this:&lt;/p&gt;

&lt;p&gt;Resources:&lt;br&gt;
  MyWebSocketApi:&lt;br&gt;
    Type: AWS::Serverless::WebSocketApi&lt;br&gt;
    Properties:&lt;br&gt;
      RouteSelectionExpression: $request.body.action&lt;br&gt;
      StageName: prod&lt;br&gt;
      Routes:&lt;br&gt;
        $connect:&lt;br&gt;
          FunctionArn: !GetAtt ConnectFunction.Arn&lt;br&gt;
        $disconnect:&lt;br&gt;
          FunctionArn: !GetAtt DisconnectFunction.Arn&lt;br&gt;
        sendMessage:&lt;br&gt;
          FunctionArn: !GetAtt SendMessageFunction.Arn&lt;br&gt;
Compare that to the CloudFormation version. Twelve resources become one. Ninety lines become twelve. The routes, integrations, Lambda permissions, deployment, stage, and resource ordering are all handled by SAM's transform.&lt;/p&gt;

&lt;p&gt;You define the routes. SAM generates the rest.&lt;/p&gt;

&lt;p&gt;What SAM handles automatically&lt;br&gt;
For each route you declare, SAM creates:&lt;/p&gt;

&lt;p&gt;The AWS::ApiGatewayV2::Route resource&lt;br&gt;
The AWS::ApiGatewayV2::Integration wiring the route to your Lambda function&lt;br&gt;
The AWS::Lambda::Permission granting API Gateway invoke access&lt;br&gt;
If you add a Lambda authorizer, the authorizer permission too&lt;br&gt;
SAM also creates the deployment and stage resources, with the correct dependency ordering. No DependsOn blocks to manage.&lt;/p&gt;

&lt;p&gt;Full feature parity&lt;br&gt;
This isn't a simplified subset. The new resource type supports everything API Gateway V2 WebSocket offers:&lt;/p&gt;

&lt;p&gt;Auth: IAM authorization and Lambda authorizers, per-route or API-wide&lt;br&gt;
Custom domains: Map your WebSocket API to your own domain&lt;br&gt;
Route settings: Configure throttling and logging per route via RouteSettings&lt;br&gt;
Models: Attach request/response models for validation&lt;br&gt;
Stage variables: Pass configuration to your integration through stage variables&lt;br&gt;
Globals: Share configuration across multiple WebSocket APIs using the SAM Globals section&lt;br&gt;
Use cases&lt;br&gt;
WebSocket APIs are the right tool when you need a persistent, bidirectional connection. Common patterns:&lt;/p&gt;

&lt;p&gt;Chat applications. Users send and receive messages in real time.&lt;br&gt;
Live dashboards. Push metric updates to connected browsers without polling.&lt;br&gt;
AI/LLM streaming. Stream token-by-token responses from a model back to the client. This one is increasingly common.&lt;br&gt;
IoT command channels. Send commands to devices and receive status updates on the same connection.&lt;br&gt;
If your current approach is a REST API that the client polls every few seconds, a WebSocket API will give you lower latency and lower cost. Fewer requests, fewer Lambda invocations, faster updates.&lt;/p&gt;

&lt;p&gt;Tradeoffs and limitations&lt;br&gt;
No local emulation. SAM CLI doesn't support sam local start-api for WebSocket APIs. You can test individual Lambda handlers with sam local invoke, but end-to-end local WebSocket testing isn't available yet. Deploy to a dev stage for integration testing.&lt;/p&gt;

&lt;p&gt;No sam local start-websocket. Related to the above, there's no dedicated local command for WebSocket APIs like there is for HTTP APIs with sam local start-api.&lt;/p&gt;

&lt;p&gt;For the Agents&lt;br&gt;
If you're using an AI coding agent to build SAM applications, both of these features work out of the box with agent-driven workflows. Your agent can scaffold a WebSocket API or add BuildKit to an existing image-based function without any special setup.&lt;/p&gt;

&lt;p&gt;For Kiro, there's an official AWS SAM Power that gives your agent SAM-aware tooling. Install it and your agent gets access to sam_init, sam_build, sam_deploy, sam_logs, and sam_local_invoke as callable tools, plus opinionated project structure guidance.&lt;/p&gt;

&lt;p&gt;Here's what a Kiro-assisted WebSocket API scaffold looks like in practice:&lt;/p&gt;

&lt;p&gt;You: Create a WebSocket API with connect, disconnect, and sendMessage routes.&lt;br&gt;
     Use the SAM Power.&lt;/p&gt;

&lt;p&gt;Kiro: [runs sam_init] → creates project structure&lt;br&gt;
      [updates template.yaml] → adds AWS::Serverless::WebSocketApi&lt;br&gt;
      [creates Lambda handlers] → connect.py, disconnect.py, send_message.py&lt;br&gt;
      [runs sam_build] → builds the project&lt;br&gt;
      [runs sam_local_invoke] → tests the connect handler locally&lt;br&gt;
The SAM Power also enforces good project structure: separate Lambda handlers in infrastructure/lambda/, proper CodeUri paths, and .aws-sam in your .gitignore. It gets you from idea to deployed WebSocket API without hand-writing boilerplate.&lt;/p&gt;

&lt;p&gt;You can install the SAM Power from the Kiro Powers marketplace or add it directly to your project's .kiro/powers/ directory.&lt;/p&gt;

&lt;p&gt;Getting Started&lt;br&gt;
Upgrade SAM CLI to get both features:&lt;/p&gt;

&lt;p&gt;sam --version&lt;/p&gt;

&lt;h1&gt;
  
  
  Need v1.156.0 or later for BuildKit, latest for WebSocket APIs
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Upgrade via pip
&lt;/h1&gt;

&lt;p&gt;pip install --upgrade aws-sam-cli&lt;/p&gt;

&lt;h1&gt;
  
  
  Or via Homebrew
&lt;/h1&gt;

&lt;p&gt;brew upgrade aws-sam-cli&lt;br&gt;
For BuildKit, add --use-buildkit to your sam build command. No template changes needed.&lt;/p&gt;

&lt;p&gt;For WebSocket APIs, replace your CloudFormation resources with the new AWS::Serverless::WebSocketApi type. If you're starting fresh, the SAM template above is a working starting point.&lt;/p&gt;

&lt;p&gt;BuildKit works with sam local for local testing. WebSocket APIs currently support sam deploy for deployment and sam local invoke for testing individual handlers, but full local WebSocket emulation isn't available yet.&lt;/p&gt;

&lt;p&gt;Two Features, Zero Breaking Changes&lt;br&gt;
These two features fill gaps that have been open for a while. BuildKit support means sam build finally uses the same build engine as the rest of the container ecosystem. WebSocket API support means you can define a real-time API in SAM the same way you define a REST API. A few lines instead of a hundred.&lt;/p&gt;

&lt;p&gt;Neither feature changes existing behavior. Both are additive. Upgrade, try them, and keep building.&lt;/p&gt;

</description>
      <category>awssam</category>
      <category>apigateway</category>
      <category>websockets</category>
      <category>docker</category>
    </item>
    <item>
      <title>The Next Casualty of the GenAI Revolution</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Tue, 05 May 2026 13:25:34 +0000</pubDate>
      <link>https://forem.com/aws/the-next-casualty-of-the-genai-revolution-3in7</link>
      <guid>https://forem.com/aws/the-next-casualty-of-the-genai-revolution-3in7</guid>
      <description>&lt;p&gt;I was at an event this morning where someone said something that really resonated with me. "The new programming language is English." And you know what? They're right. &lt;a href="https://www.moneycontrol.com/technology/most-powerful-programming-language-of-the-future-isn-t-c-or-python-it-s-says-nvidia-ceo-jensen-huang-article-13824846.html" rel="noopener noreferrer"&gt;Jensen Huang&lt;/a&gt; said the same thing. &lt;a href="https://www.theguardian.com/technology/2025/mar/16/ai-software-coding-programmer-expertise-jobs-threat" rel="noopener noreferrer"&gt;Andrej Karpathy&lt;/a&gt; has been saying it since 2023. And &lt;a href="https://a16z.com/podcast/marc-andreessen-and-amjad-masad-english-as-the-new-programming-language/" rel="noopener noreferrer"&gt;Marc Andreessen did a whole podcast about it&lt;/a&gt;. But while I was sitting there nodding along, something else clicked in my head. Something bigger. Something that I think we're not talking about enough.&lt;/p&gt;

&lt;p&gt;We talk a lot about the 'casualties' of GenAI. Students who are terrified they won't have jobs waiting for them after university. Junior developers who are finding it brutally hard to land a position in today's market. The whole "should I even study Computer Science?" existential crisis. These are real, painful, happening-right-now problems.&lt;/p&gt;

&lt;p&gt;But here is where it gets interesting. I think there's another casualty coming. One that's a few years out maybe, but it dawned on me this week, and I can't shake it. And it's a big one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The personal computer.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your agent doesn't need a MacBook Pro
&lt;/h2&gt;

&lt;p&gt;Let me explain what I mean. We're seeing more and more systems where you provide a prompt to an AI tool and it goes out and does stuff for you. A very good example of this is all these &lt;a href="https://www.anthropic.com/news/model-context-protocol" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; connections, &lt;a href="https://jangwook.net/en/blog/en/mcp-vs-a2a-vs-open-responses-agent-protocol-comparison-2026/" rel="noopener noreferrer"&gt;A2A&lt;/a&gt; protocols that hook into every system you use and do things on your behalf. They're becoming your personal assistant. Your digital butler. Your &lt;em&gt;"I'll handle it"&lt;/em&gt; person.&lt;/p&gt;

&lt;p&gt;And the way we interact with these assistants? Usually through some kind of voice interface or through instant messaging. WhatsApp, Telegram, whatever works for you. And these are the tools which I think, once upon a time, we &lt;strong&gt;hoped&lt;/strong&gt; would be the way we'd interact with our computers and phones. Siri. Alexa. Google Assistant. Gemini. &lt;em&gt;God forbid, Cortana&lt;/em&gt;. You speak to an entity and that entity does stuff for you.&lt;/p&gt;

&lt;p&gt;But they were never really able to deliver on what they were supposed to. I use my Alexa device to pretty much only turn on a timer for when I am cooking, or ask questions about how much 350 degrees Fahrenheit is in Celsius? Until now.&lt;/p&gt;

&lt;p&gt;By hooking up multiple tools to some kind of orchestrator that can do everything for you and &lt;strong&gt;actually&lt;/strong&gt; become your personal assistant, you are now able to control pretty much your entire life by talking into your phone or writing a message. Think about that for a second. Projects like &lt;a href="https://github.com/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; and its growing list of alternatives are doing exactly this. They connect to your email, your calendar, your messaging apps, your code repos, your design tools, sometimes securely, sometimes... let's just say it's a work in progress. But the point is, they're turning a simple chat interface into a full-blown command center for your digital life. And they're running on a server somewhere, not on your laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I really need a powerful laptop?
&lt;/h2&gt;

&lt;p&gt;I'll give you a personal example. Last week I needed to put together a presentation for an upcoming talk. In the past, I would have fired up PowerPoint on my laptop, spent an hour fiddling with layouts and formatting, maybe opened Photoshop to tweak an image or two. Instead, I described what I wanted to an AI assistant, and it generated the whole deck for me. Slides, structure, speaker notes, the lot. My laptop was barely breaking a sweat because &lt;strong&gt;it&lt;/strong&gt; wasn't doing the work. The agent was.&lt;/p&gt;

&lt;p&gt;What I think is going to happen in the not-too-distant future is, we're not going to &lt;strong&gt;need&lt;/strong&gt; all these powerful laptops and desktops anymore. All you'll need is some kind of personal agent running somewhere. It could be a VPS. Could be for example, in the cloud running on &lt;a href="https://aws.amazon.com/blogs/aws/introducing-openclaw-on-amazon-lightsail-to-run-your-autonomous-private-ai-agents/" rel="noopener noreferrer"&gt;Lightsail&lt;/a&gt;. Could be on a Mac Mini humming away in a cupboard at home. And that agent will be able to do &lt;strong&gt;everything&lt;/strong&gt; for you.&lt;/p&gt;

&lt;p&gt;And a way to interact with it, and we all have that, it's your phone.&lt;/p&gt;

&lt;p&gt;I can ask it to design something in a graphic design tool. Actually, scratch that. I don't even need Photoshop or Figma anymore because I can generate those things directly with an AI prompt. I can ask it to build me a website. I can ask it to write code, test it, deploy it. I can ask it to go into Canva and create a presentation. Or just skip Canva entirely and have it generate the slides from scratch.&lt;/p&gt;

&lt;p&gt;You see where this is going?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don't need a powerful computer at my fingertips anymore.&lt;/strong&gt; In the background, running all my workloads, executing my commands and requests, my agent doing it for me and that machine is running somewhere else. All I need is a simple, almost dumb, interface into something that can listen to me and relay my intent.&lt;/p&gt;

&lt;p&gt;Here's another one. I'm writing this very blog post while sitting in the car. I'm not typing. I'm talking. A voice note, dictated into my phone, transcribed into text, and then polished by an AI into something readable. My phone is doing the absolute bare minimum here. Recording audio and sending it somewhere smarter than itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  English is the new programming language
&lt;/h2&gt;

&lt;p&gt;And let me tell you, it's a &lt;strong&gt;hell&lt;/strong&gt; of a lot quicker than typing it out by hand. The same way it's a hell of a lot quicker to ask an LLM to generate code for me instead of me actually writing and typing that code character by character. I can speak a lot faster than I can type. I can talk without having to put my fingers to use. And usually, I can do it while doing something else entirely. Multi-tasking for the win.&lt;/p&gt;

&lt;p&gt;And this is precisely the point. We don't really need to know programming languages anymore (well, not &lt;em&gt;all&lt;/em&gt; of us, not for &lt;em&gt;everything&lt;/em&gt;) because we delegate our tasks and intent into spoken language, which is exactly what an LLM is &lt;strong&gt;amazing&lt;/strong&gt; at doing. Taking that natural language and transferring it into something else. A command. A JSON payload. An API call. A full-blown application. &lt;a href="https://www.startuphub.ai/news/software-30-the-english-revolution-in-computing" rel="noopener noreferrer"&gt;Karpathy calls it Software 3.0&lt;/a&gt;. I think he's onto something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers are already moving
&lt;/h2&gt;

&lt;p&gt;And here's the thing. The numbers are already starting to tell the story, even if the reasons are different today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-02-26-gartner-says-surging-memory-costs-will-reduce-global-pc-and-smartphone-shipments-in-2026" rel="noopener noreferrer"&gt;Gartner is projecting PC shipments to decline 10.4% in 2026&lt;/a&gt;. &lt;a href="https://computerworld.com/article/4138726/memory-shortage-batters-pc-market-double-digit-sales-drop-coming-say-analysts.html" rel="noopener noreferrer"&gt;IDC is calling it 11.3%&lt;/a&gt;. &lt;a href="https://ca.finance.yahoo.com/news/goldman-ai-pcs-buck-10-003812328.html" rel="noopener noreferrer"&gt;Goldman Sachs says 10%&lt;/a&gt;. Right now, the analysts are blaming memory costs and pricing pressures. Fair enough. But I think there's a deeper, more structural shift happening underneath, and coupled with the rising prices in memory, this could actually be a &lt;a href="https://en.wikipedia.org/wiki/Perfect_storm" rel="noopener noreferrer"&gt;perfect storm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If my agent can do the heavy lifting running on a remote machine, why do I need a physical computer? A MacBook Pro costs around USD 1,600 today. A VPS with 4 GB memory, 2 vCPUs, 80 GB SSD and 4 TB transfer will run you about USD 24 /month. Over 4 years that's USD 1,152. &lt;br&gt;
That's &lt;strong&gt;USD 450&lt;/strong&gt; less than the MacBook. And I know the specs are not at all the same, but that's kind of the whole point. Your agent doesn't need a Retina display or a force-touch trackpad. It needs compute, memory, and an internet connection. Oh, and it runs 24/7 in an enterprise datacenter with faaster connectivity than you could dream of, where no one can spill a cup of coffee on it.&lt;/p&gt;

&lt;p&gt;And if you need more proof that the industry sees where this is heading, look no further than Apple. They just launched the &lt;a href="https://www.cnbc.com/2026/03/04/apple-macbook-neo-budget-laptop.html" rel="noopener noreferrer"&gt;MacBook Neo&lt;/a&gt; at $599. &lt;strong&gt;Apple&lt;/strong&gt;. The company that has historically refused to compete on price. The company whose cheapest laptop was $1,099 just a year ago. They're now selling what is essentially a glorified Chromebook with a fruit logo on it. Why? Because even Apple can see that for most people, a lightweight, always-connected, thin-client device is going to be &lt;strong&gt;enough&lt;/strong&gt;. You don't need an M4 chip to talk to your agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  But Maish, what about...?
&lt;/h2&gt;

&lt;p&gt;Look, I'm not saying the PC is going to disappear overnight. Gamers will still need their rigs (for now). Video editors, 3D artists, and data scientists will still need local horsepower (for now). Developers who run local Kubernetes clusters will still need their 64GB RAM machines (don't worry, I am not one of K8s groupie).&lt;/p&gt;

&lt;p&gt;But for the vast majority of knowledge workers? The person who writes emails, creates presentations, manages spreadsheets, edits documents, and hops between 47 browser tabs? That person doesn't need a $2,000 laptop. That person needs a screen, a microphone, and a connection to their agent.&lt;/p&gt;

&lt;p&gt;Some food for thought. We went from mainframes to personal computers because we wanted computing power at our desks. Then we went from desktops to laptops because we wanted that power to be portable. Then from laptops to smartphones because we wanted it in our pockets. And now? Now we might be going from all of that back to... something that looks a lot like a dumb terminal connected to a really smart backend.&lt;/p&gt;

&lt;p&gt;History might not repeat itself. But it sure does rhyme.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question we should be asking
&lt;/h2&gt;

&lt;p&gt;So while the world is debating one set of consequences from AI, I think we're missing a completely different one.&lt;/p&gt;

&lt;p&gt;Are we going to see a significant, sustained decline in personal computer sales? Not because of memory prices or economic cycles, but because we fundamentally won't &lt;strong&gt;need&lt;/strong&gt; them anymore?&lt;/p&gt;

&lt;p&gt;I think the answer is yes. Maybe not this year. Maybe not next year. But it's coming. And when it does, it won't be a blip on a quarterly earnings report. It'll be a structural shift in how we think about personal computing.&lt;/p&gt;

&lt;p&gt;The PC had a good run. But the dumb terminal is making a comeback. And this time, it's got a &lt;strong&gt;really&lt;/strong&gt; smart backend.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>aws</category>
      <category>genai</category>
    </item>
    <item>
      <title>How to Prevent AI Agent Reasoning Loops from Wasting Tokens</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Mon, 04 May 2026 23:00:00 +0000</pubDate>
      <link>https://forem.com/aws/how-to-prevent-ai-agent-reasoning-loops-from-wasting-tokens-2652</link>
      <guid>https://forem.com/aws/how-to-prevent-ai-agent-reasoning-loops-from-wasting-tokens-2652</guid>
      <description>&lt;p&gt;&lt;strong&gt;AI agent reasoning loops&lt;/strong&gt; occur when an agent calls the same tool repeatedly without making progress, convinced that one more attempt will produce the perfect answer. The agent wastes tokens, time, and money without delivering a result. This post shows how to detect and block repeated calls, validated with a demo where ambiguous tools caused 14 calls vs clear SUCCESS states that stopped in 2.&lt;/p&gt;

&lt;p&gt;This demo uses &lt;a href="https://strandsagents.com/docs/" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;. The patterns — debounce hooks, clear tool states, and call limits — are framework-agnostic and apply to any agent that supports lifecycle hooks, including LangGraph, AutoGen, and CrewAI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working code:&lt;/strong&gt; &lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/03-reasoning-loops-demo" rel="noopener noreferrer"&gt;github.com/aws-samples/sample-why-agents-fail&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Series: Why AI Agents Fail
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/01-context-overflow-demo" rel="noopener noreferrer"&gt;Context Window Overflow&lt;/a&gt;&lt;/strong&gt; — Memory Pointer Pattern for large data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/aws-samples/sample-why-agents-fail/tree/main/stop-ai-agents-wasting-tokens/02-mcp-timeout-demo" rel="noopener noreferrer"&gt;MCP Tools That Never Respond&lt;/a&gt;&lt;/strong&gt; — Async pattern for slow external APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent Reasoning Loops&lt;/strong&gt; (this post) — Detect and block repeated tool calls&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Problem: Agents That Overthink
&lt;/h2&gt;

&lt;p&gt;AI agent reasoning loops occur when an agent calls the same tool repeatedly without making progress, wasting tokens and time without delivering a result. AI agents don't just fail by giving wrong answers; they fail by never finishing. Research shows agents get trapped in reasoning loops where they call the same tool repeatedly, convinced that "one more step" will produce the perfect answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://the-decoder.com/language-models-can-overthink-and-get-stuck-in-endless-thought-loops/" rel="noopener noreferrer"&gt;The Decoder (Jan 2025)&lt;/a&gt; found that even with unlimited computing power, overthinking leads to poor decisions. Incomplete understanding of the world causes compounding errors. Each additional reasoning step makes things worse, not better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://particula.tech/blog/ai-agent-loops-reasoning-steps-optimization" rel="noopener noreferrer"&gt;Particula (Jul 2025)&lt;/a&gt; (community observation) documented an extreme case: an agent executed &lt;strong&gt;847 reasoning steps&lt;/strong&gt; at &lt;strong&gt;$47 per minute&lt;/strong&gt; and never delivered a final answer. It kept refining logic, questioning conclusions, and requesting more data in an endless cycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codieshub.com/for-ai/prevent-agent-loops-costs" rel="noopener noreferrer"&gt;CodiesHub (Dec 2025)&lt;/a&gt; (community observation) identifies the root causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unclear goals&lt;/strong&gt; — agent doesn't know when the task is complete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguous tool feedback&lt;/strong&gt; — tools don't return clear success/failure states&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No stopping criteria&lt;/strong&gt; — no hard limits on iterations or time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Loops Happen: Ambiguous Tool Feedback
&lt;/h2&gt;

&lt;p&gt;Ambiguous tool feedback occurs when tools return partial results or suggest "more data may be available" without clear terminal states, causing agents to retry the same call. Tools that return partial results or suggest "more data may be available" cause agents to retry:&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;search_flights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&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="n"&gt;destination&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="n"&gt;max_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;Search for flights under a max price.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="n"&gt;matching&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_price&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# The problem: "More results may be available" signals the LLM to retry
&lt;/span&gt;    &lt;span class="c1"&gt;# The agent interprets this as "I should search again to find a better deal"
&lt;/span&gt;    &lt;span class="nf"&gt;return &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;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matching&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; flights under $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&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;(out of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; checked). &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Note: More results may be available. Prices change frequently.&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;That "Note: More results may be available" triggers the loop. The agent sees it and thinks: "Maybe if I search again, I'll find a better deal." It retries with the same parameters, gets similar results, and the cycle continues.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution 1: Debounce Hook with Strands
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/" rel="noopener noreferrer"&gt;Strands Hooks&lt;/a&gt; intercept the agent lifecycle at any point. A Debounce Hook uses &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/" rel="noopener noreferrer"&gt;&lt;code&gt;BeforeToolCallEvent&lt;/code&gt;&lt;/a&gt; to detect duplicate calls before they execute:&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.hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeInvocationEvent&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DebounceHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_size&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;       &lt;span class="c1"&gt;# Tracks (tool_name, input) pairs
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window_size&lt;/span&gt;  &lt;span class="c1"&gt;# Sliding window size for duplicate detection
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blocked_count&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;def&lt;/span&gt; &lt;span class="nf"&gt;register_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# BeforeInvocationEvent fires once at the start of each agent.invoke() call
&lt;/span&gt;        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeInvocationEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# BeforeToolCallEvent fires before every tool execution — this is where we intercept
&lt;/span&gt;        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_duplicate&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;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="c1"&gt;# Clear history at the start of each invocation so limits don't bleed across calls
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_history&lt;/span&gt; &lt;span class="o"&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;check_duplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="c1"&gt;# Build a fingerprint from tool name + exact inputs
&lt;/span&gt;        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&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;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window_size&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&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="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# cancel_tool is a native Strands API: blocks execution and returns this message to the LLM
&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;cancel_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCKED: Duplicate call detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blocked_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;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;search_flights&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;DebounceHook&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The hook tracks the last 3 tool calls. If the same tool with the same parameters appears twice, the third attempt is blocked via &lt;code&gt;event.cancel_tool&lt;/code&gt;, a native Strands API that prevents tool execution and returns an error message to the LLM.&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%2Fn4v3joj5k8bwwcdovbgc.jpg" 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%2Fn4v3joj5k8bwwcdovbgc.jpg" alt="Flow diagram showing how DebounceHook intercepts tool calls, checks a sliding window for duplicates, and blocks repeated calls" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution 2: Clear SUCCESS/FAILED States
&lt;/h2&gt;

&lt;p&gt;Tools that return explicit terminal states help agents know when to stop:&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;book_hotel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hotel&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="n"&gt;guest&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="n"&gt;nights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;Book a hotel room. Returns clear SUCCESS or FAILED.

    Returns:
        SUCCESS: Booking confirmed with ID
        FAILED: Booking failed with reason
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;conf&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;HT&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99999&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;SUCCESS: Booking &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; confirmed — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hotel&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;nights&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; nights, $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;nights&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&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;FAILED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; fully booked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When the agent receives &lt;code&gt;"SUCCESS: Booking HT79265 confirmed"&lt;/code&gt;, it knows the task is done. No ambiguity, no extra calls.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution 3: Hard Limits with LimitToolCounts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://codieshub.com/for-ai/prevent-agent-loops-costs" rel="noopener noreferrer"&gt;CodiesHub&lt;/a&gt; recommends: "Iterations, tokens, time, spend are non-negotiable." Strands provides &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/" rel="noopener noreferrer"&gt;&lt;code&gt;LimitToolCounts&lt;/code&gt;&lt;/a&gt; in the Hooks Cookbook — a hook that caps tool calls per invocation:&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.hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BeforeInvocationEvent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Lock&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LimitToolCounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Limits tool calls per invocation. From Strands Hooks Cookbook.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tool_counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="c1"&gt;# Per-tool call budgets: {"search_flights": 2} means max 2 searches per invocation
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_tool_counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_tool_counts&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Thread-safe for concurrent tool calls in Swarm scenarios
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeInvocationEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset_counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeforeToolCallEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intercept_tool&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;reset_counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="c1"&gt;# Reset per invocation so limits apply per task, not per agent lifetime
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_counts&lt;/span&gt; &lt;span class="o"&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;intercept_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;tool_name&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="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;max_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_tool_counts&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="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_counts&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="n"&gt;tool_name&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="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;max_count&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Hard ceiling: block the call and tell the LLM explicitly to stop
&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;cancel_tool&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;Tool &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; limit reached. DO NOT CALL ANYMORE.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Enforce a hard limit of 2 flight searches per booking task — prevents runaway costs
&lt;/span&gt;&lt;span class="n"&gt;limit_hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LimitToolCounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_tool_counts&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;search_flights&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;search_flights&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;limit_hook&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Even if the agent wants to search 10 times, it's capped at 2. Hard ceiling, predictable costs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo Results
&lt;/h2&gt;

&lt;p&gt;We tested with a travel booking agent that searches for flights and hotels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Tool Calls&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ambiguous Feedback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;21s&lt;/td&gt;
&lt;td&gt;Agent retried organically — "prices may change" caused loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DebounceHook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;15s&lt;/td&gt;
&lt;td&gt;Reduced retries but some variation in parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clear SUCCESS States&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4s&lt;/td&gt;
&lt;td&gt;Agent stopped immediately after SUCCESS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LimitToolCounts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6 (2 blocked)&lt;/td&gt;
&lt;td&gt;6s&lt;/td&gt;
&lt;td&gt;Hard ceiling enforced — no runaway&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The contrast is dramatic: &lt;strong&gt;14 calls with ambiguous tools vs 2 calls with clear SUCCESS states&lt;/strong&gt;. That is a 7x difference caused purely by tool feedback design.&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%2F549teet1ds9cr7ipp1z2.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%2F549teet1ds9cr7ipp1z2.png" alt="Bar chart comparing tool calls across ambiguous feedback, DebounceHook, clear SUCCESS states, and LimitToolCounts strategies" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  When to Use Each Solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DebounceHook&lt;/strong&gt; — prevents duplicate calls with identical parameters. Use when tools are idempotent and retrying with the same input is wasteful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clear SUCCESS/FAILED states&lt;/strong&gt; — the simplest solution. Design tools to return explicit terminal states. The agent knows when to stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LimitToolCounts&lt;/strong&gt; — hard ceiling on tool calls per invocation. Use in production to prevent runaway costs regardless of tool design. From the &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/" rel="noopener noreferrer"&gt;Strands Hooks Cookbook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All three together&lt;/strong&gt; — defense in depth. Clear states prevent most loops, debounce catches duplicates, and hard limits guarantee bounded execution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;You need &lt;a href="https://python.org/downloads" rel="noopener noreferrer"&gt;Python 3.9+&lt;/a&gt;, &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, and an &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;OpenAI API key&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/sample-why-agents-fail
&lt;span class="nb"&gt;cd &lt;/span&gt;sample-why-agents-fail/stop-ai-agents-wasting-tokens/03-reasoning-loops-demo
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key-here"&lt;/span&gt;

uv run python test_reasoning_loops.py   &lt;span class="c"&gt;# Runs all 4 scenarios&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or open &lt;code&gt;test_reasoning_loops.ipynb&lt;/code&gt; in &lt;a href="https://jupyter.org/" rel="noopener noreferrer"&gt;Jupyter&lt;/a&gt;, &lt;a href="https://jupyterlab.readthedocs.io/" rel="noopener noreferrer"&gt;JupyterLab&lt;/a&gt;, VS Code, or your preferred notebook environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguous tool feedback causes organic loops&lt;/strong&gt; — "more results may be available" makes agents retry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;14 calls vs 2 calls&lt;/strong&gt; — clear SUCCESS states reduce calls by 7x in our demo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks intercept before execution&lt;/strong&gt; — &lt;code&gt;BeforeToolCallEvent.cancel_tool&lt;/code&gt; blocks the call before the tool runs. The &lt;code&gt;DebounceHook&lt;/code&gt; is ~30 lines of code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard limits are mandatory&lt;/strong&gt; — every agent needs caps on iterations, time, and spend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;847 steps at $47/min was documented&lt;/strong&gt; (Particula, community observation) — unbounded agents burn money without delivering answers&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Why do AI agents repeat the same tool call?
&lt;/h3&gt;

&lt;p&gt;Agents repeat tool calls when tool responses contain ambiguous feedback such as "more results may be available" or "prices change frequently." The LLM interprets these signals as a reason to retry, expecting different or better results. Without clear terminal states (SUCCESS/FAILED), the agent has no way to know the task is complete.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is a DebounceHook and how does it prevent reasoning loops?
&lt;/h3&gt;

&lt;p&gt;A DebounceHook tracks recent tool calls in a sliding window. When the same tool is called with identical parameters more than a set threshold (typically 2 times within a window of 3), the hook blocks the call using &lt;code&gt;event.cancel_tool&lt;/code&gt; before the tool executes. The LLM receives a "BLOCKED: Duplicate call" message and must try a different approach. In Strands Agents, this is about 30 lines of code using the &lt;code&gt;HookProvider&lt;/code&gt; API.&lt;/p&gt;
&lt;h3&gt;
  
  
  How do clear SUCCESS/FAILED states reduce tool calls?
&lt;/h3&gt;

&lt;p&gt;When a tool returns "SUCCESS: Booking HT79265 confirmed," the LLM recognizes the task is complete and stops calling that tool. Ambiguous responses such as "Found 2 flights, more may be available" lack this signal, causing the agent to retry. In our demo, clear states reduced tool calls from 14 to 2, a 7x improvement.&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Research
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://the-decoder.com/language-models-can-overthink-and-get-stuck-in-endless-thought-loops/" rel="noopener noreferrer"&gt;Language models can overthink&lt;/a&gt; — The Decoder, Jan 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://particula.tech/blog/ai-agent-loops-reasoning-steps-optimization" rel="noopener noreferrer"&gt;How many reasoning steps do AI agents need&lt;/a&gt; — Particula (community observation), Jul 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codieshub.com/for-ai/prevent-agent-loops-costs" rel="noopener noreferrer"&gt;How to Prevent Infinite Loops and Spiraling Costs&lt;/a&gt; — CodiesHub (community observation), Dec 2025&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/" rel="noopener noreferrer"&gt;Strands Hooks&lt;/a&gt; — Lifecycle event interception and tool cancellation&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>python</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
