<?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: Amit</title>
    <description>The latest articles on DEV Community by Amit (@amitrix).</description>
    <link>https://dev.to/amitrix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962358%2F978a8f18-68b0-409b-9b3a-2156d0be550c.png</url>
      <title>DEV Community: Amit</title>
      <link>https://dev.to/amitrix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amitrix"/>
    <language>en</language>
    <item>
      <title>An Agent That Can Pay for Its Tools</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:29:43 +0000</pubDate>
      <link>https://dev.to/amitrix/an-agent-that-can-pay-for-its-tools-5e76</link>
      <guid>https://dev.to/amitrix/an-agent-that-can-pay-for-its-tools-5e76</guid>
      <description>&lt;p&gt;If agents are going to act on the open internet, they will eventually hit a price tag.&lt;/p&gt;

&lt;p&gt;That changes the design problem. Calling an API is easy compared with letting a non-human actor spend money without handing it private keys, unlimited budget, or a prompt-level spending rule it can be talked out of.&lt;/p&gt;

&lt;p&gt;The thesis: payment is not a tool call. It is a governed financial action.&lt;/p&gt;

&lt;p&gt;That means the payment path needs more than a model deciding "yes" or "no." It needs user consent, credential isolation, session budgets, policy checks, telemetry, and a narrow proof path that the model cannot bypass. The useful primitive is not "agent calls payment API." The useful primitive is "agent can pay for a bounded resource without owning the wallet."&lt;/p&gt;

&lt;p&gt;I tested that pattern with &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Payments&lt;/a&gt;, &lt;a href="https://docs.cdp.coinbase.com/wallets/using-wallets/delegated-signing" rel="noopener noreferrer"&gt;Coinbase CDP&lt;/a&gt;, &lt;a href="https://docs.cdp.coinbase.com/x402/quickstart-for-buyers" rel="noopener noreferrer"&gt;x402&lt;/a&gt;, Cedar policy enforcement, CloudWatch, and a Strands agent plugin. The result is strong enough to change the mental model, with one caveat: this is testnet evidence, and not every x402 endpoint accepted the same generated proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Payment Primitive Owns
&lt;/h2&gt;

&lt;p&gt;The clean architecture separates four jobs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Paid resource&lt;/td&gt;
&lt;td&gt;Returns HTTP &lt;code&gt;402&lt;/code&gt; with price, token, network, and &lt;code&gt;payTo&lt;/code&gt; address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent/tool&lt;/td&gt;
&lt;td&gt;Encounters &lt;code&gt;402&lt;/code&gt;, extracts the payment requirement, and asks for proof.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment manager&lt;/td&gt;
&lt;td&gt;Generates the x402 payment proof without exposing wallet secrets to the model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Governance&lt;/td&gt;
&lt;td&gt;Enforces user consent, session budget, policy allow/deny, and observability.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That split matters. If the model sees private keys, the design is already broken. If the spending limit is prompt text, the design is also broken. The payment boundary has to sit outside the model context.&lt;/p&gt;

&lt;p&gt;In the experiment, the AgentCore side owned the payment manager, connector, instrument, session, and proof generation. Coinbase CDP owned the delegated wallet/signing path. x402 provided the HTTP &lt;code&gt;402&lt;/code&gt; challenge and proof format. Cedar controlled whether the paid action was allowed before the payment path ran.&lt;/p&gt;

&lt;p&gt;The key detail: the model never needed the payment credential. It only needed a tool response that said, effectively, "this resource costs X on Y network." The proof came from the payment infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Proof: HTTP 402 To HTTP 200
&lt;/h2&gt;

&lt;p&gt;The base flow worked.&lt;/p&gt;

&lt;p&gt;The test created a payment manager, Coinbase CDP credential provider, connector, active payment instrument, and short-lived payment session. The session had a &lt;code&gt;0.10 USD&lt;/code&gt; budget and a &lt;code&gt;30&lt;/code&gt; minute TTL. The wallet was funded with Base Sepolia testnet USDC, and WalletHub permission was granted for the app to pay from that wallet.&lt;/p&gt;

&lt;p&gt;Then the tool called a paid endpoint. The endpoint returned HTTP &lt;code&gt;402&lt;/code&gt; with an x402 version &lt;code&gt;2&lt;/code&gt; payment requirement on Base Sepolia. AgentCore generated a &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; proof through the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_ProcessPayment.html" rel="noopener noreferrer"&gt;ProcessPayment API&lt;/a&gt;. The retry against the AWS-documented endpoint returned HTTP &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That proves the core primitive: an agent-side tool can hit a paid resource, receive a price challenge, generate proof through infrastructure, and retry successfully without the model touching the wallet.&lt;/p&gt;

&lt;p&gt;I also ran a controlled two-sided merchant test. Instead of paying a sample endpoint, I created a separate merchant wallet and ran a local x402 endpoint that advertised that wallet as &lt;code&gt;payTo&lt;/code&gt;. The paid retry returned HTTP &lt;code&gt;200&lt;/code&gt;, the buyer wallet decreased, and the merchant wallet increased by &lt;code&gt;0.001&lt;/code&gt; testnet USDC.&lt;/p&gt;

&lt;p&gt;That second test matters because it proves both sides of the flow: the buyer can generate proof, and the merchant can receive value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget Is Not A Prompt Instruction
&lt;/h2&gt;

&lt;p&gt;The most important test was not whether a payment could succeed. It was whether a payment could be stopped.&lt;/p&gt;

&lt;p&gt;I created a session with a &lt;code&gt;0.02 USD&lt;/code&gt; budget and used a local paid endpoint priced at &lt;code&gt;0.01&lt;/code&gt; testnet USDC. AgentCore generated two payment proofs in the session. After those attempts, the session showed &lt;code&gt;0 USD&lt;/code&gt; available spend. The next same-session proof request failed with &lt;code&gt;InsufficientBudget&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the right failure mode.&lt;/p&gt;

&lt;p&gt;The agent did not need to remember the budget. The prompt did not need to say "please do not spend more than two cents." The model could be instructed to ignore limits and the infrastructure would still reject the over-budget payment.&lt;/p&gt;

&lt;p&gt;This is the deeper point: financial constraints belong in mechanisms, not model behavior. Prompts are useful for intent. They are the wrong place to enforce spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cedar Before Payment
&lt;/h2&gt;

&lt;p&gt;Budgets answer "how much can this session spend?" They do not answer "who is allowed to invoke this paid action with these arguments?"&lt;/p&gt;

&lt;p&gt;That is where policy belongs.&lt;/p&gt;

&lt;p&gt;I reused a live AgentCore Gateway with a Cedar policy engine in enforce mode. A valid user token called a governed action with two amounts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;45.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;600.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Denied with JSON-RPC &lt;code&gt;-32002&lt;/code&gt; before backend execution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;During the denied-call window, the Payment Manager logs showed zero new &lt;code&gt;Payment processed&lt;/code&gt; events and CloudWatch showed zero new &lt;code&gt;SpendAmount&lt;/code&gt; datapoints.&lt;/p&gt;

&lt;p&gt;That proves the first control: policy can stop the workflow before the payment path starts.&lt;/p&gt;

&lt;p&gt;Then I hardened the shape. A runner first called Gateway/Cedar with a small amount. If Cedar allowed it, the runner invoked the real x402 payment path. If Cedar denied it, the runner did not attempt payment. The allowed branch generated payment proof; the denied branch made no payment attempt.&lt;/p&gt;

&lt;p&gt;Finally, I moved the paid-fetch implementation itself behind Gateway as a Lambda target. Cedar allowed &lt;code&gt;amount &amp;lt; 0.01&lt;/code&gt;, denied the over-limit branch, and the Lambda entered AgentCore Payments only after policy allow.&lt;/p&gt;

&lt;p&gt;The Gateway-hosted run proves the full chain: Cedar allow, proof generation inside the Lambda, denial before Lambda/payment for the over-limit request, and a settled &lt;code&gt;200&lt;/code&gt; on the allowed branch's retry.&lt;/p&gt;

&lt;p&gt;The architecture is still the right one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gateway policy allow/deny -&amp;gt; paid tool execution -&amp;gt; AgentCore payment proof -&amp;gt; retry paid resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The policy decision happens before money can move. The payment session budget still applies after policy allow. Those are separate controls, and they should stay separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability Is Part Of The Product
&lt;/h2&gt;

&lt;p&gt;Payment systems without audit trails are demos.&lt;/p&gt;

&lt;p&gt;AgentCore Payments publishes metrics into &lt;code&gt;AWS/Bedrock-AgentCore&lt;/code&gt; — &lt;code&gt;SpendAmount&lt;/code&gt;, &lt;code&gt;OperationSuccess&lt;/code&gt;, &lt;code&gt;OperationFailure&lt;/code&gt;, &lt;code&gt;OperationLatency&lt;/code&gt;, &lt;code&gt;ActiveSessions&lt;/code&gt;, &lt;code&gt;PaymentTokenFetchSuccess&lt;/code&gt;, &lt;code&gt;PaymentTokenFetchFailures&lt;/code&gt; — with &lt;code&gt;SpendAmount&lt;/code&gt; carrying &lt;code&gt;ProcessPayment&lt;/code&gt; datapoints per Coinbase connector and payment manager. Vended log delivery for the payment manager produces lifecycle events for session creation, instrument retrieval, and payment processing, all in your own CloudWatch log group.&lt;/p&gt;

&lt;p&gt;Metrics and logs are one delivery pipeline. X-Ray span correlation is a &lt;em&gt;second&lt;/em&gt;, entirely separate one, and it is easy to miss because nothing about &lt;code&gt;ProcessPayment&lt;/code&gt; failing to appear in X-Ray looks like a missing configuration step — it just looks like empty search results. Log delivery uses &lt;code&gt;logType=APPLICATION_LOGS&lt;/code&gt; pointed at a &lt;code&gt;CWL&lt;/code&gt; (CloudWatch Logs) destination. Spans require their own delivery source with &lt;code&gt;logType=TRACES&lt;/code&gt;, pointed at an &lt;code&gt;XRAY&lt;/code&gt; destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs put-delivery-source &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"payments-traces-source"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-arn&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:REGION:ACCOUNT:payment-manager/MANAGER-ID"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--log-type&lt;/span&gt; &lt;span class="s2"&gt;"TRACES"&lt;/span&gt;

aws logs put-delivery-destination &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"payments-xray"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delivery-destination-type&lt;/span&gt; &lt;span class="s2"&gt;"XRAY"&lt;/span&gt;

aws logs create-delivery &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delivery-source-name&lt;/span&gt; &lt;span class="s2"&gt;"payments-traces-source"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delivery-destination-arn&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:logs:REGION:ACCOUNT:delivery-destination:payments-xray"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring only the log pipeline — the natural first step, since it's what the getting-started docs walk you through — leaves X-Ray permanently empty with no error anywhere to point at the missing piece. I discovered this in July and confirmed it was not a platform gap in August: once the trace pipeline is wired up, &lt;code&gt;Bedrock.AgentCore.Payments.ProcessPayment&lt;/code&gt; spans appear within a couple of minutes carrying the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-observability.html" rel="noopener noreferrer"&gt;documented attributes&lt;/a&gt;: &lt;code&gt;payments.spend_amount&lt;/code&gt;, &lt;code&gt;payments.merchant&lt;/code&gt;, &lt;code&gt;payments.payment_session_id&lt;/code&gt;, &lt;code&gt;payments.payment_instrument_id&lt;/code&gt;, down to &lt;code&gt;aws.request_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last set of attributes is the actual payoff. Metrics tell you &lt;em&gt;that&lt;/em&gt; spend happened; a span tells you &lt;em&gt;which&lt;/em&gt; spend, tied to which session, which merchant, which request. For a platform where agents pay third parties on a customer's behalf, that's the mechanism for answering "which specific payment failed, for which customer, at which merchant, for how much" during an incident or a billing dispute — one X-Ray trace correlating the Gateway request, the Cedar decision, the Lambda execution, the &lt;code&gt;ProcessPayment&lt;/code&gt; call, and the merchant settlement, without grepping across log groups by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent-Native Payment Flow
&lt;/h2&gt;

&lt;p&gt;The script path is useful for proof. The agent-native path is what product builders will care about.&lt;/p&gt;

&lt;p&gt;I tested the Strands &lt;code&gt;AgentCorePaymentsPlugin&lt;/code&gt; with the real &lt;code&gt;http_request&lt;/code&gt; tool. The tool returned HTTP &lt;code&gt;402&lt;/code&gt;. The plugin detected it, called AgentCore Payments through &lt;code&gt;PaymentManager.generate_payment_header&lt;/code&gt;, injected a &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; header, and requested a retry.&lt;/p&gt;

&lt;p&gt;The tool retried with the injected header and got back HTTP &lt;code&gt;200&lt;/code&gt; — the plugin owns the entire handshake, proof generation through settled resource access, without the tool author writing any custom &lt;code&gt;402&lt;/code&gt; handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Prerequisites That Will Block You Cold
&lt;/h2&gt;

&lt;p&gt;Using Coinbase as a payment provider requires subscribing to "&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-marketplace-subscription.html" rel="noopener noreferrer"&gt;Coinbase Wallets for AgentCore Payments&lt;/a&gt;" in AWS Marketplace. Until that subscription is active, &lt;code&gt;CreatePaymentConnector&lt;/code&gt; and every wallet operation reject with &lt;code&gt;SubscriptionRequiredException&lt;/code&gt;. It's a real, metered charge — $0.005 per wallet operation, consolidated onto your AWS bill.&lt;/p&gt;

&lt;p&gt;Separately, &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-fund-wallet.html" rel="noopener noreferrer"&gt;Coinbase CDP wallet permissions&lt;/a&gt; are wallet-scoped, time-bound grants: the end user picks 7, 30, 60, or 90 days when authorizing the agent to sign. &lt;code&gt;ProcessPayment&lt;/code&gt; returns &lt;code&gt;AccessDeniedException: Delegated signing grant is not active&lt;/code&gt; the moment it lapses. If a payment call starts failing with that specific error, check the grant's expiry before assuming anything else is wrong — it fails the exact same way whether the grant expired yesterday or was never issued.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Create's Seatbelt
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/release-notes.html" rel="noopener noreferrer"&gt;Quick Create&lt;/a&gt; provisions Coinbase credentials for you: authorize once via OAuth in the console, and AgentCore creates the payment credential provider on your behalf, no pasted API keys. Point it at a Coinbase project that already has a manually-created Wallet Secret, though, and it refuses outright: &lt;em&gt;"This Coinbase project already has a Wallet Secret, so a new one can't be created automatically."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's a safe-failure guardrail, not a bug — silently generating a second secret could orphan wallets already derived from the first. But Quick Create only completes its automatic path for a project that has &lt;em&gt;never&lt;/em&gt; had a Wallet Secret generated, and the quick-start guides still document manual CDP key generation first. Follow the docs in order and you permanently lose access to Quick Create for that project; only a fresh Coinbase project gets the one-click path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Bugs That Weren't AWS's
&lt;/h2&gt;

&lt;p&gt;Moving the paid-fetch implementation behind Gateway as a Lambda target kept failing the same way at first: a proof generated, a retry rejected with an empty &lt;code&gt;402&lt;/code&gt; body. The obvious suspects were a rate limit on the sandbox endpoint or an edge-cache hit. Both testable, both wrong — a plain script from a local machine worked fine against the identical endpoint.&lt;/p&gt;

&lt;p&gt;Temporary CloudWatch logging inside the Lambda found the real story: &lt;code&gt;ProcessPayment&lt;/code&gt; was returning a fully valid, signed proof, authorization and signature intact, on every call. The Lambda was throwing it away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# what the Lambda was doing — wrong
&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processPayment&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;# this key does not exist
&lt;/span&gt;&lt;span class="n"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paymentOutput&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;# -&amp;gt; always {}
&lt;/span&gt;
&lt;span class="c1"&gt;# the boto3 response is flat, no wrapper
# {"processPaymentId": "...", "status": "PROOF_GENERATED",
#  "paymentOutput": {"cryptoX402": {"payload": {...signed...}}}}
&lt;/span&gt;&lt;span class="n"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paymentOutput&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;# correct
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/botocore/latest/reference/services/bedrock-agentcore/client/process_payment.html" rel="noopener noreferrer"&gt;&lt;code&gt;process_payment&lt;/code&gt; reference&lt;/a&gt; confirms it: &lt;code&gt;processPaymentId&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;paymentOutput&lt;/code&gt; are flat, top-level response fields, matching every AWS SDK example published for the API. The bad &lt;code&gt;.get()&lt;/code&gt; call silently returned &lt;code&gt;{}&lt;/code&gt;, so the retry always carried &lt;code&gt;payload: null&lt;/code&gt; — which any x402-compliant merchant correctly rejects.&lt;/p&gt;

&lt;p&gt;Two smaller bugs rode along with it: the deployed Lambda source was missing &lt;code&gt;import os&lt;/code&gt;, crashing on every cold start behind a retry loop that swallowed the real traceback; and a policy-engine ID was hardcoded from an earlier public-sanitization pass instead of reading from the environment. None of the three were platform bugs. All three had been quietly filed under "the sandbox is flaky" until logging showed otherwise. "It's the network," "it's rate limiting," and "it's flaky" are all real phenomena — in a system with this many moving parts, any of them was plausible. None of them were the fix. Reading the actual response was.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Still Missing
&lt;/h2&gt;

&lt;p&gt;This experiment ran on testnet and does not prove mainnet charging, customer billing, tax handling, refund workflows, fraud handling, or production compliance. Those are different systems.&lt;/p&gt;

