<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AWS</title>
    <description>The latest articles on DEV Community by AWS (aws).</description>
    <link>https://dev.to/aws</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1726%2F2a73f1e6-7995-4348-ae37-44b064274c59.png</url>
      <title>DEV Community: AWS</title>
      <link>https://dev.to/aws</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aws"/>
    <language>en</language>
    <item>
      <title>AI Game Mashups: Can AI Really Turn Two Games Into One?</title>
      <dc:creator>Matheus Guimaraes</dc:creator>
      <pubDate>Thu, 29 Oct 2026 11:27:37 +0000</pubDate>
      <link>https://dev.to/aws/ai-game-mashups-can-ai-really-turn-two-games-into-one-52bd</link>
      <guid>https://dev.to/aws/ai-game-mashups-can-ai-really-turn-two-games-into-one-52bd</guid>
      <description>&lt;p&gt;Mario shooting some baddies in Halo. Minecraft inside GTA V (see Appendix: a real-world example). A social media trend has people sharing videos of what’s being branded as “AI game mashups.” The claim is that people have used AI to reverse engineer different games and mash them together into one in unexpected or cool ways... but is that the whole story?&lt;/p&gt;

&lt;p&gt;As usual, hype has a tendency to exaggerate things to create, well, more hype. So let's separate reality from fiction and explore what’s actually happening behind these demos.&lt;/p&gt;

&lt;p&gt;The good news is that there is plenty on the reality side to be mind-blown about! However, there are also some disappointing truths. At least for the time being.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Mash Candy Crush And Call of Duty!
&lt;/h2&gt;

&lt;p&gt;Let's walk through a ridiculous combination as a practical example. Let's imagine that we want to combine Candy Crush and Call of Duty!&lt;/p&gt;

&lt;p&gt;However, simply naming two games does not constitute a plan. Like anything else, first we need to work out our requirements and architectural concerns.&lt;/p&gt;

&lt;p&gt;Here's my take: every time you reload your weapon, Candy Crush loads inside the Call of Duty gameplay and you have to complete a match-three puzzle before the reload can finish.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft19a00e8d1r1ownq5kr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft19a00e8d1r1ownq5kr2.png" alt="Concept illustration of a match-three puzzle overlay interrupting a first-person shooter reload." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hypothetical mashup: solve a match-three puzzle to finish reloading. AI-generated concept illustration, not a screenshot of a working mod.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have no idea whether that would be fun. I suspect my teammates would have opinions. But at least we now have something concrete enough to discuss.&lt;/p&gt;

&lt;p&gt;There are still decisions to make though. Does the shooter pause while you solve the puzzle? What happens if you fail? Can you cancel? Does a harder puzzle earn you more ammunition?&lt;/p&gt;

&lt;p&gt;Neither original game contains the answers so it's up to us to introduce new rules.&lt;/p&gt;

&lt;p&gt;And that is our first lesson: &lt;strong&gt;Connecting two game engines does not automatically create coherent new gameplay.&lt;/strong&gt; Somebody, potentially with AI assistance, still has to decide what the connection means and implement it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are We Actually Mashing Together?
&lt;/h2&gt;

&lt;p&gt;When I watch these demos, I find it useful to separate four things.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What’s happening&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visual composition&lt;/td&gt;
&lt;td&gt;Images or rendered objects from different sources appear together.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event integration&lt;/td&gt;
&lt;td&gt;Something happening in one game triggers something in another.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gameplay orchestration&lt;/td&gt;
&lt;td&gt;New rules coordinate actions and state across the games.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid game design&lt;/td&gt;
&lt;td&gt;Those rules form a coherent experience with its own objectives, constraints and balance.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These can overlap. A project might combine graphics and translate a few events without sharing every part of either simulation.&lt;/p&gt;

&lt;p&gt;For example, an explosion in one game could trigger an explosion in another. That is a meaningful integration, but it doesn’t, by itself, establish that every object in both worlds can collide, that both physics engines agree, or that every mission works across the boundary.&lt;/p&gt;

&lt;p&gt;A clip can still demonstrate a specific interaction beautifully while leaving those broader questions unanswered.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing interfaces
&lt;/h2&gt;

&lt;p&gt;Ok, so how can we go about implementing our Call of Duty and Candy Crush mash-up? Well, to begin with we need to discover each game's available integration points. Then we need to find a way to connect those before finally figuring out how to coordinate the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Find a way to control the relevant behaviour
&lt;/h3&gt;

&lt;p&gt;A game with a supported modding API might already expose the events and controls we need. Otherwise, investigating its internal behaviour may be necessary.&lt;/p&gt;

&lt;p&gt;For our shooter, we would need to identify a reload request and find a reliable way to defer it. For the puzzle, we need to start a challenge and detect its completion. Observing an animation on screen is different from having a dependable signal that the underlying action succeeded.&lt;/p&gt;

&lt;p&gt;This is where reverse engineering can contribute: &lt;em&gt;working out how software behaves when its original source code or a suitable interface isn’t available.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/morluto/rea" rel="noopener noreferrer"&gt;REA, or Reverse Engineer Anything&lt;/a&gt;, is one project in this area and the one behind the viral game mashups trend. Its documentation describes connecting agents to analysis tools, including Ghidra, Hopper and IDA, and returning findings that an agent can investigate further.&lt;/p&gt;

&lt;p&gt;It is indeed a pretty cool tool that gives an agent ways to inspect software. However, it is NOT a magical command that enables agents to recover an entire game’s original source project or to understand every subsystem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Moreover, it's important to remember that runtime modification of games predates AI tools.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/soulsmods/ModEngine2" rel="noopener noreferrer"&gt;Mod Engine 2&lt;/a&gt;, for example, documents runtime patching and injection for Souls games, including Elden Ring.[3] Existing tools and community knowledge are part of the foundation here.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build interfaces between the games
&lt;/h3&gt;

&lt;p&gt;Once we understand the relevant behaviour, we can build adapters: code that translates between what our mashup needs and what each game can actually do.&lt;/p&gt;

&lt;p&gt;From the coordinating code’s perspective, the shooter adapter might offer operations such as “defer reload” or “resume gameplay.” The puzzle adapter might offer “start challenge” and a completion event.&lt;/p&gt;

&lt;p&gt;Those are interfaces that we have to build unless the game itself provides out-of-the-box integration points, or in simpler language, hooks. This could be, for example, official support for mods. That means creating and maintaining your own adapter code, which, yes, you can also use AI to help you generate.&lt;/p&gt;

&lt;p&gt;There is an added complication: If components run in separate processes, they need a way to exchange messages, such as sockets or named pipes. This is referred to as inter-process communication, often shortened to IPC.&lt;/p&gt;

&lt;p&gt;A hook and a communication channel have different jobs. A hook can intercept behaviour inside a game whereas IPC lets separate processes exchange information. Having one doesn’t automatically give us the other.&lt;/p&gt;

&lt;p&gt;Either way, we arrive at our second lesson: AI doesn't somehow make two incompatible executables magically understand each other. &lt;em&gt;The real magic is that AI can help us build the missing interfaces between them.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Implement the new rules
&lt;/h3&gt;

&lt;p&gt;Now we need the coordinator, or orchestrator, that implements our reload rule so we can load Candy Crush every time you need to reload your weapon on Call of Duty.&lt;/p&gt;

&lt;p&gt;Here's the plan: it receives a reload request, defers the action, puts the shooter into an appropriate paused state, and opens the puzzle. On success, it allows the reload to proceed and returns control to the shooter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuq121jt80wqgvsrvgdnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuq121jt80wqgvsrvgdnp.png" alt="Flow diagram showing the reload request, deferred reload, paused shooter, puzzle outcome, and success or recovery paths." width="800" height="765"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The orchestrator coordinates the reload and puzzle, with a recovery path for failure, cancellation or timeout. This conceptual flow assumes the required integration points exist.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What if the puzzle crashes though? The player shouldn’t remain trapped forever with a frozen gun. And how about if a completion message arrives twice? We need to be careful not to grant ammunition twice. Like any piece of software we need to design for our error conditions.&lt;/p&gt;

&lt;p&gt;This is why “pause the game” deserves scrutiny. Preventing player input is different from pausing the simulation. In a networked game, the server and other players may continue regardless. Our example depends on capabilities that may be unavailable or prohibited in a protected online title.&lt;/p&gt;

&lt;p&gt;If you work with microservices, there’s a familiar architectural idea here. Connecting two services doesn’t automatically create an e-commerce application. You still need rules that define what placing an order means, what happens next, and how failure is handled.&lt;/p&gt;

&lt;p&gt;Games add another difficulty, one that becomes our third lesson: &lt;em&gt;we still need to design and build gameplay rules for the mashup much like building any other game.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Do the games become one program?
&lt;/h2&gt;

&lt;p&gt;They don’t have to.&lt;/p&gt;

&lt;p&gt;In one possible implementation of our example, the shooter and puzzle remain separate processes while our own coordinator manages their behaviour and presentation.&lt;/p&gt;

&lt;p&gt;The simplest implementation may be, for example, switching back and forth between  the Candy Crush puzzle’s window and the Call of Duty window. A more integrated version could display the puzzle within the shooter’s view as ensioned in our concept above. We'll have to work harder for it because it then adds rendering and input-routing work but it does look super cool. Either way, the objective is to have the player experience a single set of rules while multiple programs do the work.&lt;/p&gt;

&lt;p&gt;Another consideration is whether we need to create our own launcher app. This could gives even more control over the runtime and coordination between the two processes running either game, however it does not automatically give control over either game's internals. &lt;/p&gt;

&lt;p&gt;So lesson four is: &lt;em&gt;the desired user experience dictates how much control you need to have over either game and determines the architectural choices as well as the amount of code that you need to write and maintain.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where did the graphics come from?
&lt;/h2&gt;

&lt;p&gt;This is perhaps the most intriguing question.&lt;/p&gt;

&lt;p&gt;A newly written game might use original artwork, generated assets, placeholders or reused material. A mod might leave the original game’s engine and assets in place. A mashup might combine output from two running engines, each rendering its own content.&lt;/p&gt;

&lt;p&gt;Those are different workflows. A familiar character on screen doesn’t tell you which one happened.&lt;/p&gt;

&lt;p&gt;In our hypothetical example, if both original games were running, neither would need its graphics recreated merely to appear in the experience. Each game already knows how to render its own world. The integration challenge would concern control, communication, presentation and the new rules.&lt;/p&gt;

&lt;p&gt;Conversely, reproducing familiar graphics in a newly written program doesn’t establish that the original physics, enemy behaviour, timing or levels have been reproduced.&lt;/p&gt;

&lt;p&gt;But hang on. If we put Mario inside Halo, how does he know where the floor is? Doesn’t he come with his own bounding box? The important distinction is that &lt;strong&gt;a character’s appearance and its collision behaviour are separate things&lt;/strong&gt;. A visible model doesn’t automatically bring a working physics system with it. &lt;/p&gt;

&lt;p&gt;For a hypothetical model replacement, we could give an existing Halo character Mario’s appearance while keeping the host character’s movement and collision setup. Or, if we were adding a new character, we would need to create or convert the collision data into a form the host engine understands and configure its behaviour. Halo wouldn’t need to recognise “Mario”; it would need a correctly configured character in its own simulation. That could stop him falling through the floor without reproducing how he moves in a Mario game.&lt;/p&gt;

&lt;p&gt;So lesson number five is: &lt;strong&gt;Looking like two worlds have merged does not mean that their physics have merged too. There's work to be done to make that happen.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Asset provenance and permission are also separate questions from whether an integration works. A clip alone cannot answer them. Before making claims about how a creator obtained the assets, we need their documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the AI fit?
&lt;/h2&gt;

&lt;p&gt;We should distinguish AI helping build the mashup from AI running it.&lt;/p&gt;

&lt;p&gt;During development, an agent can use tools to investigate behaviour, propose integration code and help iterate on failures. REA’s documented workflow, for example, lets an agent inspect findings, ask follow-up questions, and write and test an implementation.[2]&lt;/p&gt;

&lt;p&gt;But once our reload rule works, it can run as ordinary deterministic code. Receive the event, start the puzzle, wait for a result, update the state. There is no inherent need to ask a language model what should happen every time the player reloads.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI can be the engineer that builds the bridge without being the driver that crosses it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That distinction also helps us evaluate claims about speed. A successful demo shows that someone achieved a result. Understanding how much AI accelerated it requires more evidence: the starting tools, existing mods, development history, human interventions and failed attempts. The finished clip doesn’t contain that history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appreciate the demo. Be precise about the claim.
&lt;/h2&gt;

&lt;p&gt;I find this trend exciting because it opens up questions about what people might build when investigating and connecting unfamiliar software becomes more accessible. Not to mention that level of creativity which makes me smile or even laugh out loud every time I see one out there.&lt;/p&gt;

&lt;p&gt;But, if we're gonna get down to the nitty and gritty and talk technical, I scrutinize them about what they're trying to prove. What is running? How much of it is custom integration and coordination code? Which information crosses the boundary? Which interactions actually work? Where does the AI contribute?&lt;/p&gt;

&lt;p&gt;An overlay, an event bridge and a hybrid game can all be worthwhile achievements. Describing them accurately gives the people building them credit for the work they actually did, and gives everyone else a better understanding of what remains unsolved.&lt;/p&gt;

&lt;p&gt;Reverse engineering exposes capabilities. Integration connects them. Orchestration gives them new meaning. &lt;/p&gt;

&lt;p&gt;Whatever the path you take, ultimately it comes down to our final lesson number six: &lt;em&gt;AI or no AI, REA or whatever comes next...game mashups are a project like any other and still take a lot of work!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As for whether solving Candy Crush puzzles before reloading a gun in Call of Duty is a good idea...that is up for debate!&lt;/p&gt;




&lt;p&gt;&lt;a id="appendix-a-real-world-example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: A real-world example
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/rehan-remade/universal-modder" rel="noopener noreferrer"&gt;Universal Modder repository&lt;/a&gt; documents a Minecraft and GTA V story-mode example. Its description identifies a Minecraft Fabric mod, a ScriptHookV component and a ReShade add-on, with camera, ground and event information exchanged over a local WebSocket connection. It also describes combining Minecraft’s colour and depth output with GTA’s frame, and translating selected Minecraft events into GTA effects.[1]&lt;/p&gt;

&lt;p&gt;There are two useful ideas in that description.&lt;/p&gt;

&lt;p&gt;First, the games have specific integration components. A communication channel carries information, but code still has to decide what to send and what the receiving game should do with it.&lt;/p&gt;

&lt;p&gt;Second, presentation and interaction are separate problems. Depth information helps decide which rendered surfaces should appear in front of others. That can make the combined image convincing. Making an object affect the other game’s simulation requires additional behaviour.&lt;/p&gt;

&lt;p&gt;That is a substantial achievement. It is also a much more precise claim than “any two games can now be merged.” The project’s documented approach is evidence about that example, not a universal explanation for every viral mashup.&lt;/p&gt;




&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;p&gt;Project documentation checked on 20 October 2026; collision reference added on 29 October 2026. Named implementations are described from their maintainers’ documentation; I have not independently run or benchmarked them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/rehan-remade/universal-modder#built-with-it" rel="noopener noreferrer"&gt;Universal Modder: documented Minecraft × GTA V example&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/morluto/rea#how-rea-works" rel="noopener noreferrer"&gt;REA: overview and how it works&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/soulsmods/ModEngine2" rel="noopener noreferrer"&gt;Mod Engine 2: runtime patching, launcher and extension documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html" rel="noopener noreferrer"&gt;Godot documentation: collision shapes, physics bodies and collision response&lt;/a&gt;. General collision concepts; not evidence of a particular Halo mod implementation.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Ship a Lambda Web Function with CDK</title>
      <dc:creator>Sean Boult</dc:creator>
      <pubDate>Wed, 28 Oct 2026 17:45:51 +0000</pubDate>
      <link>https://dev.to/aws/ship-a-lambda-web-function-with-cdk-16na</link>
      <guid>https://dev.to/aws/ship-a-lambda-web-function-with-cdk-16na</guid>
      <description>&lt;p&gt;So, &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html" rel="noopener noreferrer"&gt;AWS Lambda Web Functions&lt;/a&gt; just came out, and I'm super excited about them.&lt;/p&gt;

&lt;p&gt;What are they? Well, you can now ship Express, Hono, or React Router with no Lambda-specific adapters or wrappers.&lt;/p&gt;

&lt;p&gt;Let me show you how I got this demo set up with CDK.&lt;/p&gt;

