<?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: NativePort</title>
    <description>The latest articles on DEV Community by NativePort (nativeport-ai).</description>
    <link>https://dev.to/nativeport-ai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14262%2F594a73a8-e245-4eff-91ac-cc2e1914f348.png</url>
      <title>DEV Community: NativePort</title>
      <link>https://dev.to/nativeport-ai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nativeport-ai"/>
    <language>en</language>
    <item>
      <title>One Clock for the Whole Run: Deadline Budgets for AI Agent Web Tools</title>
      <dc:creator>Native Port</dc:creator>
      <pubDate>Thu, 20 Aug 2026 17:22:18 +0000</pubDate>
      <link>https://dev.to/nativeport-ai/one-clock-for-the-whole-run-deadline-budgets-for-ai-agent-web-tools-36m4</link>
      <guid>https://dev.to/nativeport-ai/one-clock-for-the-whole-run-deadline-budgets-for-ai-agent-web-tools-36m4</guid>
      <description>&lt;p&gt;&lt;em&gt;Give an agent run a single monotonic deadline, then lease every per-attempt timeout and backoff sleep out of what is left of it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An agent answering one question rarely makes one web call. It searches, fetches a few pages, extracts fields, and sometimes drives a browser to confirm something. Each of those steps usually arrives with its own configuration: a per-request timeout here, three retries there, an exponential backoff somewhere else.&lt;/p&gt;

&lt;p&gt;Configured that way, the worst case is a sum nobody ever computed. Four steps at ten seconds each, times three attempts, is two minutes of patience before the backoff sleeps are counted, and no caller agreed to it. The user gave up long before. Depending on the provider, some of those abandoned attempts are still charged, and each of them still consumed a slot in whatever rate limit applies.&lt;/p&gt;

&lt;p&gt;The fix is small and structural: decide the deadline once, at the top of the run, and make every step downstream derive its own limits from what remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deadline is not a timeout
&lt;/h2&gt;

&lt;p&gt;A timeout is a duration attached to one operation. A deadline is an instant shared by everything the run touches. That difference matters because durations do not compose. Two operations with five-second timeouts can take eleven seconds together, and neither one violated its contract.&lt;/p&gt;

&lt;p&gt;An instant composes trivially. Every step asks the same question, &lt;code&gt;how much time is left&lt;/code&gt;, and every step gets an answer that already accounts for what earlier steps spent.&lt;/p&gt;

&lt;p&gt;Store that instant on a monotonic clock. Python's &lt;code&gt;time.monotonic()&lt;/code&gt; returns a value whose reference point is undefined, so only differences between two readings are meaningful, and it cannot go backward when the system clock is adjusted. A wall-clock deadline can be moved by an NTP correction in the middle of a run, which is a rare failure that is very hard to reproduce.&lt;/p&gt;

&lt;p&gt;The undefined reference point has one consequence worth planning for. A monotonic instant is meaningful only inside the process that read it. When the deadline crosses a process or a service boundary, send remaining milliseconds instead, and have the receiver convert it back into a local instant on arrival. Each side then keeps its own clock, and the only thing on the wire is a duration that both ends can interpret without agreeing on a reference point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule one: the per-attempt timeout is a lease
&lt;/h2&gt;

&lt;p&gt;Treat the configured per-attempt timeout as a ceiling, not a value. What a step actually receives is the smaller of that ceiling and what the budget has left:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;granted = min(configured, remaining - floor)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The subtraction is the interesting part. If a call is allowed to consume the last microsecond, the run ends with a timeout and nothing to show for it. Reserving a floor leaves room to record the outcome, release resources, and return a partial answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule two: refuse calls that cannot finish
&lt;/h2&gt;

&lt;p&gt;When the remaining budget drops below the floor, the correct action is to raise locally rather than to start a remote call. A call given far less budget than the endpoint usually needs to answer is very likely to time out, and a doomed attempt still occupies a connection and a slot in whatever rate limit applies. On metered APIs it may be charged as well, since some providers bill on the request rather than on a successful response.&lt;/p&gt;

&lt;p&gt;Refusing early converts a guaranteed slow failure into an immediate one, which is what the caller wanted anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule three: sleeping is spending
&lt;/h2&gt;

&lt;p&gt;Backoff is a common place where budgets leak, because a sleep looks like inactivity rather than consumption. It is not: a retry sleep spends the same seconds a request would.&lt;/p&gt;