&lt;p&gt;The core primitive (payment proof generation, session budgets, policy gating, observability) has been validated against GA and all endpoint retry tests have succeeded end-to-end with settled HTTP &lt;code&gt;200&lt;/code&gt; responses. CloudWatch traces and X-Ray span correlation were reconfirmed as long as both delivery pipelines are wired — that was a configuration gap, not a platform gap.&lt;/p&gt;

&lt;p&gt;Coinbase's &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments.html" rel="noopener noreferrer"&gt;x402 Bazaar&lt;/a&gt; — a curated MCP server exposing thousands of pay-per-use x402 endpoints — is reachable through Gateway. The open thread: I still haven't proven end-to-end agent discovery of a live Bazaar endpoint with a successful settled payment, only that the pieces wire. The blocks are technical (Gateway-to-Bazaar MCP compatibility) not architectural. The pattern itself is sound.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What
&lt;/h2&gt;

&lt;p&gt;The interesting part of agent payments is not that an agent can call a payment API.&lt;/p&gt;

&lt;p&gt;The interesting part is that spending can be bounded by infrastructure, authorized by policy, observed through logs and metrics, and executed without putting wallet secrets into model context.&lt;/p&gt;

&lt;p&gt;That is the pattern worth carrying forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent lives in the model.
Authority lives in policy.
Budget lives in the payment session.
Credentials live outside the model.
Evidence lives in telemetry.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent that can pay for its tools is not a wallet with a chat box. It is a governed actor with a narrow financial action path. That is the difference between a demo and something you can reason about.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>payments</category>
    </item>
    <item>
      <title>MPP vs x402: Two Competing Answers to the Same HTTP 402</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:29:07 +0000</pubDate>
      <link>https://dev.to/amitrix/mpp-vs-x402-two-competing-answers-to-the-same-http-402-4g27</link>
      <guid>https://dev.to/amitrix/mpp-vs-x402-two-competing-answers-to-the-same-http-402-4g27</guid>
      <description>&lt;p&gt;Two protocols answer HTTP's thirty-year-old, never-implemented &lt;code&gt;402 Payment Required&lt;/code&gt; status code, and &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Payments&lt;/a&gt; speaks both. x402 (Coinbase) and MPP (Stripe and Tempo) are direct competitors for the same job: an agent hits a paywalled resource and pays for it inline, no API key, no billing account set up in advance. They disagree about what "pay" is allowed to mean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant A as Agent
    participant M as Merchant
    participant AC as AgentCore Payments
    A-&amp;gt;&amp;gt;M: GET /resource
    M--&amp;gt;&amp;gt;A: 402 Payment Required + challenge
    A-&amp;gt;&amp;gt;AC: ProcessPayment(challenge)
    AC-&amp;gt;&amp;gt;AC: check session budget, sign with wallet
    AC--&amp;gt;&amp;gt;A: signed proof / credential
    A-&amp;gt;&amp;gt;M: retry + proof (x402: PAYMENT-SIGNATURE, MPP: Authorization)
    M--&amp;gt;&amp;gt;A: 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both protocols follow that identical outer loop. Where they diverge is what each one chooses to standardize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same shape, different commitments
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;x402&lt;/th&gt;
&lt;th&gt;MPP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Settlement&lt;/td&gt;
&lt;td&gt;On-chain only — stablecoins, primarily USDC on Base&lt;/td&gt;
&lt;td&gt;Multi-rail — Tempo stablecoins, Stripe cards/ACH/BNPL via Shared Payment Tokens, Bitcoin via Lightning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry header&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; (v2) or &lt;code&gt;X-PAYMENT&lt;/code&gt; (v1)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Authorization: Payment &amp;lt;base64url-token&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing model&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;exact&lt;/code&gt; (fixed price) and &lt;code&gt;upto&lt;/code&gt; (metered, settle-for-less-than-authorized)&lt;/td&gt;
&lt;td&gt;Charge intent, plus a session primitive for high-frequency metered billing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Facilitator&lt;/td&gt;
&lt;td&gt;Required — a third party verifies and settles the on-chain transfer&lt;/td&gt;
&lt;td&gt;Framed as a formal HTTP auth scheme; some methods avoid a separate facilitator dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;HTTP only&lt;/td&gt;
&lt;td&gt;HTTP, plus an MCP transport binding — MCP tool servers can charge per tool call directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backing&lt;/td&gt;
&lt;td&gt;Coinbase, fully open and permissionless&lt;/td&gt;
&lt;td&gt;Stripe + Tempo, with Visa and Lightspark extending it to cards and Lightning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/release-notes.html" rel="noopener noreferrer"&gt;AgentCore's release notes&lt;/a&gt; describe x402's &lt;code&gt;exact&lt;/code&gt; and &lt;code&gt;upto&lt;/code&gt; schemes and MPP as three parallel options behind one &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-process-payment.html" rel="noopener noreferrer"&gt;&lt;code&gt;ProcessPayment&lt;/code&gt;&lt;/a&gt; API — you pick &lt;code&gt;paymentType&lt;/code&gt; (&lt;code&gt;CRYPTO_X402&lt;/code&gt; or &lt;code&gt;MPP&lt;/code&gt;) per call, and the wallet, budget check, and signing path are identical underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  x402's &lt;code&gt;upto&lt;/code&gt; scheme: pay for what you actually used
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;upto&lt;/code&gt; lets a merchant advertise a &lt;em&gt;ceiling&lt;/em&gt; instead of a fixed price. The buyer's wallet authorizes a &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-process-payment.html" rel="noopener noreferrer"&gt;Permit2&lt;/a&gt; allowance up to that ceiling, and the merchant settles for whatever was actually consumed — no larger than the ceiling, no second round trip. That's what makes per-token LLM inference or metered compute billable in a single request/response instead of a price negotiation.&lt;/p&gt;

&lt;p&gt;I proved this by extending a local x402 merchant with &lt;code&gt;UptoEvmScheme&lt;/code&gt; and a &lt;code&gt;Settlement-Overrides&lt;/code&gt; response header carrying the real charge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// merchant route: authorize up to a ceiling, settle for less&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/metered-recap&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="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Settlement-Overrides&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;  &lt;span class="c1"&gt;// actual metered charge&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;settledUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# buyer: authorize a ceiling, let the merchant settle lower
&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;...,&lt;/span&gt;
    &lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CRYPTO_X402&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;paymentInput&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;cryptoX402&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;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&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;payload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;permit2AllowanceLimit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# the ceiling
&lt;/span&gt;    &lt;span class="p"&gt;}},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ProcessPayment&lt;/code&gt; authorized a ceiling of &lt;code&gt;15000&lt;/code&gt; base units on Base Sepolia testnet; the merchant declared an actual charge of &lt;code&gt;2000&lt;/code&gt;. Checking the wallet's on-chain balance immediately before and after confirmed it moved by exactly &lt;code&gt;2000&lt;/code&gt;, not &lt;code&gt;15000&lt;/code&gt; — real settlement tracked real usage, not the authorization.&lt;/p&gt;

&lt;p&gt;The gotcha: the &lt;code&gt;ProcessPayment&lt;/code&gt; X-Ray span's &lt;code&gt;payments.spend_amount&lt;/code&gt; attribute reads the &lt;em&gt;ceiling&lt;/em&gt;, not the settled amount, because AgentCore signs the authorization before the merchant declares what it actually consumed. A spend dashboard built on that &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-observability.html" rel="noopener noreferrer"&gt;span attribute&lt;/a&gt; alone will overstate real spend for &lt;code&gt;upto&lt;/code&gt; transactions — reconcile against actual settlement, not the span, to get the true number.&lt;/p&gt;

&lt;h2&gt;
  
  
  MPP: one interface, four rails behind it
&lt;/h2&gt;

&lt;p&gt;MPP standardizes the challenge-response &lt;em&gt;interface&lt;/em&gt; and lets the merchant advertise whichever payment methods it accepts — &lt;code&gt;evm&lt;/code&gt;, &lt;code&gt;tempo&lt;/code&gt;, &lt;code&gt;solana&lt;/code&gt;, and through Stripe, card and fiat — inside one &lt;code&gt;WWW-Authenticate: Payment&lt;/code&gt; header, per the &lt;a href="https://github.com/tempoxyz/mpp-specs" rel="noopener noreferrer"&gt;mpp-specs&lt;/a&gt; IETF draft co-authored by Tempo and Stripe.&lt;/p&gt;

&lt;p&gt;No live MPP-compatible merchant exists anywhere yet to round-trip against — not from AWS, not in Coinbase's Bazaar. To prove AgentCore's side of the handshake, I built the smallest thing that could: a synthetic, spec-compliant &lt;code&gt;evm&lt;/code&gt;-method challenge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;WWW-Authenticate: Payment id="&amp;lt;unique-id&amp;gt;", realm="&amp;lt;realm&amp;gt;",
  method="evm", intent="charge",
  request="&amp;lt;base64url JSON: amount, currency, recipient, methodDetails.chainId&amp;gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;...,&lt;/span&gt;
    &lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MPP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;paymentInput&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;mpp&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;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&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;wwwAuthenticateHeaders&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="n"&gt;header_value&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# forwarded verbatim
&lt;/span&gt;    &lt;span class="p"&gt;}},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paymentOutput&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;mpp&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;paymentCredential&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# -&amp;gt; "Payment &amp;lt;base64url-token&amp;gt;", ready to attach as Authorization
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two checks confirmed AgentCore genuinely parsed the challenge rather than rubber-stamping it: the session's available budget dropped by exactly the amount declared in the request (&lt;code&gt;0.02&lt;/code&gt; → &lt;code&gt;0.019&lt;/code&gt; USD for a &lt;code&gt;$0.001&lt;/code&gt; charge), and the returned credential was a well-formed, 1176-character token with the documented &lt;code&gt;Payment&lt;/code&gt; prefix. That's genuine credential generation, matching the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-process-payment.html" rel="noopener noreferrer"&gt;&lt;code&gt;ProcessPayment&lt;/code&gt; response schema&lt;/a&gt; exactly. What it doesn't prove is settlement — there's nothing real to settle against yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for which
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;x402&lt;/strong&gt; when buyers already hold stablecoins and you want the simplest integration, a full on-chain audit trail, and sub-cent transaction costs — paywalled content, one-off compliance lookups, real-time market data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MPP&lt;/strong&gt; when your buyer population doesn't hold crypto, or you need card/ACH reach alongside stablecoins, or you're billing something metered and high-frequency where MPP's session primitive fits better than repeated &lt;code&gt;402&lt;/code&gt; round trips.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AgentCore Payments doesn't make you choose between them. It abstracts the protocol behind one API, so the decision moves from "which protocol do I integrate" to "which protocol does this merchant accept" — which is the right place for that decision to live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open thread
&lt;/h2&gt;

&lt;p&gt;Full settlement against a live MPP merchant hasn't been tested anywhere yet — no public merchant exists that accepts MPP payments through the complete cycle. I've proved that AgentCore can generate valid credentials and that budget tracking works, but settling a real charge against a real merchant and confirming the merchant received payment remains untested. This is a genuine gap, not a missing step in the documentation. Once live MPP merchants exist in production, the full picture — credential generation, settlement success, reconciliation — will be available to verify.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>payments</category>
    </item>
    <item>
      <title>A Browser That Pays</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:28:32 +0000</pubDate>
      <link>https://dev.to/amitrix/a-browser-that-pays-l0k</link>
      <guid>https://dev.to/amitrix/a-browser-that-pays-l0k</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt; names five official use cases for Payments: research, financial analysis, pay-per-inference, on-demand storage, and — quoted with a named customer — &lt;strong&gt;browser agents&lt;/strong&gt;. &lt;a href="https://aws.amazon.com/blogs/machine-learning/amazon-bedrock-agentcore-payments-is-now-generally-available-enabling-agents-to-transact-safely-and-autonomously-at-scale/" rel="noopener noreferrer"&gt;Anchor Browser&lt;/a&gt;, a cloud browser-automation platform, integrated AgentCore Payments specifically to unlock paywalled web content for its customers' agentic workflows.&lt;/p&gt;

&lt;p&gt;That combination — a real, isolated &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool.html" rel="noopener noreferrer"&gt;Browser Tool&lt;/a&gt; session driving through an actual &lt;code&gt;402&lt;/code&gt; paywall — surfaces a protocol detail most payment paths never hit: a browser doesn't see what a script does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant B as AgentCore Browser (Playwright/CDP)
    participant S as Paywalled site
    participant AC as AgentCore Payments
    B-&amp;gt;&amp;gt;S: navigate
    S--&amp;gt;&amp;gt;B: 402 + @x402/paywall HTML widget
    B-&amp;gt;&amp;gt;B: read data-requirements DOM attribute
    B-&amp;gt;&amp;gt;AC: ProcessPayment(challenge)
    AC--&amp;gt;&amp;gt;B: signed proof
    B-&amp;gt;&amp;gt;S: re-navigate, proof injected via route interception
    S--&amp;gt;&amp;gt;B: 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-browser.html" rel="noopener noreferrer"&gt;documented pattern&lt;/a&gt; uses &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; connected to a managed Browser Tool session over Chrome DevTools Protocol, with Playwright's response interception catching a &lt;code&gt;402&lt;/code&gt; mid-navigation:&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;bedrock_agentcore.tools.browser_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;browser_session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;browser_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ws_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ws_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_ws_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_over_cdp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ws_headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 402 on first navigation
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The wrinkle: two different 402s
&lt;/h2&gt;

&lt;p&gt;Here's the part that isn't written down anywhere. A script's &lt;code&gt;402&lt;/code&gt; comes back as JSON with a &lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; header holding the base64-encoded challenge. Point a real browser (Chromium's &lt;code&gt;Accept: text/html&lt;/code&gt;) at the same URL, and the server returns something else entirely: a human-facing &lt;code&gt;@x402/paywall&lt;/code&gt; HTML page, no &lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; header at all, with the identical challenge JSON embedded in a widget instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"payment-widget"&lt;/span&gt;
     &lt;span class="na"&gt;data-requirements=&lt;/span&gt;&lt;span class="s"&gt;'{"x402Version":2,"accepts":[{"scheme":"exact",
       "network":"eip155:84532","amount":"2000","payTo":"0x...","asset":"0x..."}]}'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Install @x402/paywall for full wallet integration --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the standard &lt;code&gt;@x402/paywall&lt;/code&gt; convention — the same challenge, rendered for a human wallet-connect UI, but just as readable by an automated browser:&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;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval_on_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#payment-widget&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;el =&amp;gt; el.getAttribute(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data-requirements&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;requirement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the challenge is read from the DOM instead of a header, everything downstream is identical: &lt;code&gt;ProcessPayment&lt;/code&gt; generates a real proof, &lt;a href="https://playwright.dev/docs/network#modify-requests" rel="noopener noreferrer"&gt;Playwright's route interception&lt;/a&gt; injects it into the retry, and the re-navigation settles to &lt;code&gt;200&lt;/code&gt;:&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;add_payment_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;continue_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;payment_headers&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_payment_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 200
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Every other payment path in AgentCore — a script, a Lambda, a backend service — talks to a merchant as a machine. Browser agents talk to the &lt;em&gt;same&lt;/em&gt; merchant the way a human would. The target site was built for human traffic and gates agent access the same way it gates a browser without an active subscription. The paywall doesn't know or care that a script is driving the Chromium under the hood. It serves the human-facing challenge either way, and the agent has to be able to read it.&lt;/p&gt;

&lt;p&gt;That's the actual shape of the "browser agents" use case: not a new payment mechanism, but the same &lt;code&gt;ProcessPayment&lt;/code&gt; primitive plugged into a client that has to speak the paywall's native language — HTML and DOM, not headers and JSON — before it can pay at all.&lt;/p&gt;

&lt;p&gt;For anyone building this: the gap isn't in AgentCore Payments. It's in knowing that the same endpoint returns different response shapes based on what &lt;code&gt;User-Agent&lt;/code&gt; and &lt;code&gt;Accept&lt;/code&gt; headers the request carries. A script gets the header-based challenge. A browser gets the widget. If you're working with the managed Browser Tool through a paywall, you need to handle both, or at minimum understand that the browser path requires DOM extraction instead of header parsing.&lt;/p&gt;

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

&lt;p&gt;Browsers drive the unscripted web — portals, JavaScript-rendered content, interaction-gated resources — but they're only half the story. Most of these paywalled portals still have API endpoints that prefer a script path, and the cleaner play, where available, is usually to find the native API. The real complexity emerges when a portal &lt;em&gt;only&lt;/em&gt; surfaces data through the browser UI, forcing the agent to navigate like a user. Paywalls add another layer: they're not guarding the browser itself; they're guarding specific resources behind the browser. Understanding when a paywall applies to the browser path vs. the API path, and whether the managed Browser Tool or a native API fetch is the right reach, is the decision tree most projects face first.&lt;/p&gt;

&lt;p&gt;The one thing AgentCore Payments doesn't clarify in the docs (and that teams building this pattern hit immediately) is exactly this: how to detect which challenge shape you're getting, and how the managed browser's isolation plays with credential caching across sessions. That's the open thread for the next builder who runs this live.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>payments</category>
    </item>
    <item>
      <title>AgentCore Registry Is a Governed Catalog for Agent Interfaces</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:07:27 +0000</pubDate>
      <link>https://dev.to/amitrix/agentcore-registry-is-a-governed-catalog-for-agent-interfaces-53md</link>
      <guid>https://dev.to/amitrix/agentcore-registry-is-a-governed-catalog-for-agent-interfaces-53md</guid>
      <description>&lt;p&gt;AgentCore Registry provides discovery for reusable agent interfaces, but the important behavior is not simple name resolution. The tested path was: create a registry, publish an A2A descriptor, move the record through approval, search approved records, resolve the Agent Card, and invoke the discovered agent.&lt;/p&gt;