&lt;p&gt;λ: &lt;a href="https://cxrj1w1j65n0k7ss.p7v.lambda-web.us-east-2.on.aws" rel="noopener noreferrer"&gt;https://cxrj1w1j65n0k7ss.p7v.lambda-web.us-east-2.on.aws&lt;/a&gt; (this may not live forever but the code should)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;DISCLAIMER&lt;/strong&gt;: The code we are about to see is written with L1 CDK constructs. The L2 constructs are being tracked via &lt;a href="https://github.com/aws/aws-cdk/pull/39210" rel="noopener noreferrer"&gt;this PR&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First, let's look at the server code I have in Express. I've omitted the bulk of its logic for brevity.&lt;/p&gt;

&lt;p&gt;Here, you can see that it's an &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express.js&lt;/a&gt; server binding to a port.&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="nx"&gt;express&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;express&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawPort&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="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_LAMBDA_HTTP_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
  &lt;span class="s2"&gt;`127.0.0.1:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3000&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;split&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawPort&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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;`Spinning Lambda is listening on http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hostname&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;port&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I can run the code locally with &lt;code&gt;pnpm dev&lt;/code&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5u9ymzdams5ypv2bkrs9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5u9ymzdams5ypv2bkrs9.png" alt=" " width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's look at the CDK stack used to deploy this function.&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpinningLambdaStack&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;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// code bucket stuff omitted&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webFunction&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;CfnWebFunction&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;WebFunction&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;functionName&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stackName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-web`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// many props omitted for brevity&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;revision&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;CfnWebFunctionRevision&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;WebFunctionRevision&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;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// many props omitted for brevity&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;endpoint&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;CfnWebFunctionEndpoint&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;WebFunctionEndpoint&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;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// many props omitted for brevity&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;Now for deployment. I'm using the &lt;a href="https://aws.amazon.com/blogs/aws/aws-reimagines-the-getting-started-experience/" rel="noopener noreferrer"&gt;new AWS experience&lt;/a&gt;, so I have a &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/create-project.html" rel="noopener noreferrer"&gt;project&lt;/a&gt; dedicated to this demo.&lt;/p&gt;

&lt;p&gt;I grab some credentials for my account with &lt;code&gt;aws login&lt;/code&gt;. I need to do a one-time setup to bootstrap CDK with &lt;code&gt;npx cdk bootstrap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once that's done, I can run &lt;code&gt;pnpm deploy&lt;/code&gt; to ship it.&lt;/p&gt;

&lt;p&gt;Then it's live 🚀!&lt;/p&gt;

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

&lt;p&gt;Share this on X/Twitter, and it should render as an embed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://cxrj1w1j65n0k7ss.p7v.lambda-web.us-east-2.on.aws/embed

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Full repo:&lt;br&gt;
&lt;a href="https://github.com/sboult/lwf-three-js" rel="noopener noreferrer"&gt;https://github.com/sboult/lwf-three-js&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;As always, happy coding 😉!&lt;/p&gt;

&lt;p&gt;Follow AWS for more articles like this.&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.us-east-2.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;Follow me for all things tech!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__828306"&gt;
    &lt;a href="/hacksore" 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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F828306%2Fbf0bbed7-7874-4a26-8137-bb761a4b7f23.png" alt="hacksore 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="/hacksore"&gt;Sean Boult&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/hacksore"&gt;Developer. Hacker. Creator.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>aws</category>
      <category>serverless</category>
      <category>express</category>
    </item>
    <item>
      <title>Is stateless MCP just REST? I ran 180 tests to find out</title>
      <dc:creator>Edoardo Dusi</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:22:52 +0000</pubDate>
      <link>https://dev.to/aws/is-stateless-mcp-just-rest-i-ran-180-tests-to-find-out-5dib</link>
      <guid>https://dev.to/aws/is-stateless-mcp-just-rest-i-ran-180-tests-to-find-out-5dib</guid>
      <description>&lt;p&gt;In July 2026 the Model Context Protocol &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" rel="noopener noreferrer"&gt;dropped sessions&lt;/a&gt;. The handshake is gone, the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header is gone, and every request now stands on its own. Within a week the internet did what the internet does, compressing that change into a slogan: MCP is dead, REST won, it was always just REST with extra steps.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" rel="noopener noreferrer"&gt;2026-07-28 MCP spec&lt;/a&gt;, &lt;code&gt;tools/list&lt;/code&gt; no longer varies per connection, and a server that needs cross-call state now mints an explicit handle and passes it back as an ordinary tool argument. In other words, a stateless MCP server is now a list of tool names with JSON schemas, presented to the model, which emits a tool call that the host dispatches.&lt;/p&gt;

&lt;p&gt;Say that out loud and the REST comparison writes itself. Maybe even the slogan. A REST API is &lt;em&gt;also&lt;/em&gt; stateless HTTP.&lt;/p&gt;

&lt;p&gt;I always loved that "computer scientist" has the word &lt;strong&gt;scientist&lt;/strong&gt; in it, so I want proof and measurements, even for silly slogans. I found no measurement, so I built one.&lt;/p&gt;

&lt;p&gt;I took a single backend and exposed it twice, once as a stateless MCP server and once as a plain REST API, handed both to the same agent with the same tasks, and recorded what changed across 180 runs. I'm surprised that the result is less dramatic than the slogan and actually more useful: on a small tool surface the two are a tie, and on a realistic one they diverge for one structural reason each.&lt;/p&gt;

&lt;p&gt;I'm not here to tell you "which protocol is better". But I will tell you that these two carry the &lt;strong&gt;same information in different places&lt;/strong&gt;. MCP puts the tool surface in front of the model on every turn. REST puts the contract in front of the model once, then leans on a single generic HTTP call. That difference in placement is the whole ballgame, and it is measurable.&lt;/p&gt;

&lt;p&gt;I'm giving you the data, the harness, and a way to make a decision based on your case, not your mood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The experiment
&lt;/h2&gt;

&lt;p&gt;I wanted any difference I measured to be the shape of the contract instead of the business logic. There is exactly one backend, a classic demo: a small store with products, inventory, orders and customers, that answers the same way every run.&lt;/p&gt;

&lt;p&gt;Two interfaces wrap that same backend over the same six operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; is modeled as native tool use on &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html" rel="noopener noreferrer"&gt;Amazon Bedrock's Converse API&lt;/a&gt;, because this is exactly the stateless MCP shape: the model receives tool names and JSON schemas every turn and emits a &lt;code&gt;toolUse&lt;/code&gt; block that the host dispatches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST&lt;/strong&gt; gives the model one tool, &lt;code&gt;http_request&lt;/code&gt;, plus an OpenAPI-style contract in the system prompt, that is read once. The model constructs the path itself. This interface also implements a real &lt;code&gt;ETag&lt;/code&gt; and &lt;code&gt;304 Not Modified&lt;/code&gt; cache, because conditional reads are the one thing REST can express that MCP tool use currently cannot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I gave both the same set of tasks: a simple lookup, a two-hop (order to customer name), an aggregate across a customer's orders, a missing-information recovery (an order that does not exist), a conditional (can I order an out-of-stock item?), and a repeated read that I use to test caching.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Model: Claude Haiku 4.5 on Amazon Bedrock, &lt;code&gt;eu-central-1&lt;/code&gt;, temperature 0. I picked this small, fast model because it keeps the sandbox cost down, and a smaller model is the harder test: it is quicker to fumble a schema or burn tokens, so a difference between the two interfaces shows up instead of being hidden by a frontier model's reasoning&lt;/li&gt;
&lt;li&gt;Five trials per task per interface, so thirty runs each per condition&lt;/li&gt;
&lt;li&gt;Metrics per run: success, input and output tokens, latency, round-trips, tool calls, cache hits. A round-trip here is one model turn, so a task that makes a single tool call and then answers counts as two&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result 1: on a small surface, it is a tie
&lt;/h2&gt;

&lt;p&gt;With six tools, the internet slogan seems to be correct!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Six tools, five trials per task, thirty runs per interface.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Success rate&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg total tokens&lt;/td&gt;
&lt;td&gt;2,838&lt;/td&gt;
&lt;td&gt;2,867&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg input tokens&lt;/td&gt;
&lt;td&gt;2,705&lt;/td&gt;
&lt;td&gt;2,692&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median latency&lt;/td&gt;
&lt;td&gt;2,116 ms&lt;/td&gt;
&lt;td&gt;2,070 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg round-trips&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache hits (304)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can see we have the same correctness, number of round-trips, token counts and latency. If you have a handful of tools, the choice between a small MCP server and a small REST contract is just a matter of taste, not efficiency. But look at that &lt;code&gt;Cache hits&lt;/code&gt; column...I'll come back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 2: the schema tax is real, and it scales
&lt;/h2&gt;

&lt;p&gt;Then I changed the one thing I figured was about to matter: the API surface size. I ran the whole suite again with twenty-four extra endpoints, modeling a realistic store API with roughly thirty operations. On the MCP side those are &lt;em&gt;twenty-four more tool schemas&lt;/em&gt;. On the REST side they are &lt;em&gt;twenty-four more lines in a contract the model reads once&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how that changed our bill:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thirty tools, five trials per task, thirty runs per interface.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Success rate&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg total tokens&lt;/td&gt;
&lt;td&gt;6,558&lt;/td&gt;
&lt;td&gt;4,231&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MCP +35%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg input tokens&lt;/td&gt;
&lt;td&gt;6,406&lt;/td&gt;
&lt;td&gt;4,038&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MCP +37%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median latency&lt;/td&gt;
&lt;td&gt;1,996 ms&lt;/td&gt;
&lt;td&gt;1,810 ms&lt;/td&gt;
&lt;td&gt;MCP +9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg round-trips&lt;/td&gt;
&lt;td&gt;2.67&lt;/td&gt;
&lt;td&gt;2.67&lt;/td&gt;
&lt;td&gt;tie&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Again, same success rate and same round-trips, but &lt;strong&gt;35% more tokens&lt;/strong&gt;. The reason is simple: a stateless MCP server re-sends every tool schema on every turn, so the model pays for the full catalog every time, even if it doesn't use every tool. REST's single &lt;code&gt;http_request&lt;/code&gt; primitive does not grow when the API does, because the contract is only read once.&lt;/p&gt;

&lt;p&gt;Per task, the gap is remarkable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;MCP input tokens&lt;/th&gt;
&lt;th&gt;REST input tokens&lt;/th&gt;
&lt;th&gt;MCP overhead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;order_status&lt;/td&gt;
&lt;td&gt;4,673&lt;/td&gt;
&lt;td&gt;2,850&lt;/td&gt;
&lt;td&gt;+1,823&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;customer_total&lt;/td&gt;
&lt;td&gt;4,732&lt;/td&gt;
&lt;td&gt;2,910&lt;/td&gt;
&lt;td&gt;+1,822&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;missing_order&lt;/td&gt;
&lt;td&gt;4,647&lt;/td&gt;
&lt;td&gt;2,805&lt;/td&gt;
&lt;td&gt;+1,842&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;order_customer&lt;/td&gt;
&lt;td&gt;7,181&lt;/td&gt;
&lt;td&gt;4,500&lt;/td&gt;
&lt;td&gt;+2,681&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;repeat_read&lt;/td&gt;
&lt;td&gt;7,318&lt;/td&gt;
&lt;td&gt;4,687&lt;/td&gt;
&lt;td&gt;+2,631&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stock_mech&lt;/td&gt;
&lt;td&gt;9,884&lt;/td&gt;
&lt;td&gt;6,475&lt;/td&gt;
&lt;td&gt;+3,409&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly 1,800 extra input tokens for a one-call task, and more for multi-call tasks, because the schema tax is paid &lt;strong&gt;once per round-trip&lt;/strong&gt;. The more the agent has to go back to the tools, the more times it re-reads the whole catalog.&lt;/p&gt;

&lt;p&gt;To be fair, this is the worst case for MCP and the best case for REST. Prompt caching on the provider side, and filtering the tool list down to what a task plausibly needs, both cut this cost. But neither is free and neither is automatic, and the structural fact remains that MCP's token cost is a function of surface size in a way REST's is not.&lt;/p&gt;

&lt;p&gt;This is my measurement, so I invite you to test on your own setup and challenge my findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 3: where REST wins outright
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;repeat_read&lt;/code&gt; task asks the agent to read an order, report the total, then read it again to confirm it is unchanged. This is deliberate, to test caching.&lt;/p&gt;

&lt;p&gt;The REST agent read the order, got an &lt;code&gt;ETag&lt;/code&gt;, and on the second read sent &lt;code&gt;If-None-Match&lt;/code&gt; with that tag. The server answered &lt;code&gt;304 Not Modified&lt;/code&gt; with an empty body, and the agent reused the value it already had. Five cache hits in five trials. The MCP agent could not do this, because MCP tool use has no standard notion of a conditional read or a revalidation header. It called the tool again and paid for the full result a second time. No cache, baby!&lt;/p&gt;

&lt;p&gt;The boring and old HTTP protocol with its caching mechanisms, conditional requests and revalidation, easily beats the shiny new MCP protocol on re-reads, because when your agent calls a tool it always starts from scratch, even when reading multiple times data that rarely changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 4: where MCP wins outright
&lt;/h2&gt;

&lt;p&gt;So far everything seems to be in favour of REST. I tried to build a scenario that should favour MCP.&lt;/p&gt;

&lt;p&gt;Picture a strict, unfamiliar API. A reporting endpoint whose region codes are &lt;code&gt;EUR&lt;/code&gt;, &lt;code&gt;NAM&lt;/code&gt; and &lt;code&gt;APC&lt;/code&gt;, not &lt;code&gt;EU&lt;/code&gt; or &lt;code&gt;Europe&lt;/code&gt; or &lt;code&gt;EMEA&lt;/code&gt;. Metric names you cannot guess from a description. A date format that must be exactly &lt;code&gt;YYYY-MM-DD&lt;/code&gt;. The kind of API an agent meets for the first time.&lt;/p&gt;

