<?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>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>
    <item>
      <title>What the new AWS means if you're just getting started</title>
      <dc:creator>Ramses Mata</dc:creator>
      <pubDate>Thu, 01 Oct 2026 16:13:11 +0000</pubDate>
      <link>https://dev.to/aws/what-the-new-aws-means-if-youre-just-getting-started-1een</link>
      <guid>https://dev.to/aws/what-the-new-aws-means-if-youre-just-getting-started-1een</guid>
      <description>&lt;p&gt;The first time I used AWS, it was because I had built something locally and wanted to deploy it so my friends could see what I was already seeing on &lt;code&gt;localhost&lt;/code&gt;. But that whole journey, from the moment I first landed on the AWS login screen, created my account, all the way to finally watching my app run, took me weeks.&lt;/p&gt;

&lt;p&gt;What do I need? What are Regions, and which one do I pick? What is IAM, and why is it asking me for so many permissions? How do I know how much one specific app is costing me? And why is everything so scattered?&lt;/p&gt;

&lt;p&gt;AWS is rolling out a &lt;strong&gt;new experience&lt;/strong&gt; built exactly for folks who are just getting started, because I wasn't the only one who ran into these questions and AWS listened. The new experience smooths out a lot of the friction that existed for people using AWS for the first time. In this post I'll walk you through what those changes are and what they mean if you're just starting out.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you create an AWS account?
&lt;/h2&gt;

&lt;p&gt;To kick things off, you can now create your account with Google, GitHub, or an &lt;a href="https://aws.amazon.com/builder/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS Builder ID&lt;/a&gt;. That makes life easier, because you no longer have to waste time filling out a huge form, and you can start building in just a few clicks. And what excites me the most is that for most new users &lt;strong&gt;a credit card won't be required anymore&lt;/strong&gt;, which I think is awesome for students.&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%2Fu2i38fi3bcm95orpdavm.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%2Fu2i38fi3bcm95orpdavm.png" alt="sign up in the new AWS experience" width="799" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Projects
&lt;/h2&gt;

&lt;p&gt;Something that always bugged me was that if you didn't have much experience, or if you hadn't put in a good time studying governance, getting a clear picture of the resources you were using for one specific app turned into a real challenge.&lt;/p&gt;

&lt;p&gt;Now in the new AWS experience, there's something called &lt;strong&gt;Projects&lt;/strong&gt;. A project is a separate AWS account where you create resources. The difference is that you can create several projects, and the resources you create in one project won't show up in the others. &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%2Fcwox2lo0u7v3udtdilrd.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%2Fcwox2lo0u7v3udtdilrd.png" alt="projects structure in the new AWS experience" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inviting team members
&lt;/h2&gt;

&lt;p&gt;This is something I would have loved back when I was in school, being able to invite my classmates into the same space to build together without fighting with IAM and explaining to them how to use credentials.&lt;/p&gt;

&lt;p&gt;In the new experience you can invite &lt;strong&gt;team members&lt;/strong&gt; to your projects just by sending them an email invite from AWS Settings. They create their own AWS Builder ID to get in and that's it, they're already collaborating with you, and there's no cost for inviting collaborators. They share the same project and only pay for the resources they use, just like if you were on your own. That makes it perfect for studying in a group, for a hackathon, or for your final university project. If you want the exact steps, they're in the &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/invite-team-members.html?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;official docs for inviting team members&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%2Fjyzp4opm51cdbv6ejypm.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%2Fjyzp4opm51cdbv6ejypm.png" alt="projects structure in the new AWS experience" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start building without fighting the permissions
&lt;/h2&gt;

&lt;p&gt;When I started, IAM was one of the most confusing things. Why do I have to create users, roles, and policies just to try something out? It felt like a giant wall standing between me and building anything.&lt;/p&gt;