&lt;p&gt;That makes Registry a governed catalog for the agent graph. Hardcoded runtime ARNs are the visible pain. The deeper problem is uncontrolled reuse: which agents exist, which interface they expose, who approved them, and whether a caller is using the right version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/registry-get-started.html" rel="noopener noreferrer"&gt;AWS Agent Registry&lt;/a&gt; gives that workflow a control plane: create a registry, publish a record, submit it for approval, approve it, then search only approved records. The record can describe an A2A agent, an MCP server, an agent skill, or a custom resource. The shape is intentionally catalog-first.&lt;/p&gt;

&lt;p&gt;The thesis: agent discovery becomes a governance problem before it becomes a routing problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardcoded ARNs Are Only Part of the Problem
&lt;/h2&gt;

&lt;p&gt;Hardcoded ARNs are easy to hate. They make demos brittle. They make account migration annoying. They make blue/green rollouts awkward. They also create the comforting illusion that the problem is only lookup.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;If a caller has a hardcoded ARN, at least it knows exactly what it is calling. A Registry-backed caller moves one level up: it asks for a capability, receives one or more approved records, resolves the interface, and invokes the selected agent. That adds power, but it also adds a new question:&lt;/p&gt;

&lt;p&gt;Who gets to publish something discoverable?&lt;/p&gt;

&lt;p&gt;That is where Registry differs from DNS. DNS does not ask whether a service is approved for reuse. Registry does. A record moves through lifecycle states before it becomes searchable. In our test, the A2A record moved from &lt;code&gt;DRAFT&lt;/code&gt; to &lt;code&gt;APPROVED&lt;/code&gt; before the search path could use it.&lt;/p&gt;

&lt;p&gt;That lifecycle is the control point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;The verification ran two parallel paths: one against the preview API and one against the GA surface. Both proved the Registry-to-A2A integration path works end to end.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;What It Proved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Preview: Create, approve, search, resolve&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;A live A2A Agent Card can be published as an approved Registry record and discovered by search.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preview: Invoke through discovered card&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;A caller can resolve the Agent Card from a Registry record and invoke the discovered agent through A2A without hardcoding the runtime ARN.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA: Control and discovery APIs&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;The GA Registry APIs (&lt;code&gt;agent-registry-control&lt;/code&gt;, &lt;code&gt;agent-registry&lt;/code&gt;) create, approve, list, retrieve, and delete records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA: Registry-to-A2A invocation&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;A caller retrieved an A2A Agent Card from a GA Registry record and invoked the agent through A2A, then cleaned up the record and Registry.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The target was a deployed A2A specialist agent exposed through an Agent Card:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: enterprise support escalation specialist&lt;/li&gt;
&lt;li&gt;Protocol version: 0.3.0&lt;/li&gt;
&lt;li&gt;Function: assess support tickets, return severity, owner, SLA, and next steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Registry record was created as an A2A descriptor. The test captured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registry status: &lt;code&gt;READY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Record after create: &lt;code&gt;DRAFT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Record after submit: &lt;code&gt;APPROVED&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Search by name: returned the approved record&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the search script selected the A2A record, parsed the Agent Card returned in the descriptor, and invoked the agent through the resolved card URL. The caller did not hardcode the specialist's runtime ARN in the delegation path.&lt;/p&gt;

&lt;p&gt;The test case was deliberately operational: an enterprise customer had a double charge and production API access failure during quarter-end close. The discovered specialist returned:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;P2 severity, Named TAM owner, 12-hour SLA.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That response matters less than the path. The important part is that the caller found an approved agent record, resolved its interface, and delegated over A2A.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Evidence Supports
&lt;/h2&gt;

&lt;p&gt;The preview validation and GA revalidation support a narrower and more precise statement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Registry is the catalog layer where agent interfaces become approved assets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is more than runtime address lookup. It changes what teams design around.&lt;/p&gt;

&lt;p&gt;If Registry were only ARN lookup, the natural question would be, "Why not use an environment variable?" For a single caller and a single callee, that is still a good answer. A static env var is simpler, cheaper, and easier to debug.&lt;/p&gt;

&lt;p&gt;Registry becomes worth the extra lookup when the organization needs reuse with control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple callers need to discover the same agent.&lt;/li&gt;
&lt;li&gt;A platform team wants to publish approved A2A agents or MCP servers.&lt;/li&gt;
&lt;li&gt;Records need owners, versions, status, and audit history.&lt;/li&gt;
&lt;li&gt;Callers search by capability instead of importing another team's configuration.&lt;/li&gt;
&lt;li&gt;Agent interfaces are validated before they become reusable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Registry record is not only a pointer. It is metadata plus interface contract plus lifecycle state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Namespace Migration Is Part of the Contract
&lt;/h2&gt;

&lt;p&gt;The migration is no longer future work. The current GA path uses the &lt;code&gt;agent-registry-control&lt;/code&gt; client for lifecycle operations and the &lt;code&gt;agent-registry&lt;/code&gt; client for discovery. This shift is not merely naming cleanup. It affects every script that creates, searches, or audits Registry resources, along with IAM policies, ARN formats, events, metrics, and quotas.&lt;/p&gt;

&lt;p&gt;The two namespaces differ on every surface:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Preview Namespace&lt;/th&gt;
&lt;th&gt;GA Namespace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLI/API&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bedrock-agentcore&lt;/code&gt;, &lt;code&gt;bedrock-agentcore-control&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;agent-registry-control&lt;/code&gt;, &lt;code&gt;agent-registry&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM actions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bedrock-agentcore:*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent-registry:*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARNs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arn:aws:bedrock-agentcore:...:registry/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arn:aws:agent-registry:...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events/metrics&lt;/td&gt;
&lt;td&gt;Bedrock AgentCore sources&lt;/td&gt;
&lt;td&gt;Agent Registry sources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical rule: keep Registry client names, endpoint assumptions, and IAM action prefixes isolated in one layer. Do not scatter them through experiment code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the First Search Miss Showed
&lt;/h2&gt;

&lt;p&gt;The first capability-style search did not return the record:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tier two escalation agent for enterprise billing tickets&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The name search did:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;enterprise support escalation specialist&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That miss shows that catalog quality matters. Search is only as good as the record metadata, descriptor content, and query vocabulary. A Registry record with thin descriptions creates a weak discovery surface. A record with clear capability language gives the caller more to work with.&lt;/p&gt;

&lt;p&gt;For production use, publishing an agent includes the same care as publishing an API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;crisp description&lt;/li&gt;
&lt;li&gt;clear capability names&lt;/li&gt;
&lt;li&gt;versioning&lt;/li&gt;
&lt;li&gt;owner metadata&lt;/li&gt;
&lt;li&gt;examples of when to use it&lt;/li&gt;
&lt;li&gt;examples of when not to use it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that, Registry becomes a list of endpoints. With it, Registry becomes a catalog of approved agent capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Registry Does Not Solve
&lt;/h2&gt;

&lt;p&gt;Registry does not eliminate the rest of distributed-agent design.&lt;/p&gt;

&lt;p&gt;It does not prove the agent is safe to call. Approval helps, but runtime behavior still needs observability and policy. It does not solve identity propagation across an A2A hop. The caller still needs the right permissions, and the callee still sees the authenticated caller according to the runtime auth model. It does not prevent delegation loops. Multi-agent systems still need hop budgets and loop controls.&lt;/p&gt;

&lt;p&gt;It also does not remove the need to understand the interface. In our test, the caller resolved an A2A Agent Card and invoked the agent through that card. That worked because the callee exposed a real A2A surface. A plain HTTP runtime would need a custom record or another descriptor strategy.&lt;/p&gt;

&lt;p&gt;This is the right boundary. Registry is not an orchestration layer. Its job is to make approved capabilities discoverable with enough metadata for a caller to decide what to do next.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What
&lt;/h2&gt;

&lt;p&gt;If you are building a multi-agent system, the question is not "How do agents find each other?"&lt;/p&gt;

&lt;p&gt;The better question is: "Which agents are approved for reuse, and how does a caller know?"&lt;/p&gt;

&lt;p&gt;For a small system, an environment variable is fine. For an agent graph that spans teams, accounts, or protocols, discovery needs governance. Registry gives that governance a place to live.&lt;/p&gt;

&lt;p&gt;The next frontier is not lookup. It is trust: richer metadata, better search vocabulary, signed cards, policy-aware invocation, and clear ownership for every reusable agent capability.&lt;/p&gt;

&lt;p&gt;That is where the agent graph starts to look less like a pile of runtimes and more like an operating model.&lt;/p&gt;

&lt;p&gt;The open question I am holding: I have tested single-caller discovery paths. The multi-agent pattern — agents that invoke each other over A2A, each with its own identity and Registry record — is where the authorization model gets genuinely harder, and it is where I haven't yet wired the full lifecycle.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of a series working through Amazon Bedrock AgentCore by building on it. Start with &lt;a href="https://artificialcuriositylabs.ai/posts/the-agentcore-map" rel="noopener noreferrer"&gt;The AgentCore Map&lt;/a&gt;, then &lt;a href="https://artificialcuriositylabs.ai/posts/where-agentcore-sits" rel="noopener noreferrer"&gt;Where AgentCore Sits in the Architecture&lt;/a&gt;. The mechanics behind Registry: &lt;a href="https://artificialcuriositylabs.ai/posts/agentcore-runtime-where-agent-runs" rel="noopener noreferrer"&gt;AgentCore Runtime&lt;/a&gt; and &lt;a href="https://artificialcuriositylabs.ai/posts/authorizing-agent-tools-iam-vs-oauth" rel="noopener noreferrer"&gt;Two Ways to Authorize an Agent Tool&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>registry</category>
    </item>
    <item>
      <title>What the Trace Knew That the Logs Didn't</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:06:52 +0000</pubDate>
      <link>https://dev.to/amitrix/what-the-trace-knew-that-the-logs-didnt-7cl</link>
      <guid>https://dev.to/amitrix/what-the-trace-knew-that-the-logs-didnt-7cl</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Most observability posts break something on purpose and show the dashboard light up. I started differently: observability was the lens that caught failures in other experiments for months before I ran dedicated observability tests. That turned out to be the honest way to learn what observability actually buys — the failures weren't designed to be visible, so what showed up mattered.&lt;/p&gt;

&lt;p&gt;Four accidental lessons came first (observability catching failures from unrelated experiments), then four deliberate ones (dedicated observability tests that each turned up something the previous tests had missed or misunderstood):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An agent that logged a complete answer while dead.&lt;/li&gt;
&lt;li&gt;A tool invisible to evaluation systems because it didn't emit spans.&lt;/li&gt;
&lt;li&gt;A two-agent loop only visible from the shared trace, never from either agent's logs.&lt;/li&gt;
&lt;li&gt;A delegation where 95% of the trace was protocol noise.&lt;/li&gt;
&lt;li&gt;A latency gap that looked like broken tracing but wasn't.&lt;/li&gt;
&lt;li&gt;A loop-detection signal that's visible in traces but isn't proof on count alone.&lt;/li&gt;
&lt;li&gt;A cross-agent correlator that works on two telemetry backends but hands you the wrong trace first.&lt;/li&gt;
&lt;li&gt;An agent running as a plain local process showing up in the same observability pipeline as a Runtime-hosted one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each maps to a rule for debugging a misbehaving agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  How AgentCore observability is wired
&lt;/h2&gt;

&lt;p&gt;When you enable observability on AgentCore Runtime, every invocation emits OpenTelemetry (OTel) spans to CloudWatch — the same span/trace model any instrumented service uses. Agent frameworks emit these automatically: a span per model call, a span per tool execution, nested under a trace, all tagged with a &lt;code&gt;session.id&lt;/code&gt;. The spans land in account-wide logs and a per-runtime log group. On top of the spans sit CloudWatch &lt;strong&gt;metrics&lt;/strong&gt; — &lt;code&gt;Invocations&lt;/code&gt;, &lt;code&gt;Errors&lt;/code&gt;, &lt;code&gt;SystemErrors&lt;/code&gt;, &lt;code&gt;Latency&lt;/code&gt; — all queryable in the CloudWatch GenAI Observability console.&lt;/p&gt;