&lt;p&gt;MCP gives the model its normal typed tool schema: the enum values are correctly spelled, the format is declared, and the host validates the call before it is dispatched. REST gets what people actually write for a terse API: a contract that names the endpoint and the parameters but does not enumerate every valid value. The agent has to infer them. I asked both, in plain language, questions like "what was net revenue in Europe on 2026-10-20?"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Strict API, one operation, six trials per task, thirty runs per interface.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;REST (terse contract)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Final success&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First-call success&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malformed calls&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30 (one per task)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg tool calls&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg round-trips&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg total tokens&lt;/td&gt;
&lt;td&gt;1,841&lt;/td&gt;
&lt;td&gt;3,041 (&lt;strong&gt;+65%&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median latency&lt;/td&gt;
&lt;td&gt;1,720 ms&lt;/td&gt;
&lt;td&gt;3,006 ms (&lt;strong&gt;+75%&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both get there in the end, so this is not about MCP being "more correct." It is about getting it right the first time. The MCP agent mapped "Europe" to &lt;code&gt;EUR&lt;/code&gt; on the first call, every single run, because the schema told it the allowed values up front. The REST agent guessed &lt;code&gt;EU&lt;/code&gt; or &lt;code&gt;Europe&lt;/code&gt;, got a &lt;code&gt;400&lt;/code&gt;, read the error, and fixed it on the second call, every single run. That self-correction is impressive, but it costs an extra round-trip, 65% more tokens and nearly double the latency, every time.&lt;/p&gt;

&lt;p&gt;To be honest, I could make REST match MCP by pasting the full JSON schema, enums and all, into the contract. But that is exactly the per-turn token tax that made MCP lose Result 2. The schema has to live somewhere. MCP puts it in front of the model and pays for it every turn. A terse REST contract saves those tokens right up until the API is strict enough that the saving turns into a wrong guess. No free lunch, only a choice about where you spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not measure
&lt;/h2&gt;

&lt;p&gt;A benchmark you cannot poke holes in is a benchmark that is lying! Here are mine.&lt;/p&gt;

&lt;p&gt;This is a very controlled scenario. One model, one small domain, six tasks. There is no authentication, no streaming, no &lt;em&gt;human in the loop&lt;/em&gt;. The "wire bytes" I counted are a proxy, not a packet capture. Latency differences stayed inside the noise and were dominated by the model, not the protocol, so do not read the 9% as a protocol verdict.&lt;/p&gt;

&lt;p&gt;And to be very clear, this measures per-request efficiency, which is not why most people reach for MCP. MCP's actual value is the ecosystem. Write one server and any agent can use it, without a previous knowledge of the contract. That portability is real and this harness does not price it. REST's hidden cost is the opposite: every consumer writes and maintains its own client, based on a shared contract.&lt;/p&gt;

&lt;p&gt;Efficiency is one axis, but it's not the only one.&lt;/p&gt;

&lt;p&gt;There is also a safety axis, which this harness does not touch: a stateless, per-request boundary is easier to reason about than a long-lived session, with authorization checked per call and no sticky server-side state to ride. That deserves its own measurement, so I'm thinking of a follow-up here.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to decide
&lt;/h2&gt;

&lt;p&gt;Is stateless MCP just REST with extra steps? No.&lt;/p&gt;

&lt;p&gt;It is REST with no cache, but with a typed, self-describing discovery layer bolted on top. Batteries included. Which one you want depends on your specific scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small, stable, portable tool set&lt;/strong&gt; that you want many agents to reuse: MCP wins. The schema tax is small and the ecosystem payoff is real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict or unfamiliar API&lt;/strong&gt; with non-obvious parameters: MCP wins again. Typed, validated discovery means the agent calls correctly on the first try instead of guessing, failing, and retrying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large, loose API with cache-heavy reads and a single consumer&lt;/strong&gt;: a cache-friendly REST contract is the better developer experience. You avoid the schema tax and you get &lt;code&gt;304&lt;/code&gt;s for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Somewhere in between&lt;/strong&gt;: measure it. That is the whole point of the harness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no winner-take-all. It is a trade-off about where the cost of describing your API lives, and now I hope it is a trade-off you can put some numbers on.&lt;/p&gt;

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

&lt;p&gt;The full harness is on GitHub: &lt;a href="https://github.com/edodusi/mcp-vs-rest-bench" rel="noopener noreferrer"&gt;mcp-vs-rest-bench&lt;/a&gt;. The backend, the two interfaces, the task suite, the runner, and the raw results JSON. It runs against Amazon Bedrock on demand, so there is nothing to tear down, no standing infrastructure, and a full five-trial sweep costs a few cents of Haiku tokens. Point it at your own stack, your own tasks, your own model, and settle your own version of the argument.&lt;/p&gt;

&lt;p&gt;Don't decide with vibes. Decide with data.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>Making my personal website callable for AI agents with WebMCP</title>
      <dc:creator>Salih Guler </dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:17:31 +0000</pubDate>
      <link>https://dev.to/aws/making-my-personal-website-callable-for-ai-agents-with-webmcp-38aj</link>
      <guid>https://dev.to/aws/making-my-personal-website-callable-for-ai-agents-with-webmcp-38aj</guid>
      <description>&lt;p&gt;The first time someone mentioned WebMCP to me, I assumed it was a diagnostics tool for web apps. The name sounds like something you would open next to the network tab. My second guess was worse: I thought maybe it was a Playwright alternative. Luckily, I was wrong both times, and figuring out why is how I ended up adding it to &lt;a href="https://salih.dev" rel="noopener noreferrer"&gt;salih.dev&lt;/a&gt; (where probably nobody would use).&lt;/p&gt;

&lt;p&gt;A few months ago I wrote about &lt;a href="https://salih.dev/blog/building-an-agent-friendly-personal-website-with-specs-11b4" rel="noopener noreferrer"&gt;building an agent-friendly personal website with specs&lt;/a&gt;. The site serves Markdown to agents, has an &lt;code&gt;llms.txt&lt;/code&gt;, and answers a set of &lt;code&gt;.well-known&lt;/code&gt; discovery files. One of those files was &lt;code&gt;/.well-known/webmcp.json&lt;/code&gt;, and it said, politely, that there were no browser tools here. In case you don't know me, I am a very open person, if I don't have something, I would say "I don't have it". But I also don't take "No" for an answer, even to myself. So let's see how I added this very useful set of features on a not very useful scenario for the sake of learning.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code is in the &lt;a href="https://github.com/salihgueler/salih-dev" rel="noopener noreferrer"&gt;salih-dev repository&lt;/a&gt;: the tools are in &lt;a href="https://github.com/salihgueler/salih-dev/pull/15" rel="noopener noreferrer"&gt;PR #15&lt;/a&gt;, and the speaker kit and discovery changes are in &lt;a href="https://github.com/salihgueler/salih-dev/pull/16" rel="noopener noreferrer"&gt;PR #16&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is WebMCP?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://webmcp.dev/" rel="noopener noreferrer"&gt;WebMCP&lt;/a&gt; is a proposed web standard from the W3C Web Machine Learning Community Group, written by people from Microsoft and Google. It lets a web page declare tools that an AI agent running in the browser can call. The page registers each tool with a name, a description, a JSON schema for its input and a function that runs it:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list_events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;List the conferences where Salih speaks...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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="cm"&gt;/* return the answer */&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 "MCP" is the same Model Context Protocol you may already run on a server. The difference is where it lives. A server MCP exposes tools from a backend. WebMCP exposes tools from the page the user has open, with the user's session and the page's state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools I added
&lt;/h2&gt;

&lt;p&gt;My site has three kinds of content an agent might care about: blog posts, past talks and upcoming events. I wrote eight tools around them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_site_overview&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where I am right now, content counts, my next event, the latest post, and the filter values the other tools accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_site&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full-text search across posts, talks and events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_blog_posts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Posts filtered by tag, category or text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;read_blog_post&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The full Markdown of one post&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_talks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past talks by topic, event type, year, or whether a recording exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_events&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upcoming, past or all events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_speaker_kit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;My bio, the topics I speak on, recent talks, where I am already booked, and how to reach me&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;show_talks_on_page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applies a filter on the Talks page so the visitor sees it too&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first seven are read-only and registered on every page. The last one only exists on &lt;code&gt;/talks/&lt;/code&gt;, and it is the one I like most. It presses the page's real filter button, so the person watching sees the same list the agent is describing. The page's own controller stays in charge of the state, including its &lt;code&gt;aria-pressed&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;I kept everything read-only on purpose. There is a comment form on the site, and I thought about exposing it. An agent posting comments on my behalf is a decision I want to make separately, after I have watched how agents use the read tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Bake an index at build time
&lt;/h2&gt;

&lt;p&gt;The tools need data, and I did not want them scraping my own HTML. The site is built with Astro, so I added a static endpoint that writes everything the tools need into one JSON file.&lt;/p&gt;

&lt;p&gt;Create it under &lt;code&gt;src/pages/api/webmcp-index.json.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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;talks&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;getPublishedPosts&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;getPublishedTalksSnapshot&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="nf"&gt;jsonResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;buildSiteIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&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;siteUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&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="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;talkCards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;projectTalkArchive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;talks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;siteContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSiteContent&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;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;Cache-Control&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;public, max-age=300&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;ol&gt;
&lt;li&gt;It reads the same published posts and talks the pages use, so the tools never disagree with what a human sees.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buildSiteIndex&lt;/code&gt; flattens them into a small shape with titles, URLs, dates, tags and snippets.&lt;/li&gt;
&lt;li&gt;The file is static. My content API starts the publisher build after every write, and that build regenerates the file, so there is no extra Lambda to run.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One detail took me a moment. Events move from "upcoming" to "past" on their own, and a baked file does not know what day it is. So the index stores dates, and the tools decide upcoming or past in the browser against the visitor's clock. An event that ended yesterday shows as past even if I have not rebuilt the site since.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Register the tools only when the browser supports them
&lt;/h2&gt;

&lt;p&gt;Most visitors use a browser without WebMCP, and I wanted them to see zero difference. The entry point checks for &lt;code&gt;document.modelContext&lt;/code&gt; and returns if it is missing.&lt;/p&gt;

&lt;p&gt;Add it to &lt;code&gt;src/lib/webmcp/register-tools.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;export&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;registerWebMcpTools&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&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;context&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;registerContentTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&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;registerTalksPageTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A registration failure must never break the page for the human visitor.&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;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebMCP tool registration failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then load it from a component with no markup, &lt;code&gt;src/components/WebMcpTools.astro&lt;/code&gt;, and add that component to the base layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  import { registerWebMcpTools } from "../lib/webmcp/register-tools";

  void registerWebMcpTools();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For types I used the &lt;code&gt;webmcp-types&lt;/code&gt; package, pinned to &lt;code&gt;0.1.10&lt;/code&gt;, and referenced it from &lt;code&gt;src/env.d.ts&lt;/code&gt;. That gives you &lt;code&gt;WebMCP.ModelContext&lt;/code&gt; and the &lt;code&gt;registerTool&lt;/code&gt; signature without writing your own copy of the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Write descriptions for a model, and check every input
&lt;/h2&gt;

&lt;p&gt;A tool description is a prompt. The agent reads it to decide whether to call the tool and what to pass. I wrote mine the way I would brief a colleague, including the defaults and the order of results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;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;List the conferences and events where Salih Güler speaks or runs workshops. 'upcoming' (the default) lists events that have not ended yet, soonest first; 'past' lists finished events, most recent first; 'all' lists both.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;get_site_overview&lt;/code&gt; returns the exact tag, category and topic values the other tools accept, and its description tells the agent to call it first when it is unsure. That one line saves a lot of failed filter calls.&lt;/p&gt;

&lt;p&gt;The spec does not promise that the browser validates input against your schema. So every &lt;code&gt;execute&lt;/code&gt; function checks its input again before using it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;optionalString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&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;query&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;query must be a non-empty string&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;The search and filter logic lives in &lt;code&gt;src/lib/webmcp/queries.ts&lt;/code&gt; as pure functions, which made it easy to test without a browser. There are 12 tests for it, covering search ranking, filters, the upcoming and past split, post lookup and the speaker kit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add a speaker kit
&lt;/h2&gt;

&lt;p&gt;The first version answered questions about my content. Then I thought about who actually looks me up. Most of the time it is an event organizer deciding whether to invite me, and they want a bio, the topics I cover, proof that I can give a talk, and a way to reach me. Before, an agent had to stitch that together from four tool calls and the About page.&lt;/p&gt;

&lt;p&gt;So the index got a &lt;code&gt;speaker&lt;/code&gt; block, built from the same &lt;code&gt;src/config/site.ts&lt;/code&gt; that renders my About and Contact pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bioShort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;longBio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bioLong&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;focusAreas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusAreas&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;aboutUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;contactUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/contact/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;socials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;social&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&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;And &lt;code&gt;src/lib/webmcp/queries.ts&lt;/code&gt; got a &lt;code&gt;speakerKit&lt;/code&gt; function that combines it with the talks and events. It counts how many talks I have given per topic, so the agent sees what I talk about most:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topicCounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;topic&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;talks&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;order&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;&amp;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;talk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;talks&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;talk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&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;key&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;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;topicCounts&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="nx"&gt;key&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;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;talks&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="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;topicCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;talks&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="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topicCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&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 function returns the topics sorted by talk count, my five most recent talks with slides and recordings, the next five events I am booked for, and the contact routes. My site lists no email address, so the kit says that directly and points to the profiles on the contact page. An agent should not invent an address because the field looked empty.&lt;/p&gt;

&lt;p&gt;Registering it looks like the other tools. The description names the situations where an agent should pick it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_speaker_kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Speaker kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Get what an event organizer needs to invite Salih Güler as a speaker: his role and bio, focus areas, the topics he has spoken on with talk counts, his most recent talks with slide and recording URLs, the events he is already booked for, and how to contact him. Use this when someone wants to invite him, check his availability, or write a speaker introduction.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&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;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;readOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;speakerKit&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;loadIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;today&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;Adding the &lt;code&gt;speaker&lt;/code&gt; block changed the index shape, so I bumped its &lt;code&gt;version&lt;/code&gt; from 1 to 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Tell the humans too
&lt;/h2&gt;

&lt;p&gt;The tools are invisible to a person reading the site. If someone finds this post and opens salih.dev, nothing on the page says what to ask. So the About page has a new section, "Use this site with an AI agent", with example prompts and the console check from the "Try it yourself" section below.&lt;/p&gt;

&lt;p&gt;The About page also has a Markdown version for agents, and I did not want the two to drift apart. Both now render from one small module, &lt;code&gt;src/lib/webmcp/visitor-guide.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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AGENT_GUIDE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use this site with an AI agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;promptsLead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Some questions to try:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;prompts&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;Where is Salih speaking next, and what has he talked about before?&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;Find Salih's posts about AWS Amplify and summarize the newest one.&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;Which of Salih's talks have a recording?&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;Get Salih's speaker kit so I can invite him to our meetup.&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;// intro text, the Chrome flag, and the console call are here too&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;about.astro&lt;/code&gt; maps over it for the HTML, and the Markdown serializer builds the same section from it.&lt;/p&gt;

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

&lt;p&gt;You can call the tools on salih.dev today. You need Chrome 150 or newer.&lt;/p&gt;

&lt;h3&gt;
  
  
  From the Chrome console
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;chrome://flags/#enable-webmcp-testing&lt;/code&gt;, set &lt;strong&gt;WebMCP for testing&lt;/strong&gt; to &lt;strong&gt;Enabled&lt;/strong&gt;, and relaunch Chrome.&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://salih.dev" rel="noopener noreferrer"&gt;salih.dev&lt;/a&gt;, then open DevTools and go to the Console.&lt;/li&gt;
&lt;li&gt;List the tools and call one:
&lt;/li&gt;
&lt;/ol&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;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTools&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;kit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_speaker_kit&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kit&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;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search_site&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agents&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;You should see seven tools on most pages and eight on &lt;a href="https://salih.dev/talks/" rel="noopener noreferrer"&gt;/talks/&lt;/a&gt;. If &lt;code&gt;document.modelContext&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;, the flag is not on yet. Some Chrome builds expect the input as a JSON string, so if &lt;code&gt;executeTool&lt;/code&gt; complains, pass &lt;code&gt;JSON.stringify({ query: "agents" })&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;On the Talks page, run &lt;code&gt;show_talks_on_page&lt;/code&gt; with &lt;code&gt;{ filter: "All talks" }&lt;/code&gt; or one of the topic labels and watch the filter buttons on the page change.&lt;/p&gt;

&lt;h3&gt;
  
  
  With the MCP Inspector
&lt;/h3&gt;

&lt;p&gt;If you want to see the calls before you hand them to an agent, use the &lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt;. It is the reference tool for testing MCP servers, and it can start Chrome DevTools MCP for you with the same flags as above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector &lt;span class="se"&gt;\&lt;/span&gt;
  npx chrome-devtools-mcp@latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--categoryExperimentalWebmcp&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chromeArg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--enable-features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;WebMCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints a URL with a one-time token, so open that URL rather than typing &lt;code&gt;localhost:6274&lt;/code&gt; yourself. Turn on the switch next to the server to connect, then switch to &lt;strong&gt;Tools&lt;/strong&gt;. You are now doing by hand what Claude Code and Codex did on their own:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;new_page&lt;/code&gt; with &lt;code&gt;https://salih.dev/talks/&lt;/code&gt; as the &lt;code&gt;url&lt;/code&gt;. Chrome opens the page, and the result lists its page ID.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;list_webmcp_tools&lt;/code&gt; with that &lt;code&gt;pageId&lt;/code&gt;. You get all eight tools with their descriptions and input schemas, exactly as an agent receives them.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;execute_webmcp_tool&lt;/code&gt; with the &lt;code&gt;pageId&lt;/code&gt;, a &lt;code&gt;toolName&lt;/code&gt; and the &lt;code&gt;input&lt;/code&gt; as a JSON string, for example &lt;code&gt;get_speaker_kit&lt;/code&gt; and &lt;code&gt;{}&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhch0trt2i9hdxalm3dbv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhch0trt2i9hdxalm3dbv.webp" alt="The MCP Inspector showing list_webmcp_tools for salih.dev, with each tool's name, description and input schema" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5h2ehe0uxdj3bsig3kn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5h2ehe0uxdj3bsig3kn.webp" alt="The MCP Inspector showing the result of get_speaker_kit, with my role, bio, focus areas and talk topics" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 2 is the useful one when you write your own tools. You read each description the way the model reads it, flattened into one long block with its schema, so a description that only makes sense next to your page is easy to spot. The Protocol tab on the right shows the raw JSON-RPC for every call if you need to see exactly what was sent.&lt;/p&gt;

&lt;p&gt;Every tool in the list has a small warning triangle. That is the Inspector's schema portability check on Chrome DevTools MCP's own schemas, not on my tools, and the calls work anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Claude Code, Codex or Kiro CLI
&lt;/h3&gt;

&lt;p&gt;Claude Code, Codex and Kiro CLI run in a terminal, not in a browser tab, so they cannot see &lt;code&gt;document.modelContext&lt;/code&gt; on their own. The bridge is &lt;a href="https://github.com/ChromeDevTools/chrome-devtools-mcp" rel="noopener noreferrer"&gt;Chrome DevTools MCP&lt;/a&gt;, Google's MCP server that gives a coding agent a real Chrome. It has an experimental WebMCP category with two tools: &lt;code&gt;list_webmcp_tools&lt;/code&gt; lists what a page registers, and &lt;code&gt;execute_webmcp_tool&lt;/code&gt; calls one of them with JSON input.&lt;/p&gt;

&lt;p&gt;The category is off by default. You need two flags: &lt;code&gt;--categoryExperimentalWebmcp&lt;/code&gt; turns on the two tools, and &lt;code&gt;--chromeArg=--enable-features=WebMCP&lt;/code&gt; turns on WebMCP in the Chrome that the server starts. That Chrome uses its own profile, so the flag you set in &lt;code&gt;chrome://flags&lt;/code&gt; does not carry over.&lt;/p&gt;

&lt;p&gt;For Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add chrome-devtools &lt;span class="nt"&gt;--scope&lt;/span&gt; user &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  npx chrome-devtools-mcp@latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--categoryExperimentalWebmcp&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chromeArg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--enable-features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;WebMCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Codex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex mcp add chrome-devtools &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  npx chrome-devtools-mcp@latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--categoryExperimentalWebmcp&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chromeArg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--enable-features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;WebMCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer editing &lt;code&gt;~/.codex/config.toml&lt;/code&gt; directly, this is the same server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mcp_servers.chrome-devtools]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npx"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"chrome-devtools-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"--categoryExperimentalWebmcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;"--chromeArg=--enable-features&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;WebMCP&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Kiro CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kiro-cli mcp add &lt;span class="nt"&gt;--name&lt;/span&gt; chrome-devtools &lt;span class="nt"&gt;--scope&lt;/span&gt; global &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--command&lt;/span&gt; npx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--args&lt;/span&gt; &lt;span class="s1"&gt;'["chrome-devtools-mcp@latest","--categoryExperimentalWebmcp","--chromeArg=--enable-features=WebMCP"]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That writes the server to &lt;code&gt;~/.kiro/settings/mcp.json&lt;/code&gt;. Use &lt;code&gt;--scope workspace&lt;/code&gt; instead if you only want it in one project, in that project's &lt;code&gt;.kiro/settings/mcp.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Restart the agent and ask it something about the site. I gave Codex this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open https://salih.dev/ in the browser. Use the page's WebMCP tools to answer:
where is Salih speaking next, and which topics has he given talks on?
Then say which WebMCP tools you called. Do not read the page HTML.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzjklgqj7hqhooo4asuf8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzjklgqj7hqhooo4asuf8.webp" alt="Codex Screenshot of the Tool Call" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It called &lt;code&gt;list_webmcp_tools&lt;/code&gt;, then &lt;code&gt;execute_webmcp_tool&lt;/code&gt; three times with &lt;code&gt;get_site_overview&lt;/code&gt;, &lt;code&gt;list_events&lt;/code&gt; and &lt;code&gt;list_talks&lt;/code&gt;. The answer had CityJS Athens on 21 to 23 October as my next event, the two events after it, and the topics from both talks. It did not read the page HTML once.&lt;/p&gt;

&lt;p&gt;I gave Codex a speaker question instead, and it went straight to &lt;code&gt;get_speaker_kit&lt;/code&gt; after listing the tools. That one call was enough for my bio, focus areas and contact routes. It also repeated the kit's note that the site has no email address, which is the line I added so an agent would not make one up. One small lesson from that run: I asked for "three focus areas", the kit has four, and Codex merged two of them to match my number. The agent trusted my prompt over the data.&lt;/p&gt;

&lt;p&gt;With Kiro CLI I asked which talks I have given and whether any of them has a recording. It opened the page, called &lt;code&gt;list_webmcp_tools&lt;/code&gt;, and then made a single &lt;code&gt;execute_webmcp_tool&lt;/code&gt; call with &lt;code&gt;list_talks&lt;/code&gt;. The answer had both talks with their events and dates, and it said neither has a recording because &lt;code&gt;videoUrl&lt;/code&gt; is empty for both. Kiro CLI asks before it runs each tool, so every one of those calls went through an approval prompt first.&lt;/p&gt;

&lt;p&gt;Two things to know before you try this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome DevTools MCP can read and change anything in the browser it controls. The server prints that warning on start, and it is right. Use it on sites you trust, and keep it away from tabs with accounts you care about.&lt;/li&gt;
&lt;li&gt;The site cannot see who is calling its tools or ask the visitor for permission. Your agent's own approval prompts are the only check, so read what it wants to call before you allow it. My tools only read, so it does not matter much here. It would matter on a site with a checkout tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chrome's &lt;a href="https://developer.chrome.com/docs/devtools/agents/webmcp-debugging" rel="noopener noreferrer"&gt;Debug WebMCP tools with AI agents&lt;/a&gt; page shows the same setup from the author's side: asking your coding agent to check that your own tools register and that their schemas are clear enough for a model.&lt;/p&gt;

&lt;h3&gt;
  
  
  With other agents
&lt;/h3&gt;

&lt;p&gt;If your agent runs inside a browser that supports WebMCP, ask it one of the questions from my About page, for example "Where is Salih speaking next, and what has he talked about before?", and check which tools it calls.&lt;/p&gt;

&lt;p&gt;An agent without a browser still gets the same answers. Point it at &lt;a href="https://salih.dev/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt;. It lists the tools and links &lt;a href="https://salih.dev/api/webmcp-index.json" rel="noopener noreferrer"&gt;/api/webmcp-index.json&lt;/a&gt;, the data the tools read.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://webmachinelearning.github.io/webmcp/" rel="noopener noreferrer"&gt;WebMCP specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/salihgueler/salih-dev" rel="noopener noreferrer"&gt;salih-dev repository&lt;/a&gt;, &lt;a href="https://github.com/salihgueler/salih-dev/pull/15" rel="noopener noreferrer"&gt;PR #15&lt;/a&gt; and &lt;a href="https://github.com/salihgueler/salih-dev/pull/16" rel="noopener noreferrer"&gt;PR #16&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/webmcp-types" rel="noopener noreferrer"&gt;webmcp-types on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://salih.dev/blog/building-an-agent-friendly-personal-website-with-specs-11b4" rel="noopener noreferrer"&gt;Building an agent-friendly personal website with specs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/blog/ai-webmcp-origin-trial" rel="noopener noreferrer"&gt;Join the WebMCP origin trial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromewebstore.google.com/detail/model-context-tool-inspec/gbpdfapgefenggkahomfgkhfehlcenpd" rel="noopener noreferrer"&gt;Model Context Tool Inspector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ChromeDevTools/chrome-devtools-mcp" rel="noopener noreferrer"&gt;Chrome DevTools MCP&lt;/a&gt; and its &lt;a href="https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/tool-reference.md#webmcp" rel="noopener noreferrer"&gt;WebMCP tool reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/devtools/agents/webmcp-debugging" rel="noopener noreferrer"&gt;Debug WebMCP tools with AI agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>agents</category>
      <category>webmcp</category>
    </item>
    <item>
      <title>Enséñale a tu agente a usar AWS</title>
      <dc:creator>Axel Espinosa</dc:creator>
      <pubDate>Fri, 16 Oct 2026 23:27:43 +0000</pubDate>
      <link>https://dev.to/aws/ensenale-a-tu-agente-a-usar-aws-448c</link>
      <guid>https://dev.to/aws/ensenale-a-tu-agente-a-usar-aws-448c</guid>
      <description>&lt;p&gt;Cada día que pasa estamos sorprendidos por lo capaces que los agentes de código se han convertido. Muchos incluso predicen que ya no necesitamos ingenieros o ingenieras en el futuro y que se viene un apocalipsis donde las computadoras se van a rebelar contra nosotros y contra aquellos que no le decían por favor y gracias a sus agentes después de terminar una tarea.&lt;/p&gt;

&lt;p&gt;Nada más alejado de la realidad. Cuando le preguntas algo a tu agente seguramente has notado que se inventa cosas que ni siquiera existen o que te da una solución que quizás no es la adecuada. Y es que el agente no tiene la culpa en sí de que no pueda darte la última versión de la API de una librería &lt;a href="https://en.wikipedia.org/wiki/Knowledge_cutoff" rel="noopener noreferrer"&gt;porque ellos tienen un corte en la información. Es decir, quedan congelados en el tiempo.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por supuesto, existen herramientas que le ayudan al agente a encontrar información actualizada y con eso puede ponerle un parche a lo que le falta de información y completar la tarea. Hemos visto cómo pasamos de &lt;a href="https://builder.aws.com/content/3Es2FFfx65RFsvYtFyFSPp56T5u/que-es-mcp-explicado-para-devs?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;MCPs a skills&lt;/a&gt; y luego a un mix entre ellas.&lt;/p&gt;

&lt;p&gt;Y es por eso que hoy quiero platicarte acerca de una herramienta que le va a ayudar a tu agente a hacer las cosas de la forma correcta cuando trabajas con AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS no tiene que ser complicado
&lt;/h2&gt;

&lt;p&gt;Sí... yo sé que alguien te dijo que usar AWS es difícil o que necesitas configurar demasiadas cosas. Pero algo que quizás nadie te ha dicho todavía es que mejoramos la plataforma para que puedas construir de manera más sencilla con AWS sin que tengas que gastar tantas horas configurando tu cuenta antes de empezar siquiera a construir.&lt;/p&gt;

&lt;p&gt;La nueva experiencia de AWS te permite comenzar de manera más sencilla y sin tanto rollo. &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/New-AWS-Builder-Experience?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Aquí te dejo un blog post adicional por si quieres leer más al respecto.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uno de los principales cambios para personas como tú y como yo que les gusta empezar rápido y sin tanto trámite es la forma en que puedes conectar tus agentes de IA para que trabajen por ti y te ayuden a construir en AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué hace esta herramienta?
&lt;/h2&gt;

&lt;p&gt;Como te dije antes, los agentes únicamente aprenden hasta cierta fecha y lo que hace esta herramienta es que te permite utilizar herramientas que quizás ya son familiares para ti como MCP y los Agent Skills para brindarle conocimiento a tu agente de cómo debe trabajarse en AWS de la forma CORRECTA, utilizando las mejores prácticas y aprovechando las bondades de la nueva experiencia.&lt;/p&gt;

&lt;p&gt;Si quieres que tu agente tenga estas habilidades solo tenemos que realizar unas configuraciones. Pero lo bueno es que podemos copiar un prompt y pegarlo en nuestro agente para que haga el trabajo por nosotros. Sencillo, ¿no?&lt;/p&gt;

&lt;h2&gt;
  
  
  Vamos a instalarlo
&lt;/h2&gt;

&lt;p&gt;Iniciemos sesión en nuestra cuenta. Si no tienes una cuenta todavía, &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/sign-in-new.html?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;te dejo un recurso para que crees tu cuenta&lt;/a&gt;. ¡Ahora puedes iniciar sesión hasta con GitHub!&lt;/p&gt;

&lt;p&gt;Copiamos el prompt que aparece en nuestra pantalla:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fae3ih4ulzz4dlatkbttu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fae3ih4ulzz4dlatkbttu.png" alt="Consola de AWS con la nueva experiencia AI First" width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El prompt le dice a nuestros agentes que deben configurar el &lt;a href="https://aws.amazon.com/products/developer-tools/agent-toolkit-for-aws?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Toolkit&lt;/a&gt;. Y además, les da instrucciones para que usen la &lt;a href="https://aws.amazon.com/blogs/aws/aws-reimagines-the-getting-started-experience?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;nueva experiencia de AWS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pegamos el prompt en nuestro editor y esperamos a que el agente termine. Solo necesitaremos ir dando permisos, ya que lo que hará es instalar la AWS CLI en caso de que no la tengamos, o la actualizará y muchas otras cosas.  &lt;/p&gt;

&lt;p&gt;Después de pegar el prompt empieza con esto. Verificando las herramientas que necesita:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevi8o8zgnrzzf0r2qcja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevi8o8zgnrzzf0r2qcja.png" alt="Imagen de mi agente trabajando" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ya aquí mi agente verificó todo lo que necesita para empezar con la configuración:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F87hcrormu4lws5thqzmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F87hcrormu4lws5thqzmz.png" alt="Imagen de mi agente trabajando ya que terminó de configurar" width="799" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En mi caso me pidió que le diera &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/connect-ai-coding-tool.html/?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;un nombre de perfil&lt;/a&gt;. Así que le puedo decir que use el default o darle un nombre. Yo elijo el default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F330qi2hwldudmqq0f14w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F330qi2hwldudmqq0f14w.png" alt="Seleccionar un perfil de AWS" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Esto sirve por si quieres trabajar en diferentes proyectos. Le dices al agente que proyecto tiene que usar y utilizará ese perfil.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Una vez definido el perfil me pide que inicie sesión. Solo necesitas &lt;code&gt;aws login&lt;/code&gt; y se abrirá una ventana en tu navegador para terminar de hacer login y seleccionar el proyecto al que quieras acceder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk3rd9qnszrvchxo4m2um.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk3rd9qnszrvchxo4m2um.png" alt="Seleccionar un proyecto" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En mi caso yo estoy colaborando en un proyecto con un compañero por eso se ven dos tarjetas para elegir. Una que en particular dice: Owned by Ramsés.&lt;/p&gt;

&lt;p&gt;Si quieres saber más acerca de los proyectos, &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/create-project.html/?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;checa este link&lt;/a&gt;, ya que es una ¡nueva feature!&lt;/p&gt;

&lt;p&gt;Seleccionamos la opción que necesitemos y vamos a ver la siguiente pantalla:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foo5pf1rmofg2h85ae8iv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foo5pf1rmofg2h85ae8iv.png" alt="Login completo en la consola de AWS" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Después de que haya seleccionado el proyecto se crean credenciales temporales para que pueda acceder a mi cuenta desde la terminal (este acceso lo usará el agente. Ojo, el agente nunca tiene acceso a mi token o contraseña).&lt;/p&gt;

&lt;p&gt;Ya que tenemos el acceso listo, el agente configura las &lt;a href="https://docs.aws.amazon.com/agent-toolkit/latest/userguide/skills.html/?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Skills que contienen buenas prácticas para trabajar con AWS&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabfzvzlza0n0bfk8uoir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabfzvzlza0n0bfk8uoir.png" alt="Skills instaladas para mi agente de inteligencia artificial" width="799" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto le dará a nuestro agente instrucciones adicionales para que sepa qué hacer en ciertos momentos y que no invente cosas.&lt;/p&gt;

&lt;p&gt;Después instalará las nuevas reglas e instrucciones para aprovechar al máximo la nueva experiencia de AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4383yzv7qgoyd3ogjwkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4383yzv7qgoyd3ogjwkm.png" alt="Instrucciones agregadas" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto agregará las instrucciones a las guías de tu agente. En mi caso, estoy usando Kiro, así que configurará esas instrucciones en los &lt;a href="https://kiro.dev/docs/steering/?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Steering Files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Al finalizar, el agente tiene todas las herramientas necesarias para trabajar con nuestra cuenta. Incluso a mí me aparece que puedo pedirle una nueva webapp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg43e0i70n9ogeiwegurj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg43e0i70n9ogeiwegurj.png" alt="Kiro trabajando" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Algo que también es útil es que le podemos preguntar acerca de la nueva experiencia de AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fewjfragnkw03vc75x646.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fewjfragnkw03vc75x646.png" alt="Nueva experiencia AWS respuesta del agente" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Otra cosa que está genial en la nueva experiencia son los &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/create-spend-limit.html/?trk=02b062ab-d194-401f-85b7-db6aae204076&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;límites de gastos&lt;/a&gt;, que también nos los menciona.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fttr3r0xr7rts5ei0n1cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fttr3r0xr7rts5ei0n1cc.png" alt="Informacion sobre los limites de gasto" width="800" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Empecemos a crear
&lt;/h2&gt;

&lt;p&gt;Ya que tenemos esto podemos crear lo que nosotros nos imaginemos y desplegarlo de inmediato en nuestra cuenta. Ya no tenemos que desperdiciar tanto tiempo configurando. Ahora podemos aprovechar ese tiempo construyendo, ya que AWS se encarga de darnos una cuenta con configuraciones sensatas por default para que no nos preocupemos.&lt;/p&gt;

&lt;p&gt;Recuerda: cada vez que vayas a trabajar con AWS, únicamente escribe &lt;code&gt;aws login&lt;/code&gt; para crear las credenciales temporales y que tu agente pueda desplegar tus recursos. ¡Ojo! No te olvides revisar cada acción que el agente tome por ti para siempre estar seguro de lo que estás haciendo 🙂.&lt;/p&gt;

&lt;p&gt;¿Qué es lo primero que vas a crear con esta nueva experiencia? Deja tu comentario.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>aws</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build without stopping to set up IAM roles in the new AWS experience</title>
      <dc:creator>Fabricio Flores</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:54:34 +0000</pubDate>
      <link>https://dev.to/aws/build-without-stopping-to-set-up-iam-roles-in-the-new-aws-experience-1mj2</link>
      <guid>https://dev.to/aws/build-without-stopping-to-set-up-iam-roles-in-the-new-aws-experience-1mj2</guid>
      <description>&lt;p&gt;The new AWS experience removes two pieces of permission setup that often interrupt an early build. First, a project owner can now invite another person with an emailed invite, instead of having to set up an IAM user or IAM Identity Center. Second, when you create a resource using a supported AWS service, &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_role-manager.html" rel="noopener noreferrer"&gt;IAM role manager&lt;/a&gt; can automatically set up the IAM role for you so that your project resources can talk to each other.&lt;/p&gt;

&lt;p&gt;Those actions involve different identities. Project access authorizes the person doing the work. An IAM role authorizes the service or application that runs afterward.&lt;/p&gt;

&lt;p&gt;If you created your account through the new AWS experience, role manager is already turned on. When you create a supported resource, such as a Lambda function, AWS creates or reuses the role it needs and attaches it automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share a project with collaborators
&lt;/h2&gt;

&lt;p&gt;A project owner can share access in two ways: invite someone from the&lt;a href="https://settings.aws.com/team" rel="noopener noreferrer"&gt;&lt;strong&gt;Team&lt;/strong&gt; page&lt;/a&gt; and choose one or more projects, or open a specific project and invite them from its &lt;strong&gt;Members&lt;/strong&gt; area. An invitation from the Team page does not automatically share every project the owner has; access applies only to the selected projects. After accepting, the team member has admin access to the resources and services available in those projects, including the ability to create access controls for services and applications. They cannot perform owner-only team-management, project, root, or financial actions. If you need to customize a team member’s access instead of granting project admin access, you must first &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/activate-advanced-features.html" rel="noopener noreferrer"&gt;activate advanced AWS features&lt;/a&gt;. For more details about sharing projects and team member access, see &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/invite-team-members.html" rel="noopener noreferrer"&gt;Invite team members to collaborate in AWS Settings&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmxo3ll40hdr55nybyzhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmxo3ll40hdr55nybyzhp.png" alt="Invite team members dialog showing an email field and two selected projects" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see the invitation process in action, watch &lt;a href="https://youtu.be/xk_TKy5Epzg" rel="noopener noreferrer"&gt;Invite Your Teammate to Your AWS Project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How role manager works
&lt;/h2&gt;

&lt;p&gt;An IAM role defines what an AWS service is allowed to access and do. Setting one up usually means defining which service can assume the role, choosing what that service is allowed to do, and attaching the role to the resource.&lt;/p&gt;

&lt;p&gt;IAM role manager automates that setup. When you create a resource in one of the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_role-manager.html#id_roles_create_role-manager_supported-services" rel="noopener noreferrer"&gt;supported services&lt;/a&gt;, role manager creates a role for it or reuses one that already fits. It then attaches the role as part of the same workflow, so you do not have to stop and set it up separately in IAM.&lt;/p&gt;

&lt;p&gt;For accounts created through the new AWS experience, role manager is enabled by default. You cannot disable it until the account owner &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/activate-advanced-features.html" rel="noopener noreferrer"&gt;activates advanced AWS features&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This restriction is enforced by an AWS-managed service control policy (SCP). In the new experience, AWS manages the SCPs and resource control policies (RCPs) that protect your projects' preconfigured defaults, so you do not set up or edit these guardrails. For details, see &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/scps-and-rcps-for-projects.html" rel="noopener noreferrer"&gt;Managed policies for your organization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After advanced features are activated, you can manage role manager from &lt;strong&gt;Account settings&lt;/strong&gt; in the IAM console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fepl6ipvdiy4wacxhqzy4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fepl6ipvdiy4wacxhqzy4.png" alt="IAM Account settings showing role manager enabled" width="799" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How role manager sets up a Lambda execution role
&lt;/h2&gt;

&lt;p&gt;Lambda is a useful example because AWS cannot know ahead of time which services your code will call. With role manager enabled, creating a function automatically attaches an execution role that the code can use immediately. You can narrow that role later, once you know what the function actually needs.&lt;/p&gt;

&lt;p&gt;Because those permissions are unknown at creation time, role manager starts the role with the AWS managed &lt;code&gt;PowerUserAccess&lt;/code&gt; policy. This gives the function broad access to AWS services, but it does not allow the function to manage IAM, AWS Organizations, or account settings. The role's trust policy allows only Lambda to assume it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fixw97u5mpig8ity2siq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fixw97u5mpig8ity2siq6.png" alt="Lambda Create function page with helloWorld as the function name and Python 3.14 selected" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the function is created, open its &lt;strong&gt;Configuration&lt;/strong&gt; tab and choose &lt;strong&gt;Permissions&lt;/strong&gt;. The &lt;strong&gt;Execution role&lt;/strong&gt; panel shows the &lt;code&gt;PowerUserRole&lt;/code&gt; that role manager attached.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg2wg364drfic4mncjfzn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg2wg364drfic4mncjfzn.png" alt="Lambda Configuration permissions showing PowerUserRole as the execution role" width="800" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the role link to IAM. On the role's &lt;strong&gt;Permissions&lt;/strong&gt; tab, choose &lt;code&gt;PowerUserAccess&lt;/code&gt; to inspect the AWS managed policy and the access it grants.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjfe7axy9tkh1pfgoh2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjfe7axy9tkh1pfgoh2b.png" alt="IAM policy details showing the PowerUserAccess managed policy and its policy document" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roles created by role manager are standard IAM roles, so you can inspect their trust policies and permissions, edit them, or delete them. Each role records the AWS-managed template it came from, which lets you identify roles created by role manager. AWS CloudTrail records each role's creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refine permissions before production
&lt;/h2&gt;

&lt;p&gt;Least privilege means giving a workload only the actions and resource access it needs. Role manager's &lt;code&gt;PowerUserAccess&lt;/code&gt; policy is useful while you are still discovering those needs, but it is intentionally broader than most production functions require.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-policy-generation.html" rel="noopener noreferrer"&gt;IAM Access Analyzer policy generation&lt;/a&gt; to help refine the role's permissions toward least privilege. Editing the role removes it from role manager's control and makes it a standard role that you manage.&lt;/p&gt;

&lt;p&gt;After activating advanced AWS features, you can disable role manager without affecting existing applications or removing their roles. Existing roles stay in place, but new resources use the standard service or IAM role-creation workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Role manager automates the initial IAM role setup for supported AWS services. It creates or reuses a role and attaches it while you create the resource, but the result remains a standard IAM role that you can inspect and control.&lt;/p&gt;

&lt;p&gt;Together with AWS-managed project sharing, this reduces the access setup required for both collaborators and the services they build. Before production, review how the workload actually behaves and use IAM Access Analyzer policy generation to replace broad permissions with a more focused policy.&lt;/p&gt;

&lt;p&gt;To get started, create a resource in a &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_role-manager.html#id_roles_create_role-manager_supported-services" rel="noopener noreferrer"&gt;supported service&lt;/a&gt;. In accounts created through the new AWS experience, role manager is already enabled. For more information about managing roles yourself, see &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create.html" rel="noopener noreferrer"&gt;IAM role creation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
    <item>
      <title>Cut AI agent token costs with decision models for tool selection</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:44:45 +0000</pubDate>
      <link>https://dev.to/aws/cut-ai-agent-token-costs-with-decision-models-for-tool-selection-2320</link>
      <guid>https://dev.to/aws/cut-ai-agent-token-costs-with-decision-models-for-tool-selection-2320</guid>
      <description>&lt;p&gt;Give an AI agent a real toolset (25, 40, more) and every call pays for it. The agent reads every tool description to use one, so input tokens grow with each tool you add. And with many similar tools in view (&lt;code&gt;search&lt;/code&gt;, &lt;code&gt;search_hotels&lt;/code&gt;, &lt;code&gt;search_flights&lt;/code&gt;...), the model picks the wrong one more often. &lt;/p&gt;

&lt;p&gt;Two costs on every turn: tokens you did not need to spend, and picks you did not want. Giving the agent fewer tools is not the fix, because different turns need different tools. You want the full toolset available but only the relevant few in front of the model each turn.&lt;/p&gt;

&lt;p&gt;In an &lt;a href="https://dev.to/aws/reduce-agent-errors-and-token-costs-with-semantic-tool-selection-7mf"&gt;earlier post&lt;/a&gt; I solved this with semantic tool selection over a vector store: embed every tool description into an index (FAISS), embed the query, and keep the nearest few. It works, and that walkthrough covers it in depth. But it has a cost of its own: you maintain an embedding index for your tools, every query pays an embedding call, and the index has to stay in sync as tools change.&lt;/p&gt;

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

&lt;p&gt;A new class of model changes this. A &lt;strong&gt;decision model&lt;/strong&gt; reads the request and the list of tools and returns a calibrated choice directly, with no embeddings and no index. It generates no text, so there is nothing to parse, and the choice comes with a confidence score. &lt;/p&gt;

&lt;p&gt;Three have arrived recently: &lt;strong&gt;&lt;a href="https://strandsagents.com/blog/introducing-strands-decider/" rel="noopener noreferrer"&gt;Strands Decider&lt;/a&gt;&lt;/strong&gt;, an open-source 2B model that runs locally; &lt;strong&gt;&lt;a href="https://docs.typesafe.ai/" rel="noopener noreferrer"&gt;Jev&lt;/a&gt;&lt;/strong&gt; (TypeSafe), a hosted one; and the &lt;strong&gt;&lt;a href="https://platform.openai.com/docs/guides/decisions" rel="noopener noreferrer"&gt;OpenAI Decisions API&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;gpt-6-luna&lt;/code&gt;, public beta since Oct 2026), also hosted. All three answer the same bounded question, &lt;em&gt;which tool fits this request?&lt;/em&gt;, and all three skip the vector store entirely. You get the token and accuracy win of tool selection without building or maintaining an index.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  How Strands fits in
&lt;/h2&gt;