&lt;p&gt;To understand what changes, it helps me to think of permissions as three separate layers.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;first layer is who gets into your project&lt;/strong&gt;, meaning which people can log in and work. Before, you set this up by hand, creating users, credentials, and policies for each person. In the new experience, AWS handles it. You don't create users anymore, you invite people by email and they get in with their AWS Builder ID as team members.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;second layer is the project's rules&lt;/strong&gt;, the limits that apply to the whole project and define what can be done in general, like working in a single Region or which services are available. In the new experience AWS takes care of this one too, so you don't configure it. If you want to take a look at the details, it's in the &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/scps-and-rcps-for-projects.html?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;docs on policies for projects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The third layer is access between your resources and the outside world. Here's the nice surprise, inside your project your resources can already talk to each other by default, so your Lambda can read your DynamoDB without you wiring up any IAM policies. What stays in your hands is deciding what to expose publicly, because by default nothing in your project is reachable from the internet until you configure it, like a public S3 bucket, an API Gateway, or a Lambda function URL.&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%2Ftrnv5y3zr9qz1retkatv.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%2Ftrnv5y3zr9qz1retkatv.png" alt="Permissions in three layers in the new AWS experience" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No surprise bills while you learn
&lt;/h2&gt;

&lt;p&gt;When you're starting out, the scariest part usually isn't the code, it's the bill. The new experience handles that in a way that genuinely calmed me down.&lt;/p&gt;

&lt;p&gt;It starts with the &lt;a href="https://aws.amazon.com/free/?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Free Tier&lt;/a&gt;. As a new user you get up to $200 in credits, $100 the moment you create your account and up to $100 more as you complete activities such as creating a lambda function or an EC2 instance which is great to start learning and building. You can use those credits over your first six months. On the Free plan you build with free services and those credits, and you don't get charged unless you choose to upgrade, so there are no surprise overages while you're learning.&lt;/p&gt;

&lt;p&gt;On top of that, because your resources live inside projects, you can see exactly how much each project is spending instead of digging through one big pile. That clarity per project was something I really wished when I started.&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%2Fuzc75pd52flkiki8bfjd.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%2Fuzc75pd52flkiki8bfjd.png" alt="projects structure in the new AWS experience" width="799" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when your free credits run out and you move to the Paid plan to keep building, that's where &lt;strong&gt;spend limits&lt;/strong&gt; come in. A spend limit is a monthly cap on a project, and if you reach it, AWS pauses the project so your bill never grows past what you set. Spend limits are a Paid plan feature, so think of them as your safety net for when you grow beyond the free credits. Here are the details on how to &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/create-spend-limit.html?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;create a spend limit&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%2Ffl34v8fif5hn0p3dcdmy.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%2Ffl34v8fif5hn0p3dcdmy.png" alt="Spend limits in the new AWS experience" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A single Region, and why that helps you at the start
&lt;/h2&gt;

&lt;p&gt;Another question that kept nagging at me was the one about Regions. Which one do I pick? What if I get it wrong?&lt;/p&gt;

&lt;p&gt;In the new experience each project works in a &lt;strong&gt;single Region&lt;/strong&gt;, so that's one less decision to make when you're starting. All your regional resources live there and if you want to confirm which one is yours, you'll find it in AWS Settings, in your project's additional info.&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%2Fewj4nzog3gv66s0q78n9.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%2Fewj4nzog3gv66s0q78n9.png" alt="How to see your region in the new AWS experience" width="799" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What happens if I hit my spend limit?
&lt;/h3&gt;

&lt;p&gt;Your project pauses so you don't keep spending. To bring it back, the project owner raises the limit from AWS Settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use any AWS service?
&lt;/h3&gt;

&lt;p&gt;Not all of them are available right out of the gate. If a service doesn't show up, you can check the &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/supported-services-sign-up-new.html?trk=3030e60a-17b3-4fdb-9862-d65f29e1a10c&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;list of supported services&lt;/a&gt; and, if you need it, turn on advanced features, which is AWS the way we knew it before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do I see what I'm spending?
&lt;/h3&gt;