&lt;p&gt;So bound the backoff twice. Cap its exponential growth at a maximum, and check the chosen sleep against the remaining budget before committing to it. If the sleep would consume what is left, stop retrying and surface the failure while there is still time to report it.&lt;/p&gt;

&lt;p&gt;Use jitter on the sleep. The AWS Builders' Library discussion of timeouts, retries, and backoff argues for randomizing the wait so that clients which failed together do not retry together; full jitter picks uniformly between zero and the current cap. Without it, a provider recovering from an outage can be hit by a synchronized wave of the same clients that just failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule four: retry only what is safe to retry
&lt;/h2&gt;

&lt;p&gt;A budget check answers whether there is time to retry. It does not answer whether retrying is correct. RFC 9110 defines which request methods are safe and which are idempotent, and idempotency is the property that makes an automatic retry harmless. A repeated GET on a search endpoint is usually safe to retry, because the method is defined as read-only, though the specification constrains the method and not the implementation behind it, and the second request still costs quota. A repeated POST that queues a browser job may run the job twice unless the API accepts a client-supplied idempotency key.&lt;/p&gt;

&lt;p&gt;The same specification defines &lt;code&gt;Retry-After&lt;/code&gt;, the field a server sends to say how long to wait: RFC 9110 describes it on 503 and on redirects, and servers commonly send it with 429 as well. When you get one, it should beat your local backoff calculation, and it should still be checked against the deadline. A &lt;code&gt;Retry-After&lt;/code&gt; longer than the remaining budget is a signal to give up, not to wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  An implementation
&lt;/h2&gt;