&lt;p&gt;Strands lets you plug any of these in the same way, through a hook that runs before the model and swaps the agent's tools for the turn. The selector inside the hook can be the vector store or a decision model, and the same idea extends to &lt;strong&gt;model routing&lt;/strong&gt;, choosing which model serves the request.&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;class&lt;/span&gt; &lt;span class="nc"&gt;ToolFilterHook&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;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;BeforeModelCallEvent&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="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter&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;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# vector, decider, Jev, or OpenAI Decisions
&lt;/span&gt;        &lt;span class="nf"&gt;swap&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;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# the model now sees only these
&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="n"&gt;ALL_TOOLS&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;ToolFilterHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The agent is built once with every tool; the hook trims the registry per request. This is where Strands earns its place: it swaps tools on the &lt;strong&gt;live&lt;/strong&gt; agent at runtime, so the conversation survives. Frameworks that force you to rebuild the agent to change its tools lose that history. Here you keep the memory, and you switch strategy by swapping &lt;code&gt;select&lt;/code&gt; in one line.&lt;/p&gt;

&lt;p&gt;The swap itself is a few lines against the agent's tool registry. Clear it and register the chosen tools; the running agent and its message history stay intact:&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;swap&lt;/span&gt;&lt;span class="p"&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;tools&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;reg&lt;/span&gt; &lt;span class="o"&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;tool_registry&lt;/span&gt;
    &lt;span class="n"&gt;reg&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;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;# drop the previous turn's tools