&lt;p&gt;In AWS Settings, under Billing. Your billing is shared across all your projects, so you pay in one place instead of juggling a separate bill for each. Even so, you can still see how much each project is spending on its own, so nothing feels scattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;What would have helped me most when I started wasn't another tutorial, it was clearing away the roadblocks that kept me from even getting going. And that's exactly what the new AWS experience solves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You sign in with a social login and work inside a project, without so much upfront setup.&lt;/li&gt;
&lt;li&gt;You invite your friends to collaborate at no extra cost for adding them.&lt;/li&gt;
&lt;li&gt;You start with free credits and clear costs per project, and spend limits cap your bill once you move to the Paid plan.&lt;/li&gt;
&lt;li&gt;AWS manages human access, so IAM stops being the first pebble in your shoe.&lt;/li&gt;
&lt;li&gt;You work in a single Region, with fewer decisions to make at the start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of this sparked your curiosity, the best next step is to create your project and build something small. Theory really clicks when you put it into practice, and now the path to doing that is a whole lot shorter.&lt;/p&gt;

&lt;p&gt;If you like this kind of content and you're more of a video person, follow us on our YouTube channel &lt;a href="https://www.youtube.com/@awsdevelopers" rel="noopener noreferrer"&gt;AWS Developers&lt;/a&gt;, we'll be waiting for you there.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>beginners</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Switching Between Your Personal ChatGPT Account and Amazon Bedrock</title>
      <dc:creator>Matheus Guimaraes</dc:creator>
      <pubDate>Thu, 01 Oct 2026 10:33:52 +0000</pubDate>
      <link>https://dev.to/aws/switching-between-your-personal-chatgpt-account-and-amazon-bedrock-4h08</link>
      <guid>https://dev.to/aws/switching-between-your-personal-chatgpt-account-and-amazon-bedrock-4h08</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/aws/how-to-set-up-the-chatgpt-desktop-app-with-amazon-bedrock-ggp"&gt;previous post in this series&lt;/a&gt;, I walked through how to configure the ChatGPT desktop app to use OpenAI models through Amazon Bedrock.&lt;/p&gt;

&lt;p&gt;That works great if Bedrock is the only environment you want to use. But what if you want to use both your personal ChatGPT account and Amazon Bedrock?&lt;/p&gt;

&lt;p&gt;That's when it gets awkward.&lt;/p&gt;

&lt;p&gt;I found that out the hard way after setting up my Bedrock configuration, because my intention was always to keep using my personal ChatGPT account as well and switch between the two on demand to help me manage my token limits.&lt;/p&gt;

&lt;p&gt;I also didn't want two completely separate Codex environments. I wanted to keep the same local conversations, files and working state, and simply change which provider was handling the model requests.&lt;/p&gt;

&lt;p&gt;So I built a small account switcher to make that easier.&lt;/p&gt;

&lt;p&gt;If you just want the solution, you can grab it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/techwithmatheus/codex-account-switcher" rel="noopener noreferrer"&gt;&lt;strong&gt;techwithmatheus/codex-account-switcher&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It includes an installer, uninstaller and two simple commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-personal
codex-bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather understand what it's doing before installing it, keep reading. That's what the rest of this post is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with simply switching accounts
&lt;/h2&gt;

&lt;p&gt;When you're using the normal ChatGPT experience, switching accounts is something you generally think about at the application level.&lt;/p&gt;

&lt;p&gt;Bedrock is different.&lt;/p&gt;

&lt;p&gt;In the setup from the previous post, Codex is configured locally with something like:&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="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openai.gpt-6-astra"&lt;/span&gt;
&lt;span class="py"&gt;model_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"amazon-bedrock"&lt;/span&gt;

&lt;span class="nn"&gt;[model_providers.amazon-bedrock.aws]&lt;/span&gt;
&lt;span class="py"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"codex-bedrock"&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"us-west-2"&lt;/span&gt;
&lt;span class="py"&gt;wire_api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"responses"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tells Codex to send model requests through Amazon Bedrock using the AWS profile and Region you've configured.&lt;/p&gt;

&lt;p&gt;So Bedrock isn't another ChatGPT account that can simply appear in the normal account switcher. It's a different model-provider configuration.&lt;/p&gt;

