<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>agents&amp;ultras</title>
    <link>https://agentsandultras.com/</link>
    <atom:link href="https://agentsandultras.com/feed.xml" rel="self" type="application/rss+xml" />
    <description>Agentic software development, LLMs, and ultrarunning. By Robey Beswick.</description>
    <language>en</language>
    <lastBuildDate>Mon, 19 Oct 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>What is Grit?</title>
      <link>https://agentsandultras.com/ultras/what-is-grit</link>
      <guid isPermaLink="true">https://agentsandultras.com/ultras/what-is-grit</guid>
      <pubDate>Mon, 19 Oct 2026 00:00:00 GMT</pubDate>
      <category>ultras</category>
      <description>Why do we still doubt we can change, even after doing things we once thought were beyond us?</description>
      <content:encoded><![CDATA[<link rel="preload" as="image" href="https://agentsandultras.com/images/posts/what-is-grit/IMG_4196.JPG"/><p>In ultra distance trail running you have a choice in what shoes to wear. Either you wear water proof shoes, generally Goretex, or you don&#x27;t and allow your feet to breathe. It&#x27;s a trade off you make. If you choose not to wear the waterproof shoes and you step in a puddle about an hour into a 10 to 15 hour race, issues arise. Along with wet feet being uncomfortable, friction becomes a problem. The friction between your sock and the sole of your foot reduces and your socks start to rub against them. Within a few hours you start to lose skin.</p>
<p>The pain is difficult to describe, it&#x27;s not excruciating but it&#x27;s constant. A discomfort that you can ignore initially. But eventually it becomes the centre of your world. Every step you take drives you closer to the brink of quitting.</p>
<p><em>Why did I do this Godforsaken run? No one will care if you stop. I might do permanent damage to my foot. Finishing this race won&#x27;t bring her back.</em></p>
<p>Your mind tries to find any excuse that could justify stopping.</p>
<p>Why can some of us endure this and some of us not? This is a question I have been thinking about  a lot recently. Eighteen months ago I hadn&#x27;t run more than 10kms in my life. I hadn&#x27;t pushed myself particularly hard in any facet for a while really. And now I am here. What changed? What hardened my resolve that allowed me to get to where I am now?</p>
<p>I have always been terrified by the thought that we might never really be in control of who we become. We make decisions based on the people we are, but we did not choose much of what made us this way. I find it difficult to stop that thought from becoming something more pervasive: what if we could see the person we wanted to become, sincerely want to change, and still spend our lives unable to get there?</p>
<p>That fear is still with me. I have achieved things I would have considered impossible for me a short while ago, and I still question whether I can change. I thought those achievements would have settled the matter. But honestly, they have not.</p>
<p>The question also follows me into considerably less dramatic settings.</p>
<p>For example, my arch nemesis in the gym is mobility work. Almost every time I go, I have an internal conflict for at least ten minutes about whether I should do some stretching after completing a lovely arm day with the lads.</p>
<p><img src="https://agentsandultras.com/images/posts/what-is-grit/IMG_4196.JPG" alt="The lads"/></p>
<p><em>The lads</em></p>
<p>Generally, I don&#x27;t do it. I know why I want to do it. I still leave it out.</p>
<p>But occasionally, I just decide I do not have the option, and do it. I think what happens on those occasions is that I stop asking whether I feel like it. I have already decided I want the benefit. For once, I let that decision survive the predictable discovery that I would rather leave.</p>
<p>While I am doing it, it feels inconsequential. But over time I can feel the cumulative benefit. There is also the small fact that I did something I usually talk myself out of. That gives me something to remember the next time the argument starts.</p>
<p>This is where I believe grit lives. We notice it in the person still moving twelve hours into a race, or the people on stage talking about the future of the world, but perhaps much of it is developed on ordinary days. The runs when we are tired. The late nights working when we are alone. Small decisions, made repeatedly, with nobody particularly interested in whether we make them or not.</p>
<p>I still skip the stretching. I am reluctant to claim a permanent change in character on the strength of occasionally doing something I had already planned to do. But the next time I tell myself I cannot change, there is that small, inconvenient exception.</p>
<p>I do not want to believe grit is something some of us have innately. I don&#x27;t think there is much use in believing so anyway.</p>
<p>Maybe it&#x27;s that simple, we get some practice at doing things we doubt we can do or hate to do. Sometimes they go badly. Sometimes we discover that we were capable of more than we thought. Either way, we have something more to go on the next time.</p>
<p>We may still have doubts about where we are going and whether we will get there. But there is enough we can do today to make trying worthwhile. And if it is the case that we can not really change the outcomes of our destinies. There is no harm in trying.</p>]]></content:encoded>
    </item>
    <item>
      <title>A JWT Is Not Authorization</title>
      <link>https://agentsandultras.com/agents/a-jwt-is-not-authorization</link>
      <guid isPermaLink="true">https://agentsandultras.com/agents/a-jwt-is-not-authorization</guid>
      <pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
      <category>agents</category>
      <description>Header, claims, signature, and the small details that matter when agents start carrying tokens.</description>
      <content:encoded><![CDATA[<link rel="preload" as="image" href="https://agentsandultras.com/images/posts/a-jwt-is-not-authorization/jwt-authorization-gates.svg"/><p><em>A detour from <a href="https://agentsandultras.com/agents/agent-authorization-standard"><strong>The Agent Authorization Standard We Already Had</strong></a>, for anyone who nodded along at &quot;JWT&quot; and hoped nobody would ask a follow-up question.</em></p>
<p>JSON Web Tokens show up in almost every conversation about agent identity and authorization. Then, almost immediately, people start using &quot;JWT,&quot; &quot;access token,&quot; &quot;identity,&quot; and &quot;authorization&quot; as if they are the same thing. They are not. A signed blob appears, everybody relaxes, and authorization is taken for granted as everyone quietly pretends they know how it works.</p>
<p>This is a short refresher for the rest of us who want to stop pretending. I am not going to turn this into a cryptography lecture. I just want to make sure we agree on what is inside a JWT, what the signature proves, and what the resource server still has to decide for itself.</p>
<p>We are often quite fast and loose with the term &quot;body&quot; when referring to what&#x27;s inside a JWT. The more precise term is the JWT <strong>payload</strong>, or <strong>JWT Claims Set</strong>. It&#x27;s the JSON object containing the claims conveyed by the token. That&#x27;s it.<sup><a href="#user-content-fn-rfc7519" id="user-content-fnref-rfc7519" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup></p>
<p>Also remember that an OAuth access token is not always a JWT. This piece applies when the authorization server issues a JWT, specifically the signed compact form commonly carried as a bearer token.</p>
<h2>The Three Parts</h2>
<p>A signed JWT using the JSON Web Signature (JWS) Compact Serialization normally appears as three base64url-encoded values separated by periods:<sup><a href="#user-content-fn-rfc7515" id="user-content-fnref-rfc7515" data-footnote-ref="true" aria-describedby="footnote-label">2</a></sup></p>
<pre><code class="language-text">BASE64URL(header).BASE64URL(payload).BASE64URL(signature)
</code></pre>
<p><img src="https://agentsandultras.com/images/posts/a-jwt-is-not-authorization/jwt-authorization-gates.svg" alt="A compact JWT followed by the signature, issuer, audience, lifetime, authority, and policy checks required for authorization"/></p>
<p>Base64url encoding makes the value safe to transport in URLs and HTTP headers. It does <strong>not</strong> encrypt the content. Anybody holding a normal signed JWT can decode its header and payload. The signature protects integrity and helps the recipient verify who issued it.</p>
<p>Base64 has spent decades being mistaken for encryption and seems unlikely to correct anyone now.</p>
<h2>1. Header</h2>
<p>The header describes how the token was secured:</p>
<pre><code class="language-json">{
  &quot;alg&quot;: &quot;RS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;,
  &quot;kid&quot;: &quot;auth-key-2026-09&quot;
}
</code></pre>
<ul>
<li><code>alg</code> identifies the signing algorithm. The recipient must verify that it is one the application accepts.</li>
<li><code>typ</code> indicates that the object is a JWT. It is commonly present but optional.</li>
<li><code>kid</code> is a key identifier that helps the recipient select the correct public key for signature validation.</li>
</ul>
<h2>2. Payload, or JWT Claims Set</h2>
<p>This is the part we spend most of the article discussing. Here is a decoded example of the token the coding agent presents to the deployment MCP:</p>
<pre><code class="language-json">{
  &quot;iss&quot;: &quot;https://auth.example.com&quot;,
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;deployment-mcp&quot;,
  &quot;iat&quot;: 1760000000,
  &quot;exp&quot;: 1760000900,
  &quot;jti&quot;: &quot;at-123&quot;,
  &quot;scope&quot;: &quot;deploy.production&quot;,
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;coding-agent&quot;,
    &quot;act&quot;: {
      &quot;sub&quot;: &quot;personal-assistant&quot;
    }
  }
}
</code></pre>
<ul>
<li><code>iss</code> identifies the authorization server that issued the token.</li>
<li><code>sub</code> identifies Nadia, whose authority is being exercised.</li>
<li><code>aud</code> identifies the intended recipient. The deployment MCP must reject a token not intended for it.</li>
<li><code>iat</code> records when the token was issued.</li>
<li><code>exp</code> records when it expires. These time claims use NumericDate values, measured in seconds since the Unix epoch.</li>
<li><code>jti</code> is a unique token identifier that can support logging or replay controls.</li>
<li><code>scope</code> limits the capability to <code>deploy.production</code>.</li>
<li>The outermost <code>act</code> identifies the coding agent as the current actor. The nested <code>act</code> preserves the personal assistant as the previous actor.</li>
</ul>
<p>You might see <code>may_act</code> in an earlier subject token to state that a particular agent is eligible to become the actor. It is not included here because the deployment MCP is the final resource in this example and is not being authorized to continue the delegation chain.</p>
<h2>3. Signature</h2>
<p>The issuer signs the base64url-encoded header and payload. In simplified form:</p>
<pre><code class="language-text">SIGN(
  BASE64URL(header) + &quot;.&quot; + BASE64URL(payload),
  issuer-private-key
)
</code></pre>
<p>The deployment MCP validates that signature using a trusted key associated with the issuer. A valid signature shows that the protected header and payload have not been tampered with since the issuer signed them. It does not mean the request is automatically authorized.</p>
<p>A signed mistake is still a mistake. It simply has better provenance.</p>
<p>The deployment MCP must still validate the trusted issuer, intended audience, expiration, scope, current actor, and its own local policy. A token can be correctly signed and still be completely wrong for the resource receiving it. If you buy a ticket from Lufthansa, you can&#x27;t just get on any plane at the airport that has Lufthansa written on it.</p>
<blockquote>
<p>A JWT is a container for claims. The authorization decision comes from validated claims plus policy, not from the existence of the token itself.</p>
</blockquote>
<h2>The Part People Forget</h2>
<p>A valid signature does not tell you whether the token belongs at your service, whether it has expired, whether its scope permits the action, or whether the current actor is allowed to exercise the subject&#x27;s authority. It only tells you that the signed content has not changed and that it was signed by a key you associate with the issuer.</p>
<p>The payload is the important part of the token once its signature is verified! There we can get all of the information we need about what this token actually allows the client using it to do. That is how we can get spicy with the claims in the token!</p>
<p>That distinction matters even more in agent systems. A token can be perfectly valid and still represent the wrong user, the wrong agent, the wrong audience, or far too much authority.</p>
<h2>References</h2>
<ul>
<li><a href="https://www.rfc-editor.org/rfc/rfc8693.html">RFC 8693: OAuth 2.0 Token Exchange</a></li>
</ul>
<section data-footnotes="true" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-rfc7519">
<p><a href="https://www.rfc-editor.org/rfc/rfc7519.html">RFC 7519: JSON Web Token</a> <a href="#user-content-fnref-rfc7519" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-rfc7515">
<p><a href="https://www.rfc-editor.org/rfc/rfc7515.html">RFC 7515: JSON Web Signature</a> <a href="#user-content-fnref-rfc7515" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
    </item>
    <item>
      <title>The Agent Authorization Standard We Already Had</title>
      <link>https://agentsandultras.com/agents/agent-authorization-standard</link>
      <guid isPermaLink="true">https://agentsandultras.com/agents/agent-authorization-standard</guid>
      <pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
      <category>agents</category>
      <description>Using OAuth Token Exchange (RFC 8693) and actor claims to control and audit agent delegation.</description>
      <content:encoded><![CDATA[<link rel="preload" as="image" href="https://agentsandultras.com/images/posts/agent-authorization-standard/agent-auth-intro.webp"/><link rel="preload" as="image" href="https://agentsandultras.com/images/posts/agent-authorization-standard/agent-auth-401-discovery.svg"/><link rel="preload" as="image" href="https://agentsandultras.com/images/posts/agent-authorization-standard/agent-auth-token-exchange.svg"/><p>Agents may be a new thing, but the way they authenticate with each other and with external services does not have to be.</p>
<p>This year has been all about agents, as I am sure you are aware. They are everywhere, and we are trying to use them for everything, from Outlook schedulers to incident response. As we go down this road, the conversation about tightening our controls on them becomes even more relevant. It seems every week we get another announcement about how AI will take over the world tomorrow. Predictably, this day has not yet come, and I am skeptical it ever will. But regardless, it might make sense to scope down what agents can do.</p>
<p><img src="https://agentsandultras.com/images/posts/agent-authorization-standard/agent-auth-intro.webp" alt="An agent politely asking for production access, one permission at a time"/></p>
<p>So, as agents change the way we work, the permissions we give them have to change too. The evolution is already familiar. First came the local agent with shell access and a bunch of API keys. God-level permissions on your laptop. Rudimentary, but extremely effective, which probably explains why I still see it more often than anybody would like to admit. ;)</p>
<p>The next step is isolation. Dump the agent in a container. Restrict the network. Give it a narrowly scoped GitHub token. Let one container pull the repository, persist only the required files, and let a second container with a headless coding harness go wild inside a smaller box. Again: rudimentary, but effective.</p>
<p>Eventually you arrive at OAuth 2.0, a convention that is as old as time by AI standards. This is normally where people start drawing complicated architecture diagrams and inventing new vocabulary. What these &quot;experts&quot; seem to miss is that a lot of the authorization model we need already exists.</p>
<p>For the sake of this article, let&#x27;s define a case most of us high-minded tech people will understand. Meet Nadia. She uses a personal assistant agent to coordinate her development work. It can read requests from a chat Model Context Protocol (MCP) server (Slack) and delegate coding tasks to a coding agent (headless Claude). That coding agent can eventually call a deployment MCP server to get our beautiful code into the only real testing environment: PRODUCTION.</p>
<h2>It Starts With 401 Unauthorized</h2>
<p>Need a JWT refresher? <a href="https://agentsandultras.com/agents/a-jwt-is-not-authorization"><em>A JWT Is Not Authorization</em></a> covers the claims and signatures used below. Otherwise, the 401 awaits.</p>
<p>Nadia asks her personal assistant to handle a development request that arrived in Slack. Before it can decide what work to delegate, the assistant needs to read the conversation through the chat MCP server.</p>
<p>The assistant calls the endpoint without a usable token and receives this pretty rude response:</p>
<pre><code class="language-http">HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata=&quot;https://chat.example.com/.well-known/oauth-protected-resource&quot;
</code></pre>
<p>It is not the server saying, &quot;I will never love you, please stop texting me.&quot; It is saying: you need a token, and here is where you can go to try and get one.</p>
<p>RFC 9728 defines this protected-resource metadata pattern. Bear with me here. The client fetches the metadata, confirms that the returned <code>resource</code> exactly matches the resource it tried to access, discovers an authorization server it is willing to trust, completes the appropriate OAuth flow, and retries with a token created for that resource.<sup><a href="#user-content-fn-rfc9728" id="user-content-fnref-rfc9728" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup></p>
<p><img src="https://agentsandultras.com/images/posts/agent-authorization-standard/agent-auth-401-discovery.svg" alt="The agent calls the chat MCP without a token, gets a 401 pointing to the resource metadata, checks it, completes the OAuth flow and retries with an audience-bound token"/></p>
<p>There is one important caveat: a 401 response does not magically create trust. An agent cannot blindly follow any authorization-server URL placed in a response. It still validates Transport Layer Security (TLS), resource metadata, issuer metadata, and its local trust policy. Discovery tells the client where to look. It does not tell the client who to believe. The client has to decide that for itself.</p>
<p>The endpoint now accepts a token, but that token has to say more than &quot;Nadia is allowed.&quot;</p>
<h2>Three Identities Are Hiding in the Request</h2>
<p>The authorization model becomes easier to reason about if we stop saying &quot;the agent&#x27;s token&quot; and ask three separate questions:</p>
<pre><code class="language-text">Subject:  Who owns the authority?
Actor:    Who is currently exercising that authority?
Audience: Where may that authority be used?
</code></pre>
<p>The personal assistant can act as itself:</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;personal-assistant&quot;,
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>That is fine when the work genuinely belongs to the machine. A bot posting its own health status does not need to pretend a human clicked the button.</p>
<p>The assistant can instead impersonate Nadia:</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>This can still be tightly scoped. Impersonation does not automatically mean every permission Nadia has. The problem is that the chat MCP server sees Nadia and only Nadia. It cannot tell whether Nadia read the thread directly, whether her assistant did it, or whether somebody copied the token into a curl command at 02:00.</p>
<p>Or the assistant can act for Nadia through explicit delegation:</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot;
  },
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>Now the token says two things at once: Nadia owns the authority, and the personal assistant is exercising it. The resource can preserve Nadia as the subject while applying different policy to the agent acting for her.</p>
<p>For delegation to work, the assistant needs its own identity and a way to prove it, the same way a human would. A <code>client_id</code> identifies a client registration, but it does not authenticate the agent by itself. The agent still needs a credential or another authentication mechanism before an authorization server can trustfully name it as an actor.</p>
<h2>Exchange Authority Instead of Forwarding Tokens</h2>
<p>Now, how do we get these delegated tokens we mentioned earlier? How does the agent get a token that retains Nadia as the subject but exists exclusively for the agent and what it is supposed to do? The answer has existed for a while.</p>
<p>RFC 8693 defines OAuth 2.0 Token Exchange, an HTTP and JSON protocol for asking an authorization server to validate one security context and issue a new token for another.<sup><a href="#user-content-fn-rfc8693" id="user-content-fnref-rfc8693" data-footnote-ref="true" aria-describedby="footnote-label">2</a></sup></p>
<p><img src="https://agentsandultras.com/images/posts/agent-authorization-standard/agent-auth-token-exchange.svg" alt="Nadia&#x27;s subject token, the agent&#x27;s actor token and the target resource go into the authorization server, which mints one new token with a single audience, bounded scope and a short lifetime"/></p>
<p>The <code>subject_token</code> represents Nadia, whose authority is being exercised. The personal assistant authenticates as itself and can provide an explicit <code>actor_token</code>. The requested <code>resource</code> or <code>audience</code> says where the new token will be used, and <code>scope</code> says what it should permit there.</p>
<p>The authorization server validates Nadia&#x27;s token, authenticates the assistant, and applies policy. Is this assistant allowed to act for Nadia? Is it allowed to access this resource? Is the requested scope appropriate? Only then does the authorization server mint a new token for the next boundary. Think of it like a power of attorney for humans. Your bank does a whole lot of stuff to make sure your cousin is actually your cousin when you want him to buy a house for you.</p>
<p>The agent does not crack open Nadia&#x27;s token, change the audience, add itself as an actor, and sign it again. If an agent can mint its own trusted delegation evidence, you do not have delegation. As I said earlier, the agents are probably not going to take over, but this would be an unusually considerate way to help them if they were going to.</p>
<p>Assuming the authorization server is configured to issue a composite JSON Web Token (JWT) for delegation, the result can preserve Nadia as <code>sub</code>, identify the assistant in <code>act</code>, pin the token to the chat MCP server with <code>aud</code>, restrict it to <code>messages.read</code>, and give it a short lifetime with <code>exp</code>.</p>
<p>One audience. Bounded capability. Short lifetime. If that token leaks, another service checking <code>aud</code> should reject it, and it will be dead soon anyway.</p>
<p>This is usually where someone asks about the latency of all this exchanging. But a token exchange does not have to happen for every API call. If the assistant already holds a suitable, unexpired token for the same audience, it can reuse it while policy permits. The important boundary is a change in audience or authorization context. A chat token should not wander into the deployment MCP server just because both systems accept OAuth.</p>
<h2>The Audit Record Finally Stops Lying</h2>
<p>Without explicit delegation, the production deployment record might look like this:</p>
<pre><code class="language-text">principal=nadia
action=deploy_release
</code></pre>
<p>Technically true, in the sense that Nadia&#x27;s authority was used. Operationally it is a flat-out lie, because Nadia did not perform the deployment.</p>
<p>With explicit delegation, the deployment system can record something closer to the truth:</p>
<pre><code class="language-text">principal=nadia
current_actor=coding-agent
previous_actor=personal-assistant
audience=deployment-mcp
scope=deploy.production
action=deploy_release
</code></pre>
<p>This is where agent identity becomes useful rather than decorative. The resource can distinguish Nadia acting directly from a coding agent acting for Nadia. It can apply different controls to different agents, block one actor without pretending Nadia herself is compromised, and reconstruct the path during the inevitable production incident.</p>
<p>Delegation separates ownership of authority from responsibility for exercising it. That sentence is a bit dry, but it is the entire point.</p>
<h2>What This Standard Does Not Solve</h2>
<p>RFC 8693 gives us delegation semantics, not a complete agent security architecture. It does not define how unrelated agent domains establish federation, a universal agent identifier, maximum delegation depth, user approval experiences, agent reputation, or a global mechanism that immediately revokes one agent&#x27;s authority everywhere.</p>
<p>We still have to design the trust model and policy. In practice that means short-lived and audience-specific tokens, independently authenticated actors, local limits on delegation depth, complete audit records, and sender-constrained tokens when theft presents a material risk.</p>
<p>But compare this with where we started. A chat MCP server returned a 401 because Nadia&#x27;s assistant needed a token. By the time an action reaches production, the deployment system can answer five useful questions:</p>
<pre><code class="language-text">Who owns the authority?       Nadia
Who is exercising it?         Coding Agent
Who acted previously?         Personal Assistant
Where can it be used?         Deployment MCP
What can it do?               deploy.production
</code></pre>
<p>AI agents may be new to the party, but delegation is not a new security problem. RFC 8693 already gives us a vocabulary for the subject, the actor, the permitted delegate, and the target. RFC 9728 gives an unfamiliar client a standard way to begin the authorization conversation. Agent-to-Agent (A2A) communication can carry the task, while OAuth carries bounded authority and MCP exposes capabilities behind explicit authorization boundaries.</p>
<p>The challenge is not inventing another token format. It is applying the authorization machinery we already have with discipline.</p>
<p>The feeling I get more and more these days is that the conversations we are having around agents feel increasingly familiar: boundaries, domains, responsibilities, and accountability. The safest agent systems may not be built on novel authorization technology, but on old authorization technology used precisely.</p>
<h2>Optional Detours</h2>
<p>This article stands on its own. The three below are detours, not homework. Pick whichever covers the part you want to poke at:</p>
<ul>
<li><a href="https://agentsandultras.com/agents/a-jwt-is-not-authorization"><em>A JWT Is Not Authorization</em></a> explains headers, claims, signatures, and why a valid token is not automatically authorized.</li>
<li><a href="https://agentsandultras.com/agents/who-is-actually-acting"><em>Who Is Actually Acting?</em></a> goes deeper into <code>act</code>, optional <code>may_act</code>, and the distinction between subject and actor.</li>
<li><a href="https://agentsandultras.com/agents/the-task-travels-the-token-should-not"><em>The Task Travels. The Token Should Not.</em></a> follows Nadia&#x27;s task across multiple agents and resources without forwarding the original token.</li>
</ul>
<h2>References</h2>
<ul>
<li><a href="https://modelcontextprotocol.io/specification/draft/basic/authorization/authorization-server-discovery">Model Context Protocol: Authorization Server Discovery</a></li>
<li><a href="https://a2a-protocol.org/latest/specification/">Agent2Agent Protocol Specification 1.0</a></li>
</ul>
<section data-footnotes="true" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-rfc9728">
<p><a href="https://www.rfc-editor.org/rfc/rfc9728.html">RFC 9728: OAuth 2.0 Protected Resource Metadata</a> <a href="#user-content-fnref-rfc9728" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-rfc8693">
<p><a href="https://www.rfc-editor.org/rfc/rfc8693.html">RFC 8693: OAuth 2.0 Token Exchange</a> <a href="#user-content-fnref-rfc8693" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
    </item>
    <item>
      <title>The Task Travels. The Token Should Not.</title>
      <link>https://agentsandultras.com/agents/the-task-travels-the-token-should-not</link>
      <guid isPermaLink="true">https://agentsandultras.com/agents/the-task-travels-the-token-should-not</guid>
      <pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
      <category>agents</category>
      <description>Following one development task across a personal assistant, coding agent, chat MCP, and production deployment without forwarding the original token.</description>
      <content:encoded><![CDATA[<link rel="preload" as="image" href="https://agentsandultras.com/images/posts/the-task-travels-the-token-should-not/task-travels-token-changes.svg"/><p><em>A detour from <a href="https://agentsandultras.com/agents/agent-authorization-standard"><strong>The Agent Authorization Standard We Already Had</strong></a> that follows the whole chain end to end, because one hop never got anyone paged.</em></p>
<p>Forwarding the token you already have is seductive. It is also how authority intended for one service quietly wanders into three others, how every action gets attributed to the user, and how an audit trail becomes a work of fiction.</p>
<p>So let&#x27;s follow one task through the whole chain. Nadia remains the subject whose authority is being exercised. The current actor changes at each hop. The audience changes for every receiving service. The authorization server evaluates each exchange and creates a token for that boundary.<sup><a href="#user-content-fn-rfc8693" id="user-content-fnref-rfc8693" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup></p>
<p>The examples use JWT claims because they make the delegation chain visible. The agents are not opening Nadia&#x27;s token, changing the audience, adding themselves as actors, and signing it again. The authorization server does that work after validating identity and policy.</p>
<p><img src="https://agentsandultras.com/images/posts/the-task-travels-the-token-should-not/task-travels-token-changes.svg" alt="The task travels from Nadia to the personal assistant and coding agent while the authorization server creates separate audience-specific tokens for chat, coding, and deployment"/></p>
<h2>Follow One Development Task Through the Chain</h2>
<p>Let&#x27;s take a look at what might happen if we follow a whole dev task down a chain. It starts with Nadia being issued a token intended for her personal assistant:</p>
<p>Note: These examples show the claims in tokens minted by the authorization server at each boundary. The agents are not editing or re-signing Nadia&#x27;s original token.</p>
<pre><code class="language-json">{
  &quot;iss&quot;: &quot;https://auth.example.com&quot;,
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;personal-assistant&quot;,
  &quot;scope&quot;: &quot;development.coordinate&quot;,
  &quot;may_act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot;
  }
}
</code></pre>
<p>The personal assistant first needs the context from Slack. It authenticates as itself and exchanges Nadia&#x27;s authority for a token intended only for the chat MCP:</p>
<pre><code class="language-json">{
  &quot;iss&quot;: &quot;https://auth.example.com&quot;,
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;,
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot;
  }
}
</code></pre>
<p>The personal assistant reads the development request, decides that code needs to change, and exchanges Nadia&#x27;s original authority for a separate token intended for the coding agent:</p>
<pre><code class="language-json">{
  &quot;iss&quot;: &quot;https://auth.example.com&quot;,
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;coding-agent&quot;,
  &quot;scope&quot;: &quot;code.change&quot;,
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot;
  },
  &quot;may_act&quot;: {
    &quot;sub&quot;: &quot;coding-agent&quot;
  }
}
</code></pre>
<h3>Where A2A Fits</h3>
<p>In this example, the personal assistant could invoke the coding agent over A2A. The coding agent&#x27;s Agent Card advertises its authentication requirements, but A2A does not replace the authorization model described above. With an HTTP-based A2A binding, the personal assistant still sends a token intended specifically for the coding agent in the <code>Authorization</code> header. When the coding agent later calls the deployment MCP, it exchanges that authority for a new token whose audience is the deployment MCP. <strong>A2A carries the task; OAuth carries the bounded authority.</strong><sup><a href="#user-content-fn-a2a" id="user-content-fnref-a2a" data-footnote-ref="true" aria-describedby="footnote-label">2</a></sup></p>
<p>The chat-MCP token and coding-agent token are siblings. One is not forwarded into the other. The same user authority has been exchanged into two separate tokens, each with its own audience and capability (delegation).</p>
<p>The coding agent then does its thing and produces the production code and, because staging is for cowards, needs to communicate with the Prod deployment MCP. It does not forward the token it received from the personal assistant. It authenticates as the coding agent and exchanges the incoming authority for a new token intended only for deployment:</p>
<pre><code class="language-jsonc">{
  &quot;iss&quot;: &quot;https://auth.example.com&quot;,
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;deployment-mcp&quot;,
  &quot;scope&quot;: &quot;deploy.production&quot;,
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;coding-agent&quot;, // the current actor, not a ceremonial title
    &quot;act&quot;: {
      &quot;sub&quot;: &quot;personal-assistant&quot; // useful for audit, less useful as a scapegoat
    }
  }
}
</code></pre>
<p>The outermost <code>act</code> entry is now the current actor, and the nested entry is the previous actor. Continue nesting and the token carries a delegation history. It is bureaucracy, but at least it is useful bureaucracy.</p>
<p>There is a counter-intuitive rule here. RFC 8693 says authorization decisions should consider the token&#x27;s top-level claims and the current outermost actor. Previous actors are informational only. Log the entire chain, but do not quietly turn the third agent in the history into an undocumented policy input.</p>
<p>The subject stays Nadia. The current actor changes. The audience changes. The scope changes to the minimum capability required at each boundary. <strong>This is what we wanted from the start: the development task travels, but the original access token does not.</strong></p>
<h2>The Audit Record Finally Stops Lying</h2>
<p>Without explicit delegation, the production deployment record might look like this:</p>
<pre><code class="language-text">principal=nadia
action=deploy_release
</code></pre>
<p>Technically true, in the sense that Nadia&#x27;s authority was used. Operationally it&#x27;s a flat out lie, because Nadia did not perform the deployment.</p>
<p>With the delegated token, the deployment MCP can record:</p>
<pre><code class="language-text">principal=nadia
current_actor=coding-agent
previous_actor=personal-assistant
audience=deployment-mcp
scope=deploy.production
token_id=at-123
action=deploy_release
</code></pre>
<p>The policy can also distinguish between Nadia deploying directly and the coding agent deploying for Nadia:</p>
<pre><code class="language-text">Allow when:
  iss       = trusted authorization server
  sub       = Nadia
  act.sub   = Coding Agent
  aud       = Deployment MCP
  scope     = deploy.production