&lt;/span&gt;    &lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dynamic_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;               &lt;span class="c1"&gt;# register only the ones picked this turn
&lt;/span&gt;        &lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# agent.messages is untouched — the conversation is preserved
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;References: &lt;a href="https://strandsagents.com/docs/user-guide/sdk/agents/hooks/" rel="noopener noreferrer"&gt;Strands hooks&lt;/a&gt;, &lt;a href="https://strandsagents.com/docs/user-guide/sdk/agents/hooks-events/" rel="noopener noreferrer"&gt;hook events&lt;/a&gt;,&lt;br&gt;
and &lt;a href="https://strandsagents.com/docs/user-guide/sdk/model-providers/model-routing/" rel="noopener noreferrer"&gt;model routing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keeping the live conversation is memory &lt;em&gt;within&lt;/em&gt; a session. For memory that lasts &lt;em&gt;across&lt;/em&gt; sessions, Strands has a dedicated&lt;br&gt;
&lt;a href="https://strandsagents.com/docs/user-guide/sdk/memory/overview/" rel="noopener noreferrer"&gt;memory library&lt;/a&gt;: pluggable &lt;strong&gt;memory stores&lt;/strong&gt; on a unified storage interface. You attach a store, a local file store for prototyping or &lt;strong&gt;Amazon Bedrock Knowledge Bases&lt;/strong&gt; with semantic search for production (or your own backend), and the agent gets recall, automatic injection into the prompt, and extraction of new memories. So the hook keeps this turn's context, and the memory store carries durable knowledge between runs.&lt;/p&gt;


&lt;h2&gt;
  
  
  Model routing
&lt;/h2&gt;

&lt;p&gt;The hook decides &lt;em&gt;which tools&lt;/em&gt;. &lt;strong&gt;Strands model routing&lt;/strong&gt; decides &lt;em&gt;which model&lt;/em&gt;. Give a &lt;code&gt;ModelRouter&lt;/code&gt; a few candidates and a strategy; the strategy picks one per request. A decision model fits this role well: picking the model is a &lt;code&gt;choice&lt;/code&gt; among a few unordered options — exactly what these models are built for — and it votes with a calibrated pick instead of a chat call. &lt;/p&gt;

&lt;p&gt;All three decision models plug into the same &lt;code&gt;RoutingStrategy&lt;/code&gt; interface: &lt;strong&gt;Jev&lt;/strong&gt; (hosted), &lt;strong&gt;Strands Decider&lt;/strong&gt; (local 2B), and the &lt;strong&gt;OpenAI Decisions API&lt;/strong&gt; (&lt;code&gt;gpt-6-luna&lt;/code&gt;, a &lt;code&gt;choice&lt;/code&gt; question over the candidate models). The example below uses Jev — the one wired in the demo (&lt;code&gt;model_routing_with_jev.py&lt;/code&gt;); swapping in Strands Decider or OpenAI Decisions is a one-class change, so you pick on hosted-vs-local and which provider you are already on. Routing to a cheaper model for the easy requests is where this pays off — OpenAI reports a run that cut cost 54% by classifying each job before picking the model.&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModelRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;JevRoutingStrategy&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;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Same shape as the tool hook: a cheap decision in front, the expensive model only when it is needed.&lt;/p&gt;


&lt;h2&gt;
  
  
  The comparison
&lt;/h2&gt;

&lt;p&gt;Same 12 queries, same ~25-tool pool, one demo run (AWS Bedrock Claude Haiku as the agent):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Chat tokens&lt;/th&gt;
&lt;th&gt;Latency (end to end)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Send all tools&lt;/td&gt;
&lt;td&gt;10 / 12&lt;/td&gt;
&lt;td&gt;~56k&lt;/td&gt;
&lt;td&gt;~1.1s, fastest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hook + vector&lt;/td&gt;
&lt;td&gt;11 / 12&lt;/td&gt;
&lt;td&gt;~20k&lt;/td&gt;
&lt;td&gt;~1.8s (+ an embedding call)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hook + decision model&lt;/td&gt;
&lt;td&gt;11 / 12&lt;/td&gt;
&lt;td&gt;~16k&lt;/td&gt;
&lt;td&gt;~2.3s (+ a decision call)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both selectors reach the same accuracy and cut chat tokens by about two-thirds. But each adds a call &lt;strong&gt;before&lt;/strong&gt; the model: vector embeds the query (and pays embedding tokens), the decision model runs one choice, so here they are &lt;strong&gt;slower end to end&lt;/strong&gt;, not faster. At 25 tools, sending everything is the quickest.&lt;/p&gt;

&lt;p&gt;That ordering flips as the toolset grows. The model's prefill (reading the prompt before it answers) scales worse than linearly with input length, so a bigger pile of tool descriptions makes every call slower, and an agent makes many calls. Past some tool count the baseline's growing prefill overtakes the fixed cost of one selection step, and selecting first becomes the faster path, not just the cheaper one. &lt;/p&gt;

&lt;p&gt;So the real takeaway is not a fixed latency winner; it is that you spend a small, constant selection cost to keep the prompt, the token bill, and the prefill small, and that pays off more as you add tools.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where the delay is
&lt;/h3&gt;

&lt;p&gt;Follow the request through the pipeline and you can see exactly where each approach spends time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send all tools:&lt;/strong&gt; one step, the model call. No selection, so nothing extra, but the model reads all ~25 tool descriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hook + vector:&lt;/strong&gt; three steps before the answer: &lt;em&gt;embed the query&lt;/em&gt; (a network call to the embedding model), &lt;em&gt;search the index&lt;/em&gt; (local, microseconds), then the &lt;em&gt;model call&lt;/em&gt;. The added delay is the embedding round-trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hook + decision model:&lt;/strong&gt; two steps before the answer: &lt;em&gt;the decision&lt;/em&gt; (local inference for Strands Decider, or a network call for Jev), then the &lt;em&gt;model call&lt;/em&gt;. The added delay is that one decision.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8u6hj36s404xdyljx66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8u6hj36s404xdyljx66.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the extra latency lives in the selection step, before the model runs. The model call itself is cheaper afterwards (a short tool list), but not cheap enough at this scale to beat skipping selection entirely.&lt;/p&gt;