&lt;p&gt;My first thought was therefore to maintain two separate Codex environments: one for my personal account and another for Bedrock.&lt;/p&gt;

&lt;p&gt;That would work, but there's a downside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I decided not to separate the whole Codex environment
&lt;/h2&gt;

&lt;p&gt;Codex stores its local state under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there's considerably more in there than &lt;code&gt;config.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On my machine, that directory contains authentication state, sessions, thread history, memories, plugins, skills, model caches, trusted-project settings and several local databases used by the desktop experience.&lt;/p&gt;

&lt;p&gt;If I gave my personal and Bedrock setups completely separate Codex homes, I would also be separating much of that useful state.&lt;/p&gt;

&lt;p&gt;That wasn't really what I wanted.&lt;/p&gt;

&lt;p&gt;My use case was closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;same Codex workspace
same conversations
same local state
different model provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I tried something simpler.&lt;/p&gt;

&lt;p&gt;I completely quit the ChatGPT app, removed only the Bedrock-specific configuration from &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, and launched it again.&lt;/p&gt;

&lt;p&gt;The desktop app returned to my personal ChatGPT-backed Codex environment.&lt;/p&gt;

&lt;p&gt;More importantly, a test conversation that I had originally created while using Bedrock was still there. It contained a small .NET console application, and I could still open the generated files in the desktop preview.&lt;/p&gt;

&lt;p&gt;I then quit the app, restored the Bedrock configuration and launched it again.&lt;/p&gt;

&lt;p&gt;The same conversation and files were still there.&lt;/p&gt;

&lt;p&gt;So that's the model we'll use here: &lt;strong&gt;keep the Codex state shared and switch only the part of the configuration that selects Amazon Bedrock.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There was another nice surprise when I later tested the full uninstall flow. When I switched back to my personal ChatGPT environment, I expected to have to sign in again. I didn't.&lt;/p&gt;

&lt;p&gt;My existing personal authentication was still part of the shared Codex state. Bedrock had changed which provider handled the model requests, but it hadn't logged me out of my personal ChatGPT account.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is behaviour I've personally verified on macOS with the ChatGPT desktop app. I haven't tested every piece of desktop state, so I'm deliberately not claiming that every feature behaves identically across providers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Back up your configuration first
&lt;/h2&gt;

&lt;p&gt;Before changing anything, make a backup of your existing Codex configuration.&lt;/p&gt;

&lt;p&gt;I like using the date and time in the filename so old backups remain meaningful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; ~/.codex/config.toml ~/.codex/config.toml.backup.&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F-%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd also back up your &lt;code&gt;.zshrc&lt;/code&gt;, since the installer will add a small block to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; ~/.zshrc ~/.zshrc.backup.&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F-%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need to copy the whole &lt;code&gt;~/.codex&lt;/code&gt; directory for this. The switcher doesn't touch your sessions, memories, authentication databases or other Codex state.&lt;/p&gt;

&lt;p&gt;We'll also make the switcher maintain a rolling safety backup before it modifies &lt;code&gt;config.toml&lt;/code&gt;, but that's different from this manual checkpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the switcher
&lt;/h2&gt;

&lt;p&gt;I've put the maintained version of the switcher on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/techwithmatheus/codex-account-switcher" rel="noopener noreferrer"&gt;&lt;strong&gt;techwithmatheus/codex-account-switcher&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clone the repository:&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/techwithmatheus/codex-account-switcher.git
&lt;span class="nb"&gt;cd &lt;/span&gt;codex-account-switcher
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm deliberately using &lt;code&gt;bash install.sh&lt;/code&gt; rather than &lt;code&gt;./install.sh&lt;/code&gt;. That means you don't have to change executable permissions on the downloaded script first.&lt;/p&gt;

&lt;p&gt;The installer puts the switcher under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config/codex-switcher/
├── codex-switcher.zsh
└── config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and adds a small marked block to &lt;code&gt;~/.zshrc&lt;/code&gt; so zsh loads it whenever you open a shell.&lt;/p&gt;