&lt;p&gt;Two prerequisites: you must enable &lt;strong&gt;CloudWatch Transaction Search&lt;/strong&gt; once per account (the docs don't mention it, but span queries silently return nothing without it), and there's a &lt;strong&gt;2–10 minute ingestion lag&lt;/strong&gt; between an invocation and its spans being queryable. Both cost me time because neither is documented loudly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: logs lie, metrics don't
&lt;/h2&gt;

&lt;p&gt;The first real lesson came from a deployment that looked successful and wasn't.&lt;/p&gt;

&lt;p&gt;An early version of the deployment had a &lt;code&gt;__main__&lt;/code&gt; block that ran a one-shot test — &lt;code&gt;print(json.dumps(triage(...)))&lt;/code&gt; — instead of starting the HTTP server. The agent called the model, escalated, produced a correct answer, and printed it to stdout. &lt;strong&gt;The logs showed a complete, correct response.&lt;/strong&gt; But the process exited without ever starting the HTTP server. &lt;code&gt;/ping&lt;/code&gt; had nothing listening, so the platform reported initialization failure. The agent was simultaneously &lt;em&gt;working&lt;/em&gt; (logic ran, logged a perfect answer) and &lt;em&gt;broken&lt;/em&gt; (nothing served, health check dead).&lt;/p&gt;

&lt;p&gt;If I'd debugged from the logs, I'd have wasted hours. The metrics told the truth instantly: &lt;code&gt;Invocations&lt;/code&gt; and a failure signature that didn't match the happy log line. &lt;strong&gt;Logs capture what the code printed; the platform contract is HTTP.&lt;/strong&gt; A correct-looking log line is not proof the agent is serving.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 1:&lt;/strong&gt; Logs capture what the code &lt;em&gt;printed&lt;/em&gt;. Metrics capture what the platform &lt;em&gt;observed&lt;/em&gt;. When they disagree, the metrics are right. Check &lt;code&gt;Invocations&lt;/code&gt;/&lt;code&gt;Errors&lt;/code&gt;/&lt;code&gt;SystemErrors&lt;/code&gt; and the client's actual HTTP response before you trust a log line.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 2: if there's no span, it didn't happen (as far as tooling is concerned)
&lt;/h2&gt;

&lt;p&gt;The second lesson came from evaluation work, and nothing "failed" in the obvious sense.&lt;/p&gt;

&lt;p&gt;I was scoring an agent with a trajectory evaluator, which grades whether the agent called the expected tools in order. The scenarios kept returning the correct outcome (a refund was approved or blocked as policy required) but showed an &lt;strong&gt;empty tool trajectory&lt;/strong&gt; — &lt;code&gt;[]&lt;/code&gt; — as if no tool had been called at all. The refund tool clearly had run; its effect was in the response.&lt;/p&gt;

&lt;p&gt;The root cause: the tool was a plain Python function, not a framework-native one. Plain function calls don't emit OTel spans. The trajectory evaluator reads spans; if there are none, the trajectory is empty. The tool was working and completely invisible to the observability layer simultaneously.&lt;/p&gt;

&lt;p&gt;Once I promoted it to a framework-native tool, the same scenarios showed a full trajectory. The deeper lesson: &lt;strong&gt;an empty trajectory is not proof the tool didn't run. It's proof the tool isn't instrumented.&lt;/strong&gt; Understanding that made "empty trajectory" a useful signal in its own right — it tells you exactly which parts of your agent are invisible to everything downstream (evaluators, dashboards, audits), which is far more dangerous than a tool that visibly fails.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 2:&lt;/strong&gt; In an agent, "observable" is a property you opt into. Framework-native tools emit spans; raw function calls don't. Anything not emitting a span is a blind spot — not just for debugging, but for evaluation and audit. Instrument the actions that matter before you need to see them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 3: some failures are only visible from above
&lt;/h2&gt;

&lt;p&gt;This one would cost real money in production, and it's the clearest argument for tracing over logging.&lt;/p&gt;

&lt;p&gt;Two agents — a frontline and a specialist — can each make locally-correct routing decisions and still form a global loop: frontline escalates to specialist, specialist decides it's out of scope and routes back, forever. There's a &lt;a href="https://aws.amazon.com/blogs/machine-learning/debugging-production-agents/" rel="noopener noreferrer"&gt;documented incident where exactly this ran for four weeks and roughly $47K&lt;/a&gt; before anyone noticed. &lt;strong&gt;No single agent's logs contain the loop.&lt;/strong&gt; The frontline's logs show "I escalated." The specialist's logs show "I routed back." Neither shows the cycle.&lt;/p&gt;

&lt;p&gt;The only place the loop is visible is a view that spans both agents. With a shared correlation id propagated across every hop — the same &lt;code&gt;session.id&lt;/code&gt; on both agents' spans — you can see the entire path in one trace. Without cross-agent tracing, you're debugging a distributed failure from two half-views that each look fine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 3:&lt;/strong&gt; Single-agent logs can't see multi-agent failures. Propagate one correlation id (&lt;code&gt;session.id&lt;/code&gt;) across every hop so a single trace shows the whole path — that's the substrate that makes loops, dropped context, and delegation failures detectable at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 4: observability has a volume problem too
&lt;/h2&gt;

&lt;p&gt;The fourth lesson is the counterweight, because more spans is not strictly better.&lt;/p&gt;

&lt;p&gt;When I put a frontline→specialist delegation under evaluation, the shared session came back with &lt;strong&gt;207 spans — and 197 of them were protocol plumbing&lt;/strong&gt; (queue operations, fired dozens of times). The eight spans that actually mattered — the model calls and the tool execution — were buried in protocol noise, and the evaluator choked trying to reconstruct a coherent trace from the flood.&lt;/p&gt;

&lt;p&gt;The fix was a span filter that dropped the protocol scope before analysis. The general point: &lt;strong&gt;an agent's trace is not automatically useful.&lt;/strong&gt; Frameworks and protocols emit spans at wildly different granularities, and a naive "capture everything" posture produces traces where signal is a rounding error. You end up filtering by instrumentation scope to get back to the spans a human or an evaluator can reason about.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 4:&lt;/strong&gt; Capture-everything is not a strategy. Know which instrumentation scopes carry the signal for your question and filter to them. The goal is a trace you can read, not the most spans.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 5: a latency gap isn't proof the trace is lying
&lt;/h2&gt;

&lt;p&gt;This came from a dedicated test, and it's a mistake I made and caught in the same session — worth keeping because it corrected its own first conclusion.&lt;/p&gt;

&lt;p&gt;I invoked a deployed agent twice, once with a prompt that triggers a tool call and once with pure reasoning, and compared wall-clock against the trace's duration. Both showed the same pattern: the client measured roughly 6.5–7 seconds &lt;em&gt;more&lt;/em&gt; than the trace accounted for. My first conclusion: a real gap in trace attribution, latency the trace simply couldn't see.&lt;/p&gt;

&lt;p&gt;It wasn't. Both calls minted a fresh &lt;code&gt;runtimeSessionId&lt;/code&gt;. &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/agents-how-it-works.html" rel="noopener noreferrer"&gt;AWS documents plainly&lt;/a&gt; that every unique session provisions a new, dedicated microVM. Startup splits into a platform-managed phase (microVM provisioning, before your instrumentation exists) and an application phase (your code). I confirmed directly: two calls on the &lt;em&gt;same&lt;/em&gt; session collapsed the gap to about 0.2 seconds (replicated 3x). Cold-call wall-clock stayed at 9–12 seconds, but the &lt;em&gt;attribution&lt;/em&gt; changed — the platform overhead was invisible to the trace by structural design, not by accident.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 5:&lt;/strong&gt; A latency gap between client wall-clock and trace duration is not automatically a tracing defect. Check whether you're minting a new &lt;code&gt;runtimeSessionId&lt;/code&gt; per call first. A fresh session is a fresh microVM, and cold-start provisioning happens before your instrumentation exists, so it will never show as a span. Reuse session ids across real calls in a conversation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 6: a repeated span isn't provably a loop
&lt;/h2&gt;

&lt;p&gt;Tracing can show you a &lt;em&gt;pattern&lt;/em&gt;, but a pattern isn't proof of a &lt;em&gt;cause&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I forced an agent to make three tool calls in one turn (a three-part research prompt) and checked whether the trace showed a distinct signal. It did: three identically named spans, cross-confirmed by the runtime's logs. But a real stuck loop — the same call repeated because the agent isn't making progress — would produce the exact same span count. Neither the trace nor the application log carries the actual tool-call &lt;em&gt;arguments&lt;/em&gt;, so span count alone can't tell the two apart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/debugging-production-agents/" rel="noopener noreferrer"&gt;AWS's own debugging guide&lt;/a&gt; resolves this cleanly: the primary infinite-loop signal is a volume anomaly — high token usage combined with a low or zero error rate, plus a span count and session duration far outside the normal range — not argument-level inspection as a first move. I confirmed this by temporarily enabling Bedrock model invocation logging (disposable, account-wide, so I disabled it immediately after) and re-ran the same test. The recovered model completion showed the real, distinct query strings behind each tool call — proof, not inference, that this was legitimate parallel tool use and not a loop.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 6:&lt;/strong&gt; Repeated identically-named spans are a real, countable loop &lt;em&gt;symptom&lt;/em&gt; — but not proof by themselves, because legitimate parallel tool use produces the same shape. Reach for the volume signal first (token usage, span count, session duration vs. normal range). Only drop into argument-level inspection via Bedrock model invocation logging once a session already looks suspicious, and treat that logging as temporary — it has no per-agent scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 7: the correlator is real, but the first trace it hands you is a decoy
&lt;/h2&gt;

&lt;p&gt;This came from a dedicated test run a month after Lesson 3, specifically to check whether that finding still held and on what infrastructure.&lt;/p&gt;

&lt;p&gt;Lesson 3 established that a shared &lt;code&gt;session.id&lt;/code&gt; correlates a two-agent A2A handoff on CloudWatch logs. The open question: does that correlation also work through AWS X-Ray? I invoked the same kind of delegation and checked X-Ray directly: it returned the session's spans just as reliably as CloudWatch had.&lt;/p&gt;

&lt;p&gt;But checking twice surfaced a real gotcha. My first pass came back with &lt;em&gt;zero&lt;/em&gt; matching rows — for a few minutes, that looked like the correlation had broken or the platform had moved this telemetry. It hadn't. A wider query window (my first check waited 45 seconds and searched a narrow slice of time; a retry with several more minutes of margin) found the rows, right in line with what I'd measured before. The lesson: &lt;strong&gt;"zero results" from an observability query is frequently just a too-tight window, not proof a backend has no data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The second, more durable finding: the correlated session always contains at least two distinct traces, not one. The first is the A2A discovery handshake — the receiving agent fetching the caller's metadata — and it's essentially empty: near-instant, no model calls. The actual work is the &lt;em&gt;second&lt;/em&gt; trace. Any tooling that assumes "the first trace in a session is the interesting one" will silently grab the handshake and miss the substance every time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 7:&lt;/strong&gt; Don't trust a single empty query result as proof a telemetry backend has no data — widen the window before you conclude that. When correlating a multi-agent session, don't assume the first trace is the one that matters; the A2A discovery handshake produces a real, separate, empty trace ahead of the actual work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lesson 8: the pipeline doesn't care who deployed the agent
&lt;/h2&gt;

&lt;p&gt;Does AgentCore Observability only work for agents AgentCore itself deployed, or does it reach agents running anywhere?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/scaling-and-monitoring-distributed-agents/" rel="noopener noreferrer"&gt;AWS's August 2026 blog&lt;/a&gt; answers plainly: agents on-premises, on other clouds, anywhere with internet access and IAM credentials, all land in the same dashboard as Runtime-hosted agents. I tested the plainest version of that claim: a local Python script on this machine, zero AgentCore Runtime involvement, calling Bedrock directly via &lt;code&gt;boto3&lt;/code&gt;. Wrapped in &lt;code&gt;opentelemetry-instrument&lt;/code&gt; with the documented AWS Distro for OpenTelemetry environment variables, it appeared in both telemetry backends tagged &lt;code&gt;aws.service.type: gen_ai_agent&lt;/code&gt;, with the same &lt;code&gt;gen_ai.*&lt;/code&gt; semantic-convention spans, the same token-usage metrics, and the same structured logs a Runtime-hosted agent produces — indistinguishable in the telemetry.&lt;/p&gt;

&lt;p&gt;It wasn't frictionless. The first run threw a &lt;code&gt;400: The specified log stream does not exist&lt;/code&gt; on its first export batches — the log stream doesn't pre-exist, and ADOT creates it lazily on first write. That's a one-time, self-resolving cost, structurally the same as Lesson 5's cold start: an artifact of first contact with infrastructure that provisions itself lazily, not a persistent defect. A second run, after the stream existed, produced no errors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule 8:&lt;/strong&gt; AgentCore Observability's reach isn't bounded by AgentCore Runtime. The same ADOT SDK plus IAM credentials plus documented OTEL environment variables that instrument a Runtime-hosted agent will land a plain local process in the identical pipeline, tagged the same way. Expect a one-time, self-resolving provisioning hiccup on first contact with a new log group — the non-Runtime cousin of a cold start.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The operational workflow this leaves you with
&lt;/h2&gt;

&lt;p&gt;Put the eight lessons together and you get a concrete way to debug a misbehaving AgentCore agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start at metrics, not logs.&lt;/strong&gt; &lt;code&gt;Errors&lt;/code&gt;/&lt;code&gt;SystemErrors&lt;/code&gt;/&lt;code&gt;Latency&lt;/code&gt; on the runtime tell you whether the platform saw a failure. A clean-looking log line proves nothing (Lesson 1).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull the trace for the failed invocation&lt;/strong&gt; by &lt;code&gt;session.id&lt;/code&gt;. Wait out the ingestion lag — spans aren't instant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter to the scopes that carry signal&lt;/strong&gt; before you read it, or the protocol noise will bury the failure (Lesson 4).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the first span with an error status&lt;/strong&gt; — that's your root-cause step. If the step you &lt;em&gt;expect&lt;/em&gt; isn't in the trace at all, it isn't instrumented, which is its own bug (Lesson 2).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For multi-agent paths, confirm the correlation id propagated&lt;/strong&gt; across every hop, or you're only seeing half the failure (Lesson 3) — and skip the first trace in the session, which is usually just A2A's discovery handshake (Lesson 7).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before treating a latency gap as a tracing bug, rule out cold start.&lt;/strong&gt; A fresh &lt;code&gt;runtimeSessionId&lt;/code&gt; is a fresh microVM (Lesson 5).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't call a repeated span "a loop" on count alone.&lt;/strong&gt; Reach for the token-usage/span-count volume signal first; only turn on model invocation logging, temporarily, if you need to prove it with actual tool arguments (Lesson 6).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widen the query window before trusting a zero-result answer.&lt;/strong&gt; An empty result from a telemetry query is frequently just a too-tight window (Lesson 7).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set a CloudWatch alarm&lt;/strong&gt; on &lt;code&gt;Errors&lt;/code&gt;/&lt;code&gt;SystemErrors&lt;/code&gt; so the next one pages you instead of hiding in a log that looks fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't assume Runtime deployment is a prerequisite for visibility.&lt;/strong&gt; If part of your system runs outside AgentCore Runtime entirely (on-prem, another cloud, a plain process), the same ADOT-based setup gets it into the same dashboard — expect one lazy-provisioning hiccup on first contact (Lesson 8).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's still open
&lt;/h2&gt;

&lt;p&gt;Four of these eight lessons came from experiments that broke in ways I didn't plan for, and a trace (or missing one) that explained why. The other four came from finally testing observability directly — and two of those corrected their own first conclusion within the same session.&lt;/p&gt;

&lt;p&gt;One thing I didn't test: the sequential "stuck, retrying across turns" pattern specifically. Lessons 2 and 6 tested parallel tool-calling and multi-step tool use, but not the case where an agent makes the same tool call, fails, and retries it turn after turn until the token budget runs out. That pattern is a real, non-redundant gap — AWS's own canonical loop-detection example uses a sequential loop, not a parallel one. The volume signals that work for detecting high token usage with low error rates apply to it, but whether the &lt;em&gt;trace shape&lt;/em&gt; itself gives you a distinct signal for sequential retries (beyond volume) is something I haven't confirmed yet. That's the open thread I'm leaving with: the same observability pipeline that caught and distinguished the other seven patterns hasn't been tested against the one loop shape it's hardest to debug.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>observability</category>
    </item>
    <item>
      <title>Multi-Agent Memory: Agents That Actually Share Context</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:06:16 +0000</pubDate>
      <link>https://dev.to/amitrix/multi-agent-memory-agents-that-actually-share-context-2j3l</link>
      <guid>https://dev.to/amitrix/multi-agent-memory-agents-that-actually-share-context-2j3l</guid>
      <description>&lt;h2&gt;
  
  
  The shared-context problem
&lt;/h2&gt;

&lt;p&gt;The default way multi-agent systems share context is the prompt. Agent A finishes, you take what it produced, and you paste it into agent B's input. It works for two agents and one hop. It stops working the moment you have a chain: the context grows with every hand-off, you can't query it after the fact, and there is no boundary between what agent B should see and what it shouldn't. The "shared state" is a string that keeps getting longer and that every agent in the chain can read in full.&lt;/p&gt;

&lt;p&gt;AgentCore Memory offers a different mechanism, and it's worth being precise about what it is. Agents don't message each other's memory. They write to and read from &lt;strong&gt;one shared memory store&lt;/strong&gt;, and records are organized by &lt;strong&gt;namespace&lt;/strong&gt;. An agent retrieves by naming a namespace prefix; it gets back only what lives under that prefix. The namespace &lt;em&gt;is&lt;/em&gt; the sharing boundary and the isolation boundary at the same time — which is the whole idea, and also where the interesting failure mode lives.&lt;/p&gt;

&lt;p&gt;I wanted to prove three things on live infrastructure: (1) a writer agent can deposit context that a reader agent recovers &lt;strong&gt;without that context being in the reader's prompt&lt;/strong&gt;, (2) the recovery is by namespace, not by passing a session id or the text around, and (3) a second actor's namespace sees none of it. I built a minimal multi-agent system to test this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: one store, two agents, three actors
&lt;/h2&gt;

&lt;p&gt;I modelled a real support workflow. A &lt;strong&gt;classifier agent&lt;/strong&gt; (the "writer") stores a ticket determination under a shared actor namespace. A &lt;strong&gt;specialist agent&lt;/strong&gt; (the "reader") queries that namespace and should recover the determination. A &lt;strong&gt;third actor&lt;/strong&gt; — a different tenant — gets its own unrelated ticket, and a query scoped to &lt;em&gt;its&lt;/em&gt; namespace must come back empty.&lt;/p&gt;

&lt;p&gt;All three run against the same memory resource with the SEMANTIC strategy. The SEMANTIC strategy's &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/specify-long-term-memory-organization.html" rel="noopener noreferrer"&gt;namespace template&lt;/a&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/strategies/{memoryStrategyId}/actors/{actorId}/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That template matters more than it looks, and I'll come back to why.&lt;/p&gt;

&lt;p&gt;The classifier stores a short exchange: a customer posing a two-part incident, and the classifier's determination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USER:      Customer Contoso reports two problems at once: a billing
           double-charge and a production API-key authentication failure.
           Are these one incident or two separate tickets, and who should own it?

ASSISTANT: Ticket classification: TIER-2 ESCALATION. The billing
           double-charge and the production API-key auth failure share a
           root cause: an identity-service outage. Treat as a single
           multi-system incident, not two tickets. Recommended owner: the
           named technical account manager.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specialist never receives that text. It asks a question — &lt;em&gt;"What is the escalation tier and owner for the Contoso multi-system incident?"&lt;/em&gt; — against the shared actor's namespace, and semantic retrieval is left to find the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;The specialist recovered the context on the first poll after extraction. Scoped to the shared actor's namespace, its query came back with a single record — a distilled summary of the incident (Contoso, the billing problem, the API-key auth failure) that the classifier had stored, with the incident text never in the specialist's prompt. The same query scoped to the decoy tenant's namespace returned &lt;strong&gt;zero records&lt;/strong&gt;. Verdict: the specialist recovered the classifier's context by namespace, and it did not leak to the other actor.&lt;/p&gt;

&lt;p&gt;Two things to be honest about in that record. First, what came back is the &lt;em&gt;extracted fact&lt;/em&gt; — the durable summary of the incident — not the verbatim "TIER-2" label. That is what SEMANTIC does: it distills the conversation into a retrievable fact, and the fact carries the substance a specialist needs (the two simultaneous failures, the customer, the amount) so it doesn't re-derive the situation from scratch. If you need the literal label preserved word-for-word, semantic retrieval is the wrong strategy; a summarization strategy or an exact-record write is. Second, the isolation result is the quiet win: the decoy tenant's namespace returned &lt;strong&gt;zero&lt;/strong&gt;, not "a low-scoring match." The boundary is structural, not a ranking threshold you hope holds.&lt;/p&gt;

&lt;p&gt;This lines up with what &lt;a href="https://aws.amazon.com/blogs/machine-learning/organizing-agents-memory-at-scale-namespace-design-patterns-in-agentcore-memory/" rel="noopener noreferrer"&gt;AWS documents&lt;/a&gt;. The namespace-design guidance is explicit that &lt;code&gt;retrieve_memory_records&lt;/code&gt; "Returns ONLY records stored at" the given namespace, and that namespaces give you "clean isolation between users, and IAM-based access control." The &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/specify-long-term-memory-organization.html" rel="noopener noreferrer"&gt;specification docs&lt;/a&gt; go further: you can write IAM policies that restrict retrieval by actor, session, and namespace scope, using those scopes as condition keys. So the isolation I observed at the API level can be enforced at the IAM level too — the application-level scoping and the identity-level policy reinforce each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode that cost me two runs
&lt;/h2&gt;

&lt;p&gt;The first two times I ran this, the specialist got &lt;strong&gt;zero records&lt;/strong&gt; and it looked like a broken feature. It wasn't. The classifier had stored a &lt;strong&gt;single lone &lt;code&gt;ASSISTANT&lt;/code&gt; message&lt;/strong&gt; — the determination, by itself. SEMANTIC extraction produced nothing from it, waited out, and I very nearly wrote "namespaced sharing doesn't work."&lt;/p&gt;

&lt;p&gt;The fix, and the finding: &lt;strong&gt;SEMANTIC extracts facts from a conversation, not from a statement.&lt;/strong&gt; The working semantic runs earlier always wrote alternating &lt;code&gt;USER&lt;/code&gt;/&lt;code&gt;ASSISTANT&lt;/code&gt; turns — a real exchange. When I changed the classifier to store the incident as a two-turn exchange (the customer's problem, then the classifier's determination), extraction produced a record within about 60 seconds and the specialist recovered it on the first poll. One message in, nothing out; a turn pair in, a clean fact out.&lt;/p&gt;

&lt;p&gt;This is the same shape of trap that single-agent memory hits from a different angle: the feature is fine, but if you feed it the wrong input shape or query the wrong namespace, it hands you zero records and lets you believe it's broken. Two rules fall out of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write conversations, not lines.&lt;/strong&gt; If you want SEMANTIC to extract something, give it a turn it can extract &lt;em&gt;from&lt;/em&gt;. A determination with no surrounding exchange is not something it will summarize into a durable fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the namespace from the strategy's real template.&lt;/strong&gt; Querying &lt;code&gt;/actors/{id}/&lt;/code&gt; when the strategy writes to &lt;code&gt;/strategies/{strategyId}/actors/{id}/&lt;/code&gt; returns zero and looks identical to "extraction failed." Read the template off the resource; don't assume it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The namespace design that makes this composable
&lt;/h2&gt;

&lt;p&gt;The pattern that scales isn't "everyone shares one namespace." It's deliberate separation by actor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A system/shared actor&lt;/strong&gt; — for facts that many agents legitimately share (a ticket classification, a research finding, a computed result). Any agent scoped to that actor can read it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A per-user actor&lt;/strong&gt; (keyed by, say, the caller's JWT &lt;code&gt;sub&lt;/code&gt;) — for context that must never bleed across users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;System facts don't pollute per-user recall, and user facts don't leak across accounts, because they live under different namespaces and retrieval is prefix-scoped. That's the same reasoning AWS's &lt;a href="https://aws.amazon.com/blogs/machine-learning/organizing-agents-memory-at-scale-namespace-design-patterns-in-agentcore-memory/" rel="noopener noreferrer"&gt;namespace-design guidance&lt;/a&gt; gives: treat it like a key schema or an object-storage prefix layout, and design the access patterns up front. This experiment is the minimal proof of the mechanism underneath that design — write under one actor, read it back by namespace, and confirm a different actor sees nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;Shared memory across agents is real and it's clean: the specialist recovered the classifier's context by namespace with nothing passed in its prompt, and the isolation boundary returned a hard zero rather than a hopeful low score. That's a genuinely better primitive than prompt-stuffing — it's queryable, it's bounded, and the boundary can be backed by IAM.&lt;/p&gt;

&lt;p&gt;The practical finding is the input contract. Semantic sharing works when you write a conversation and query the strategy's real namespace; it silently returns nothing when you write a lone message or guess the namespace prefix. Both mistakes look exactly like a broken feature and neither is. Design for the contract — turn pairs in, correct namespace out — and one memory store becomes the shared context layer a multi-agent system actually needs.&lt;/p&gt;

&lt;p&gt;The thing I haven't fully solved: semantic extraction needs a real conversation to extract from. A single message in isolation — even if it's the exact determination the next agent needs — returns zero records and forces you to either feed it a fake exchange or fall back to prompt-stuffing. That's the honest limitation of the approach, and it's the whole reason this post exists.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of a series on Amazon Bedrock AgentCore. This post follows &lt;a href="https://artificialcuriositylabs.ai/posts/agentcore-memory-what-survives-the-session" rel="noopener noreferrer"&gt;AgentCore Memory: What an Agent Remembers When the Session Is Gone&lt;/a&gt; — that post covers single-agent memory recovery across sessions; this one extends it to multi-agent sharing with namespace isolation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>memory</category>
    </item>
    <item>
      <title>Beyond Semantic Retrieval: Metadata Filtering in Agent Memory</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:05:40 +0000</pubDate>
      <link>https://dev.to/amitrix/beyond-semantic-retrieval-metadata-filtering-in-agent-memory-50ak</link>
      <guid>https://dev.to/amitrix/beyond-semantic-retrieval-metadata-filtering-in-agent-memory-50ak</guid>
      <description>&lt;h2&gt;
  
  
  When semantic search isn't enough
&lt;/h2&gt;

&lt;p&gt;Semantic retrieval answers "what's related to this?" You give it a query, it returns records close in meaning. That's the right tool for "what has this customer told us about their architecture" — you want proximity, not exactness.&lt;/p&gt;

&lt;p&gt;It's the wrong tool for "the high-priority billing records for this customer" or "events from the last seven days." Those aren't proximity questions; they're predicates. Within a single namespace, a broad semantic search returns everything close in meaning and gives you no way to say &lt;em&gt;only these&lt;/em&gt;. That's what metadata filtering is for. AWS's framing is apt: namespaces isolate by primary entity (user, tenant), and &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-metadata.html" rel="noopener noreferrer"&gt;metadata filtering narrows within a namespace by attribute&lt;/a&gt; — priority, category, department, time range. It's the WHERE clause for agent memory.&lt;/p&gt;

&lt;p&gt;I set out to prove the WHERE clause works. It does — with a prerequisite that isn't optional and isn't loud about itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: records that differ only by metadata
&lt;/h2&gt;

&lt;p&gt;I wanted records that semantic search couldn't cleanly separate, so the metadata predicate would be doing the real work. Five support memories under one actor namespace, differing mostly in their tags:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Text&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;priority&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Disputed duplicate charge; refund issued&lt;/td&gt;
&lt;td&gt;billing&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;4200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How to read the monthly billing statement&lt;/td&gt;
&lt;td&gt;billing&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production API 500s during deploy; rolled back&lt;/td&gt;
&lt;td&gt;reliability&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature request: export usage as CSV&lt;/td&gt;
&lt;td&gt;product&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing overage of $1,300 flagged for review&lt;/td&gt;
&lt;td&gt;billing&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;1300&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rather than wait for extraction — the slow path where an LLM reads events and pulls records — I wrote the records &lt;strong&gt;directly&lt;/strong&gt; with &lt;code&gt;batch_create_memory_records&lt;/code&gt;. That API takes custom &lt;code&gt;content&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; a &lt;code&gt;metadata&lt;/code&gt; map with typed values (&lt;code&gt;stringValue&lt;/code&gt;, &lt;code&gt;numberValue&lt;/code&gt;, &lt;code&gt;stringListValue&lt;/code&gt;, &lt;code&gt;dateTimeValue&lt;/code&gt;). It's the deterministic path: no extraction lag, records I fully control.&lt;/p&gt;

&lt;p&gt;The write returned clean: &lt;strong&gt;5 records written, 0 failed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate: success that isn't
&lt;/h2&gt;

&lt;p&gt;Then I listed the records back and inspected what metadata actually survived:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stored metadata keys observed:
  ['x-amz-agentcore-memory-createdAt',
   'x-amz-agentcore-memory-recordType',
   'x-amz-agentcore-memory-updatedAt']
custom keys (category/priority/amount) present on records? False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, and &lt;code&gt;amount&lt;/code&gt; were &lt;strong&gt;gone&lt;/strong&gt;. The batch API accepted them, reported success, and dropped them on the floor. Only system-generated keys remained.&lt;/p&gt;

&lt;p&gt;And filtering on a custom key doesn't fail soft — it fails hard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[custom filter] category = billing:
  ERROR: ValidationException — Filter key 'category' is not a valid filter key
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not "zero results." A 400. The service refuses to filter on a key it doesn't know about.&lt;/p&gt;

&lt;p&gt;This is the prerequisite the tutorials gloss over. Metadata filtering is gated on the memory having those keys &lt;strong&gt;declared as indexed metadata&lt;/strong&gt;. &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-metadata.html" rel="noopener noreferrer"&gt;For an extraction strategy, that's the strategy's &lt;code&gt;memoryRecordSchema.metadataSchema&lt;/code&gt;&lt;/a&gt; — it tells the LLM which keys to populate. The AWS docs are explicit: "only keys defined in the strategy's metadataSchema are populated on extracted records — event metadata keys not in the schema are ignored." The same gate applies to direct batch writes: an undeclared key is not stored and not filterable. The memory I was writing to had no metadata schema and no indexed custom keys, so the custom tags had nowhere to live and nothing to filter against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The working path: system-indexed keys
&lt;/h2&gt;

&lt;p&gt;To confirm the &lt;em&gt;mechanism&lt;/em&gt; is sound and it was only the index declaration missing, I filtered on the keys that are indexed by default — the system-generated ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[system filter] recordType = BASE:              5 record(s)
[system filter] createdAt AFTER 2026-07-01:     5 record(s)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both work. An exact string match (&lt;code&gt;EQUALS_TO&lt;/code&gt; on &lt;code&gt;recordType&lt;/code&gt;) and a date-range predicate (&lt;code&gt;AFTER&lt;/code&gt; on &lt;code&gt;createdAt&lt;/code&gt;) both return correctly, no errors. So &lt;code&gt;metadataFilters&lt;/code&gt; is real: the operators fire, the date type works, and the compound call is accepted. The feature isn't broken — it's &lt;em&gt;conditional&lt;/em&gt;. Give it an indexed key and it does exactly what the WHERE-clause framing promises. Give it an undeclared key and it rejects the request.&lt;/p&gt;

&lt;p&gt;I also ran the same query through &lt;code&gt;retrieve_memory_records&lt;/code&gt; (semantic) with and without a metadata filter — &lt;code&gt;"customer billing problem"&lt;/code&gt; alone, then the same query plus &lt;code&gt;recordType = BASE&lt;/code&gt;. Both returned the set without error, confirming &lt;a href="https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-agentcore/client/list_memory_records.html" rel="noopener noreferrer"&gt;the metadata filter composes with semantic search in one call&lt;/a&gt;. (On five records the &lt;em&gt;counts&lt;/em&gt; don't diverge; the point being verified here is that semantic search accepts an indexed-key predicate as a pre-filter, which it does.)&lt;/p&gt;

&lt;h2&gt;
  
  
  One retrieval-shape footnote that cost real time
&lt;/h2&gt;

&lt;p&gt;A smaller trap worth recording: &lt;code&gt;list_memory_records&lt;/code&gt; with the &lt;code&gt;namespace&lt;/code&gt; parameter (prefix match) returned &lt;strong&gt;zero&lt;/strong&gt; for my batch-written records, while the same call with &lt;code&gt;namespacePath&lt;/code&gt; (hierarchical match) returned all five. The records were there the whole time; the prefix-vs-path distinction decided whether I saw them. This is the same class of mistake — query the wrong shape and a working feature looks empty. When batch-written records seem to vanish, try &lt;code&gt;namespacePath&lt;/code&gt; before concluding the write failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use each strategy
&lt;/h2&gt;

&lt;p&gt;The decision table for retrieval still holds — with the gate made explicit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Prerequisite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"What has this user said before?"&lt;/td&gt;
&lt;td&gt;SEMANTIC retrieval&lt;/td&gt;
&lt;td&gt;none beyond the strategy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"How does this user prefer to be addressed?"&lt;/td&gt;
&lt;td&gt;USER_PREFERENCE&lt;/td&gt;
&lt;td&gt;none beyond the strategy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Only high-priority billing records this week"&lt;/td&gt;
&lt;td&gt;metadata filter&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;custom keys declared as indexed metadata&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Filter by when it happened / record type"&lt;/td&gt;
&lt;td&gt;metadata filter on system keys&lt;/td&gt;
&lt;td&gt;none — indexed by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"What did the agent learn on similar tickets?"&lt;/td&gt;
&lt;td&gt;EPISODIC&lt;/td&gt;
&lt;td&gt;episode must be closed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;Metadata filtering on AgentCore Memory is a genuine capability, not a stub: string equality and date-range operators filter correctly, and the filter composes with semantic search in a single call. That's the WHERE clause the pitch promises.&lt;/p&gt;

&lt;p&gt;But the capability is gated, and the gate is quiet. Custom metadata keys must be declared as indexed metadata on the memory — &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-metadata.html" rel="noopener noreferrer"&gt;via a strategy's &lt;code&gt;metadataSchema&lt;/code&gt; for extracted records&lt;/a&gt;, or the equivalent index configuration for direct writes — &lt;em&gt;before&lt;/em&gt; they'll be stored or filtered. Skip that step and &lt;code&gt;batch_create_memory_records&lt;/code&gt; still returns success while silently dropping your tags, and the first filter call fails with &lt;code&gt;ValidationException: not a valid filter key&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The honest takeaway for anyone reaching for this: design your indexed metadata keys up front, the same way you'd design a database index, because an un-indexed tag on AgentCore Memory isn't a slow query — it's no query at all. The mechanism works the moment the index exists; it does nothing, loudly and then silently, until it does.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-metadata.html" rel="noopener noreferrer"&gt;Structured metadata for long-term memories&lt;/a&gt;" — AgentCore docs on metadata schema, per-strategy configuration, value types, and indexed key declaration&lt;/li&gt;
&lt;li&gt;"&lt;a href="https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-agentcore/client/batch_create_memory_records.html" rel="noopener noreferrer"&gt;batch_create_memory_records&lt;/a&gt;" — boto3 reference for direct record creation with metadata maps and typed values&lt;/li&gt;
&lt;li&gt;"&lt;a href="https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-agentcore/client/list_memory_records.html" rel="noopener noreferrer"&gt;list_memory_records&lt;/a&gt; / &lt;a href="https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-agentcore/client/retrieve_memory_records.html" rel="noopener noreferrer"&gt;retrieve_memory_records&lt;/a&gt;" — boto3 reference for retrieval with metadataFilters, operators, and namespace scoping&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>memory</category>
    </item>
    <item>
      <title>Bedrock Managed Knowledge Bases as a Native Gateway Tool</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:05:05 +0000</pubDate>
      <link>https://dev.to/amitrix/bedrock-managed-knowledge-bases-as-a-native-gateway-tool-4fhn</link>
      <guid>https://dev.to/amitrix/bedrock-managed-knowledge-bases-as-a-native-gateway-tool-4fhn</guid>
      <description>&lt;h2&gt;
  
  
  The Lambda you no longer have to write
&lt;/h2&gt;

&lt;p&gt;Earlier in this series, giving an agent access to a knowledge base meant writing a Lambda that wraps &lt;code&gt;bedrock:Retrieve&lt;/code&gt;, registering that Lambda as an AgentCore Gateway target, and letting the agent call it as an MCP tool. It worked. The Lambda was pure glue — it existed only to translate an MCP tool call into a Bedrock retrieval API call and translate the response back. You owned its code, its IAM role, its deployment, its cold starts, and its bugs, for a function that did nothing but forward a query.&lt;/p&gt;

&lt;p&gt;AgentCore now has a &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-connector-managed-kb.html" rel="noopener noreferrer"&gt;native &lt;code&gt;bedrock-knowledge-bases&lt;/code&gt; connector&lt;/a&gt;. You attach a Bedrock Knowledge Base directly to a Gateway as a managed-connector target, and the Gateway exposes the retrieval tools over MCP with no Lambda in the path. I replaced the wrapper with the connector, probed it over MCP, and found the one constraint that decides whether you can use it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the connector exposes
&lt;/h2&gt;

&lt;p&gt;I attached a Knowledge Base to an existing Gateway as a &lt;code&gt;bedrock-knowledge-bases&lt;/code&gt; connector target, then ran a SigV4-signed MCP &lt;code&gt;tools/list&lt;/code&gt; against the Gateway. Two tools came back, auto-discovered, with no schema I had to write:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Retrieve&lt;/code&gt;&lt;/strong&gt; — "Search the managed knowledge base for relevant source passages." A single hybrid search returning the most relevant passages (1–100, defaulting to 10). This is the direct analogue of what my old Lambda wrapped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AgenticRetrieveStream&lt;/code&gt;&lt;/strong&gt; — the interesting one. Per &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-gateway-target.html" rel="noopener noreferrer"&gt;AWS's docs it "plans a retrieval strategy, runs multiple retrieval steps across your managed knowledge bases, optionally expands to full documents, and streams back both the supporting results and a synthesized, citation-backed answer."&lt;/a&gt; It takes a conversation (query + history), not just a string, and does multi-step retrieval with planning rather than a single lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second tool is a real capability jump. The Lambda wrapper only ever did single-shot retrieval, because writing a planning-and-multi-step-retrieval loop into a glue Lambda would have been a project. Here it arrives as a discovered tool the agent can call.&lt;/p&gt;

&lt;p&gt;The Gateway handles schema management, endpoint resolution, and service authentication itself — the docs are explicit that &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-connector-managed-kb.html" rel="noopener noreferrer"&gt;the knowledge base tools are auto-discovered through &lt;code&gt;tools/list&lt;/code&gt; and available to the agent without additional configuration.&lt;/a&gt; My probe confirmed it: both tools present, correct input schemas, no wrapper code anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding: the Lambda actually goes away
&lt;/h2&gt;

&lt;p&gt;The point wasn't "a KB tool works" — the wrapper already proved that. It was whether the managed connector genuinely &lt;em&gt;removes&lt;/em&gt; the custom infrastructure or just hides it.&lt;/p&gt;

&lt;p&gt;Comparing the two targets on the same Gateway directly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Old path&lt;/th&gt;
&lt;th&gt;New path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target kind&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lambda&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;managed_connector&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;a Lambda function&lt;/td&gt;
&lt;td&gt;&lt;code&gt;connectorId: bedrock-knowledge-bases&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom code&lt;/td&gt;
&lt;td&gt;a retrieval Lambda you own&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;one (whatever you coded)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Retrieve&lt;/code&gt; + &lt;code&gt;AgenticRetrieveStream&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The old target pointed at a Lambda ARN. The new target points at a connector ID. Lambda removed from new path: verified true. That is the whole value proposition: the retrieval integration moves from &lt;em&gt;code you operate&lt;/em&gt; to &lt;em&gt;configuration the platform operates&lt;/em&gt;, and you gain the agentic tool you wouldn't have built yourself. Same Gateway, same MCP surface, same auth model — one fewer thing to own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The caveat that decides everything: Managed only
&lt;/h2&gt;

&lt;p&gt;Here is the constraint that isn't obvious until it stops you. The connector is supported &lt;strong&gt;only for Amazon Bedrock &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html" rel="noopener noreferrer"&gt;Managed Knowledge Bases&lt;/a&gt;&lt;/strong&gt; — not customer-managed ones. &lt;/p&gt;

&lt;p&gt;Bedrock has two kinds of KB. A &lt;strong&gt;customer-managed&lt;/strong&gt; KB is the classic one: you set up and operate the vector store (OpenSearch Serverless, Aurora, Neptune) and control ingestion, parsing, and indexing. A &lt;strong&gt;managed&lt;/strong&gt; KB hands all of that to Bedrock — no vector store to provision, no retrieval infrastructure to run.&lt;/p&gt;

&lt;p&gt;The connector only talks to the managed kind. So before I could attach anything, I had to &lt;strong&gt;create a managed KB&lt;/strong&gt; and ingest the corpus into it — I couldn't point the connector at a pre-existing customer-managed KB. That's a real migration cost the feature grid doesn't foreground: "native Gateway integration" quietly assumes you're already on (or willing to move to) managed KBs. If your organization standardized on customer-managed KBs for control over the vector store, adopting this connector isn't a config change — it's a re-platforming of the knowledge base itself.&lt;/p&gt;

&lt;p&gt;That's the honest trade. You delete a Lambda, but the price of entry is a Managed KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance stays where you'd want it
&lt;/h2&gt;

&lt;p&gt;The reason this fits the "governed hub" pattern rather than being a pure convenience feature: the governance surface doesn't degrade when the Lambda disappears.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Least-privilege IAM survives.&lt;/strong&gt; The Gateway service role needs only &lt;code&gt;bedrock:Retrieve&lt;/code&gt; (and the agentic action) on the specific KB ARN — the same narrow grant the Lambda's role needed. The &lt;code&gt;knowledgeBaseId&lt;/code&gt; is bound in the target's parameter values, &lt;strong&gt;not exposed to the model&lt;/strong&gt;, so the agent can't redirect the query at a different KB by talking its way there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability survives.&lt;/strong&gt; I enabled Gateway vended-log delivery for the connector and captured lifecycle events for managed-KB calls in CloudWatch, plus Gateway metrics — the same audit trail the rest of the Gateway tools get. A retrieval tool with no audit trail is a liability; this one keeps the trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The MCP auth boundary is unchanged.&lt;/strong&gt; Callers still reach the tool through the Gateway's existing auth (SigV4, &lt;code&gt;aws_service="bedrock-agentcore"&lt;/code&gt;), so whatever inbound authorization and Cedar policy you've layered on the Gateway still applies. The connector didn't punch a hole around your controls; it slotted into them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the right shape: less infrastructure to own, no loss of the controls that make a retrieval tool safe to expose to an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;bedrock-knowledge-bases&lt;/code&gt; connector is a clean win with a clear boundary. If you're on Managed Knowledge Bases, it deletes the retrieval Lambda outright, hands you an agentic multi-step retrieval tool you wouldn't have built, and keeps IAM, observability, and MCP auth exactly where they were. If you're on customer-managed KBs, the connector isn't for you yet — adopting it means migrating the knowledge base, not just re-pointing a target.&lt;/p&gt;

&lt;p&gt;The broader pattern is the one this series keeps hitting: AgentCore is steadily replacing &lt;em&gt;glue you operate&lt;/em&gt; with &lt;em&gt;connectors the platform operates&lt;/em&gt; — first for APIs (the Gateway itself), then web search, now retrieval. Each one removes a Lambda and keeps the governance. The catch is always in the fine print of what the managed version requires — here, that your KB is the managed kind.&lt;/p&gt;

&lt;p&gt;What I haven't tested yet is per-user access control at the document level within the KB. The managed connector works with IAM scoping (the Gateway role can only retrieve from this KB), but per-user authorization inside the KB — whether Cedar at the Gateway is enough, or whether you need managed KB document-level controls — remains an open question.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>gateway</category>
    </item>
    <item>
      <title>Harness vs. Runtime: When to Graduate</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:04:29 +0000</pubDate>
      <link>https://dev.to/amitrix/harness-vs-runtime-when-to-graduate-465b</link>
      <guid>https://dev.to/amitrix/harness-vs-runtime-when-to-graduate-465b</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;I ran the same support-triage task on both AgentCore run modes. Harness stood up a working agent in 20.4 seconds against 398 lines of Runtime Python, and it passed the IAM-gated and no-tool scenarios cleanly. It could not enforce per-user Cedar policies on the refund tool.&lt;/p&gt;

&lt;p&gt;Both modes end up on the same compute. CloudTrail records harness operations under &lt;code&gt;AWS::BedrockAgentCore::Runtime&lt;/code&gt;. The difference is who owns orchestration — you write the code (Runtime) or AgentCore handles it (Harness).&lt;/p&gt;

&lt;p&gt;The documented graduation triggers are hooks, custom loops, and bidirectional streaming. The barrier I hit first was none of those — it was per-user outbound identity. What follows is what worked, what broke, and where the hard boundary is.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Harness gives you without writing agent code
&lt;/h2&gt;

&lt;p&gt;For nearly every AgentCore capability, Harness delivers it in configuration: Memory (all four strategies), Gateway tool wiring, Browser and Code Interpreter as built-in tools, outbound Identity, automatic Observability, model switching mid-session, versioning, and VPC networking. All available without customer code.&lt;/p&gt;

&lt;p&gt;Runtime offers the same capabilities — with "you write the code required" for each. The session manager, the memory client, the tool orchestration, the span instrumentation — all available, all require code.&lt;/p&gt;

&lt;p&gt;The three things the documented feature grid says Harness cannot do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗  Hooks (AfterToolCallEvent, BeforeToolCallEvent)
✗  Non-agent-loop patterns (graph, workflow, tree-of-thought)
✗  Bidirectional streaming
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are the published graduation triggers. A fourth one — per-user outbound identity — isn't documented; I found it by running the config.&lt;/p&gt;




&lt;h2&gt;
  
  
  H1: Two API calls, no entrypoint code
&lt;/h2&gt;

&lt;p&gt;The Harness create-and-invoke path is exactly as small as advertised. The entire orchestration for a triage agent — model, system prompt, one Gateway tool, memory — is about 25 lines passed to &lt;code&gt;create_harness()&lt;/code&gt;. The equivalent Runtime agent is &lt;strong&gt;398 lines&lt;/strong&gt;. Harness is roughly 6% of the Runtime file.&lt;/p&gt;

&lt;p&gt;Two measurements stand out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time to READY: 20.4 seconds.&lt;/strong&gt; From &lt;code&gt;create_harness()&lt;/code&gt; to a harness that answers, a little over 20 seconds. The equivalent Runtime path builds a container — around 180 seconds before the first invoke. Harness is ~9× faster to first response because there's no image to build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The invoke is not SSE.&lt;/strong&gt; &lt;code&gt;boto3&lt;/code&gt;'s &lt;code&gt;invoke_harness&lt;/code&gt; returns a proper event stream. A JWT-protected harness requires raw HTTPS &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;/harnesses/invoke?harnessArn=...&lt;/code&gt; and parsing an &lt;code&gt;application/vnd.amazon.eventstream&lt;/code&gt; body with &lt;code&gt;botocore.eventstream.EventStreamBuffer&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two scenarios passed cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The no-tool password reset:&lt;/strong&gt; correct steps, clean end of turn, no tool call. Passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The web-search region question:&lt;/strong&gt; the harness called the WebSearch gateway tool twice and cited live results. Passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both authorize outbound with the harness's own IAM role (SigV4). There is no per-user identity involved. This is Harness at its best: a governed tool, wired by ARN, no code.&lt;/p&gt;

&lt;p&gt;Then I pointed it at the Cedar-gated refund tool. It stopped there.&lt;/p&gt;




&lt;h2&gt;
  
  
  H2: The wall is identity, not hooks
&lt;/h2&gt;

&lt;p&gt;The support agent has a refund tool behind a &lt;code&gt;CUSTOM_JWT&lt;/code&gt; gateway with a Cedar policy engine in enforce mode. The policies are per-user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="n"&gt;permit&lt;/span&gt;  &lt;span class="n"&gt;alice&lt;/span&gt;  &lt;span class="n"&gt;process_refund&lt;/span&gt;  &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;      &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;
&lt;span class="n"&gt;forbid&lt;/span&gt;  &lt;span class="n"&gt;bob&lt;/span&gt;    &lt;span class="n"&gt;process_refund&lt;/span&gt;                          &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="n"&gt;permit&lt;/span&gt;  &lt;span class="n"&gt;carol&lt;/span&gt;  &lt;span class="n"&gt;process_refund&lt;/span&gt;  &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;     &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;enterprise&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cedar evaluates on the &lt;strong&gt;principal&lt;/strong&gt; — explicitly the invoking user's identity from the JWT &lt;code&gt;sub&lt;/code&gt; claim.&lt;/p&gt;

&lt;p&gt;The Runtime path is trivial: the agent forwards the caller's own inbound JWT straight through to the gateway. alice's refund is permitted, bob's is forbidden, on the first call, for any user, with no extra machinery.&lt;/p&gt;

&lt;p&gt;A Harness has no code, so it can't forward anything. It authorizes outbound calls through its configured &lt;code&gt;outboundAuth&lt;/code&gt;, which offers exactly three modes: &lt;code&gt;awsIam&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt;, and &lt;code&gt;oauth&lt;/code&gt;. I tried the two that could carry identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;awsIam&lt;/code&gt; outbound.&lt;/strong&gt; The harness signs the gateway call with its own execution role. Cedar sees the role, not a user — there is no &lt;code&gt;OAuthUser&lt;/code&gt; principal, so no per-user policy matches. The harness simply is not alice. Correct behavior, wrong outcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;oauth&lt;/code&gt; outbound.&lt;/strong&gt; This is the intended path: the harness mints the invoking user's token from AgentCore's Identity token vault. It's also where the real work was. Making it go took five distinct fixes, each a genuine requirement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The response is a binary event stream&lt;/strong&gt;, not SSE — parse with &lt;code&gt;EventStreamBuffer&lt;/code&gt;, and headers are plain strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The execution role needs vault access&lt;/strong&gt; — &lt;code&gt;bedrock-agentcore:GetResourceOauth2Token&lt;/code&gt; plus &lt;code&gt;secretsmanager:GetSecretValue&lt;/code&gt;. Without it the tool fails with &lt;code&gt;AccessDeniedException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The grant type defaults to the wrong flow.&lt;/strong&gt; &lt;code&gt;oauth&lt;/code&gt; outbound defaults to &lt;code&gt;CLIENT_CREDENTIALS&lt;/code&gt; (machine-to-machine). The provider is user-federation (3LO) — it's either M2M &lt;em&gt;or&lt;/em&gt; 3LO, never both. The request fails with &lt;code&gt;Error parsing ClientCredentials response&lt;/code&gt;. Fix: set &lt;code&gt;grantType=AUTHORIZATION_CODE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization-code outbound needs a return URL&lt;/strong&gt; — &lt;code&gt;defaultReturnUrl&lt;/code&gt;, registered on the workload identity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The token is vaulted under the wrong identity.&lt;/strong&gt; This is the blocker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The vault is keyed on &lt;code&gt;(workload_identity, user_id)&lt;/code&gt;. I had pre-consented users and stored their tokens under a standalone workload identity. But a Harness calls the vault under &lt;strong&gt;its own&lt;/strong&gt; workload identity — minted when the harness is created. It looks up alice under &lt;em&gt;that&lt;/em&gt; identity, finds nothing, and falls back to starting a fresh browser-consent flow — which a headless server invocation cannot complete. Every case failed with &lt;code&gt;You must provide a ResourceOauth2ReturnUrl to proceed with this flow&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why re-vaulting doesn't fix it
&lt;/h2&gt;

&lt;p&gt;You could fix issue 5 mechanically: create the harness, read back its generated workload identity, register return URLs on it, run the 3LO consent loop keyed to &lt;em&gt;that&lt;/em&gt; identity, then invoke. It would work. It also carries three operational costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The harness's workload identity &lt;strong&gt;doesn't exist until the harness does&lt;/strong&gt;, so you can't pre-consent users ahead of deploying.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;changes on every rebuild&lt;/strong&gt; — a new suffix each time — so re-consenting all users becomes part of your deploy.&lt;/li&gt;
&lt;li&gt;3LO requires &lt;strong&gt;per-user browser consent&lt;/strong&gt; the first time regardless, which is inherent to the flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one clean escape would be token passthrough — let the harness forward the inbound user JWT unchanged, the way Runtime code does. The gateway target model has a passthrough concept, but it is not exposed on the harness &lt;code&gt;outboundAuth&lt;/code&gt; surface. Token exchange (OBO, RFC 8693) is the other theoretical route, and it's dead because Cognito doesn't implement it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest finding: per-user Cedar through a Harness is architecturally possible and operationally impractical.&lt;/strong&gt; Not because policy enforcement fails — Cedar at the gateway is identical for both paths — but because the code-less path binds outbound identity to an ephemeral, per-deploy workload identity and a consent step it can't drive headless.&lt;/p&gt;




&lt;h2&gt;
  
  
  The decision rule
&lt;/h2&gt;

&lt;p&gt;Use Harness until you hit one of these. The first three are documented; the fourth is the one this run added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger 1 — Hooks.&lt;/strong&gt; You need to intercept what the model sees after a tool call. The &lt;code&gt;AfterToolCallEvent&lt;/code&gt; pattern — sanitizing an authorization failure, normalizing output, grounding the model on a deterministic fact — is Runtime-only. If your tool returns something the model must &lt;em&gt;interpret&lt;/em&gt; rather than a clean fact, you need the hook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger 2 — Custom loop logic.&lt;/strong&gt; Tree-of-thought, graph routing, supervisor/worker, A2A handoffs — any pattern where the orchestration loop itself carries business logic. Harness runs one managed loop; you can't fork it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger 3 — Model-level controls.&lt;/strong&gt; Prompt-cache keys, extended-thinking budgets, per-token routing between a cheap model and an expensive one. Harness &lt;code&gt;bedrockModelConfig&lt;/code&gt; exposes &lt;code&gt;modelId&lt;/code&gt;, &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;maxTokens&lt;/code&gt;, &lt;code&gt;topP&lt;/code&gt;. Anything finer belongs in code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger 4 — Per-user outbound identity.&lt;/strong&gt; If a tool enforces authorization on the end user's identity — per-user Cedar, an OAuth API scoped to the user, anything that needs the caller's own token downstream — the Harness path forces you through vault tokens keyed to an ephemeral workload identity plus a consent flow it can't run headless. Runtime forwards the caller's JWT in one line. This is the trigger the feature grid doesn't name, and for identity-aware agents it's the one you hit first.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for the support agent
&lt;/h2&gt;

&lt;p&gt;The support agent needs Runtime for two independent reasons, each tied to specific evidence.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;Trigger 1&lt;/strong&gt;. The &lt;code&gt;AfterToolCallEvent&lt;/code&gt; hook intercepts the policy engine's raw output and replaces it with a clean &lt;code&gt;REFUND APPROVED / BLOCKED:&lt;/code&gt; fact before the model writes the customer reply. Without it, the model infers from raw authorization data — sometimes correct, sometimes fabrication. Cedar at the gateway works in Harness; the deterministic post-processing of Cedar's decision does not.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;Trigger 4&lt;/strong&gt;. The refund tool is per-user Cedar. Runtime forwards each caller's JWT straight to the gateway, so the standard-tier user is permitted and the forbidden user is blocked on the first call with no vault, no pre-consent, no workload-identity juggling. The Harness equivalent is the five-layer, per-deploy, consent-bound path above.&lt;/p&gt;

&lt;p&gt;Both are boundaries, not defects. Harness handled the IAM-gated web search and the no-tool tickets with 25 lines of config and a 20-second cold start — less work than the Runtime equivalent. The identity-bound, hook-dependent refund path is what needs code.&lt;/p&gt;

&lt;p&gt;That is the graduation the two-model system is designed for: start on Harness, cross to Runtime when a trigger fires, and pay the code cost only for the parts that require it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results summary
&lt;/h2&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;Harness&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Config lines to working agent&lt;/td&gt;
&lt;td&gt;~25 lines&lt;/td&gt;
&lt;td&gt;398 lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time from create to first invoke&lt;/td&gt;
&lt;td&gt;20.4 s&lt;/td&gt;
&lt;td&gt;~180 s (container build)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password reset (no tool)&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EU region web search (IAM gateway)&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-user Cedar refund (JWT gateway)&lt;/td&gt;
&lt;td&gt;blocked — outbound identity&lt;/td&gt;
&lt;td&gt;pass (JWT forwarded)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory wiring&lt;/td&gt;
&lt;td&gt;one config block&lt;/td&gt;
&lt;td&gt;~40 lines of code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;ADOT + instrumentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is consistent: for governed tools that authorize on the agent's own identity (IAM) or on nothing, Harness is less work and faster. For tools that authorize on the &lt;em&gt;user's&lt;/em&gt; identity, or that need deterministic post-processing of the tool result, Runtime is not optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;Start on Harness. For governed tools that authorize on the agent's identity or on nothing, 25 lines of config and a 20-second cold start beat the Runtime equivalent. Watch for the identity boundary. The first time a tool needs to act as the user rather than as the agent, move to Runtime. That is where the config-only model ends and code begins.&lt;/p&gt;

&lt;p&gt;I still haven't fully worked through what happens with token passthrough if it &lt;em&gt;were&lt;/em&gt; exposed on the Harness &lt;code&gt;outboundAuth&lt;/code&gt; surface — whether the per-user vault keying would become irrelevant if the harness could forward the inbound token verbatim. That's the operational escape hatch nobody's tried yet.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>harness</category>
    </item>
    <item>
      <title>Build a Regression Suite for an Agent</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:03:54 +0000</pubDate>
      <link>https://dev.to/amitrix/build-a-regression-suite-for-an-agent-3ld4</link>
      <guid>https://dev.to/amitrix/build-a-regression-suite-for-an-agent-3ld4</guid>
      <description>&lt;p&gt;A support agent I was testing produced a confident, empathetic reply telling a customer it had authority to override a refund policy. A human reviewer would have marked it good. Two quality scores agreed: correct and fully helpful.&lt;/p&gt;

&lt;p&gt;The agent never consulted the policy engine. The authority was fabricated. The refund was blocked by design, and the agent wrote reassuring prose anyway.&lt;/p&gt;

&lt;p&gt;That gap — a response that reads well, scores well on obvious metrics, and is wrong in ways only a behavioral check can catch — is the reason to build an evaluation suite. Not "does the output sound good," which is what humans and output-quality scores measure, but "did the agent do the right things, and was what it said true."&lt;/p&gt;

&lt;p&gt;I built a suite against a live, policy-governed support agent. This is what four evaluation dimensions caught, and three classes of bugs nothing else did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four things worth measuring
&lt;/h2&gt;

&lt;p&gt;Output quality — is the answer fluent and helpful — is the baseline. But on its own it rated a fabricated refund as a success. A useful suite measures four distinct things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal success&lt;/strong&gt; — did the agent accomplish the task behaviorally? Did it call the right tool, respect the policy, route to a human when it should. Phrased as assertions about observable customer outcomes, not tone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helpfulness&lt;/strong&gt; — was the response useful and well-formed. The conventional metric.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correctness&lt;/strong&gt; — was what the agent said factually true, checked against ground truth. Fluent and factually wrong is its own failure mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trajectory&lt;/strong&gt; — did the actual sequence of tool calls match what the task required? This reads the execution trace, not the text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two are easy to over-trust. The lessons below all come from the last two, and from goal-success phrased behaviorally.&lt;/p&gt;

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

&lt;p&gt;Eight scenarios, one support agent for a fictional analytics company. A refund tool gated by a real policy engine. Three users with different rules: one may approve refunds under $500, one is forbidden from refunds entirely, one may approve up to $2,000. The policy engine ran live on every tool call; the allows and denies were real, not mocks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple ticket: password reset, no tools needed&lt;/li&gt;
&lt;li&gt;Complex ticket: repeat billing, needs escalation&lt;/li&gt;
&lt;li&gt;Refund allowed by policy ($45, under $500 limit)&lt;/li&gt;
&lt;li&gt;Refund denied by policy ($750, over $500 limit)&lt;/li&gt;
&lt;li&gt;Unconditional forbid (any amount, any user)&lt;/li&gt;
&lt;li&gt;Enterprise-tier refund allowed ($1,500, under $2,000 limit)&lt;/li&gt;
&lt;li&gt;Search-grounded question: EU region availability&lt;/li&gt;
&lt;li&gt;Over-limit refund blocked, routed to human ($5,000 over $2,000 limit)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lesson 1: outcome metrics can score a wrong agent perfectly
&lt;/h2&gt;

&lt;p&gt;The fabricated-authority response was warm, articulate, actionable. Output-quality scoring loved it. The goal-success assertion — "the response tells the customer the refund is blocked by policy" — scored it zero, because the agent never mentioned policy, never said no, and offered to process refunds it had no authority to process.&lt;/p&gt;

&lt;p&gt;The root cause was architectural: the refund check ran &lt;em&gt;after&lt;/em&gt; the agent had already written its reply, so the policy decision never reached the model. The agent was composing support prose with no idea what the policy would decide.&lt;/p&gt;

&lt;p&gt;A human reviewer would not have caught it — the reply read great. Only an assertion about &lt;em&gt;what the agent should behaviorally do&lt;/em&gt; separated "sounded good" from "did the right thing."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; outcome-quality metrics can score a fully wrong agent perfectly. The behavioral goal-success assertion is what catches policy failures in the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: the trajectory check is a probe for observability gaps
&lt;/h2&gt;

&lt;p&gt;After moving the policy check earlier so the decision reached the model, goal success went green on those scenarios. The trajectory check came back empty.&lt;/p&gt;

&lt;p&gt;Both were true: the refund tool ran and returned the right answer, and the trajectory evaluator could not see it. The reason: the refund was a plain Python function, not an instrumented tool, so it did not emit execution spans the trajectory evaluator reads.&lt;/p&gt;

&lt;p&gt;The code worked. The observability layer was blind. Promoting the function to an instrumented tool made the same call appear in traces. Then trajectory went green.&lt;/p&gt;

&lt;p&gt;That is the sharp, non-obvious value of trajectory scoring: an empty trajectory on a scenario where you &lt;em&gt;know&lt;/em&gt; the tool ran tells you that part of your agent is invisible to tracing, and therefore invisible to debugging, audit, and production monitoring too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; trajectory scoring is a test of your observability as much as your agent. A tool that runs invisibly to the evaluator is invisible to everything else too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: a fluent answer can still be factually wrong
&lt;/h2&gt;

&lt;p&gt;One scenario asked about regional availability. The agent correctly called web search — the trajectory confirmed it. It then wrote a clean, confident answer listing a UK region as meeting EU data-residency requirements.&lt;/p&gt;

&lt;p&gt;Goal success passed: the assertions checked that the agent used search, addressed the compliance question, and cited results — all true. The correctness check, which only judges factual accuracy, flagged it: post-Brexit, a UK region does not satisfy EU data residency. Meaningful factual error, inside a response that followed the right procedure.&lt;/p&gt;

&lt;p&gt;This is why goal success and correctness are both in the suite and neither replaces the other. Goal success tells you the agent did the right &lt;em&gt;things&lt;/em&gt;; correctness tells you whether what it &lt;em&gt;said&lt;/em&gt; was true. A "did the right things, said something false" result is a real failure mode. You only see it if you measure both.&lt;/p&gt;

&lt;p&gt;The fix that lasts: turn the caught error into a behavioral assertion, so the next run fails if the agent repeats the claim. The hallucination a judge caught once becomes a deterministic regression test forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The property that makes it a regression suite
&lt;/h2&gt;

&lt;p&gt;Each lesson, once caught, became a locked scenario in a fixed dataset. That is the mechanism that turns a pile of evaluations into a &lt;em&gt;regression suite&lt;/em&gt;: a failure found today becomes a permanent test on every future deploy. Nobody has to remember what went wrong last week — the scenario remembers it. Reintroduce the bug and the scenario goes red.&lt;/p&gt;

&lt;p&gt;Two things make the locked dataset durable rather than brittle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assert on observable outcomes, not internal architecture.&lt;/strong&gt; An assertion like "the agent classifies this as complex tier" breaks the moment you change the architecture. "The response confirms the refund was approved" tests the business requirement and survives any rewrite underneath. When I first wrote assertions against the agent's internal steps, they went stale immediately. Rewriting them as customer-observable outcomes is what made the suite stable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the same evaluators in production.&lt;/strong&gt; The suite's value multiplies when the exact evaluators used in development also sample live traffic. A policy regression that drops goal success from green to red in the test dataset shows the identical drop in production sampling. Alarm on it directly with no separate pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the suite caught
&lt;/h2&gt;

&lt;p&gt;On the first run, all eight scenarios fired. The suite caught:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An ordering bug where the policy decision never reached the model (caught by goal success)&lt;/li&gt;
&lt;li&gt;Instrumentation gaps making refund calls invisible to tracing (caught by trajectory)&lt;/li&gt;
&lt;li&gt;A policy engine that was not attached to the gateway (caught by goal success reverting to zero)&lt;/li&gt;
&lt;li&gt;A factual error about Brexit and UK region classification (caught by correctness)&lt;/li&gt;
&lt;li&gt;Missing acknowledgments of billing patterns (caught by goal success with tight assertions)&lt;/li&gt;
&lt;li&gt;A missing runtime configuration for policy gateway URLs (caught by trajectory and goal success converging on failure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The single line of signal that connected all of them: the evaluators ran the same way in development and production, and they all measured behaviors that mattered, not just prose quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;The reason to evaluate an agent like software — not like a demo — is that the failures that matter are invisible to the checks you reach for first. A human reviewer and an output-quality score both passed a response that fabricated authority and ignored a policy. Only a behavioral goal-success assertion caught it. Only a trajectory check caught a tool that ran but could not be traced. Only a correctness check caught a fluent, procedurally correct, factually false answer.&lt;/p&gt;

&lt;p&gt;So the suite worth building measures four things and leans on the three past the obvious one: did it &lt;em&gt;do&lt;/em&gt; the right things behaviorally, was what it &lt;em&gt;said&lt;/em&gt; true, and did the tool calls actually get traced. Lock every failure into a fixed dataset, phrase the assertions as customer-observable outcomes, and run the same evaluators against production. That is the distance between "the agent looked fine in the demo" and "the agent is still doing the right thing on the ten-thousandth ticket."&lt;/p&gt;




&lt;h2&gt;
  
  
  What's still open
&lt;/h2&gt;

&lt;p&gt;I proved this on one domain with eight scenarios, which is enough to find the pattern but not enough to say it scales. The real unknown is running the suite on an agent that matters — one with enough agent-driven branching that the locked dataset starts to feel like a bottleneck instead of a check. Eight scenarios caught real bugs. Eight hundred? That is where the cost of maintaining a fixed dataset starts to matter against the value of what it catches. I have not hit that edge yet.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>evaluations</category>
    </item>
    <item>
      <title>AgentCore Browser: A Managed, Isolated Browser an Agent Can Drive</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:03:18 +0000</pubDate>
      <link>https://dev.to/amitrix/agentcore-browser-a-managed-isolated-browser-an-agent-can-drive-66h</link>
      <guid>https://dev.to/amitrix/agentcore-browser-a-managed-isolated-browser-an-agent-can-drive-66h</guid>
      <description>&lt;p&gt;Some work only exists on a screen. A carrier portal that won't show a rate until you log in. A price that isn't in the HTML until you scroll. A vendor dashboard that exports data only after three nested clicks. None of it has an API, and that's exactly why it's still manual. &lt;a href="https://www.mckinsey.com/capabilities/quantumblack/our-insights/generative-ai-and-the-future-of-work" rel="noopener noreferrer"&gt;Industry surveys put browser-bound, portal-heavy processes as the single biggest place agent pilots stall&lt;/a&gt; — reported at 79% of organizations adopting agents but only 11% running them in production, with portals cited as a named blocker.&lt;/p&gt;

&lt;p&gt;The reflex, when you hit one of these, is to run Playwright on a box. That works until you ask the operational questions: where does the browser run, who else can see its session, what happens when the agent logs into something as a user, and how do you audit what it did. Those are the questions AgentCore Browser answers. It is a &lt;strong&gt;managed, per-session, isolated Chromium&lt;/strong&gt; that an agent drives — and the "managed and isolated" part is the whole point, not an implementation detail.&lt;/p&gt;

&lt;p&gt;This is the browser organ from the AgentCore map. Reach for a browser only after ruling out a native API and an existing connector. This post covers what the managed primitive actually gives you, and where it stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is: a browser as a session, not a process
&lt;/h2&gt;

&lt;p&gt;The mental shift is the same one Runtime asks for. You don't run a browser; you &lt;em&gt;start a session&lt;/em&gt;. Each session is its own containerized (Firecracker) environment, isolated from your system and from every other session, ephemeral, and terminated on a TTL or an explicit stop. You get a CDP (Chrome DevTools Protocol) endpoint and drive it with Playwright — the same Playwright API you'd use locally, pointed at a browser AWS operates instead of one you manage.&lt;/p&gt;

&lt;p&gt;I drove it directly to confirm the basic loop. One session, against a scraping-friendly sandbox site: start the session, connect Playwright over the CDP WebSocket, navigate, scroll, read the DOM, close.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;managed session started: session_id=01KXP11N...
navigate -&amp;gt; HTTP 200  title='All products | Books to Scrape - Sandbox'
extracted: 20 product listings, 20 price tokens
browser closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing about that is surprising once you hold "it's a session" in your head. The session id is the handle; the CDP endpoint is how you drive it; the isolation and the teardown are the platform's job, not yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you reach it: two paths
&lt;/h2&gt;

&lt;p&gt;There are two ways to call the managed browser, and picking the right one is most of the practical decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The local MCP path — interactive, zero infrastructure.&lt;/strong&gt; AWS ships an MCP server (&lt;code&gt;awslabs.amazon-bedrock-agentcore-mcp-server&lt;/code&gt;) that, scoped to the browser primitive, exposes the full ~21-tool Playwright-style surface — &lt;code&gt;browser_navigate&lt;/code&gt;, &lt;code&gt;browser_click&lt;/code&gt;, &lt;code&gt;browser_type&lt;/code&gt;, &lt;code&gt;browser_snapshot&lt;/code&gt;, &lt;code&gt;browser_evaluate&lt;/code&gt;, &lt;code&gt;browser_mouse_wheel&lt;/code&gt;, and the &lt;code&gt;start/stop/get/list_browser_session&lt;/code&gt; lifecycle. You wire it into a client, it authenticates with your AWS profile, and you're driving a managed browser with no code to maintain. This is the daily driver for interactive and exploratory work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The governed path — IAM-gated, server-side, autonomous.&lt;/strong&gt; For an agent that runs without a human at the keyboard, the browser sits behind an AgentCore Gateway (IAM-authorized) in front of a Lambda that drives the CDP session. Now the browser is a first-class, governed tool: only principals in your account can call it, it composes with Cedar policy and AgentCore Identity, and it's the reach path an autonomous Runtime agent uses. A SigV4-signed &lt;code&gt;navigate → extract → close&lt;/code&gt; through the gateway returns results multi-call, with an application-level SSRF guard rejecting metadata/loopback/private-range URLs. It's the same managed browser underneath; the difference is who's allowed to drive it and what governs the call.&lt;/p&gt;

&lt;p&gt;The rule of thumb: &lt;strong&gt;local MCP for a human exploring, governed gateway for an agent operating.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's for: the use cases, grounded
&lt;/h2&gt;

&lt;p&gt;The managed browser earns its place on the tasks a plain HTTP fetch can't do — dynamic, JavaScript-rendered, or behind interaction. The market taxonomy is consistent: web research and extraction dominates, followed by form-filling and authenticated portal workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research and extraction on real e-commerce.&lt;/strong&gt; A consumer task — find the best-value 65" and 55" TVs — run through the managed browser against a live retailer. It surfaced a detail worth keeping because it shows the primitive doing real work: the prices weren't in the initial DOM. Modern retail sites lazy-load prices via an IntersectionObserver, so the elements render only after the viewport scrolls past them. The pattern that works, every time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;navigate(url)            -&amp;gt; DOM loaded, price elements empty
mouse.wheel(0, 800)      -&amp;gt; triggers lazy-load, prices render
evaluate(innerText regex)-&amp;gt; extract the now-present prices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;document.body.innerText.match(/\$[\d,]+/g)&lt;/code&gt; scan proved far more durable than CSS selectors, because the site rewrites its class names frequently. The run also caught a genuine pricing anomaly (a newer TV priced &lt;em&gt;below&lt;/em&gt; its older sibling) that a systematic scrape surfaces and a human skimming the page would miss — which is the actual argument for automating it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autonomous multi-step research.&lt;/strong&gt; In a parallel experiment, an agent used the managed browser to answer a "find three highly-rated lunch spots within a 5-minute walk, with a signature dish each" prompt end to end — navigate, cross-reference two sites (it fell back from one that blocked scraping to another's data plus a maps site for walking times), synthesize, and save the result to a file, with zero human intervention and zero errors across a ~20-minute run. That's the #1 use case — research and extraction on sites without a clean API — running autonomously on the managed browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The differentiator: isolation you can prove
&lt;/h2&gt;

&lt;p&gt;Anyone can run Playwright. What the &lt;em&gt;managed&lt;/em&gt; browser adds is the operational envelope, and the first piece of it is isolation. The docs say each session runs in its own isolated environment; I wanted to prove it rather than trust it, so I ran the browser analog of a memory-isolation test.&lt;/p&gt;

&lt;p&gt;Two sessions, same origin. In session A, I wrote a distinctive secret to both &lt;code&gt;localStorage&lt;/code&gt; and a cookie, then read it back within the session. Then I opened a &lt;strong&gt;separate&lt;/strong&gt; session B and read the same keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;session A  -&amp;gt;  wrote + read back: SECRET-3d33...   (state persists in-session)
session B  -&amp;gt;  localStorage=None, cookie=None      (sees nothing of A)
VERDICT: PASS — isolation holds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Session A kept its own state; session B, a concurrent separate session, saw none of it. That is the property that matters when two agents — or two tenants' agents — drive browsers at the same time: no shared cookie jar, no leaked login, no cross-contamination. It's structural, not a setting you hope holds.&lt;/p&gt;

&lt;p&gt;Two more governance features come from the same "managed" fact, and they're the reason to reach for this over a self-run browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live View — human-in-the-loop takeover.&lt;/strong&gt; The platform can stream a real-time video feed of the session over DCV (via a time-limited SigV4-presigned URL), and a human can &lt;em&gt;take control&lt;/em&gt; mid-session and hand it back. That's the concrete answer to "the agent hit a login it can't solve" or "this refund needs a human to look" — the human-in-the-loop seam made real for the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session replay — audit to your S3.&lt;/strong&gt; A recording-enabled browser writes the session's DOM changes, actions, console, and network events to &lt;em&gt;your&lt;/em&gt; S3 bucket, replayable in the console or programmatically. For a browser acting on your behalf against a portal, that recording is the audit trail. The configuration is a &lt;code&gt;recording&lt;/code&gt; block on the browser (&lt;code&gt;{enabled, s3Location: {bucket, prefix}}&lt;/code&gt;) and a least-privilege IAM role that can write to that prefix and log to CloudWatch — nothing else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these three — provable isolation, live human takeover, replay-to-S3 — is something you get for free by running Playwright on an EC2 instance. They're the managed envelope, and they're what make the browser a &lt;em&gt;governed&lt;/em&gt; tool rather than a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ceiling: managed is not stealth
&lt;/h2&gt;

&lt;p&gt;The honest limitation, stated plainly because the post is worthless without it: &lt;strong&gt;the managed browser is a clean, isolated Chromium — not an anti-detection product.&lt;/strong&gt; It does not rotate fingerprints, match TLS/JA3 signatures, or solve CAPTCHAs, and well-configured anti-bot systems detect it.&lt;/p&gt;

&lt;p&gt;I ran a small matrix to characterize exactly where it stops. Open sites navigated fine; an anti-bot-protected site did not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;books.toscrape.com   -&amp;gt; HTTP 200   PASS
httpbin.org/html     -&amp;gt; HTTP 200   PASS
costco.com/...       -&amp;gt; net::ERR_HTTP2_PROTOCOL_ERROR   BLOCKED (before render)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Costco's protection refused the managed Chromium at the connection layer — an HTTP/2 protocol error before a single byte of page rendered. This isn't a bug to file; it's the design boundary. Modern anti-bot (Cloudflare, DataDome, PerimeterX, and AWS's own WAF Bot Control) fingerprints TLS handshakes, HTTP/2 SETTINGS frames, header ordering, and behavioral signals. A clean cloud browser with datacenter egress and no stealth layer is, correctly, identifiable as automation.&lt;/p&gt;

&lt;p&gt;If your task requires getting past aggressive bot protection, AgentCore Browser is not the tool — you're in residential-proxy-and-stealth territory, which is a different product category and a different risk conversation.&lt;/p&gt;

&lt;p&gt;Know the ceiling and it stops being a surprise: the managed browser is for the open web, your own authenticated portals, and sites that don't actively fight automation — which is most enterprise back-office work — not for adversarial scraping of sites that have decided they don't want you.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;p&gt;The primitive has a clear negative space, and naming it is part of positioning it honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-throughput pure scraping&lt;/strong&gt; — if you need 10,000 static pages fast, raw Playwright or a scraping API is an order of magnitude quicker and cheaper. The managed browser's value is isolation and governance per session, not raw crawl throughput.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing your own known preview URL&lt;/strong&gt; — a build pipeline smoke-testing an app it just deployed wants local &lt;code&gt;@playwright/test&lt;/code&gt; against that URL in CI, not a managed cloud browser. There's no untrusted web and no governance need, so the managed envelope buys nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A site that has an API&lt;/strong&gt; — a browser should follow a native API and an existing connector. Don't drive a UI for data you could fetch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;AgentCore Browser is what you reach for when the task is genuinely browser-bound &lt;em&gt;and&lt;/em&gt; you need the operational envelope: a per-session isolated Chromium, drivable with ordinary Playwright over CDP, reachable interactively through a local MCP or as an IAM-gated governed tool for an autonomous agent. The isolation is real — two sessions, zero cross-visibility. It does real research and extraction on live sites. It fails cleanly and predictably against anti-bot protection.&lt;/p&gt;

&lt;p&gt;The one-line decision: if you'd otherwise stand up Playwright on a box &lt;em&gt;and then have to solve isolation, human-takeover, and audit yourself&lt;/em&gt;, use the managed browser — that's the work it does for you. If you just need to hit an API, or hammer static pages, or test your own URL, don't — the managed envelope is overhead you don't need. Match the tool to whether the task is governed and browser-bound, and the primitive earns its place exactly where the screen is the only interface and someone has to be able to prove what the agent did on it.&lt;/p&gt;

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

&lt;p&gt;The managed browser is a first-class primitive for browser-bound work inside your account, with the operational envelope built in. What it doesn't ship: stealth against adversarial anti-bot systems. That's the one thing a managed cloud browser structurally cannot hide — it's a datacenter egress with a clean fingerprint, and sites fighting automation will detect it. If stealth is your problem, the tool you need isn't a managed browser on a native cloud infrastructure — it's a purpose-built browser agent with a forked Chromium underneath, which is a different product category entirely.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentcore</category>
      <category>bedrock</category>
      <category>browser</category>
    </item>
    <item>
      <title>Grok 4.6 Is on Amazon Bedrock. Here Is How to Wire Grok Build to It</title>
      <dc:creator>Amit</dc:creator>
      <pubDate>Fri, 21 Aug 2026 20:39:52 +0000</pubDate>
      <link>https://dev.to/amitrix/grok-46-is-on-amazon-bedrock-here-is-how-to-wire-grok-build-to-it-1elp</link>
      <guid>https://dev.to/amitrix/grok-46-is-on-amazon-bedrock-here-is-how-to-wire-grok-build-to-it-1elp</guid>
      <description>&lt;p&gt;On August 19, 2026, &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/amazon-bedrock-grok-4-6/" rel="noopener noreferrer"&gt;AWS made SpaceXAI's Grok 4.6 available on Amazon Bedrock&lt;/a&gt;. The launch added US and Global cross-Region inference through Bedrock Runtime, plus OpenAI-compatible API access.&lt;/p&gt;

&lt;p&gt;That creates an obvious builder question: can &lt;a href="https://github.com/xai-org/grok-build" rel="noopener noreferrer"&gt;Grok Build&lt;/a&gt;, SpaceXAI's coding CLI, run its own model through an AWS account rather than a separate provider key and billing boundary?&lt;/p&gt;

&lt;p&gt;Yes. Grok Build can call Grok 4.6 on Bedrock directly, without a local inference proxy or a long-lived provider key. The working setup uses Grok Build's custom-model support, Bedrock's OpenAI-compatible endpoints, and a small credential helper that converts an existing AWS session into a short-lived bearer token.&lt;/p&gt;

&lt;p&gt;I wired and tested three Grok 4.6 routes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model picker entry&lt;/th&gt;
&lt;th&gt;Bedrock path&lt;/th&gt;
&lt;th&gt;Model ID&lt;/th&gt;
&lt;th&gt;Client Region&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime US&lt;/td&gt;
&lt;td&gt;US cross-Region inference&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us.xai.grok-4.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us-east-1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime Global&lt;/td&gt;
&lt;td&gt;Global cross-Region inference&lt;/td&gt;
&lt;td&gt;&lt;code&gt;global.xai.grok-4.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us-east-1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mantle&lt;/td&gt;
&lt;td&gt;In-Region inference&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xai.grok-4.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us-west-2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three run from the official &lt;code&gt;grok&lt;/code&gt; command. Selecting the Bedrock route happens inside Grok Build with &lt;code&gt;/model&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The comparison is operational: &lt;a href="https://code.claude.com/docs/en/amazon-bedrock" rel="noopener noreferrer"&gt;Claude Code can run Claude through Amazon Bedrock&lt;/a&gt;, and &lt;a href="https://developers.openai.com/codex/amazon-bedrock" rel="noopener noreferrer"&gt;Codex can run OpenAI models through Amazon Bedrock&lt;/a&gt;. This setup gives Grok Build the equivalent path for Grok. The CLI remains the model provider's own coding harness, while AWS supplies authentication, inference access, routing, and billing.&lt;/p&gt;

&lt;p&gt;The three Grok Build entries do not select different model families. They select three Bedrock access paths for the same Grok 4.6 model.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Grok 4.6 became available on Amazon Bedrock on August 19, 2026. Grok Build has no native Bedrock provider, but its custom models and named credential helpers cover the integration.&lt;/li&gt;
&lt;li&gt;The same AWS profile used by other developer tools remains the source of identity; the helper only turns that session into a regional Bedrock bearer token.&lt;/li&gt;
&lt;li&gt;Grok Build reads &lt;code&gt;expires_in&lt;/code&gt; from the helper response and reruns the helper before the token expires.&lt;/li&gt;
&lt;li&gt;Runtime US, Runtime Global, and Mantle require different model IDs, and Mantle Grok 4.6 is available in &lt;code&gt;us-west-2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;One &lt;code&gt;grok&lt;/code&gt; process can list and switch among all three routes for the same Grok 4.6 model. The model picker becomes the routing control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the credential helper exists
&lt;/h2&gt;

&lt;p&gt;AWS credentials and a Bedrock bearer token represent the same underlying IAM identity, but they are different request credentials.&lt;/p&gt;

&lt;p&gt;A client with native Bedrock support can read an AWS profile and authenticate the request itself. Grok Build treats a custom model as an OpenAI-compatible service. It expects bearer authentication and does not interpret an AWS profile directly.&lt;/p&gt;

&lt;p&gt;The credential helper closes that narrow gap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS profile
    ↓
AWS credential provider chain
    ↓
regional short-lived Bedrock token
    ↓
Grok Build → Bedrock OpenAI-compatible endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The helper does not proxy inference. Grok Build sends the model request directly to Amazon Bedrock. The helper runs only when Grok needs a token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html" rel="noopener noreferrer"&gt;Amazon Bedrock short-term API keys&lt;/a&gt; inherit the permissions of the IAM principal that creates them. Their lifetime cannot exceed the underlying AWS session. &lt;a href="https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-shell/README.md#per-model-auth-providers" rel="noopener noreferrer"&gt;Grok Build's per-model authentication providers&lt;/a&gt; accept a command that returns an access token and its lifetime, then refresh that token automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the AWS identity first
&lt;/h2&gt;

&lt;p&gt;This setup starts with a working AWS profile that already has permission to invoke the selected Grok model.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Read the returned account and principal before continuing. A valid AWS session in the wrong account produces a configuration that looks correct and fails at inference time.&lt;/p&gt;

&lt;p&gt;Install or update &lt;a href="https://docs.x.ai/build/overview" rel="noopener noreferrer"&gt;Grok Build&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://x.ai/cli/install.sh | bash
grok update &lt;span class="nt"&gt;--stable&lt;/span&gt;
grok &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I validated this configuration with Grok Build &lt;code&gt;1.0.5&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the Bedrock token generator
&lt;/h2&gt;

&lt;p&gt;Keep the Python dependency under Grok's own directory rather than changing the system Python environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.grok/bedrock-auth"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.grok/bin"&lt;/span&gt;

python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.grok/bedrock-auth"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  aws-bedrock-token-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS documents the package in its guide to &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.html" rel="noopener noreferrer"&gt;generating Amazon Bedrock API keys&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;~/.grok/bin/bedrock-token-helper&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Print a short-lived regional Bedrock token for Grok Build.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.grok/bedrock-auth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_bedrock_token_generator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;provide_token&lt;/span&gt;


&lt;span class="n"&gt;TOKEN_TTL_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2700&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--region&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_REGION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;provide_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TOKEN_TTL_SECONDS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unable to generate Bedrock token: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expires_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOKEN_TTL_SECONDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restrict execution to the current user:&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;chmod &lt;/span&gt;700 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.grok/bin/bedrock-token-helper"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command must write only JSON to standard output. Diagnostic messages belong on standard error because Grok parses standard output as the credential response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the three Bedrock models
&lt;/h2&gt;

&lt;p&gt;Add the following to &lt;code&gt;~/.grok/config.toml&lt;/code&gt;, replacing &lt;code&gt;YOUR_AWS_PROFILE&lt;/code&gt; with the profile verified earlier:&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;[auth_provider.bedrock-us-east-1]&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;'"$HOME/.grok/bin/bedrock-token-helper" --profile YOUR_AWS_PROFILE --region us-east-1'&lt;/span&gt;
&lt;span class="py"&gt;timeout_secs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="nn"&gt;[auth_provider.bedrock-us-west-2]&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;'"$HOME/.grok/bin/bedrock-token-helper" --profile YOUR_AWS_PROFILE --region us-west-2'&lt;/span&gt;
&lt;span class="py"&gt;timeout_secs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="nn"&gt;[model.bedrock-grok-us]&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;"us.xai.grok-4.6"&lt;/span&gt;
&lt;span class="py"&gt;base_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1"&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Grok 4.6 - Bedrock Runtime US"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"US cross-Region inference through Bedrock Runtime"&lt;/span&gt;
&lt;span class="py"&gt;api_backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"chat_completions"&lt;/span&gt;
&lt;span class="py"&gt;auth_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bedrock-us-east-1"&lt;/span&gt;
&lt;span class="py"&gt;context_window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500000&lt;/span&gt;

&lt;span class="nn"&gt;[model.bedrock-grok-global]&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;"global.xai.grok-4.6"&lt;/span&gt;
&lt;span class="py"&gt;base_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1"&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Grok 4.6 - Bedrock Runtime Global"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Global cross-Region inference through Bedrock Runtime"&lt;/span&gt;
&lt;span class="py"&gt;api_backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"chat_completions"&lt;/span&gt;
&lt;span class="py"&gt;auth_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bedrock-us-east-1"&lt;/span&gt;
&lt;span class="py"&gt;context_window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500000&lt;/span&gt;

&lt;span class="nn"&gt;[model.bedrock-grok-mantle]&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;"xai.grok-4.6"&lt;/span&gt;
&lt;span class="py"&gt;base_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://bedrock-mantle.us-west-2.api.aws/openai/v1"&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Grok 4.6 - Bedrock Mantle"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"In-Region inference through Bedrock Mantle in us-west-2"&lt;/span&gt;
&lt;span class="py"&gt;api_backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"chat_completions"&lt;/span&gt;
&lt;span class="py"&gt;auth_provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bedrock-us-west-2"&lt;/span&gt;
&lt;span class="py"&gt;context_window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500000&lt;/span&gt;

&lt;span class="nn"&gt;[models]&lt;/span&gt;
&lt;span class="py"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bedrock-grok-us"&lt;/span&gt;
&lt;span class="py"&gt;default_reasoning_effort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"medium"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model IDs are not interchangeable. &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-xai-grok-4-6.html" rel="noopener noreferrer"&gt;Amazon Bedrock's Grok 4.6 model card&lt;/a&gt; distinguishes Runtime inference profiles from the Mantle base model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime US uses &lt;code&gt;us.xai.grok-4.6&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Runtime Global uses &lt;code&gt;global.xai.grok-4.6&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mantle in &lt;code&gt;us-west-2&lt;/code&gt; uses &lt;code&gt;xai.grok-4.6&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;xai&lt;/code&gt; namespace remains part of the public model ID even though the vendor is now SpaceXAI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch once and switch models
&lt;/h2&gt;

&lt;p&gt;Start Grok Build with its normal command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;/model&lt;/code&gt; inside the CLI to select:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bedrock-grok-us
bedrock-grok-global
bedrock-grok-mantle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The selected entry determines the endpoint, Region, model ID, and credential provider. One Grok session can move among all three Grok 4.6 routes without restarting a proxy or exporting a new API key.&lt;/p&gt;

&lt;p&gt;For a non-interactive smoke test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;grok &lt;span class="nt"&gt;--model&lt;/span&gt; bedrock-grok-us &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Reply with exactly: BEDROCK_GROK_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat with &lt;code&gt;bedrock-grok-global&lt;/code&gt; and &lt;code&gt;bedrock-grok-mantle&lt;/code&gt;. A successful response proves more than token generation: it confirms the AWS identity, regional token, endpoint, model ID, and Grok request path work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token refresh is part of the design
&lt;/h2&gt;

&lt;p&gt;The helper requests a 45-minute token and returns &lt;code&gt;"expires_in": 2700&lt;/code&gt;. Grok Build uses that field to cache the token in memory and rerun the provider before expiry. Its authentication guide states that external credentials refresh proactively and retry after an authentication rejection.&lt;/p&gt;

&lt;p&gt;The AWS profile remains the actual source of authority. If it uses IAM Identity Center, &lt;code&gt;credential_process&lt;/code&gt;, or another refreshable provider, each helper run resolves the current session through that provider chain.&lt;/p&gt;

&lt;p&gt;There is one hard boundary: an expired upstream AWS login cannot refresh itself through Grok. Reauthenticate the AWS profile, then retry the model request. No Bedrock token helper can extend an AWS session that has already expired.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this setup does not solve
&lt;/h2&gt;

&lt;p&gt;This pattern gives Grok Build direct Bedrock inference and automatic credential rotation. It does not turn Grok Build into a native AWS client.&lt;/p&gt;

&lt;p&gt;Native Bedrock integrations can expose AWS-specific configuration directly. Grok Build sees an OpenAI-compatible model endpoint plus bearer credentials. That difference is why the helper exists and why the Runtime and Mantle routes must be declared explicitly.&lt;/p&gt;

&lt;p&gt;The setup also depends on current model availability. Grok 4.6 on Mantle is a &lt;code&gt;us-west-2&lt;/code&gt; route in the current AWS model card. A model appearing in one Region does not imply that the same base model ID works in every Mantle Region.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;The durable pattern is smaller than a proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one AWS identity
    + one regional token helper
    + explicit model routes
    = one Grok Build CLI with a Bedrock model picker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coding harness stays Grok Build, and the model stays Grok 4.6. Amazon Bedrock supplies model access, routing, and billing. The developer launches the official &lt;code&gt;grok&lt;/code&gt; command and selects the required AWS route.&lt;/p&gt;

&lt;p&gt;The open question is whether Grok Build will add a native Bedrock provider. If it does, the helper can disappear and the three model entries can collapse into AWS-aware configuration. Until then, named credential providers are the smallest integration layer that preserves direct inference and short-lived identity.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>bedrock</category>
      <category>codingagents</category>
    </item>
  </channel>
</rss>