&lt;p&gt;The following is a synthetic, self-contained example. The timings, failures, and outputs are fabricated to make the control flow observable, and they are not measurements of any real provider.&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Synthetic deadline-budget demo. Fabricated timings, not provider measurements.&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;BASE_BACKOFF_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;
&lt;span class="n"&gt;MAX_BACKOFF_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.40&lt;/span&gt;
&lt;span class="n"&gt;FLOOR_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;  &lt;span class="c1"&gt;# below this, no remote attempt is worth starting
&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeadlineExceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;One monotonic deadline, shared by every step of a single agent run.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;total_s&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want_s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Largest timeout worth granting, or a refusal if nothing useful is left.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;FLOOR_S&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;DeadlineExceeded&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s left, below floor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;want_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;FLOOR_S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per_attempt_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;timeout_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per_attempt_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;timeout_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Transient&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;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_BACKOFF_S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BASE_BACKOFF_S&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;sleep_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# full jitter
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sleep_s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;FLOOR_S&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;DeadlineExceeded&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: backoff outlasts the deadline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sleep_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;last_error&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;(attempt=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hangs_first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# cut short by the per-attempt timeout
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body(attempt=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Budget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;quick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;hangs_first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;quick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per_attempt_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;per_attempt_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__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="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body(attempt=2)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granted&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;granted&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;  &lt;span class="c1"&gt;# the whole plan finished inside the one deadline it was given
&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;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;DeadlineExceeded&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&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;refused before dialing out: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&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="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;plan complete, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s unused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three properties are worth reading off that code. &lt;code&gt;expires_at&lt;/code&gt; is written once and never extended, so no step can grant itself more room by asking twice. Every timeout passed to &lt;code&gt;asyncio.wait_for&lt;/code&gt; comes from &lt;code&gt;lease&lt;/code&gt;, so the ceiling and the remaining budget are enforced in one place. And the backoff sleep is checked against the budget before it happens, so a retry cannot outlive the run it belongs to. The second run never reaches that check: its budget starts below the floor, so &lt;code&gt;lease&lt;/code&gt; refuses before the first request is dialed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the numbers
&lt;/h2&gt;

&lt;p&gt;A budget makes limits explicit, which immediately raises the question of what the limits should be. Percentiles from your own traffic are the best source. Where you have none yet, published evaluations give a starting point: NativePort, for example, publishes &lt;a href="https://nativeport.ai/leaderboards/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=agent_web_tool_deadline_budgets" rel="noopener noreferrer"&gt;run-dated latency and error-rate figures per web capability&lt;/a&gt;, alongside quality and a cost metric, so that a search timeout and a browser timeout are not chosen from the same number. Whatever the source, keep the run date attached, because provider behavior drifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it visible
&lt;/h2&gt;

&lt;p&gt;Finally, record what the budget did. Log the granted timeout and the remaining budget on every attempt, not just the final outcome, so that a slow run can be explained without rerunning it.&lt;/p&gt;

&lt;p&gt;OpenTelemetry's HTTP semantic conventions already cover part of this: retries and redirects produce more than one physical request for one logical call, and each resend carries &lt;code&gt;http.request.resend_count&lt;/code&gt;. Add the budget fields beside it and a trace answers the question that matters during an incident, which is whether the deadline was too small or the provider too slow.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.python.org/3/library/asyncio-task.html#timeouts" rel="noopener noreferrer"&gt;Coordinating Concurrent Tasks: Timeouts&lt;/a&gt; and &lt;a href="https://docs.python.org/3/library/time.html#time.monotonic" rel="noopener noreferrer"&gt;time.monotonic&lt;/a&gt;, Python standard library documentation, on &lt;code&gt;asyncio.wait_for&lt;/code&gt; and on the monotonic clock whose reference point is undefined.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/" rel="noopener noreferrer"&gt;Timeouts, retries, and backoff with jitter&lt;/a&gt;, Amazon Builders' Library, on capping retries and randomizing waits so clients do not retry in unison.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc9110" rel="noopener noreferrer"&gt;RFC 9110: HTTP Semantics&lt;/a&gt;, IETF, on safe and idempotent methods and on the &lt;code&gt;Retry-After&lt;/code&gt; header field.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/specs/semconv/http/http-spans/" rel="noopener noreferrer"&gt;Semantic conventions for HTTP spans&lt;/a&gt;, OpenTelemetry, on representing resends with &lt;code&gt;http.request.resend_count&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;We’re the NativePort team, whose public leaderboards are cited once above as one possible source of published latency figures. AI assisted with drafting this article. During preparation, the code example was run and every cited URL was checked; editorial approval of this exact copy remains separate. All timings, failures, and outputs shown here are synthetic and describe no real provider.&lt;/p&gt;

</description>
      <category>python</category>
      <category>asyncio</category>
      <category>ai</category>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>Reproducible Benchmarks for Web-Access APIs: A Five-Layer Design</title>
      <dc:creator>Native Port</dc:creator>
      <pubDate>Wed, 12 Aug 2026 02:56:15 +0000</pubDate>
      <link>https://dev.to/nativeport-ai/reproducible-benchmarks-for-web-access-apis-a-five-layer-design-2p1m</link>
      <guid>https://dev.to/nativeport-ai/reproducible-benchmarks-for-web-access-apis-a-five-layer-design-2p1m</guid>
      <description>&lt;p&gt;Benchmarking a web-access API — search, scraping, extraction, document parsing, browser actions — is easy to do once and hard to do twice. Target pages change. The provider ships an update. Your harness gains a retry. Three months later the numbers move, and nothing in the output says which of those three moved them.&lt;/p&gt;

&lt;p&gt;Reproducibility here does not mean identical numbers on a second run; against the live web, you will not get them. It means every published number carries enough structure to say what was measured, over which population, on what date. Five layers produce that property.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: A versioned task contract
&lt;/h2&gt;

&lt;p&gt;The unit of comparison is a task, not a call. Keep the corpus as data, separate from harness code: each task carries a stable &lt;code&gt;task_id&lt;/code&gt;, the capability it exercises, its input — a query, a URL, a target schema — and the pass criteria that decide whether a response is usable. Every system under test receives the identical list.&lt;/p&gt;

&lt;p&gt;Two rules make that list trustworthy. Hold it fixed across systems within a run: comparing one scraper on easy pages against another on pages behind commercial bot defenses ranks the sample, not the tools. And version it — when you add, remove, or reword a task, bump the corpus version and re-run everything, because numbers produced under &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt; are different measurements that share a metric name.&lt;/p&gt;

&lt;p&gt;Keep the corpus version distinct from the run identifier; one says what was asked, the other when. And build the corpus around what breaks: scanned PDFs, sites that refuse datacenter IPs, queries needing synthesis across sources. Easy tasks compress every system toward the same score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: A normalized observation schema
&lt;/h2&gt;

&lt;p&gt;Record one row per attempt, before aggregating anything. A harness that computes a mean inside the request loop loses the ability to answer any question nobody anticipated.&lt;/p&gt;

&lt;p&gt;A workable attempt record holds &lt;code&gt;run_id&lt;/code&gt;, &lt;code&gt;corpus_version&lt;/code&gt;, &lt;code&gt;task_id&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;capability&lt;/code&gt;, &lt;code&gt;observed_at&lt;/code&gt;, &lt;code&gt;latency_ms&lt;/code&gt;, &lt;code&gt;billed_usd&lt;/code&gt;, &lt;code&gt;outcome&lt;/code&gt;, and &lt;code&gt;outcome_reason&lt;/code&gt;. Write &lt;code&gt;observed_at&lt;/code&gt; as an RFC 3339 date-time with an explicit offset; a bare local timestamp turns ambiguous as soon as runs cross machines or zones.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;outcome&lt;/code&gt; deserves a small closed vocabulary — &lt;code&gt;usable&lt;/code&gt;, &lt;code&gt;unusable&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt; — because the distinction that matters is not the HTTP status. A &lt;code&gt;200 OK&lt;/code&gt; carrying an empty extraction is a failed task and a billed call. Folding that into a success rate is the most common way a benchmark flatters what it measures.&lt;/p&gt;

&lt;p&gt;The OpenTelemetry metrics data model is worth borrowing from even if you never emit an OTLP point. It requires every data point to carry the time window it covers, and sums to declare whether they are delta or cumulative rather than leaving a reader to guess. Benchmark rows deserve the same discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: Named denominators and failure accounting
&lt;/h2&gt;

&lt;p&gt;Every rate has a denominator, and in a benchmark of web tools the denominators differ inside a single table. A latency median covers attempts that produced a timing. An error rate covers all attempts. A cost-per-usable-result covers usable results only, while failed-but-billed calls stay in the numerator.&lt;/p&gt;

&lt;p&gt;So publish the denominator as a field, not a footnote. This normalizer turns synthetic attempt observations into long-form metric rows, each carrying the name and size of its own denominator. The values illustrate the shape; they are not measurements of any real service.&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;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt;

&lt;span class="n"&gt;RUN_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-11-synthetic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;CORPUS_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo-v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;OUTCOMES&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;usable&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;unusable&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Synthetic. Not measurements of any real service.
&lt;/span&gt;&lt;span class="n"&gt;OBSERVATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task&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;t1&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;system&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;alpha&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;capability&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;search&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;outcome&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;usable&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;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;620&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billed_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.002&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;task&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;t2&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;system&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;alpha&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;capability&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;search&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;outcome&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;unusable&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;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billed_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.002&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;task&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;t3&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;system&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;alpha&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;capability&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;search&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;outcome&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;error&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;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billed_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.002&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;task&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;t1&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;system&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;beta&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;capability&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;search&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;outcome&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;usable&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;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;910&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billed_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.003&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;task&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;t2&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;system&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;beta&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;capability&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;search&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;outcome&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;usable&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;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;990&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billed_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.003&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metric_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;OUTCOMES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;  &lt;span class="c1"&gt;# no attempt escapes an outcome bucket
&lt;/span&gt;        &lt;span class="n"&gt;timed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;billed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billed_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="ow"&gt;in&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;error_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_p50_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;timed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timed_attempts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timed&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;cost_per_usable_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;billed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usable_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RUN_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;corpus_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CORPUS_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metric_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;denominator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;denominator_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;# every key present, so absent never reads as zero
&lt;/span&gt;                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome_counts&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;OUTCOMES&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="n"&gt;rows&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="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;metric_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OBSERVATIONS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metric_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alpha&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;latency_p50_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="nf"&gt;assert &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;denominator_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempts&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alpha&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;cost_per_usable_usd&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.006&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;beta&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;cost_per_usable_usd&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.003&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="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows, every denominator named&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script prints &lt;code&gt;6 rows, every denominator named&lt;/code&gt;. The latency assertion pins alpha's median to two timed attempts against three attempts: the errored call produced no timing, so it cannot join the latency population. The cost assertions show alpha billing less per attempt than beta and more per usable result, because a paid failure sits in the numerator, not the denominator.&lt;/p&gt;

&lt;p&gt;Long form — one row per system, capability, and metric — earns its verbosity. Metric sets differ by capability, so a wide table degenerates into a sparse union of columns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: Snapshots with provenance
&lt;/h2&gt;

&lt;p&gt;A result set that cannot be traced back to its inputs is an assertion, not a measurement.&lt;/p&gt;

&lt;p&gt;Hash the corpus file and the raw observation file, and record both digests in the published output. Make the serializer deterministic: fixed field order, stable sort, no wall-clock timestamp inside the artifact. A rebuild from the same inputs then produces byte-identical output, and a diff between two runs shows only what changed.&lt;/p&gt;

&lt;p&gt;Repeat the provenance fields on every row rather than once in a header. Rows get filtered, joined, and pasted into other people's notebooks; a slice carrying its own &lt;code&gt;run_id&lt;/code&gt; and &lt;code&gt;corpus_version&lt;/code&gt; survives that trip, and one that does not becomes an orphan number.&lt;/p&gt;

&lt;p&gt;The W3C's Data on the Web Best Practices covers publication: provide machine-readable metadata, state provenance and licensing, supply version information and a version history, and keep superseded versions reachable rather than rewriting numbers in place. Someone cited the old figures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 5: Limitations you state before anyone asks
&lt;/h2&gt;

&lt;p&gt;The last layer is presentational, and the one most often dropped.&lt;/p&gt;

&lt;p&gt;Start by naming the operator. A first-party benchmark — one run by a party with a commercial interest in the outcome — is not disqualified by that fact, but a reader is entitled to weigh it. One concrete example is &lt;a href="https://nativeport.ai/methodology/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=backlink_dev_reproducible_web_access_benchmarks_20260811" rel="noopener noreferrer"&gt;a published first-party benchmark methodology&lt;/a&gt; for web-access services, operated by NativePort: it keeps one versioned corpus per capability, puts a run date on every scorecard, labels each cost denominator, publishes raw per-metric values beneath each composite, and lists what its runner does not observe — uptime, SLA conformance, throughput ceilings — as claims it therefore does not make.&lt;/p&gt;

&lt;p&gt;Three habits then earn trust cheaply. Publish weak results, including unflattering ones. Treat absent as absent and never as zero, because "not measured" and "measured at zero" are opposite facts that a null-to-zero coercion merges. And put the run date beside the number everywhere, since provider behavior and site defenses shift underneath you.&lt;/p&gt;

&lt;p&gt;Hugging Face's dataset card documentation models where this belongs: a card ships with the data, carries machine-readable metadata in its header, and reserves explicit sections for collection process and limitations. Caveats belong in the artifact, not in a post that consumers of your table will never open.&lt;/p&gt;

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

&lt;p&gt;None of this needs a large harness. It needs one decision, made before the first run: that the output is a dataset with a contract, not a table in a slide.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/docs/hub/datasets-cards" rel="noopener noreferrer"&gt;Dataset Cards&lt;/a&gt; — Hugging Face Hub documentation on shipping a card alongside data, with machine-readable metadata and dedicated sections for collection process and limitations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/" rel="noopener noreferrer"&gt;Metrics Data Model&lt;/a&gt; — OpenTelemetry specification requiring data points to carry their time window and to declare aggregation temporality explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/dwbp/" rel="noopener noreferrer"&gt;Data on the Web Best Practices&lt;/a&gt; — W3C Recommendation covering metadata, provenance, licensing, versioning, and version history for published datasets.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc3339" rel="noopener noreferrer"&gt;RFC 3339&lt;/a&gt; — the internet date/time format used for the &lt;code&gt;observed_at&lt;/code&gt; field, including explicit UTC offsets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;I work on NativePort, and its public methodology is cited once above as an example of first-party benchmark disclosure. AI assisted with drafting; a human reviewed the text, the code example, and every cited source. All values shown here are synthetic.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>testing</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Capability-First Routing for Production AI Agents</title>
      <dc:creator>Native Port</dc:creator>
      <pubDate>Tue, 04 Aug 2026 19:13:13 +0000</pubDate>
      <link>https://dev.to/nativeport-ai/capability-first-routing-for-production-ai-agents-2c7c</link>
      <guid>https://dev.to/nativeport-ai/capability-first-routing-for-production-ai-agents-2c7c</guid>
      <description>&lt;p&gt;Most agent systems start with a simple integration decision: choose a provider for search, scraping, or browser automation, then call it whenever the agent needs the web.&lt;/p&gt;

&lt;p&gt;That works in a prototype. It becomes fragile in production because “web access” is not one capability.&lt;/p&gt;

&lt;p&gt;A search request needs relevant links. A scraping request needs clean content from a known URL. A sourced-answer request needs synthesis with citations. A browser-action request needs successful interaction with a live page. These tasks have different success conditions, latency profiles, costs, and failure modes.&lt;/p&gt;

&lt;p&gt;The routing unit should therefore be the &lt;strong&gt;capability&lt;/strong&gt;, not the vendor.&lt;/p&gt;

&lt;p&gt;This article presents a practical way to build that routing layer. The goal is not to choose one universally best provider. It is to make each tool call measurable, replaceable, and aligned with the outcome the agent actually needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the outcome, not the endpoint
&lt;/h2&gt;

&lt;p&gt;Before selecting a provider, define what a successful result means for each capability.&lt;/p&gt;

&lt;p&gt;A useful capability contract contains five parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input contract&lt;/strong&gt;: query, URL, schema, document, or natural-language objective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success predicate&lt;/strong&gt;: the minimum conditions that make the response usable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality measure&lt;/strong&gt;: relevance, extraction accuracy, coverage, content cleanliness, or task completion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deadline&lt;/strong&gt;: the latency budget available before the agent should fall back or stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure policy&lt;/strong&gt;: which errors are retryable, which require another provider, and which should be returned to the agent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Consider web search. A &lt;code&gt;200 OK&lt;/code&gt; response is not enough. The result can still be useless if it contains irrelevant links, misses the requested time range, or returns snippets without enough context for the next reasoning step.&lt;/p&gt;

&lt;p&gt;For document parsing, success might require readable text, a minimum page-coverage ratio, and OCR on scanned pages. For browser actions, success should be tied to the requested state change rather than the absence of an exception.&lt;/p&gt;

&lt;p&gt;This distinction prevents a common observability mistake: counting transport success as task success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build one scorecard per capability
&lt;/h2&gt;

&lt;p&gt;Once success is explicit, evaluate providers within each capability instead of combining unrelated tasks into one global ranking.&lt;/p&gt;

&lt;p&gt;A practical scorecard needs four dimensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Quality
&lt;/h3&gt;

&lt;p&gt;Quality must match the capability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search: result recall or relevance at a chosen cutoff.&lt;/li&gt;
&lt;li&gt;Scraping: content completeness, markdown cleanliness, and success on difficult pages.&lt;/li&gt;
&lt;li&gt;Structured extraction: field-level precision and recall against a known schema.&lt;/li&gt;
&lt;li&gt;Crawling: discovered-page coverage and duplicate control.&lt;/li&gt;
&lt;li&gt;Document parsing: text accuracy, reading order, and OCR coverage.&lt;/li&gt;
&lt;li&gt;Browser actions: completion of the requested interaction and verification of the resulting state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid one generic “quality” test across all of them. The metric should describe what a downstream agent can safely use.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Latency
&lt;/h3&gt;

&lt;p&gt;Measure wall-clock latency from the caller’s perspective. Median latency is useful for normal behavior, but it should not be the only number used for operational decisions.&lt;/p&gt;

&lt;p&gt;A routing policy also needs a timeout based on the agent’s total deadline. If a workflow has ten seconds left, a provider with excellent quality and a twelve-second median is not a viable primary route for that call.&lt;/p&gt;

&lt;p&gt;Tail latency matters when calls are chained. Several individually acceptable delays can consume the entire agent budget before the final synthesis step begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cost per successful result
&lt;/h3&gt;

&lt;p&gt;Price per request is easy to compare and often misleading.&lt;/p&gt;

&lt;p&gt;The operational metric is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cost per successful result = total billed cost / number of usable outcomes&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Assume one provider charges $0.001 per request and succeeds on half of the tasks. Another charges $0.0015 and succeeds on 90%. Ignoring retries and secondary processing, their effective costs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider A: $0.002 per successful result.&lt;/li&gt;
&lt;li&gt;Provider B: about $0.00167 per successful result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nominally cheaper provider is more expensive when measured against the outcome.&lt;/p&gt;

&lt;p&gt;Failures should remain in the denominator calculation even when they return a technically valid response. If the agent cannot use the data, the system still paid for an unsuccessful attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Error rate
&lt;/h3&gt;

&lt;p&gt;Track transport errors, timeouts, malformed responses, and capability-level failures separately.&lt;/p&gt;

&lt;p&gt;This separation helps answer different questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the provider unavailable?&lt;/li&gt;
&lt;li&gt;Is the integration parsing the response incorrectly?&lt;/li&gt;
&lt;li&gt;Is the provider returning valid but low-quality data?&lt;/li&gt;
&lt;li&gt;Is the target site blocking the request?&lt;/li&gt;
&lt;li&gt;Is the task outside the provider’s supported shape?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single error-rate number hides the remediation path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the corpus fixed
&lt;/h2&gt;

&lt;p&gt;Comparisons are only useful when providers receive the same work.&lt;/p&gt;

&lt;p&gt;Use a versioned task corpus per capability. Every provider in a given evaluation should receive the same queries, URLs, target schemas, deadlines, and pass criteria. When the corpus changes, increment its version and record a new run date.&lt;/p&gt;

&lt;p&gt;This controls a subtle source of bias: giving one provider easy pages and another provider pages protected by anti-bot systems, then comparing their success rates as if the workloads were equivalent.&lt;/p&gt;

&lt;p&gt;The corpus should include difficult cases, not only demo-friendly inputs. Production failures tend to appear on scanned documents, dynamic pages, rate-limited domains, unusual schemas, and queries that require evidence from more than one source.&lt;/p&gt;

&lt;p&gt;One public example of this design is the &lt;a href="https://nativeport.ai/methodology/" rel="noopener noreferrer"&gt;capability-based benchmark methodology&lt;/a&gt; operated by NativePort. It keeps a fixed corpus per capability and publishes quality, median latency, cost per successful call, and error rate with run dates. The important pattern is not its particular composite score; it is the separation of capabilities and the publication of the underlying measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate selection from execution
&lt;/h2&gt;

&lt;p&gt;The agent should ask for a capability. A routing layer should decide which provider executes it.&lt;/p&gt;

&lt;p&gt;Conceptually, the request contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;required capability;&lt;/li&gt;
&lt;li&gt;input payload;&lt;/li&gt;
&lt;li&gt;deadline;&lt;/li&gt;
&lt;li&gt;minimum quality threshold;&lt;/li&gt;
&lt;li&gt;optional constraints such as geography or output format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The router evaluates only providers that support that contract. It then selects a primary route using the latest eligible scorecard and current operational health.&lt;/p&gt;

&lt;p&gt;Do not let provider-specific request fields leak into the agent’s planning interface unless they represent a real user requirement. Otherwise, every provider change becomes an agent-prompt change.&lt;/p&gt;

&lt;p&gt;At the same time, avoid forcing every upstream response into one overly generic schema. Normalization is useful at the routing boundary, but capability-specific information should remain available when the agent needs it.&lt;/p&gt;

&lt;p&gt;A good compromise is a stable envelope containing status, timing, cost, provenance, and an outcome classification, with the capability response preserved inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat fallback as a policy, not a retry
&lt;/h2&gt;

&lt;p&gt;Retries and provider fallbacks solve different problems.&lt;/p&gt;

&lt;p&gt;A retry sends the same task to the same provider, usually after a transient failure. A fallback sends it to a different provider because the first route cannot satisfy the capability contract within the remaining budget.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/" rel="noopener noreferrer"&gt;AWS Builders’ Library guidance on retries and backoff&lt;/a&gt; explains why retries need timeouts, limits, backoff, and jitter. Unbounded retries can amplify overload and turn a partial failure into a broader incident.&lt;/p&gt;

&lt;p&gt;For agent tools, the fallback policy should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether the failure is transient;&lt;/li&gt;
&lt;li&gt;whether the call is safe to repeat;&lt;/li&gt;
&lt;li&gt;remaining latency budget;&lt;/li&gt;
&lt;li&gt;cost already spent;&lt;/li&gt;
&lt;li&gt;whether another provider offers a genuinely independent path;&lt;/li&gt;
&lt;li&gt;whether duplicate execution could create an external side effect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search and read-only scraping are usually easier to retry than browser actions that submit forms or mutate state. For stateful actions, the system needs idempotency or post-action verification before it can safely try again.&lt;/p&gt;

&lt;p&gt;A useful rule is to retry only when the same route is likely to produce a different outcome. Otherwise, switch routes or stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument outcomes, not just requests
&lt;/h2&gt;

&lt;p&gt;Routing improves only if production feedback reaches the scorecards.&lt;/p&gt;

&lt;p&gt;Record at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;capability and corpus or task class;&lt;/li&gt;
&lt;li&gt;selected provider and fallback sequence;&lt;/li&gt;
&lt;li&gt;start time, deadline, and duration;&lt;/li&gt;
&lt;li&gt;billed cost when available;&lt;/li&gt;
&lt;li&gt;transport status;&lt;/li&gt;
&lt;li&gt;parse status;&lt;/li&gt;
&lt;li&gt;capability-level success;&lt;/li&gt;
&lt;li&gt;reason for fallback;&lt;/li&gt;
&lt;li&gt;final outcome used by the agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use distributed traces to connect the agent decision, tool call, retry, fallback, and final result. The &lt;a href="https://opentelemetry.io/docs/specs/semconv/http/http-spans/" rel="noopener noreferrer"&gt;OpenTelemetry HTTP semantic conventions&lt;/a&gt; provide a standard basis for HTTP client spans, while capability and outcome fields can be added as application attributes.&lt;/p&gt;

&lt;p&gt;Be careful with high-cardinality data. Raw queries, full URLs, and extracted content may contain sensitive information and can make telemetry expensive. Prefer bounded task classes, hashed identifiers where appropriate, and explicit retention rules.&lt;/p&gt;

&lt;p&gt;Most importantly, preserve the difference between “request completed” and “agent received a usable result.” The second metric is the one the router is meant to improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roll out without creating a routing black box
&lt;/h2&gt;

&lt;p&gt;A capability router should be explainable to operators.&lt;/p&gt;

&lt;p&gt;For every decision, retain enough information to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which providers were eligible?&lt;/li&gt;
&lt;li&gt;Why was the primary route selected?&lt;/li&gt;
&lt;li&gt;Which threshold triggered the fallback?&lt;/li&gt;
&lt;li&gt;How old was the benchmark data?&lt;/li&gt;
&lt;li&gt;Did the final result meet the success predicate?&lt;/li&gt;
&lt;li&gt;What did the complete attempt cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start in shadow mode. Let the existing integration continue serving traffic while the router computes the decision it would have made. Compare those decisions with real outcomes before enabling automatic switching.&lt;/p&gt;

&lt;p&gt;Then introduce routing one capability at a time. Search is usually easier than stateful browser automation because the success predicate and retry behavior are simpler. Keep a manual override for incidents and a deterministic default for cases where score data is missing or stale.&lt;/p&gt;

&lt;p&gt;The router should degrade predictably. Missing benchmark data must not silently become evidence that a provider is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical implementation checklist
&lt;/h2&gt;

&lt;p&gt;Before enabling capability-first routing, confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every capability has a written success predicate;&lt;/li&gt;
&lt;li&gt;providers are compared on identical, versioned tasks;&lt;/li&gt;
&lt;li&gt;failures are included in effective-cost calculations;&lt;/li&gt;
&lt;li&gt;transport errors and unusable responses are classified separately;&lt;/li&gt;
&lt;li&gt;retries have limits, timeouts, backoff, and jitter;&lt;/li&gt;
&lt;li&gt;fallbacks respect the remaining deadline and side-effect risk;&lt;/li&gt;
&lt;li&gt;traces connect provider attempts to the final agent outcome;&lt;/li&gt;
&lt;li&gt;benchmark dates are visible to operators;&lt;/li&gt;
&lt;li&gt;missing or stale data has an explicit default policy;&lt;/li&gt;
&lt;li&gt;routing decisions can be explained after the fact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The central idea is simple: an agent does not need “a web provider.” It needs a successful search, extraction, crawl, parsed document, or completed browser action within a specific budget.&lt;/p&gt;

&lt;p&gt;When routing follows those capability contracts, provider choice becomes an operational policy rather than a permanent architectural commitment. That makes the agent easier to measure, safer to fail over, and less expensive to evolve.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This article is published by NativePort, whose public benchmark methodology is cited as an implementation example. It does not recommend a product or provider. The draft was prepared with AI assistance and reviewed against the cited sources and public methodology before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>agents</category>
      <category>abotwrotethis</category>
    </item>
  </channel>
</rss>