&lt;p&gt;The actual switching code lives in its own file rather than dumping a large shell function directly into your &lt;code&gt;.zshrc&lt;/code&gt;. That makes it much easier to inspect, maintain or replace later.&lt;/p&gt;

&lt;p&gt;After installation, reload your shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure your Bedrock settings
&lt;/h2&gt;

&lt;p&gt;The switcher's Bedrock-specific settings live in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config/codex-switcher/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The defaults used throughout this series are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CODEX_BEDROCK_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"openai.gpt-6-astra"&lt;/span&gt;
&lt;span class="nv"&gt;CODEX_BEDROCK_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"codex-bedrock"&lt;/span&gt;
&lt;span class="nv"&gt;CODEX_BEDROCK_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy46pd2mvmq4ke7jduos1.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%2Fy46pd2mvmq4ke7jduos1.png" alt="Finder window showing the codex-switcher configuration folder and the config file containing the Amazon Bedrock model, AWS profile and Region settings." width="739" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you used different values when configuring Bedrock, update them there.&lt;/p&gt;

&lt;p&gt;For example, if your AWS profile is called &lt;code&gt;work-bedrock&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CODEX_BEDROCK_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"work-bedrock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model and Region should likewise match the Bedrock setup you've already verified.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if Bedrock is already configured?
&lt;/h3&gt;

&lt;p&gt;If you've just followed the first article, you may already have Amazon Bedrock active when you install the switcher.&lt;/p&gt;

&lt;p&gt;The installer handles that case too.&lt;/p&gt;

&lt;p&gt;If it finds the standard Bedrock configuration already active, it shows the values it discovered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing Amazon Bedrock configuration found.

Model:       openai.gpt-6-astra
AWS profile: codex-bedrock
Region:      us-west-2

These provider settings will be adopted by the switcher and will not be replaced.

Continue? [Y/n]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It then adopts the existing settings rather than creating a duplicate Bedrock provider configuration.&lt;/p&gt;

&lt;p&gt;If you're starting from your normal personal ChatGPT environment instead, your current Codex configuration is left alone and the switcher starts from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the switcher actually changes
&lt;/h2&gt;

&lt;p&gt;When Bedrock is active, the tool owns one clearly marked block inside &lt;code&gt;~/.codex/config.toml&lt;/code&gt;:&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="c"&gt;# &amp;gt;&amp;gt;&amp;gt; codex-bedrock-switcher &amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openai.gpt-6-astra"&lt;/span&gt;
&lt;span class="py"&gt;model_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"amazon-bedrock"&lt;/span&gt;

&lt;span class="nn"&gt;[model_providers.amazon-bedrock.aws]&lt;/span&gt;
&lt;span class="py"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"codex-bedrock"&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"us-west-2"&lt;/span&gt;
&lt;span class="py"&gt;wire_api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"responses"&lt;/span&gt;
&lt;span class="c"&gt;# &amp;lt;&amp;lt;&amp;lt; codex-bedrock-switcher &amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values come from the separate switcher configuration file we just looked at.&lt;/p&gt;

&lt;p&gt;Everything outside those markers belongs to Codex, or to you, and the switcher leaves it alone.&lt;/p&gt;

&lt;p&gt;That's important because the desktop-generated &lt;code&gt;config.toml&lt;/code&gt; can contain much more than provider configuration: desktop preferences, plugins, MCP servers, trusted projects and other settings may all live in the same file.&lt;/p&gt;

&lt;p&gt;Replacing the entire file whenever you switch would be unnecessarily risky.&lt;/p&gt;

&lt;p&gt;Before each automated change, the current configuration is also copied to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.codex/config.toml.switcher-backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a rolling safety backup. It contains the state immediately before the most recent switch and is intentionally overwritten on later switches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch to Amazon Bedrock
&lt;/h2&gt;

&lt;p&gt;Once everything is installed and your configuration looks right, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The switcher will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;make a safety backup of your current &lt;code&gt;config.toml&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;add the managed Bedrock block;&lt;/li&gt;
&lt;li&gt;launch the ChatGPT desktop app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest of your Codex state remains where it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch back to your personal ChatGPT account
&lt;/h2&gt;