&lt;h2&gt;
  
  
  Which selector should I use?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Already embedding things?&lt;/strong&gt; Vector is the smallest add.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want a calibrated pick and no index?&lt;/strong&gt; A decision model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a small model locally?&lt;/strong&gt; Strands Decider 2B. &lt;strong&gt;Prefer a managed endpoint?&lt;/strong&gt; Jev.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already on OpenAI, or need image context?&lt;/strong&gt; The OpenAI Decisions API — hosted, calibrated &lt;code&gt;choice&lt;/code&gt; + confidence, billed on input tokens only ($0.10/1M), and it accepts images as well as text (which Jev does not yet).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not exclusive. The hook makes them interchangeable in one line.&lt;/p&gt;
&lt;h3&gt;
  
  
  A quick selection benchmark
&lt;/h3&gt;

&lt;p&gt;Measuring only the pick — &lt;em&gt;which tool each selector returns&lt;/em&gt; — over 20 labelled queries against a ~25-tool pool (warm, single run on 2026-10-07):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Selector&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vector (FAISS/Titan)&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;155 ms&lt;/td&gt;
&lt;td&gt;144 ms&lt;/td&gt;
&lt;td&gt;— (embedding call)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jev (hosted)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;210 ms&lt;/td&gt;
&lt;td&gt;200 ms&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Decisions (&lt;code&gt;gpt-6-luna&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;275 ms&lt;/td&gt;
&lt;td&gt;258 ms&lt;/td&gt;
&lt;td&gt;$0.054 / 1k picks (538 input tok each)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Small set, one run — treat it as an early signal, not a verdict. Both decision models matched the right tool on every query here; vector missed one. (This is the &lt;em&gt;selection&lt;/em&gt; step in isolation; the end-to end table above measures a different 12-query run including the agent's answer.)&lt;/p&gt;


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

&lt;p&gt;All of this runs in the open-source demo (Demo 02),&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/elizabethfuentes12/why-agents-fail-sample-for-amazon-agentcore" rel="noopener noreferrer"&gt;why-agents-fail-sample-for-amazon-agentcore&lt;/a&gt;&lt;/strong&gt;. It has &lt;code&gt;chat.py&lt;/code&gt; (vector), &lt;code&gt;chat_decider.py&lt;/code&gt; (local decision model), &lt;code&gt;model_routing_with_jev.py&lt;/code&gt; (model routing), and a notebook that measures accuracy and tokens for each — including the hosted OpenAI Decisions API as a third selector.&lt;/p&gt;


&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does trimming tools hurt accuracy?&lt;/strong&gt; No. In the demo it raised accuracy, because fewer similar tools means less confusion. The risk is dropping the right tool; tune how many you keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why a hook instead of rebuilding the agent?&lt;/strong&gt; Rebuilding loses the conversation. The hook changes the tools on the live agent, so memory survives and you switch strategy in one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a decision model save tokens?&lt;/strong&gt; It sends fewer tool descriptions to the agent and generates no text itself. It is a separate, cheap call, not a chat turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this tied to Strands?&lt;/strong&gt; The idea (filter before the model) is general. Strands makes it first-class: hooks, the tool-registry swap, and model routing are built in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do I need to run the OpenAI Decisions selector?&lt;/strong&gt; An &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; and &lt;code&gt;openai&amp;gt;=3.26.0&lt;/code&gt; (or just POST to &lt;code&gt;/v1/decisions&lt;/code&gt;). It is in public beta on &lt;code&gt;gpt-6-luna&lt;/code&gt;, returns a typed &lt;code&gt;choice&lt;/code&gt; with confidence, and bills input tokens only — no output or cache charges.&lt;/p&gt;



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

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


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



</description>
      <category>ai</category>
      <category>python</category>
      <category>jev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I built this serverless API with the new AWS</title>
      <dc:creator>Fabricio Flores</dc:creator>
      <pubDate>Thu, 08 Oct 2026 21:56:17 +0000</pubDate>
      <link>https://dev.to/aws/how-i-built-this-serverless-api-with-the-new-aws-56jc</link>
      <guid>https://dev.to/aws/how-i-built-this-serverless-api-with-the-new-aws-56jc</guid>
      <description>&lt;p&gt;AI coding agents have shortened the path from an idea to working code. The &lt;a href="https://signin.aws.amazon.com/signup?request_type=builderId&amp;amp;trk=0a3e63d3-e29a-4c69-b70d-caa08a783f72&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;updated AWS experience&lt;/a&gt; is designed to make deployment keep pace. It connects your agent to an AWS project, configures the AWS tools it needs, and handles permissions between the resources it creates.&lt;/p&gt;

&lt;p&gt;I used that workflow to turn a short prompt into a Python API using Amazon API Gateway, AWS Lambda, and Amazon DynamoDB. The build starts by connecting the agent to AWS and ends with a working endpoint that I call from my terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect a coding agent to the AWS project
&lt;/h2&gt;

&lt;p&gt;After signing in, I followed the instructions in the &lt;strong&gt;Setup Agent Toolkit for AWS&lt;/strong&gt; dialog and pasted its setup prompt into my coding agent.&lt;/p&gt;

&lt;p&gt;The agent then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installed the AWS Command Line Interface (AWS CLI),&lt;/li&gt;
&lt;li&gt;used &lt;code&gt;aws login&lt;/code&gt; to sign me in,&lt;/li&gt;
&lt;li&gt;configured the AWS MCP server and core AWS skills, and&lt;/li&gt;
&lt;li&gt;added customizable rules for building on AWS.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2zqtaje453zxh3lyr6zp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2zqtaje453zxh3lyr6zp.png" alt="Setup Agent Toolkit for AWS dialog containing instructions to paste into a coding agent" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe the API in one prompt
&lt;/h2&gt;

&lt;p&gt;With the agent connected, I described the service I wanted:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjyekiwjqdtxnkgydlfih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjyekiwjqdtxnkgydlfih.png" alt="Prompt asking a coding agent to create and deploy a serverless API on AWS" width="796" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I specified Python, asked for a stateful response I could test, and told the agent to show me the architecture before deploying. I left the AWS service choices open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the proposed architecture
&lt;/h2&gt;

&lt;p&gt;Before creating resources, the agent proposed this architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbei5vecgnfc36hyl4iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbei5vecgnfc36hyl4iw.png" alt="Serverless API architecture showing a client calling Amazon API Gateway, AWS Lambda, and Amazon DynamoDB" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The detailed proposal defined &lt;code&gt;GET /counter&lt;/code&gt; as an atomic increment of a value stored in DynamoDB. API Gateway would expose the public endpoint, a Python Lambda function would handle each request, and an on-demand DynamoDB table would maintain state across calls. The atomic update would prevent duplicate values during concurrent requests.&lt;/p&gt;

&lt;p&gt;The plan also included conservative throttling, a least-privilege IAM role for the table update and function logging, CloudWatch log retention, local tests, and deployment through an AWS CloudFormation stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9tbydh5dwx17f973pcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9tbydh5dwx17f973pcf.png" alt="Coding agent presenting the detailed serverless architecture and asking for approval before deployment" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent explicitly said that no AWS resources had been changed and asked me to approve the architecture and public endpoint before continuing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the agent build and deploy
&lt;/h2&gt;

&lt;p&gt;After I approved the plan, the agent deployed the &lt;code&gt;serverless-counter-api&lt;/code&gt; CloudFormation stack in &lt;code&gt;us-east-2&lt;/code&gt;. The deployment included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a public API Gateway endpoint,&lt;/li&gt;
&lt;li&gt;a Python 3.12 Lambda function,&lt;/li&gt;
&lt;li&gt;a DynamoDB table to store the counter, and&lt;/li&gt;
&lt;li&gt;an IAM role giving the function the access it needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API was intentionally public for this demo. Anyone with the URL could invoke it and generate usage, so a production API should use authorization appropriate to its callers and data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqy37tr0pevqeai4r6afe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqy37tr0pevqeai4r6afe.png" alt="Coding agent listing the AWS resources deployed for the serverless service" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Call the live endpoint
&lt;/h2&gt;

&lt;p&gt;The agent verified the deployment by calling the endpoint three times. Each call returned HTTP 200 with &lt;code&gt;{"value": 1}&lt;/code&gt;, &lt;code&gt;{"value": 2}&lt;/code&gt;, and &lt;code&gt;{"value": 3}&lt;/code&gt;. A consistent DynamoDB read confirmed that the stored value was &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Later, I set &lt;code&gt;API_URL&lt;/code&gt; to the &lt;code&gt;/prod/counter&lt;/code&gt; invoke URL and called it three times in a loop:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0k4d2br1ak8mfe95ljr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0k4d2br1ak8mfe95ljr.png" alt="Terminal showing three calls to the deployed API returning values 7, 8, and 9" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those calls returned &lt;code&gt;{"value": 7}&lt;/code&gt;, &lt;code&gt;{"value": 8}&lt;/code&gt;, and &lt;code&gt;{"value": 9}&lt;/code&gt;. The increasing values confirmed that the service was live and maintaining state across requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I kept control of
&lt;/h2&gt;

&lt;p&gt;I chose what the API should do, reviewed the proposed architecture, and approved the public deployment. The agent handled the tooling setup, implementation, deployment, and first tests.&lt;/p&gt;

&lt;p&gt;Before wrapping up, I verified the result myself: Lambda updated DynamoDB atomically and returned the new value, the permissions were limited to what the function needed, and &lt;code&gt;GET /counter&lt;/code&gt; worked. The agent reported success, but the final call was mine.&lt;/p&gt;

&lt;p&gt;Try this with a coding agent you already use. &lt;a href="https://signin.aws.amazon.com/signup?request_type=builderId&amp;amp;trk=0a3e63d3-e29a-4c69-b70d-caa08a783f72&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Get started here&lt;/a&gt;, then give your agent a small AWS project and ask to see the plan before it deploys anything. You make the calls and check the result. Let the agent handle the setup and get the first version running.&lt;/p&gt;

&lt;p&gt;Now go build something 😄.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to use the OpenAI Decisions API with Strands Agents</title>
      <dc:creator>Elizabeth Fuentes L</dc:creator>
      <pubDate>Wed, 07 Oct 2026 18:50:13 +0000</pubDate>
      <link>https://dev.to/aws/how-to-use-the-openai-decisions-api-with-strands-agents-4eok</link>
      <guid>https://dev.to/aws/how-to-use-the-openai-decisions-api-with-strands-agents-4eok</guid>
      <description>&lt;p&gt;OpenAI shipped a Decisions API. It isn't a chat model you coax into choosing: it&lt;br&gt;
answers a bounded question directly. You give it a request and a fixed list of&lt;br&gt;
options, it returns one of them with a confidence. No generated text, no option&lt;br&gt;
it invents, nothing to parse. The returned choice is always one you supplied.&lt;/p&gt;

&lt;p&gt;An agent is full of bounded questions, so I wired it into a&lt;br&gt;
&lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands&lt;/a&gt; agent two ways. Both are in the repo and&lt;br&gt;
both run. Here they are side by side.&lt;/p&gt;
&lt;h2&gt;
  
  
  A quick word on Strands
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; is an open-source SDK for building AI agents. You create one in a couple of lines: give it a model, a system prompt, and the tools it can 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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;global.anthropic.claude-haiku-4-5-20251001-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;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a travel agent that helps people with hotels.&lt;/span&gt;&lt;span class="sh"&gt;"&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;get_hotel_room_rates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_hotel&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How much is a room at the Cliffside Resort?&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;Tools, hooks, a model router: they attach to the agent like Lego pieces. Add a tool and the agent can call it; add a hook and you run your own code at a point in the agent's lifecycle; swap the model for a router and the agent chooses its model per request. The agent stays the same two lines; you compose behavior by clicking pieces onto it. The two ways below are exactly that: one hook, one router.&lt;/p&gt;

&lt;h2&gt;
  
  
  The call, once
&lt;/h2&gt;

&lt;p&gt;Both uses share one request shape, through the official async client:&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncOpenAI&lt;/span&gt;

&lt;span class="n"&gt;oai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncOpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads OPENAI_API_KEY
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;oai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decisions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-luna&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How much is a room at the Cliffside Resort?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;questions&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;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;choice&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;name&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;pick&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;instructions&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;Which option best answers this request?&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;choices&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="c1"&gt;# one {value, description} per option
&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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;get_hotel_room_rates&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;description&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;Nightly room rates for a hotel&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;value&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;get_weather&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;description&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;Current weather for a city&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="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pick&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;        &lt;span class="c1"&gt;# -&amp;gt; "get_hotel_room_rates"
&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt;    &lt;span class="c1"&gt;# -&amp;gt; 0.97
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What changes between the two uses is only &lt;em&gt;what the choices are&lt;/em&gt;: tools in one,&lt;br&gt;
models in the other. Both are below. One narrows the toolbox a model sees; the&lt;br&gt;
other picks which model runs. Pick the one your agent needs, or use both.&lt;/p&gt;
&lt;h2&gt;
  
  
  Way 1: choose the tool, and cut the tokens
&lt;/h2&gt;

&lt;p&gt;Give a small model forty overlapping tools and it picks the wrong one or invents&lt;br&gt;
one. Every tool is also tokens it pays for on every turn: name, description,&lt;br&gt;
schema. Choosing the relevant tool before the model runs fixes both, and in&lt;br&gt;
Strands that choice lives in a &lt;strong&gt;hook&lt;/strong&gt; (&lt;code&gt;BeforeModelCallEvent&lt;/code&gt;), because it&lt;br&gt;
changes what the agent sees: the hook swaps the agent's tool registry down to&lt;br&gt;
the pick. The selector is one function; everything else is the hook.&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;oai_choices&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_spec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALL_TOOLS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;openai_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;oai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decisions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-luna&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;questions&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;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;choice&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;name&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;tool&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;instructions&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;Which tool answers this request?&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;choices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;oai_choices&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&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="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="n"&gt;BY_NAME&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&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;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AGENT_MODEL&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="n"&gt;ALL_TOOLS&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;PROMPT&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;ToolFilterHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openai_select&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;callback_handler&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How much is a room at the Cliffside Resort?&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;Over the demo's 40-tool pool, each pick is a bounded choice rather than a&lt;br&gt;
generated tool name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_hotel_room_rates   (confidence 1.00)  &amp;lt;- How much is a room at the Cliffside Resort?
get_weather_forecast   (confidence 0.99)  &amp;lt;- What's the weather forecast for Tokyo next week?
book_hotel             (confidence 0.97)  &amp;lt;- Book Cliffside Resort for John Smith for 2 nights
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Way 2: choose the model, and match cost to the request
&lt;/h2&gt;

&lt;p&gt;Not every request needs your strongest model. Routing the easy ones to a small,&lt;br&gt;
cheap model and reserving the capable model for hard work keeps quality where it&lt;br&gt;
matters without paying top price on trivial questions.&lt;/p&gt;

&lt;p&gt;Strands routes each request to one of several models with &lt;code&gt;ModelRouter&lt;/code&gt;, and the&lt;br&gt;
choice is a pluggable &lt;code&gt;RoutingStrategy&lt;/code&gt;. This choice does not live in a hook: you&lt;br&gt;
pass the router as the agent's model, which is Strands' seam for picking the&lt;br&gt;
model (the docs say so plainly: pass the router as the model, not through&lt;br&gt;
plugins). Plug the Decisions API in as the classifier: the router's candidates&lt;br&gt;
become the choices, and the decision model picks which model serves the turn. It&lt;br&gt;
judges the request against each candidate's &lt;em&gt;description&lt;/em&gt;, and knows nothing&lt;br&gt;
about the models themselves.&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;class&lt;/span&gt; &lt;span class="nc"&gt;OpenAIDecisionsRoutingStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RoutingStrategy&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_oai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncOpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;select&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;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# route the opening attempt only
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_user_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;choices&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;_oai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decisions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-luna&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;questions&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;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;choice&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;name&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;model&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;instructions&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;Which model should handle this request?&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;choices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModelRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;advanced&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIDecisionsRoutingStrategy&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;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_handler&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The routine lookups go to the small model, the reasoning tasks to the strong&lt;br&gt;
one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routine   (confidence 1.0)   &amp;lt;- What time zone is Tokyo in?
routine   (confidence 1.0)   &amp;lt;- What is the capital of Australia?
advanced  (confidence 1.0)   &amp;lt;- Design a rollback-safe idempotency-key migration
advanced  (confidence 0.98)  &amp;lt;- Compare optimistic vs pessimistic locking, recommend one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  So which one?
&lt;/h2&gt;

&lt;p&gt;Both are the same one-call pattern behind different Strands seams: a hook for the&lt;br&gt;
tool, a router for the model. A decision model is a &lt;em&gt;chooser&lt;/em&gt;, so you stop asking&lt;br&gt;
a &lt;em&gt;generator&lt;/em&gt; to choose and hoping its text parses into a real option. How would&lt;br&gt;
you point it at your agent? Which choice in your loop is the one you don't trust&lt;br&gt;
a chat model to make?&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;Both ways run end to end against your own questions. Set &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; and go:&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/elizabethfuentes12/why-agents-fail-sample-for-amazon-agentcore
&lt;span class="nb"&gt;cd &lt;/span&gt;why-agents-fail-sample-for-amazon-agentcore/02-semantic-tools-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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it saves you time, give the repo a ⭐. It helps others find it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/" rel="noopener noreferrer"&gt;OpenAI Decisions API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Strands &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/" rel="noopener noreferrer"&gt;hooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Strands &lt;a href="https://strandsagents.com/docs/user-guide/sdk/model-providers/model-routing/" rel="noopener noreferrer"&gt;model routing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/elizabethfuentes12/why-agents-fail-sample-for-amazon-agentcore" rel="noopener noreferrer"&gt;Why AI Agents Fail&lt;/a&gt; (the companion repo)&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;

</description>
      <category>openai</category>
      <category>strands</category>
      <category>agents</category>
      <category>python</category>
    </item>
    <item>
      <title>Por que os assistentes de IA ainda erram tanto (e o que fazer sobre isso)</title>
      <dc:creator>Giovana Armani</dc:creator>
      <pubDate>Fri, 02 Oct 2026 20:21:28 +0000</pubDate>
      <link>https://dev.to/aws/por-que-os-assistentes-de-ia-ainda-erram-tanto-e-o-que-fazer-sobre-isso-4n00</link>
      <guid>https://dev.to/aws/por-que-os-assistentes-de-ia-ainda-erram-tanto-e-o-que-fazer-sobre-isso-4n00</guid>
      <description>&lt;p&gt;Em janeiro desse ano, perguntei para meu assistente de IA sobre AWS Lambda Durable Functions. Ele muito gentilmente me falou que eu estava ficando maluca, a AWS não tinha uma feature com esse nome...&lt;/p&gt;

&lt;p&gt;Acontece que essa feature foi lançada em dezembro do ano passado e quando fiz a pergunta, estava usando um modelo de IA que tinha sido treinado vários meses antes. Meu assistente não tinha ferramentas que conectasse ele à documentação da AWS ou nenhuma instrução de quando ela deveria ser consultada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Os desafios de trabalhar com IA
&lt;/h2&gt;

&lt;p&gt;Conto essa história de frustração pessoal para dizer que trabalhar com IA não é um mar de rosas. Se você é desenvolvedor nos dias de hoje, você já sabe disso, mas quando tudo ao seu redor fala sobre como a IA vai dominar o mundo, é difícil entender porque ainda tem tantos problemas. &lt;/p&gt;

&lt;p&gt;Para entender isso, é legal olhar para como os modelos de IA são construídos e como funcionam. Para começo de conversa, a maioria dos assistentes de IA atuais são construídos com LLMs (Large Language Models, ou em português, Grandes Modelos de Linguagem). Esses modelos são treinados com um volume gigante de dados que ajudam eles a entender, interpretar e produzir texto. Isso vale para linguagem humana e também para código.&lt;/p&gt;

&lt;p&gt;Quando mandamos um prompt a um assistente de IA, o modelo por trás faz um &lt;em&gt;cálculo probabilístico&lt;/em&gt; para nos dar uma resposta. Em outras palavras, o modelo produz o texto da resposta prevendo parte por parte qual é a próxima palavra mais provável de se encaixar no que o usuário espera. Isso nos traz resultados que parecem coerentes, mas não são necessariamente o que queremos ou a melhor solução para nosso problema.&lt;/p&gt;

&lt;p&gt;Esse funcionamento traz vários desafios que vamos ver ao longo do artigo (e também como lidar com eles), mas também traz uma boa notícia para os desenvolvedores que vira e mexe me perguntam se a IA vai roubar o trabalho deles. &lt;strong&gt;Escrever código é diferente de construir software&lt;/strong&gt;, assim como &lt;strong&gt;escrever texto é diferente de produzir conteúdo&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Quando falamos sobre escrever código, queremos dizer transformar instruções em implementação. Já quando falamos em software, nos referimos a transformar ideias abstratas em um sistema confiável. Ou seja, por mais poderosa que a IA fique, alguém ainda precisa entender o problema antes de ele virar código, acompanhar enquanto o sistema é desenvolvido e ser dono do software depois que ele existe. É aí que mora o perigo dos resultados que parecem certos, estão quase certos, mas não exatamente. Se a gente aceita tudo de olhos fechados, herdamos esses "quase" como se fossem nossos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como trabalhar melhor com IA
&lt;/h2&gt;

&lt;p&gt;Vamos então falar mais especificamente sobre desafios do uso de IA no desenvolvimento e criação e o que podemos fazer sobre eles. Os assistentes de IA trazem várias features que podem nos ajudar a administrar esses desafios e trabalhar melhor com a ferramenta.&lt;/p&gt;

&lt;p&gt;Vou usar o &lt;a href="https://kiro.dev/?trk=4c2f66ff-5d75-4a07-aa36-eb6219a429db&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; e suas features como base da discussão, mas a maioria das dicas aqui podem ser aplicadas com qualquer assistente.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que fazer quando a IA te chama de maluca
&lt;/h3&gt;

&lt;p&gt;Como vimos, os modelos de IA têm como base o conhecimento que receberam através de seus dados de treinamento. Se não conectamos eles a ferramentas ou fontes de informação externas, eles acabam ficando "presos no tempo", com o conhecimento limitado a esses dados. É isso que gera situações como a que eu descrevi no começo do artigo em que o Kiro me falou que não existia uma feature chamada AWS Lambda Durable Functions.&lt;/p&gt;

&lt;p&gt;A forma mais comum de conectar seu assistente a ferramentas externas é o &lt;strong&gt;MCP&lt;/strong&gt;, ou &lt;strong&gt;Model Context Protocol&lt;/strong&gt;. Ele é um protocolo aberto que conecta assistentes de IA a ferramentas externas. &lt;/p&gt;

&lt;p&gt;Um bom exemplo é o servidor MCP &lt;a href="https://aws.amazon.com/pt/about-aws/whats-new/2025/10/aws-knowledge-mcp-server-generally-available/?trk=4c2f66ff-5d75-4a07-aa36-eb6219a429db&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;aws-knowledge&lt;/a&gt;, mantido pela própria AWS, que dá ao assistente acesso à documentação oficial sempre atualizada. Com ele conectado, o Kiro deixa de depender só do que aprendeu no treinamento e passa a consultar a fonte na hora de responder. Foi assim que resolvi meu problema lá no começo do ano enquanto estudava sobre Lambda Durable Functions. Com esse servidor configurado, em vez de me dizer que eu estava ficando maluca, o Kiro agora consulta a documentação, descobre que a feature existe sim, e me responde baseado nela.&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;"mcpServers"&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;"aws-knowledge-mcp-server"&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;"url"&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="s2"&gt;"https://knowledge-mcp.global.api.aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"disabled"&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="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MCP - Melhores Práticas
&lt;/h4&gt;

&lt;p&gt;É importante lembrar que além de leitura, um servidor MCP pode também ter acesso a ferramentas para criar, editar ou até apagar coisas por você. Por isso, vale tomar cuidado com o uso. Algumas melhores práticas que sugiro:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Revise antes de aprovar:&lt;/em&gt; Toda vez que o assistente quiser usar uma ferramenta MCP, ele pede sua aprovação. Não aprove no automático, antes leia o que ele vai fazer, confira os parâmetros e entenda o efeito. Se parecer suspeito, negue. Deixe o auto-approve apenas para ações que você sabe que são seguras e repetitivas, como uma consulta de leitura. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Bloqueie ações perigosas:&lt;/em&gt; Além do cuidado de revisar o que o assistente faz, você pode explicitamente configurar o que você jamais quer que ele faça. Por exemplo, se você conecta seu assistente com um MCP do GitHub, pode querer impedir que ele tome ações destrutivas como apagar repositórios ou forçar o merge de uma alteração. No Kiro, por exemplo, faríamos isso com &lt;code&gt;disabledTools&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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;"mcpServers"&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;"github"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"github-mcp-server@latest"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"disabledTools"&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;"delete_repository"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"force_push"&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="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;3 . &lt;em&gt;Auto approve para ações seguras:&lt;/em&gt; Por outro lado, existem ações cotidianas que sabemos que são seguras e não queremos ficar clicando aprove o tempo todo, para isso podemos declarar as ferramentas que são automaticamente aprovadas.&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;"mcpServers"&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;"github"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"github-mcp-server@latest"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"disabledTools"&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;"delete_repository"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"force_push"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"autoApprove"&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;"search_repositories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_file_contents"&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="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;4 . &lt;em&gt;Proteja suas chaves e tokens:&lt;/em&gt; Muitos servidores MCP precisam de credenciais para funcionar. Nunca faça commit de uma config com segredos dentro, prefira variáveis de ambiente. E quando gerar um token, use o de menor permissão possível e rotacione de tempos em tempos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por que você recebe respostas genéricas mesmo com prompts gigantes
&lt;/h3&gt;

&lt;p&gt;Outro problema que enfrentamos é de fazer a IA entender exatamente o que queremos. Costumamos achar que colocar o máximo de informação possível no prompt vai fazer com que as respostas sejam mais assertivas, o que muitas vezes até ajuda. Mas quando o assistente tem informação demais sem referência de prioridade, pode ter dificuldade de determinar o que é realmente importante. A verdade é que contexto irrelevante não é neutro, compete por atenção e piora a qualidade da resposta.&lt;/p&gt;

&lt;p&gt;A solução para isso é direcionar o foco do seu assistente, e uma das formas mais práticas de fazer isso são os &lt;strong&gt;steering files&lt;/strong&gt;. São arquivos markdown onde você registra as regras, padrões e preferências do seu projeto, e que o assistente passa a considerar na hora de responder. Em vez de repetir no prompt que você usa um certo padrão de nomes, que prefere uma biblioteca a outra ou que todo componente precisa ser acessível, você escreve isso uma vez em um steering file e o assistente lembra. No Kiro eles ficam na pasta &lt;code&gt;.kiro/steering/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Um detalhe importante é que você não precisa deixar todo esse contexto ligado o tempo todo, o que nos traria de volta o problema de contexto demais. Para isso existem os &lt;strong&gt;inclusion modes&lt;/strong&gt;, que controlam quando cada arquivo entra na conversa. No Kiro, um steering file pode estar sempre ativo (&lt;code&gt;always&lt;/code&gt;), ser carregado só quando você mexe em certos arquivos (&lt;code&gt;fileMatch&lt;/code&gt;), entrar sob demanda quando você chama (&lt;code&gt;manual&lt;/code&gt;), ou ser puxado por relevância com base na descrição (&lt;code&gt;auto&lt;/code&gt;). Assim, o Kiro carrega cada regra no momento em que ela é realmente necessária.&lt;/p&gt;

&lt;h4&gt;
  
  
  Steering Files - Melhores Práticas
&lt;/h4&gt;

&lt;p&gt;Para aproveitar bem os steering files, algumas boas práticas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Um domínio por arquivo:&lt;/em&gt; Separe API, testes, deploy, etc em arquivos distintos, em vez de amontoar tudo em um arquivão só. É o mesmo princípio de um bom código. Isso traz dois ganhos. Primeiro, os inclusion modes só funcionam bem se cada arquivo tem um escopo único, afinal não dá para carregar "só a parte de testes" de um arquivo que mistura dez assuntos diferentes. Segundo, facilita a manutenção, já que arquivos focados geram diffs pequenos e legíveis, o que facilita a revisão.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Nomes claros e descritivos:&lt;/em&gt; Prefira nomes específicos como &lt;code&gt;api-rest-conventions.md&lt;/code&gt;, &lt;code&gt;testing-unit-patterns.md&lt;/code&gt; ou &lt;code&gt;components-form-validation.md&lt;/code&gt;. A própria lista de arquivos vira um índice do que existe e de onde mexer, tanto para você quanto para o assistente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Inclua o "porquê":&lt;/em&gt; Não registre só o "o quê" da regra, explique a razão por trás dela. Uma regra sem justificativa só funciona nos casos que ela previu literalmente, enquanto uma regra com justificativa funciona também nos casos que ninguém antecipou. Se o steering diz "fazemos X porque já tentamos Y e deu o problema Z", o assistente não vai tentar "consertar" uma decisão que foi deliberada.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Faça manutenção regular:&lt;/em&gt; Trate mudanças de steering como mudanças de código, com revisão. Revise os arquivos em momentos como planejamento de sprint ou mudanças de arquitetura, e confira as referências a arquivos depois de reestruturações, para não deixar o assistente seguindo uma regra que não vale mais.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Para quando não aguentar mais escrever os mesmos prompts
&lt;/h3&gt;

&lt;p&gt;Já teve algum momento que você percebeu que vive sempre pedindo a mesma coisa para seu assistente de IA? E mesmo assim, a cada vez que pede tem que ir fazendo várias correçõezinhas no processo? Talvez esteja na hora de criar uma &lt;strong&gt;skill&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Skills são pacotes de conhecimento especializado e instruções que você empacota uma vez e reutiliza sempre que precisar. Em vez de reescrever o mesmo prompt detalhado e ir fazendo as mesmas correções toda vez, você formaliza aquele processo recorrente numa skill: o passo a passo, as regras principais e até scripts de apoio ou arquivos de referência. O resultado é mais assertividade, porque o assistente deixa de reinventar o processo a cada pedido e passa a seguir sempre o mesmo caminho que você já validou.&lt;/p&gt;

&lt;p&gt;A diferença das skills para os steering files está em &lt;em&gt;quando&lt;/em&gt; e &lt;em&gt;como&lt;/em&gt; cada um atua. O steering é amplo e sempre lembra o assistente dos padrões do seu projeto. Já a skill é especializada e sob demanda. Dá para pensar assim: steering é "sempre tenha isso em mente", enquanto skill é "quando alguém pedir para fazer X, carregue todo esse conhecimento especializado e siga este workflow". No Kiro, as skills ficam em pastas (uma por skill, com um arquivo que descreve o que ela faz) e são ativadas automaticamente quando o pedido combina com a descrição que você escreveu, ou você pode chamá-las explicitamente.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwbesh576dfto3pgqatc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwbesh576dfto3pgqatc.png" alt="Imagem ilustrando a diferença entre steerings (sempre tenha isso em mente durante as tarefas) e skills (quando te pedirem tarefa X, faça dessa maneira)" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Skills - Melhores Práticas
&lt;/h4&gt;

&lt;p&gt;Para criar skills que realmente ajudam, valem algumas boas práticas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Escreva descrições precisas:&lt;/em&gt; É a descrição que decide quando o assistente ativa a skill, então inclua palavras-chave e ações concretas. No início da conversa, só o nome e a descrição são carregados (um mecanismo chamado &lt;em&gt;progressive disclosure&lt;/em&gt;, que evita encher o contexto do assistente), e o resto só entra quando a skill é acionada. Por isso quanto mais clara e precisa for sua descrição, mais fácil será para o assistente saber o momento de usá-la.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Use scripts para tarefas determinísticas:&lt;/em&gt; Tarefas determinísticas como validação, geração de arquivos e chamadas de API são mais confiáveis como scripts do que como código gerado pelo LLM na hora. Se o passo sempre acontece do mesmo jeito, deixe-o num script dentro da skill, em vez de contar com o modelo para recriá-lo a cada vez.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Escolha o escopo certo:&lt;/em&gt; Use o escopo global (&lt;code&gt;~/.kiro/skills/&lt;/code&gt;) para workflows pessoais que você usa em qualquer projeto, e o escopo de workspace (&lt;code&gt;.kiro/skills/&lt;/code&gt;) para procedimentos do time e convenções específicas daquele projeto. Nesse segundo caso, versione a pasta junto do código para que todo mundo compartilhe os mesmos workflows.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Como ser mais assertivo no vibe coding
&lt;/h3&gt;

&lt;p&gt;Por último, vamos falar sobre o uso de IA quando desenvolvemos aplicações. Com o uso de IA para desenvolvimento de software, a produção de código está mais rápida do que nunca. Mas será que com tanta velocidade e volume, estamos sendo atentos à qualidade produzida? Temos clareza dos requisitos e certeza que o código os cumpre como esperado? Estamos seguindo os padrões de código que determinamos para facilitar revisão e manutenção?&lt;/p&gt;

&lt;p&gt;Para nos ajudar com isso, o Kiro traz uma funcionalidade de &lt;strong&gt;spec-driven development&lt;/strong&gt;, ou o &lt;strong&gt;Spec mode&lt;/strong&gt;. Se trata de uma forma de desenvolvimento de features guiado por especificações. Você começa o desenvolvimento pela especificação de requisitos e documentação. Isso acontece no Kiro através da criação de 3 arquivos: um &lt;code&gt;requirements.md&lt;/code&gt; com o que precisa ser feito, um &lt;code&gt;design.md&lt;/code&gt; com as decisões técnicas e um &lt;code&gt;tasks.md&lt;/code&gt; com o passo a passo da implementação. Só depois de alinhar esses documentos é que a IA parte para escrever o código, agora com um alvo claro em vez de um prompt solto. Se quiser saber mais sobre specs e o que eu construi usando esse modo, veja &lt;a href="https://builder.aws.com/content/381ae4bUb4KxjJOnTVWAO9FnnXC/garanta-o-shape-do-verao-com-kiro-e-spec-driven-development?trk=4c2f66ff-5d75-4a07-aa36-eb6219a429db&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;esse artigo&lt;/a&gt; sobre a aplicação que construí para acompanhar meus treinos.&lt;/p&gt;

&lt;h4&gt;
  
  
  Spec Driven Development - Melhores Práticas
&lt;/h4&gt;

&lt;p&gt;Para tirar o melhor do spec-driven development, sugiro algumas boas práticas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Uma spec por feature, não uma spec gigante:&lt;/em&gt; O padrão recomendado é ter várias specs no repositório, cada uma para uma feature, em vez de uma única spec tentando descrever o codebase inteiro. Algo como &lt;code&gt;.kiro/specs/user-authentication/&lt;/code&gt;, &lt;code&gt;.kiro/specs/product-catalog/&lt;/code&gt;, &lt;code&gt;.kiro/specs/shopping-cart/&lt;/code&gt;. Isso mantém cada documento gerenciável e ainda permite que pessoas diferentes trabalhem em features diferentes em paralelo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Itere nas specs:&lt;/em&gt; As specs não devem ser escritas em pedra no começo e esquecidas. Conforme o entendimento da feature evolui, atualize o &lt;code&gt;requirements.md&lt;/code&gt; e o &lt;code&gt;design.md&lt;/code&gt;, e mantenha o &lt;code&gt;tasks.md&lt;/code&gt; sincronizado com o que realmente vai ser feito. A spec só continua útil enquanto reflete a realidade do que você está construindo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Versione e compartilhe:&lt;/em&gt; Guarde as specs no repositório, junto do código, para que elas sejam versionadas da mesma forma. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  O que levar dessa leitura
&lt;/h2&gt;

&lt;p&gt;Começamos esse artigo com um assistente me chamando de maluca, e ao longo dele vimos que boa parte das dores de trabalhar com IA tem explicação, e também solução. Quando o modelo está preso no tempo, conectamos ferramentas externas com &lt;strong&gt;MCP&lt;/strong&gt;. Quando ele se perde em contexto demais, direcionamos o foco com &lt;strong&gt;steering files&lt;/strong&gt;. Quando ele dá respostas genéricas ou nos faz repetir o mesmo pedido mil vezes, empacotamos conhecimento em &lt;strong&gt;skills&lt;/strong&gt;. E quando a pressa de produzir código ameaça atropelar os requisitos, trazemos método com o &lt;strong&gt;spec-driven development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nenhuma dessas técnicas precisa andar sozinha. Na prática, o melhor resultado vem de combinar várias (ou todas) delas, e de nunca abrir mão da revisão humana, principalmente nas etapas de criação e nas ações mais críticas. Porque, no fim do dia, por mais que a IA faça o trabalho pesado, é a gente que entende o problema antes dele virar código e responde pelo software depois que ele existe.&lt;/p&gt;

&lt;p&gt;E apesar de eu ter usado ferramentas de desenvolvimento como exemplo, esse raciocínio vale para qualquer pessoa que trabalha com IA, seja escrevendo código, texto ou qualquer outra coisa. A IA é uma ferramenta poderosa, talvez a mais poderosa que a gente já teve nas mãos, mas continua sendo uma ferramenta. E toda orquestra, por melhores que sejam os instrumentos, ainda precisa de um maestro. &lt;/p&gt;

&lt;p&gt;Se se interessou pelo Kiro e gostaria de saber mais, assista à &lt;a href="https://www.youtube.com/playlist?list=PLQHh55hXC4ypBnGgadqk7pjZv6hQwHHe7" rel="noopener noreferrer"&gt;playlist sobre Kiro&lt;/a&gt; no canal do YouTube mantido pelo meu time da AWS e me siga para mais conteúdos sobre IA, desenvolvimento e cloud! 😉&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kiro</category>
      <category>productivity</category>
      <category>development</category>
    </item>
    <item>
      <title>Kiro Crew + Discord: Running My Agent From My Phone</title>
      <dc:creator>Laura Salinas</dc:creator>
      <pubDate>Fri, 02 Oct 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/aws/kiro-crew-discord-running-my-agent-from-my-phone-4cb5</link>
      <guid>https://dev.to/aws/kiro-crew-discord-running-my-agent-from-my-phone-4cb5</guid>
      <description>&lt;p&gt;I've been working on coding projects in my terminal and my IDE for years, (shoutout to CodeBlocks for my C++ fans!) but as agents continue to make the case for streamlined task management in our development cycles, one thing that I found frustrating was the moment I had to step away, close the terminal or lost network access my tasks would stop or stall dead in their tracks.&lt;/p&gt;

&lt;p&gt;A task is running, a review is halfway done, and then I decide I want to grab a coffee at the office and I'm on my phone in line with no way to check in on any of it. &lt;/p&gt;

&lt;p&gt;So when &lt;a href="https://kiro.dev/blog/introducing-kiro-crew/?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro Crew&lt;/a&gt; went open source and launched on August 4th, the feature that grabbed me wasn't the multi-agent orchestration or the scheduling (though they are neat!). It was the idea that you could connect to your agent sessions across multiple channels, Discord being one of them. That meant I could kick off work at my desk and check on my crew from my phone, without SSHing into anything or being chained to my laptop.&lt;/p&gt;

&lt;p&gt;In this post I'll walk you through how I got started with Kiro Crew and how I wired up the Discord channel so I could interact with my agent session on the go. I'll cover a quick crash course on what Kiro Crew actually is for those who haven't heard of it yet, my setup steps, and the honest rough edges I hit along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is Kiro Crew?
&lt;/h2&gt;

&lt;p&gt;Kiro Crew is a persistent, open source development workspace that remembers your context and learns how you work. It runs locally or remotely on your own hardware. Think of it less like a chat window and more like a workspace that keeps going when you're not there. It's self-learning and self-evolving, which means it carries memory and context across sessions instead of starting cold every time. &lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent&lt;/strong&gt; - Sessions, memory, schedules, and task checkpoints survive beyond a single chat and across restarts. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-learning&lt;/strong&gt; - Corrections you give it become durable lessons that change how it behaves later. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-evolving&lt;/strong&gt; - Repeated patterns can turn into reusable skills you can inspect and edit. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs where you choose&lt;/strong&gt; - Keep it on your own machine, in a local container, or on a remote box you control. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One Gateway, many surfaces&lt;/strong&gt; - The desktop app, web dashboard, and CLI all talk to the same runtime, and so do channels like Slack, Discord, and Telegram, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interesting sidenote: Kiro Crew started internally at Amazon as "MeshClaw", built by 3 engineers as a side project to improve their own day to day workflows. In a couple months it was adopted by 39,000+ Amazon builders. If you're curious about the engineers and their story on this project, you can check out the &lt;a href="https://kiro.dev/blog/introducing-kiro-crew/?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; they wrote. &lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Kiro Crew
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Install the Desktop App
&lt;/h3&gt;

&lt;p&gt;The easiest way to get started is trying the desktop app which can be downloaded directly from &lt;a href="https://kiro.dev/crew/?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;kiro.dev/crew/&lt;/a&gt;. During download Kiro Crew will recognize any existing agents you have installed locally (by default it picks up Kiro so you won't see it on the list). &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;⚠️ You will need the Kiro CLI installed as well given that Kiro Crew runs LLM use over ACP through the CLI. More info in the &lt;a href="https://kiro.dev/docs/crew/installation?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;docs.&lt;/a&gt;&lt;/em&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmgfdcq3j73uyejmwags.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmgfdcq3j73uyejmwags.png" alt=" " width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, the wizard will ask you to decide whether you want Kiro Crew to import agent settings from other coding agents as well. If you want to carry over existing skills, MCP servers, steering files etc this is where you can pick and choose. &lt;/p&gt;

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

&lt;p&gt;Finally decide what your UI theme and mode will be and you're ready to start using your crew. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Confirm It's Running
&lt;/h3&gt;

&lt;p&gt;The UI can be a bit overwhelming on first glance, but once you understand where everything lives you'll be flowing through workflows easily.&lt;/p&gt;

&lt;p&gt;The place where everything begins is your Sessions pane, and you start all your new agent sessions from here as well. Click "&lt;strong&gt;New chat&lt;/strong&gt;" to begin your first session. &lt;/p&gt;

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

&lt;p&gt;Type in the chat panel and press Enter. Each tab is an independent session with full tool access, ability to read/write files, run commands, browse the web, or call any configured MCP tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling the Discord Channel 📲
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Discord Bot
&lt;/h3&gt;

&lt;p&gt;The connection I wanted to setup was Discord, to do so you can step through the in detail instructions in the &lt;a href="https://github.com/kirodotdev/KiroCrew/blob/main/src/kiro_crew/docs/discord-integration.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, but I'll outline the major steps here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;⚠️ Any steps after this will require a Discord account (which is free!)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1/ Head to the &lt;a href="https://discord.com/developers/applications" rel="noopener noreferrer"&gt;Discord Developer Portal&lt;/a&gt; create a new "&lt;strong&gt;New Application&lt;/strong&gt;" and give it a name you'll recognize to link with Kiro Crew.&lt;/p&gt;

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

&lt;p&gt;2/ On the Bot pane in the left generate a new token by clicking "&lt;strong&gt;Reset Token&lt;/strong&gt;" (you'll need this handy to paste back into the Kiro Crew settings)&lt;/p&gt;

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

&lt;p&gt;3/ Log into your Discord account and head to Settings -&amp;gt; Developer -&amp;gt; Toggle "On" Developer Mode (this allows you to grab your user ID)&lt;/p&gt;

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

&lt;p&gt;4/ Click on your account profile and now you'll see a "&lt;strong&gt;Copy User ID&lt;/strong&gt;" button&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Step 2: Bind It to Your Gateway
&lt;/h3&gt;

&lt;p&gt;5/ Open up the Kiro Crew settings -&amp;gt; Messaging Channels -&amp;gt; Discord -&amp;gt; Toggle "On" Enable Discord&lt;/p&gt;

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

&lt;p&gt;6/ Add in your Discord bot token and Allowed user IDs in their respective areas and when done don't forget to hit "&lt;strong&gt;Save Discord Settings&lt;/strong&gt;" at the very bottom&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Talk to Your Crew from Discord
&lt;/h3&gt;

&lt;p&gt;7/ The final step is to install your new Discord bot into your Discord server. I did this with the Install Link from the Installation pane in the developer portal.&lt;/p&gt;

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

&lt;p&gt;I now go into my Discord and can start DM'ing my bot which will communicate to Kiro Crew and create a new instance on the desktop app that shows it's being "Driven from Discord DM"&lt;/p&gt;

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

&lt;p&gt;I have this session linked up to my portfolio website repo locally on my Mac so the agent use is scoped only to that workspace. What that allows me to do though is exactly what I discussed earlier, I can now go get that coffee, run that errand or be away from my laptop while still getting work done through my agent or checking in on progress from previous interactions.&lt;/p&gt;

&lt;p&gt;In the case of my portfolio website session, on the go I can now add new event resource pages from the events I'm presenting at, or wire up my content page to reflect any new videos or blogs I've been working on. &lt;/p&gt;

&lt;h3&gt;
  
  
  The On-the-Go Workflow in Practice
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;⚠️ Note that the setup I discuss here is using my Mac desktop app as the gateway for Kiro Crew, but you can run Kiro Crew remotely on your own hardware (like a Mac Mini/EC2 instance/Docker container/etc) and maintain a gateway that never shuts off, and thus have always on use of your sessions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Challenges and Rough Edges 😵‍💫
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pain point 1&lt;/strong&gt; - Discord bot setup requires lots of moving back and forth across windows, developer settings and your Discord app to confirm everything was setup properly. I kept bot settings super simple and only had it be used through DMs so I didn't have to setup new Discord servers or threads in order to interact with the bot/agent session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pain point 2&lt;/strong&gt; - You are still bound by speed of light (network and model 😅) here so some interactions with the Kiro agent take a little longer to generate a response than others.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;This channel connection ability, in addition to some Kiro Crew features like persistent sessions, built in memory and scheduling, make this the perfect time to explore this open source project for yourself. Kiro Crew uses your existing Kiro account to consume credits so one login works across the various Kiro surfaces (IDE/CLI/Crew/Web).  &lt;/p&gt;

&lt;p&gt;The team managing the repo is accepting and merging something like 143+ commits per week so if you're also someone interested in contributing to Kiro Crew, create a PR with your bug/feature request and see where it takes you. This experience is being built in the open, for developers by developers and I'd like to think it can only get better as more contributions are made. &lt;/p&gt;

&lt;p&gt;Don't forget to give me a 🦄 if you got this far and let me know what you're using Kiro Crew for in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources 📚
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/docs/crew/?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro Crew Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kirodotdev?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro Crew on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kirodotdev/KiroCrew/blob/main/src/kiro_crew/docs/discord-integration.md?trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Setting up Discord with Kiro Crew Instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gObLBr_PYDw&amp;amp;trk=c6a9670e-a495-43a4-85ae-16eae0a1aadc&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro Crew + Discord Video Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>learning</category>
      <category>agents</category>
    </item>
    <item>
      <title>What Is an AWS Builder ID?</title>
      <dc:creator>Sean Boult</dc:creator>
      <pubDate>Fri, 02 Oct 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/aws/what-is-an-aws-builder-id-1pc9</link>
      <guid>https://dev.to/aws/what-is-an-aws-builder-id-1pc9</guid>
      <description>&lt;p&gt;yeah... you've probably heard of an "&lt;a href="https://docs.aws.amazon.com/signin/latest/userguide/differences-builder-id.html?trk=02c7b25c-78f4-4968-8fa2-241bdf0bcf97&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Builder ID&lt;/a&gt;" but you're not quite sure what it means.&lt;/p&gt;

&lt;p&gt;I'm going to break it down nicely so you have a good idea of what it is.&lt;/p&gt;

&lt;p&gt;You may or may not have a Builder ID, but if you're reading this on &lt;a href="https://builder.aws.com" rel="noopener noreferrer"&gt;builder.aws.com&lt;/a&gt; you do 😉. If not, today is your chance to create one, stick around til the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: Builder ID is a personal login (your email) for most AWS tools, including &lt;a href="https://builder.aws.com/" rel="noopener noreferrer"&gt;AWS Builder Center&lt;/a&gt;, &lt;a href="https://quick.aws.com/sn" rel="noopener noreferrer"&gt;Amazon Quick&lt;/a&gt; and &lt;a href="https://skillbuilder.aws/" rel="noopener noreferrer"&gt;AWS Skill Builder&lt;/a&gt;. And now, for the new AWS experience too!&lt;/p&gt;




&lt;p&gt;We just the launched &lt;a href="https://aws.amazon.com/start-here?trk=02c7b25c-78f4-4968-8fa2-241bdf0bcf97&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;new AWS experience&lt;/a&gt; this week, which makes it easier to get started with AWS, and Builder ID is also your AWS sign-in and billing identity. No root user or IAM user needed.&lt;/p&gt;

&lt;p&gt;You can sign up with an email or login providers like Google, Apple, Github, and Amazon. If you didn't already have a Builder ID, one will be created for you as part of this process.&lt;/p&gt;

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

&lt;p&gt;Once you're signed in you can create your first AWS project build the next awesome idea.&lt;/p&gt;

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

&lt;p&gt;Have more ideas, make another project!&lt;/p&gt;

&lt;p&gt;Give the new exp a try and let us what you think.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://signin.aws.amazon.com/signup?request_type=builderId" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Try the new AWS experience&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;As always, happy coding 😎!&lt;/p&gt;

&lt;p&gt;Follow AWS for more articles like this.&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.us-east-2.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;Follow me for all things tech.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__828306"&gt;
    &lt;a href="/hacksore" 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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F828306%2Fbf0bbed7-7874-4a26-8137-bb761a4b7f23.png" alt="hacksore 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="/hacksore"&gt;Sean Boult&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/hacksore"&gt;Developer. Hacker. Creator.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


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