Deny:
  scope     = deployment.admin
</code></pre>
<p>This is where agent identity becomes useful rather than decorative. The deployment system can apply different limits to Nadia&#x27;s personal assistant and coding agent, block one actor without pretending Nadia herself is compromised, and reconstruct the complete path during the inevitable production incident.</p>
<p>Delegation separates ownership of authority from responsibility for exercising it. That sentence is a bit dry, but it is the entire point.</p>
<h2>This Is Not a Complete Agent Security Architecture</h2>
<p>RFC 8693 does not define how unrelated agent domains establish federation. It does not define a universal agent identifier, a maximum delegation depth, an approval experience, agent reputation, task-specific permissions, or a global button that revokes one agent&#x27;s authority everywhere immediately.</p>
<p>It gives us delegation semantics. We still have to design the trust model and policy. Which is something I fear many people are abstracting away too willingly these days.</p>
<p>In practice that means setting a maximum actor-chain depth, using short-lived and audience-specific access tokens, validating issuer, audience, expiry and scope, authenticating the actor independently during exchange, and verifying what the chosen authorization server actually emits.</p>
<p>Bearer tokens still have an obvious weakness: whoever holds one can usually use it. If token theft is a material risk, sender-constrained mechanisms such as Demonstrating Proof of Possession (DPoP)<sup><a href="#user-content-fn-rfc9449" id="user-content-fnref-rfc9449" data-footnote-ref="true" aria-describedby="footnote-label">3</a></sup> or mutual Transport Layer Security (mTLS)<sup><a href="#user-content-fn-rfc8705" id="user-content-fnref-rfc8705" data-footnote-ref="true" aria-describedby="footnote-label">4</a></sup> can bind the token to a key held by the intended presenter.</p>
<h2>What I Would Implement First</h2>
<p>If I were turning this example into a real system, I would start with four controls: one token per audience, short token lifetimes, independently authenticated actors during exchange, and audit records that store the full delegation chain. Then I would add local limits for chain depth and sender-constrained tokens where theft presents a material risk.</p>
<p>You do not need every possible agent-security mechanism on day one. You do need to stop forwarding broad user tokens and pretending that the user performed every downstream action.</p>
<p>But compare this with where we started. The chat MCP required Nadia&#x27;s personal assistant to obtain a token. By the time the code reaches production, the deployment MCP can answer five useful questions:</p>
<pre><code class="language-text">Who owns the authority?       Nadia
Who is exercising it?         Coding Agent
Who acted previously?         Personal Assistant
Where can it be used?         Deployment MCP
What can it do?               deploy.production
</code></pre>
<p>AI agents may be new to the party, but delegation is not a new security problem. RFC 8693 already gives us a vocabulary for the subject, the actor, the permitted delegate, and the target. RFC 9728 gives an unfamiliar client a standard way to begin the authorization conversation. A2A carries the task from personal assistant to coding agent, while MCP exposes the chat and deployment capabilities behind explicit authorization boundaries.</p>
<p>The challenge is not inventing another token format. It is applying the authorization machinery we already have with discipline.</p>
<p>The conversations around agents feel increasingly familiar: boundaries, domains, responsibilities, and accountability. Apparently the future of AI still requires the same old arguments about who is allowed to do what. The technology changes quickly. The meetings remain remarkably stable.</p>
<p>The safest agent systems may not be built on novel authorization technology, but on old authorization technology used precisely.</p>
<h2>References</h2>
<ul>
<li><a href="https://www.rfc-editor.org/rfc/rfc9728.html">RFC 9728: OAuth 2.0 Protected Resource Metadata</a></li>
</ul>
<section data-footnotes="true" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-rfc8693">
<p><a href="https://www.rfc-editor.org/rfc/rfc8693.html">RFC 8693: OAuth 2.0 Token Exchange</a> <a href="#user-content-fnref-rfc8693" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-a2a">
<p><a href="https://a2a-protocol.org/latest/specification/">Agent2Agent Protocol Specification 1.0</a> <a href="#user-content-fnref-a2a" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-rfc9449">
<p><a href="https://www.rfc-editor.org/rfc/rfc9449.html">RFC 9449: OAuth 2.0 Demonstrating Proof of Possession</a> <a href="#user-content-fnref-rfc9449" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-rfc8705">
<p><a href="https://www.rfc-editor.org/rfc/rfc8705.html">RFC 8705: OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens</a> <a href="#user-content-fnref-rfc8705" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Who Is Actually Acting?</title>
      <link>https://agentsandultras.com/agents/who-is-actually-acting</link>
      <guid isPermaLink="true">https://agentsandultras.com/agents/who-is-actually-acting</guid>
      <pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
      <category>agents</category>
      <description>Understanding act, may_act, and the identities hidden inside an agent request.</description>
      <content:encoded><![CDATA[<link rel="preload" as="image" href="https://agentsandultras.com/images/posts/who-is-actually-acting/subject-actor-audience.svg"/><p><em>A detour from <a href="https://agentsandultras.com/agents/agent-authorization-standard"><strong>The Agent Authorization Standard We Already Had</strong></a>, for the moment &quot;the agent&#x27;s token&quot; stops being a good enough answer.</em></p>
<p>Agent authorization gets confusing because we keep calling everything &quot;the agent&#x27;s token.&quot; That phrase hides the exact questions the token needs to answer: whose authority is being used, which agent is exercising it, and where that authority is accepted.</p>
<p>This is the piece of OAuth delegation I wish more people discussed. Once you separate the subject from the actor, the whole model becomes much easier to reason about.</p>
<h2>Subject, Actor, and Audience</h2>
<p><em>If JSON Web Token claims are unfamiliar, I cover the header, payload, and signature in <a href="https://agentsandultras.com/agents/a-jwt-is-not-authorization"><strong>A JWT Is Not Authorization</strong></a>. Most of what follows lives in the JWT payload, or JWT Claims Set.</em></p>
<p>Bear with me here. The authorization model becomes much easier to reason about if we stop saying &quot;the agent&#x27;s token&quot; and ask three separate questions:</p>
<pre><code class="language-text">Subject:  Who owns the authority?
Actor:    Who is currently exercising that authority?
Audience: Where may that authority be used?
</code></pre>
<p><img src="https://agentsandultras.com/images/posts/who-is-actually-acting/subject-actor-audience.svg" alt="Own authority, impersonation, and explicit delegation compared using subject, actor, audience, and scope claims"/></p>
<p>If you buy a ticket for a Eurowings flight and try to use it to enter the Louvre, they are going to laugh at you. The ticket may be genuine. The audience is still wrong.</p>
<p>In Nadia&#x27;s case, the same three questions produce three different token shapes. The examples below show only the relevant claims from each payload.</p>
<p>The personal assistant can act as itself, that is, with its own authority:</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;personal-assistant&quot;,
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>That is fine when the work genuinely belongs to the machine. A bot posting its own health status does not need to pretend a human clicked the button.</p>
<p>The personal assistant can impersonate Nadia, which is the case with many systems currently:</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>This impersonation can still be tightly scoped. Impersonation does not automatically mean every permission Nadia has. The problem is that the chat MCP sees Nadia and only Nadia. It cannot tell whether Nadia read the thread directly, whether her personal assistant did it, or whether somebody copied the token into a curl command at 02:00.</p>
<p>Or, and this is where things get interesting, the personal assistant can act for Nadia through explicit delegation:</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot;
  },
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>Now the token says two things at once: Nadia owns the authority, and the personal assistant is currently exercising it. <code>act</code> is the key here, and it is fundamental to the discussion we are going to have here.</p>
<p>A delegate cannot walk into a bank, announce that a friend sent them, and expect access to the savings. Banks remain oddly attached to proof.</p>
<p>Nadia&#x27;s personal assistant faces the same requirement. It needs its own identity and a way to prove it. A <code>client_id</code> is not enough by itself. It identifies a client registration, but the client still needs a credential or another authentication mechanism to prove that it controls that identity.</p>
<p>That identity could be an OAuth client, a workload identity, or something based on a system such as SPIFFE. The specific mechanism matters operationally, but the logical requirement is simple: <strong>the agent must be able to prove that it is itself before an authorization server can name it as an actor.</strong></p>
<h2><code>act</code> Tells Us Who Is Acting</h2>
<p>The <code>act</code> claim is a central piece in this puzzle:</p>
<pre><code class="language-jsonc">{
  &quot;sub&quot;: &quot;nadia&quot;,// nadia remains the primary authority
  &quot;act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot; // assistant working for Nadia
  },
  &quot;aud&quot;: &quot;chat-mcp&quot;,
  &quot;scope&quot;: &quot;messages.read&quot;
}
</code></pre>
<p>The top-level token is about Nadia. The nested object identifies the personal assistant as the current actor. This is delegation in a form the chat MCP can inspect and log.</p>
<p>RFC 8693 also defines <code>may_act</code>. It makes a statement that a party is eligible to become the actor:</p>
<pre><code class="language-jsonc">{
  &quot;sub&quot;: &quot;nadia&quot;,
  &quot;aud&quot;: &quot;personal-assistant&quot;,
  &quot;may_act&quot;: {
    &quot;sub&quot;: &quot;personal-assistant&quot; // personal assistant is allowed to assume act
  }
}
</code></pre>
<p>The easiest mental model I have found is this:</p>
<pre><code class="language-text">actor authentication = proves who the personal assistant is
may_act               = puts the personal assistant on the guest list
act                   = records the personal assistant in the visitor book
</code></pre>
<p>But beware: <code>may_act</code> is not mandatory, and I am not a huge fan. In many systems, this is better handled on the authorization server. An auth server can keep the same relationship in server-side policy, user consent, registered client relationships, or organizational rules. It becomes useful when the eligibility decision needs to travel inside a token to another trusted exchange service.</p>
<p>Neither claim grants a capability on its own. <code>act</code> identifies the actor. <code>may_act</code> identifies an eligible actor. <strong>Audience, scope, expiry, issuer, and authorization-server policy still do the actual work of bounding authority.</strong></p>
<h2>Why This Distinction Matters</h2>
<p>Without <code>act</code>, the resource can identify Nadia or the personal assistant, but not both roles at the same time. With it, the system can preserve Nadia as the owner of the authority while applying actor-specific policy to the software performing the action.</p>
<p><code>may_act</code> can make the eligibility relationship portable, but it remains optional. The authorization server can keep that decision in consent, registered-client relationships, workload policy, or organizational rules instead.</p>
<p>The important part is not adding more claims because claims are fashionable. It is giving the resource enough trustworthy context to answer a simple question: who is actually doing this?</p>
<h2>References</h2>
<ul>
<li><a href="https://www.rfc-editor.org/rfc/rfc8693.html">RFC 8693: OAuth 2.0 Token Exchange</a></li>
<li><a href="https://www.rfc-editor.org/rfc/rfc6749.html">RFC 6749: The OAuth 2.0 Authorization Framework</a></li>
<li><a href="https://spiffe.io/docs/latest/spiffe-specs/spiffe/">SPIFFE specifications</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>18 Times the Cost, the Same Backlog: Why AI Model Rightsizing Matters</title>
      <link>https://agentsandultras.com/agents/model-rightsizing</link>
      <guid isPermaLink="true">https://agentsandultras.com/agents/model-rightsizing</guid>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <category>agents</category>
      <description>And whether open-weight models are ready to do more of the heavy lifting.</description>
      <content:encoded><![CDATA[<link rel="preload" as="image" href="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-comparison-table.svg"/><link rel="preload" as="image" href="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-cumulative-cost.svg"/><link rel="preload" as="image" href="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-opus-premium.svg"/><link rel="preload" as="image" href="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-escalation-ladder.svg"/><p>Recently, a friend of mine asked me to help him out with some architecture-testing libraries he is working on as part of <a href="https://www.archunit.net/">ArchUnit</a>. The idea is satisfyingly concrete: express architectural decisions as fluent assertions, then run them alongside ordinary tests. Boundaries, cycles, dependencies, all the things our diagrams promise the code will definitely respect forever. ;)</p>
<p>It also gave me a convenient excuse to explore tools I do not use every day, particularly OpenCode, and see what would happen if I pointed an unattended agent harness at a real engineering backlog.</p>
<p>I am lucky enough to work for a company that encourages us to stay at the cutting edge of frontier models. That is a genuine privilege. It can also become a vice. When the biggest and cleverest model is always within reach, using it for everything starts to feel less like a decision and more like the default setting.</p>
<p>At AWS, Leadership Principles guide how decisions are approached, and one of the most relevant here is Frugality:</p>
<blockquote>
<p><strong>Accomplish more with less. Constraints breed resourcefulness, self-sufficiency, and invention. There are no extra points for growing headcount, budget size, or fixed expense.</strong> (<a href="https://www.amazon.jobs/content/en/our-workplace/leadership-principles">Amazon Leadership Principles</a>)</p>
</blockquote>
<p>It is easy to discuss frontier models as if everyone has the same freedom to choose them. Many companies do not. They work with real financial constraints every day, and those constraints do not disappear just because the model at the top of a leaderboard is especially shiny. Constant access to frontier models can give those of us at the cutting edge a blind spot: we start optimizing for maximum capability before asking how much capability the task actually needs.</p>
<p>We humans have a wonderfully simple instinct when choosing AI models: find the biggest, cleverest one available and point it at everything. Architecture review? Biggest model. Rename a field? Biggest model. Reformat a table? You guessed it, summon the expensive oracle.</p>
<p>I understand the appeal. If you can speak to God, why chat to Einstein? Nobody wants to explain that the production incident happened because they tried to save twelve cents on tokens.</p>
<p>But there is another question hiding underneath all of this, and it became the question I really wanted to answer:</p>
<blockquote>
<p><strong>What is the least expensive model that can reliably clear the quality bar for the work in front of us?</strong></p>
</blockquote>
<p>That is model rightsizing. It sounds like something invented by a committee with a spreadsheet, but it becomes rather interesting once you let two very different models loose on the same real backlog.</p>
<p>ArchUnitDev gave me a way to try it. It is an unattended software-engineering harness that works through GitHub issues one by one. The same C# architecture-testing library was built twice from the same starting commit and the same 46-issue backlog. One run used DeepSeek V4 Flash. The other used Claude Opus 5.5 on Amazon Bedrock. Each model had to do the whole job: implement the feature, review the code, criticize the tests, check the idioms, and fix whatever its various personalities complained about.</p>
<p>Both builds landed 45 of 46 issues. Issue 44, publishing to NuGet, was held back because it needed credentials. Even agents should not be given publishing credentials simply because they asked nicely.</p>
<p>The interesting part is what the two journeys cost.</p>
<h2>The Numbers, Because Eventually Someone Opens the Spreadsheet</h2>
<table><thead><tr><th>Metric</th><th style="text-align:right">DeepSeek V4 Flash</th><th style="text-align:right">Claude Opus 5.5</th></tr></thead><tbody><tr><td>Issues landed</td><td style="text-align:right">45 of 46</td><td style="text-align:right">45 of 46</td></tr><tr><td>Model spend</td><td style="text-align:right"><strong>$19.86</strong></td><td style="text-align:right"><strong>$346.70</strong></td></tr><tr><td>Model and estimated infrastructure</td><td style="text-align:right"><strong>$41.34</strong></td><td style="text-align:right"><strong>$359.98</strong></td></tr><tr><td>Model spend per landed issue</td><td style="text-align:right">$0.44</td><td style="text-align:right">$7.70</td></tr><tr><td>Issues landed on the first attempt</td><td style="text-align:right">40</td><td style="text-align:right">43</td></tr><tr><td>Issues abandoned at least once</td><td style="text-align:right">5</td><td style="text-align:right">2</td></tr><tr><td>Model turns</td><td style="text-align:right">7,183</td><td style="text-align:right">4,033</td></tr><tr><td>Output tokens</td><td style="text-align:right">9.74 million</td><td style="text-align:right">4.40 million</td></tr></tbody></table>
<p><img src="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-comparison-table.svg" alt="The benchmark results at a glance. Both models landed 45 of 46 issues, with a 17.5 times difference in model spend."/></p>
<p><em>The benchmark results at a glance. Both models landed 45 of 46 issues, with a 17.5 times difference in model spend.</em></p>
<p><img src="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-cumulative-cost.svg" alt="Cumulative model spend by issue. Both runs landed 45 issues; issue 44 was deliberately held back."/></p>
<p><em>Cumulative model spend by issue. Both runs landed 45 issues; issue 44 was deliberately held back.</em></p>
<p>Yes, you read that correctly. Both models reached the same backlog completion result, but Opus cost 17.5 times as much in model spend.</p>
<p>Before anybody runs off to replace every frontier model with DeepSeek and buys a small yacht with the savings, there is more going on here. Opus landed three more issues on the first attempt, abandoned fewer issues, used 44% fewer turns, and produced 55% fewer output tokens. It was more direct, needed less correction, and finished in a much shorter operating window.</p>
<p>So the frontier model was better behaved. It simply did not finish more of the backlog.</p>
<p>For this particular workload, the premium bought efficiency and reliability rather than a different final outcome. That distinction is the entire article.</p>
<p><img src="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-opus-premium.svg" alt="The Opus premium bought a smoother journey, not a different destination."/></p>
<p><em>The Opus premium bought a smoother journey, not a different destination.</em></p>
<h2>Rightsizing Does Not Mean Being Cheap</h2>
<p>Let us get one thing out of the way: model rightsizing is not selecting the cheapest model and hoping that positive thinking will carry it through production.</p>
<p>Rightsizing means matching the model to the task, surrounding it with suitable controls, watching what happens, and escalating when the evidence says the first choice is out of its depth. Think of it less like finding one champion model and more like assembling a mildly eccentric toolbox. Sometimes you need the huge power drill. Sometimes you need the little screwdriver that costs almost nothing and does the job perfectly well.</p>
<p>ArchUnitDev was unusually well suited to this approach. Every issue was bounded. The repository contained explicit architecture and style rules. A deterministic gate compiled the code and ran formatting, tests, vulnerability checks, and cross-platform checks before a model reviewer was allowed to have an opinion. Three read-only critics then looked at correctness, test strength, and idiomatic conformity. Invalid verdicts failed closed. Findings went back into repair rounds, and work that did not converge was parked instead of being quietly waved through.</p>
<p>In other words, the model was not wandering through the codebase alone at night with a flashlight and good intentions.</p>
<p>The harness turned mistakes into visible, recoverable events. DeepSeek could afford to take more turns because every turn was inexpensive, while the surrounding machinery stopped an inexpensive mistake from becoming an inexpensive production incident.</p>
<p>This leads to the important bit: <strong>the model capability you need depends partly on the strength of the system around the model.</strong> A frontier model can compensate for vague instructions and weak verification, but using intelligence as a substitute for engineering discipline is quite an expensive hobby.</p>
<h2>Chinese Open-Weight Models Are No Longer Sitting at the Children&#x27;s Table</h2>
<p>DeepSeek is operated by Hangzhou DeepSeek Artificial Intelligence Co., Ltd., which identifies its registered address as being in China. Its official V4 release describes DeepSeek V4 Flash as a Mixture-of-Experts model with 284 billion total parameters and 13 billion active parameters. So, despite being the cheaper option here, it is not exactly a small woodland creature.</p>
<p>The model is positioned for fast and economical agentic workloads, and its weights are published under the MIT license. DeepSeek also publishes its own benchmark results, although vendor benchmarks should always be consumed with the same healthy skepticism as a restaurant describing its own lasagna as world famous.</p>
<p>The ArchUnitDev result is more useful because it asks a narrower and less glamorous question: can this model complete a long, dependency-ordered software build inside a fairly demanding agent harness?</p>
<p>In this experiment, yes, it can.</p>
<p>That does not make DeepSeek interchangeable with every proprietary frontier model. It does mean Chinese open-weight models deserve to be evaluated as serious production candidates rather than dismissed as interesting science projects. Where tasks are testable, failures are contained, data-handling requirements are understood, and inference happens often enough for cost to matter, they may be more than good enough. And whether we like it or not, they are going to get better.</p>
<p>&quot;Good enough&quot; sounds underwhelming until it delivers the same backlog outcome for a seventeenth of the model spend. There is an inflection point somewhere between 18 times the cost and double the cost where these models start to become very appealing.</p>
<h2>So What Did the Extra Money Buy?</h2>
<p>It would be easy, and wrong, to conclude that Opus brought nothing to the party except a more impressive bar tab. It is a monster of a model and frontier for a reason.</p>
<p>Opus needed less correction and fewer attempts. That matters when the task is ambiguous, the specification is thin, mistakes carry real consequences, or no deterministic test can tell you whether the answer is nonsense. In those situations, fewer retries and stronger first-pass performance can be worth considerably more than the token bill suggests.</p>
<p>The cost ratio was also not remotely consistent. Across the 45 completed issues, Opus cost between 3.2 and 70.1 times as much as DeepSeek, with a median ratio of 18.1. Extra review rounds explain much of that spread. Every additional round starts another context-heavy model session, and expensive context remains expensive even when the agent is using it to rediscover something it forgot five minutes ago.</p>
<p>Prompt caching adds another wrinkle. Amazon Bedrock documentation notes that cache reads receive a discounted rate, while cache writes may cost more than normal input. In the Opus run, cache writes were the largest measured cost component at $165.64, or 47.8% of model spend. Output cost $96.81 and cache reads cost $84.22.</p>
<p>Nearly half the Opus model bill came from writing context into caches. If your model-routing spreadsheet looks only at the headline input and output price you may have carefully optimized the smaller half of the problem.</p>
<h2>A Routing Strategy for People Who Enjoy Sleeping at Night</h2>
<p>You do not need a grand artificial-intelligence parliament to apply this. A practical escalation ladder works rather well:</p>
<p><img src="https://agentsandultras.com/images/posts/model-rightsizing/model-rightsizing-escalation-ladder.svg" alt="Start economical, verify deterministically, and escalate when the evidence asks you to."/></p>
<p><em>Start economical, verify deterministically, and escalate when the evidence asks you to.</em></p>
<ol>
<li><strong>Classify the task.</strong> Consider ambiguity, business impact, context size, testability, and reversibility. Renaming an internal field is not the same thing as rewriting an authorization boundary, even if both arrive as innocent-looking tickets.</li>
<li><strong>Start with the least expensive qualified model.</strong> Use economical models for bounded implementation, extraction, transformation, and other work where automated checks can catch mistakes.</li>
<li><strong>Escalate when the evidence asks you to.</strong> Repeated gate failures, contradictory critic findings, stalled progress, or a high-risk decision are good reasons to call in the expensive oracle.</li>
<li><strong>Keep deterministic checks independent.</strong> Compilation, tests, schemas, policy checks, and security controls should not depend on the same model declaring itself brilliant.</li>
<li><strong>Measure the whole workflow.</strong> Track accepted outcomes, retries, elapsed time, human intervention, cache behavior, and infrastructure. Price per token is interesting, but cost per useful result pays the bill.</li>
<li><strong>Recalibrate regularly.</strong> Models and prices change at a slightly absurd pace. Today&#x27;s premium model may be tomorrow&#x27;s sensible default, and today&#x27;s benchmark champion may be forgotten by next quarter.</li>
</ol>
<p>The goal is not to keep frontier models locked in a glass cabinet for special occasions. It is to spend their capability where it changes the result.</p>
<h2>Before We Get Carried Away</h2>
<p>Now for the caveats, because every benchmark eventually reaches the section where the author admits reality is complicated.</p>
<p>First, each model reviewed its own work. A landed issue means its own critics were satisfied and the deterministic gate passed. It does not mean an independent judge applied exactly the same semantic quality standard to both repositories. Asking a model whether its own work is excellent has certain annual performance review vibes.</p>
<p>Second, both final test suites were green, but they were structured differently. DeepSeek produced 1,852 tests and Opus produced 1,534. More tests do not automatically mean better tests. You can write ten excellent tests, or two thousand tests that enthusiastically confirm the code does whatever the code currently does. Mutation testing or an independent hidden suite would tell us much more.</p>
<p>Third, the runs were not operationally identical. They happened about a month apart, used different regions and instance types, and the first seven DeepSeek issues ran on an earlier harness revision. Some early DeepSeek abandonments may therefore belong partly to the harness rather than the model. Software blaming AI for its own childhood mistakes would be a little unfair.</p>
<p>Fourth, DeepSeek&#x27;s $19.86 came from provider pricing reported through OpenCode and did not have the same provider-side cross-check available for Opus. The Opus token counts were compared with Amazon CloudWatch and came within roughly 3% to 5%.</p>
<p>These limitations stop us from claiming universal quality equivalence. They do not make the main observation disappear: inside this controlled, fail-closed harness, a much less expensive open-weight model completed the same issue backlog.</p>
<h2>The Answer Is a Portfolio, Not a Winner</h2>
<p>Model conversations have a habit of turning into horse races. Somebody produces a leaderboard, everybody picks a favorite, and a week later a new model arrives to knock over the refreshments.</p>
<p>Production systems need something less dramatic: a portfolio. Some tasks deserve the strongest model available. Others need a model that is strong enough because deterministic software, explicit constraints, and escalation policies provide the rest of the assurance.</p>
<p>The ArchUnitDev benchmark suggests a practical default. Start with the least expensive model that has demonstrated success on the actual workload. Watch the correction signals. Escalate deliberately. In this case, DeepSeek required more turns and a little more patience, but it still reached the target for 17.5 times less model spend.</p>
<p>This is not an argument against frontier models. It is an argument against paying frontier prices before proving that the workload needs one.</p>
<p>So the next time somebody reaches automatically for the largest model in the catalog to rename a field, perhaps gently move their hand away from the expensive oracle. There is probably a perfectly good screwdriver nearby.</p>
<h2>Sources</h2>
<h3>Benchmark evidence</h3>
<ul>
<li><a href="https://github.com/RobeyBeswick/ArchUnitDev/blob/main/ArchUnitSharp-build-report.md">DeepSeek V4 Flash build report</a></li>
<li><a href="https://github.com/RobeyBeswick/ArchUnitDev/blob/main/ArchUnitSharpTest-opus-5-5-report.md">Claude Opus 5.5 build report</a></li>
<li><a href="https://github.com/RobeyBeswick/ArchUnitDev/blob/main/ArchUnitSharp-deepseek-v4-flash-vs-opus-5-5.md">DeepSeek V4 Flash versus Opus 5.5 comparison</a></li>
<li><a href="https://github.com/RobeyBeswick/ArchUnitSharp">DeepSeek-built ArchUnitSharp repository</a></li>
<li><a href="https://github.com/RobeyBeswick/ArchUnitNET">Opus-built ArchUnitNET repository</a></li>
</ul>
<h3>Model and platform sources</h3>
<ul>
<li><a href="https://api-docs.deepseek.com/news/news260424">DeepSeek V4 Preview Release</a></li>
<li><a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash">Official DeepSeek V4 Flash model card and weights</a></li>
<li><a href="https://cdn.deepseek.com/policies/en-US/deepseek-privacy-policy.html">DeepSeek Privacy Policy</a></li>
<li><a href="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html">Amazon Bedrock prompt caching</a></li>
<li><a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5-5.html">Amazon Bedrock Claude Opus 5.5 model card</a></li>
</ul>
<blockquote>
<p><strong>Last verified.</strong> Benchmark reports and linked official sources checked on 2026-09-26. The quantitative comparison is specific to this workload and harness configuration.</p>
</blockquote>
<p>The views expressed here are my own and do not necessarily reflect the views of Amazon Web Services (AWS) or Amazon.com.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