&lt;p&gt;To go back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The switcher removes only the Bedrock block it owns.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; insert some handcrafted OpenAI provider configuration in its place.&lt;/p&gt;

&lt;p&gt;That's deliberate.&lt;/p&gt;

&lt;p&gt;During testing, removing the Bedrock override was enough for the desktop app to return naturally to my personal ChatGPT-backed environment using its existing authentication and defaults.&lt;/p&gt;

&lt;p&gt;So rather than trying to reproduce configuration that Codex already knows how to manage, the switcher simply gets out of the way.&lt;/p&gt;

&lt;p&gt;In my tests, I didn't need to authenticate again either. My personal ChatGPT login was still available because we had never replaced or deleted the shared Codex authentication state.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if ChatGPT is still running?
&lt;/h2&gt;

&lt;p&gt;I didn't want the switcher modifying &lt;code&gt;config.toml&lt;/code&gt; underneath a running desktop application.&lt;/p&gt;

&lt;p&gt;So if ChatGPT is still open when you run either command, you'll get:&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%2F41i69lxcy7a0hic04bch.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%2F41i69lxcy7a0hic04bch.png" alt="Terminal showing the codex-bedrock command detecting that the ChatGPT desktop app is still running and asking whether it should close the app and continue." width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Answer &lt;code&gt;y&lt;/code&gt; and the switcher asks macOS to quit ChatGPT normally, waits until the application has exited, performs the switch and launches it again.&lt;/p&gt;

&lt;p&gt;Anything else cancels the operation.&lt;/p&gt;

&lt;p&gt;The important part here is that it uses the application's normal quit behaviour rather than abruptly killing the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip the question
&lt;/h3&gt;

&lt;p&gt;If you already know you want it to close ChatGPT, use &lt;code&gt;-f&lt;/code&gt; or &lt;code&gt;--force&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-bedrock &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-personal &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That closes the app automatically and continues with the switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do the conversations actually carry across?
&lt;/h2&gt;

&lt;p&gt;This was the part I cared about most.&lt;/p&gt;

&lt;p&gt;I created a test conversation while Codex was configured through Amazon Bedrock. In that conversation I had it create a small .NET console application that displayed the date.&lt;/p&gt;

&lt;p&gt;I then switched the desktop app back to my personal ChatGPT environment.&lt;/p&gt;

&lt;p&gt;The conversation was still in the app. I could open it and inspect the generated files in the preview panel.&lt;/p&gt;

&lt;p&gt;I switched back to Bedrock and it remained there as well.&lt;/p&gt;

&lt;p&gt;So at least for the local conversation and generated-file workflow I tested, this gives me what I wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Personal ChatGPT
        │
        ▼
same local Codex workspace
        ▲
        │
Amazon Bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conversations aren't being recreated each time you switch providers. We're keeping the same local Codex state and changing which provider handles model requests.&lt;/p&gt;

&lt;p&gt;That doesn't mean the underlying services suddenly share billing, authentication or infrastructure. They don't.&lt;/p&gt;

&lt;p&gt;What we're preserving is the local working environment around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if I already use another custom provider?
&lt;/h2&gt;

&lt;p&gt;Codex can contain definitions for multiple model providers in the same configuration, and the switcher leaves unrelated provider definitions alone.&lt;/p&gt;

&lt;p&gt;However, this utility is &lt;strong&gt;not intended to be a universal provider manager&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It specifically manages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Personal ChatGPT / OpenAI
            ⇅
       Amazon Bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you also use another custom provider, you can continue switching to it using whatever method you already use.&lt;/p&gt;

&lt;p&gt;There is one important behaviour to understand.&lt;/p&gt;

&lt;p&gt;Running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;deliberately makes Bedrock active.&lt;/p&gt;

&lt;p&gt;Running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;removes the Bedrock override and returns Codex to its normal personal ChatGPT/OpenAI behaviour.&lt;/p&gt;

&lt;p&gt;It does not remember that you previously happened to be using some third provider and restore that provider instead.&lt;/p&gt;

&lt;p&gt;Because of that, if the automatic installer finds an unrelated custom provider currently active, it stops instead of trying to guess what your configuration means.&lt;/p&gt;

&lt;p&gt;You can still install the switcher manually alongside a more complex provider setup. The repository README contains the manual installation steps.&lt;/p&gt;

&lt;p&gt;And if there's another provider you'd like the switcher to support directly, feel free to &lt;a href="https://github.com/techwithmatheus/codex-account-switcher/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;. Pull requests are welcome too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uninstalling the switcher
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash uninstall.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The slightly interesting part of uninstalling isn't removing the shell files. It's deciding which provider should remain configured afterward.&lt;/p&gt;

&lt;p&gt;The uninstaller therefore asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Removing the switcher means ChatGPT will be left configured for a single provider.

Which provider would you like to keep?

1) Personal ChatGPT / OpenAI
2) Amazon Bedrock
3) Cancel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you choose &lt;strong&gt;Personal ChatGPT / OpenAI&lt;/strong&gt;, the switcher-managed Bedrock block is removed.&lt;/p&gt;

&lt;p&gt;If you choose &lt;strong&gt;Amazon Bedrock&lt;/strong&gt;, the Bedrock settings stay in &lt;code&gt;config.toml&lt;/code&gt;, but the switcher's marker comments are removed. What remains is simply a normal Codex Bedrock configuration like the one from the first article.&lt;/p&gt;

&lt;p&gt;And if you choose &lt;strong&gt;Cancel&lt;/strong&gt;, nothing changes.&lt;/p&gt;

&lt;p&gt;One thing the uninstaller deliberately does &lt;strong&gt;not&lt;/strong&gt; do is restore some snapshot of &lt;code&gt;config.toml&lt;/code&gt; from the day you installed the switcher.&lt;/p&gt;

&lt;p&gt;Your Codex configuration may have legitimately evolved since then with new desktop settings, project permissions, plugins or other configuration. Restoring an old snapshot could wipe changes that have nothing to do with this tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;~/.config&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;You'll notice the utility lives under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config/codex-switcher/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than inside the Codex directory itself.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.config&lt;/code&gt; is a common convention in Unix-style command-line environments for user-specific configuration. macOS doesn't guarantee that the directory already exists, so the installer creates it when necessary.&lt;/p&gt;

&lt;p&gt;I also deliberately keep the utility outside &lt;code&gt;~/.codex&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The switcher isn't part of Codex. It's a small shell tool that modifies one well-defined part of Codex configuration, and keeping its own code and settings separate makes that boundary clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A provider switch, not a second account system
&lt;/h2&gt;

&lt;p&gt;There's one distinction worth finishing on.&lt;/p&gt;

&lt;p&gt;This doesn't turn Amazon Bedrock into another ChatGPT account.&lt;/p&gt;

&lt;p&gt;Your personal ChatGPT environment and your Bedrock environment are still separate in the ways that matter: authentication, billing and the infrastructure processing the model requests are different.&lt;/p&gt;

&lt;p&gt;What we've done is take advantage of the fact that Codex's local workspace doesn't need to be discarded just because the active model provider changes.&lt;/p&gt;

&lt;p&gt;For my workflow, that's considerably more useful than maintaining two completely isolated desktop environments.&lt;/p&gt;

&lt;p&gt;I can keep working in the same Codex setup and choose whether the next part of that work should run through my personal ChatGPT environment or through Amazon Bedrock.&lt;/p&gt;

&lt;p&gt;Until the desktop app has a first-class way to handle that scenario, two shell commands are doing the job nicely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The switcher, installer and uninstaller used in this article are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/techwithmatheus/codex-account-switcher" rel="noopener noreferrer"&gt;&lt;strong&gt;techwithmatheus/codex-account-switcher&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository also contains manual installation instructions, configuration details and notes for people already using other custom providers.&lt;/p&gt;

&lt;p&gt;If Codex changes in a way that affects the switching mechanism, I'll maintain the implementation there rather than expecting readers to keep copying an old shell snippet.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>chatgpt</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
