<?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: Chen Yuan</title>
    <description>The latest articles on DEV Community by Chen Yuan (@chenyuan20509).</description>
    <link>https://dev.to/chenyuan20509</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3935918%2F55c92f67-ea0a-42da-a9f2-f44b2d4c60b2.png</url>
      <title>DEV Community: Chen Yuan</title>
      <link>https://dev.to/chenyuan20509</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chenyuan20509"/>
    <language>en</language>
    <item>
      <title>Object Storage Can Replace a Database Under Four Contracts</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Thu, 10 Sep 2026 15:00:24 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/object-storage-can-replace-a-database-under-four-contracts-5dbe</link>
      <guid>https://dev.to/chenyuan20509/object-storage-can-replace-a-database-under-four-contracts-5dbe</guid>
      <description>&lt;p&gt;Object storage is not a database. It stores bytes under keys. Yet it can serve as a database-like control-plane store when the workload fits a narrow set of contracts. The useful test is not a yes-or-no replacement claim. It is the set of guarantees the application needs, the ones the storage service supplies, and the ones the application must rebuild. The discussion around Ampbase and Tigris makes the tradeoffs concrete, because the system in question did not start from a desire to be clever. It started from a desire to avoid running a database it did not need, and the work was in rebuilding four guarantees that a database would have provided by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data shape decides the storage engine
&lt;/h2&gt;

&lt;p&gt;The first thing to establish is that object storage is not a general replacement for a database. It is a replacement for a particular shape of data and access. In the Ampbase case, the data partitioned cleanly per organization, the writes were low volume and mostly uncontended, the reads were point lookups on keys the application controlled, and the interesting history was append-only by nature. Those properties are not incidental. They are the reason the design works at all.&lt;/p&gt;

&lt;p&gt;Change one of those assumptions and the design changes. Contention on one key turns CAS retries into a possible livelock. A transaction across objects cannot be built from per-object preconditions. Ad-hoc queries turn each new question into a hand-written backfill and index, with application code responsible for correctness.&lt;/p&gt;

&lt;p&gt;Storage selection follows the data shape and the questions the application asks. Small, append-only, pre-partitioned, already-aggregated records can fit object storage. High-volume, frequently updated, cross-referenced state is a database workload, and object storage will make you rebuild one badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four guarantees behind the headline
&lt;/h2&gt;

&lt;p&gt;When people reach for a database engine, they often need four behaviors: uniqueness, transactions, indexes, and history. Object storage supplies none of those as database features. Some services do supply two useful primitives: strong read-after-write consistency and conditional writes. The application can build the other behaviors only within the limits of those primitives.&lt;/p&gt;

&lt;p&gt;Strong read-after-write consistency means that a successful write is immediately visible to a later read. Amazon S3 documents this behavior for new objects, overwrites, deletes, and listings, and dates its change to December 2020. Without a strong consistency model, a stale read could make an application misjudge uniqueness or compare-and-swap state.&lt;/p&gt;

&lt;p&gt;Conditional writes turn compare-and-swap into HTTP preconditions. &lt;code&gt;If-None-Match: *&lt;/code&gt; creates only when a key is absent. &lt;code&gt;If-Match: {etag}&lt;/code&gt; replaces only the version whose ETag was read. These checks run against the object's latest state within the bucket's consistency model. They provide a concurrency primitive, not a complete transaction system.&lt;/p&gt;

&lt;p&gt;These primitives map to the four behaviors in narrow ways. Uniqueness is conditional creation on a content-derived key. Transaction-like mutation is a read, pure computation, and conditional replacement of one object. An index is a key designed around a frequent lookup. History is an append-only key space whose order matches the question being asked.&lt;/p&gt;

&lt;p&gt;Each contract has a boundary. Every writer must use conditional creation for uniqueness. A mutation retry must be pure. An index answers only its planned question, and an append-only log records only the events that were successfully appended.&lt;/p&gt;

&lt;h2&gt;
  
  
  A control plane built from immutable objects
&lt;/h2&gt;

&lt;p&gt;An object-first control plane can use two bucket layers. A global directory bucket records organizations; each organization gets a scoped bucket. Credentials for one customer cannot address another customer's objects, so tenant isolation is an infrastructure boundary rather than an application predicate. There is no &lt;code&gt;WHERE org_id = ?&lt;/code&gt; for a developer to forget.&lt;/p&gt;

&lt;p&gt;Within a customer bucket, a few key families provide the database-like behavior: a membership key for point lookup, a mutable pointer to the active configuration, immutable version objects, and append-only event objects. The application controls the paths, so these records do not require a general query engine.&lt;/p&gt;

&lt;p&gt;Version objects and events are never rewritten, so their audit property comes from the key space rather than from an &lt;code&gt;UPDATE&lt;/code&gt; path. Pointers are different: deploying overwrites the active pointer, and rollback moves it to an older version. The history remains because version objects are retained.&lt;/p&gt;

&lt;p&gt;The first block shows the key pattern: derive paths from the lookup, then create immutable records conditionally.&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;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ulid&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;membership_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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="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="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&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;members/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.json&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;version_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel_id&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;created_at&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="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;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;versions/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel_id&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;created_at&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.pb&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;event_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_id&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;event_time&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;event_id&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="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;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;events/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;org_id&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;event_time&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;event_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.pb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The membership key is an index expressed as a path. Hashing the email lets the application compute one &lt;code&gt;GetObject&lt;/code&gt; without a listing or secondary index. Version keys use ULIDs; because object listings are lexicographic, a prefix or time range can return history in order. That works only for access patterns designed into the key names. It is not an &lt;code&gt;ORDER BY&lt;/code&gt; substitute for arbitrary queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What conditional writes can and cannot protect
&lt;/h2&gt;

&lt;p&gt;Conditional writes protect one key from concurrent mutation. They do not coordinate multiple keys or create cross-key atomicity. That boundary is easy to forget when the primitive feels strong.&lt;/p&gt;

&lt;p&gt;The second block shows the two forms: create-if-absent and replace-if-unchanged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;precondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etag&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&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;etag&lt;/span&gt; &lt;span class="o"&gt;==&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="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;*&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;etag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;put_with_precondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&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;key&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;etag&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="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;if_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;if_none_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;precondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&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;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IfMatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;if_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IfNoneMatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;if_none_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concurrent writers to one key receive one success and one precondition error. The loser must re-read: the existing record may be its own retried write or a real conflict. That idempotency check belongs in the application contract.&lt;/p&gt;

&lt;p&gt;A mutation function runs again after a conflict, so it must be pure. Side effects inside it, such as an email, counter, or payment call, may happen once per attempt. The compiler will not enforce this; the interface and review must.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why indexes and history change the design
&lt;/h2&gt;

&lt;p&gt;An index precomputes lookup paths; a unique index also rejects duplicate values. Object-first systems encode both in key names and conditional creation. The tradeoff is fixed access patterns: the application can ask only questions it designed for.&lt;/p&gt;

&lt;p&gt;Every access pattern becomes a precomputed key. A new question requires a new key and backfill, which is a hand-written index migration. That is the largest ongoing tax, and it returns with every feature.&lt;/p&gt;

&lt;p&gt;An append-only history can resemble event sourcing, but this design stores current state directly and uses events to explain changes. The log is an audit record, not a replay engine.&lt;/p&gt;

&lt;p&gt;The third block shows an index record whose path is the lookup and whose conditional creation enforces uniqueness.&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_index_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&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;key&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&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;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&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;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IfNoneMatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without multi-object transactions, state and event writes can split. If state is written first and the process dies before the event, current state is correct but history has a gap. Every log entry is real, but completeness requires reconciliation.&lt;/p&gt;

&lt;p&gt;Deletion is also different. Removing a key erases the distinction between never written and deleted unless a tombstone remains. Keep version objects and move pointers when history matters; storage then grows until a lifecycle policy removes old data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-region writes need an explicit conflict policy
&lt;/h2&gt;

&lt;p&gt;Multi-region writes are the hardest boundary. A conditional write is evaluated against the state visible in its region. Two regions can therefore accept the same compare-and-swap before replication converges, after which only one version remains current.&lt;/p&gt;

&lt;p&gt;A read-after-write check cannot repair this because the lost update happens at the write. The safer rule is to adjudicate each CAS in one primary region and reject dependent writes elsewhere with a client-side guard. Replicas can serve reads, but the write policy must be explicit.&lt;/p&gt;

&lt;p&gt;The fourth block makes the retry policy visible: the caller supplies a pure mutation and the loop has a hard attempt limit.&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;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mutate_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&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;key&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;mutate&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="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&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="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;etag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&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="n"&gt;new_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&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="nf"&gt;put_with_precondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&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="n"&gt;new_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;etag&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;except&lt;/span&gt; &lt;span class="n"&gt;PreconditionFailed&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;==&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="k"&gt;raise&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;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;delay&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;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses a short exponential backoff and five attempts. If contention persists, the caller should surface a conflict rather than loop forever.&lt;/p&gt;

&lt;p&gt;Hot keys expose the workload assumption: continuous contention can turn retries into a livelock. Read amplification is the other cost. A directory read per request is repeated by every instance, and listing members before fetching each object creates many round trips. A read-through cache can reduce latency, but it cannot become the source of truth; cold-cache operation must remain correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision checklist for object-first systems
&lt;/h2&gt;

&lt;p&gt;Before deciding that object storage can replace a database, check the contracts. The data should partition cleanly per tenant, so that isolation is a credential boundary rather than a predicate. Writes should be low volume and mostly uncontended, so that compare-and-swap with retry resolves on the first attempt. Reads should be point lookups on keys you control, so that the access pattern is a key you chose rather than a query you wrote. The interesting history should be append-only, so that immutability is a property of the key space rather than a feature you implemented.&lt;/p&gt;

&lt;p&gt;Then check what you are giving up. You are giving up multi-key atomicity, so any invariant that spans objects has to be enforced by the application or abandoned. You are giving up ad-hoc queries, so every new question is a new key and a backfill job. You are giving up the guarantee that the audit log is complete, so if completeness matters you need a reconciliation process. You are giving up simple deletion, so retention has to be an explicit lifecycle policy. And you are giving up single-region simplicity, so multi-region writes need an explicit conflict policy that names the primary region and treats writes elsewhere as errors.&lt;/p&gt;

&lt;p&gt;If the workload fits and the contracts are acceptable, object storage can replace a database for that workload. If the workload does not fit, the honest move is to use a database and stop trying to build one from preconditions. The practical rule is this: let the workflow pick the storage engine, and only notice when the data shape has changed enough that the choice should change with it. The original discussion is at &lt;a href="https://news.ycombinator.com/item?id=49618450" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=49618450&lt;/a&gt; and the Tigris writeup is at &lt;a href="https://www.tigrisdata.com/blog/object-storage-all-need/" rel="noopener noreferrer"&gt;https://www.tigrisdata.com/blog/object-storage-all-need/&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/object-storage-can-replace-a-database-under-four-contracts" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>When Safe Browsing Says Clean but Google Ads Says Malware</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Wed, 09 Sep 2026 15:10:00 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/when-safe-browsing-says-clean-but-google-ads-says-malware-a7n</link>
      <guid>https://dev.to/chenyuan20509/when-safe-browsing-says-clean-but-google-ads-says-malware-a7n</guid>
      <description>&lt;p&gt;How can a website and a download look clean in several public checks while an advertising account is still rejected for malware? The contradiction is easier to understand when the ad campaign is treated as a delivery system rather than as a single web page.&lt;/p&gt;

&lt;p&gt;A current Hacker News discussion points to a useful case. The author of RACE, a native macOS terminal multiplexer written in Rust, reported that a Google Ads account was suspended after spending USD 500 on a campaign. The notice named "Malicious software" and "Compromised Site." The author then checked the website, the download infrastructure, the application, and several public security services, but the appeals still did not reveal which observation caused the decision.&lt;/p&gt;

&lt;p&gt;That report does not prove that Google made a mistake, and it does not reveal how the classifier worked. It does show why a publisher needs a record of the whole path from ad impression to first launch. A clean result from one layer is evidence about that layer. It is not a universal verdict about every layer that a visitor may cross.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the RACE case actually shows
&lt;/h2&gt;

&lt;p&gt;RACE is described as a terminal multiplexer that keeps shell sessions alive across application restarts. Its website is a static Bridgetown site, with downloads hosted separately. The author says it had no custom server-side application at submission time.&lt;/p&gt;

&lt;p&gt;The advertising attempt produced a suspension rather than a useful diagnostic. The notice named malicious software and a compromised site. Repeated appeals were rejected, and one path was blocked for a week. The review covered Safe Browsing, Search Console, VirusTotal, signatures, notarization, JavaScript bundles, logs, and different user agents.&lt;/p&gt;

&lt;p&gt;One detail matters more than the number of checks. RACE starts and manages background shell processes because process persistence is part of a terminal multiplexer. That behavior can look unusual to a detector even when documented. The report says the application did not inject code into other applications, change browser behavior, or conceal its process activity.&lt;/p&gt;

&lt;p&gt;Those are observations from the publisher's investigation, not the platform's internal reason. "The public checks were clean" is defensible; "the campaign was proven safe" is not supported by the same evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why clean scanners do not clear an ad campaign
&lt;/h2&gt;

&lt;p&gt;The checks in this case answer different questions. Google Safe Browsing examines URLs and warns users in Search and browsers when it detects unsafe sites. Its public status tool can tell a publisher what it reports about a URL at the time of the check. It does not promise that the URL will be treated the same way by an advertising review.&lt;/p&gt;

&lt;p&gt;Search Console's Security issues report has a different purpose. Google says the report presents findings when an evaluation determines that a site was hacked or shows behavior that could harm a visitor or their computer. The report can include sample affected URLs, but Google also says the sample may not be complete and that some issues have no example URL. A green result in that report is therefore useful evidence about the report, not a signed clearance for every ad destination and binary.&lt;/p&gt;

&lt;p&gt;Apple notarization answers a third question for macOS software. Apple describes its notary service as an automated system that scans for malicious components, checks code-signing issues, and returns a ticket that Gatekeeper can find. Apple also says notarization is not App Review. A valid Developer ID signature and ticket help a user evaluate an application, but they do not determine whether an ad platform accepts a campaign.&lt;/p&gt;

&lt;p&gt;Google Ads documentation says reviews may use multiple sources, including the ad, website, accounts, and third-party sources. It does not identify the RACE signal, but it explains why independent reports can disagree. A policy system can inspect more than a URL scanner or notarization service.&lt;/p&gt;

&lt;p&gt;The outputs are partial observations. An ad review can consider the relationship between the account, creative, destination, redirects, download, and prior activity. Treating the outputs as interchangeable creates false confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the download path as a changing state machine
&lt;/h2&gt;

&lt;p&gt;A publisher should record the path as a sequence of states, not as a screenshot of a landing page. A practical sequence is search query, ad, landing URL, HTML response, script and redirect chain, download host, archive, signed application, first launch, and process or network behavior.&lt;/p&gt;

&lt;p&gt;Each state needs a timestamp, URL or artifact identity, observation method, and result. If the download changes while the ad remains unchanged, the reviewed object may no longer be the delivered object. A redirect can also vary by referrer, region, user agent, or cookie. These are engineering possibilities, not claims about the RACE classifier.&lt;/p&gt;

&lt;p&gt;A small record type makes the distinction concrete:&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;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;artifact_sha256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;timestamp&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;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;landing-page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://downloads.example.invalid/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;artifact_sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observed_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;isolated-http-client&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The record does not make a verdict. It preserves context for comparing a later fetch with the earlier one, which matters when an appeal asks which release or response was examined.&lt;/p&gt;

&lt;p&gt;A state-machine view prevents a category error. A signed file can be safe at rest while a landing page is compromised, or a clean page can redirect to a different download. A legitimate application can also launch a helper that was not in the original hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify redirects and artifacts without trusting one layer
&lt;/h2&gt;

&lt;p&gt;The first pass should collect metadata without launching the program. Google’s security guidance warns against opening infected pages directly in a browser and recommends safer methods for examining responses. Use an isolated HTTP client and disposable environment, not a daily workstation.&lt;/p&gt;

&lt;p&gt;The redirect chain should be preserved, not reduced to the final URL. Record status codes, location headers, response headers, the request method, and the user agent. Run the same capture from a clean environment when the result matters, then compare the chains rather than choosing the most reassuring one.&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;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;build_opener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPRedirectHandler&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TraceRedirects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTTPRedirectHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&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;redirect_request&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;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_url&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;events&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;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;redirect_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_url&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;fetch_with_trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&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="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TraceRedirects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;opener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_opener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&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;release-audit/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;opener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&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;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geturl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a capture tool, not a bypass for an advertising review. Do not rotate identities to evade a policy control. Document the ordinary path a reviewer can reproduce and note whether the destination changes.&lt;/p&gt;

&lt;p&gt;The file itself needs an identity. A filename is not enough because a publisher can replace the bytes while keeping the same name. Hash the exact archive that was uploaded, hash the extracted executable when appropriate, and keep the command output with the release record.&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;hashlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sha256&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&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;stream&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;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&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;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;archive_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RACE.dmg&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&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;RACE.dmg&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;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;archive_hash&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a macOS release, keep the signing identity, entitlements, notarization result, and hash of the notarized deliverable. If a ticket is stapled after hashing, the reviewed object may differ from the object offered to users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build an evidence bundle for a manual review
&lt;/h2&gt;

&lt;p&gt;A useful appeal package is a reproducible bundle, not a pile of screenshots. Identify the ad text, destination, time window, redirect chain, response headers, HTML and script hashes, download hash, signature details, notarization status, and observation environment.&lt;/p&gt;

&lt;p&gt;Separate facts from interpretation. An &lt;code&gt;observed&lt;/code&gt; field can contain a status code or hash; a &lt;code&gt;hypothesis&lt;/code&gt; field can say that a background process may have looked unusual. This keeps an explanation from being mistaken for a platform finding.&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReviewBundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ad_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;landing_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;final_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;artifact_sha256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;observed&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;hypotheses&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="nb"&gt;str&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;write_bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReviewBundle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&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="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="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&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;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReviewBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ad_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;campaign-record-2026-09-09&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;landing_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://race-term.example.invalid/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;final_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://downloads.example.invalid/RACE.dmg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;artifact_sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;record-after-download&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observed&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;source&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;isolated-http-client&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;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;hypotheses&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;documented process persistence may need an explicit explanation&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="nf"&gt;write_bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review-bundle.json&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 example values are placeholders and must not be submitted as evidence. In a real bundle, do not redact the artifact identity or the time of observation, but do remove cookies, access tokens, and private account data. Keep the original response bytes and logs in a protected archive so the summary can be regenerated.&lt;/p&gt;

&lt;p&gt;This format also makes version changes visible. If the binary, redirect chain, or JavaScript bundle changes after an appeal, create a new bundle rather than editing the old one. A reviewer should be able to tell whether the publisher is defending the same object or a later release.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ad platforms should expose to developers
&lt;/h2&gt;

&lt;p&gt;The RACE report points to a product problem as well as a publisher problem: a generic label leaves no path to test the alleged fault. A review system should expose the category of object that triggered the decision, even if it cannot reveal a sensitive classifier: ad, destination, redirect, download, executable, account context, or third-party report.&lt;/p&gt;

&lt;p&gt;It should provide the observation time, sampled URL, response status, artifact hash when available, and environment class. It should distinguish a page warning from an account suspension. If a third-party signal was involved, the publisher needs enough information to identify the asset without seeing private detection rules.&lt;/p&gt;

&lt;p&gt;Search Console acknowledges that sample URLs may be incomplete or absent. An appeal interface should show that limit rather than treating a missing sample as proof that nothing was checked, or a clean public report as proof that the ad review had no other input.&lt;/p&gt;

&lt;p&gt;That would help both sides: a real compromise would be easier to reproduce and fix, while a false positive would be easier to isolate. The platform could protect its detection logic while giving the publisher a stable object to inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safer publishing checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Freeze the release. Record the exact ad text, destination URL, redirect chain, archive hash, executable hash, signature identity, and notarization result before submitting a campaign.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Capture the normal path with an isolated HTTP client. Preserve redirects, response headers, and final URLs. Do not open an unknown page or program on the workstation used for credentials and daily work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check the layers separately. Run Safe Browsing, review Search Console security findings, inspect the download, and verify the platform-specific signing process. Store each result with its timestamp and scope.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explain unusual behavior plainly. A terminal multiplexer that keeps shell processes alive should document that behavior, its configuration options, and its cleanup path. Documentation does not prove safety, but silence makes a legitimate behavior harder to review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Submit a factual appeal. State what was observed, what was not observed, and what remains unknown. Do not call a clean scan a certificate, and do not claim to know the classifier's reason without a platform response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep every appeal tied to an immutable bundle. If the binary or destination changes, start a new record, and ask which object and observation must change before a new review can be meaningful.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The RACE case does not establish that Google Ads misclassified a legitimate application. It establishes a narrower engineering lesson: security evidence is scoped to the system that produced it. Safe Browsing, Search Console, Apple notarization, file scanners, and ad review can each observe a different part of the same delivery path. Publishers need reproducible artifacts and a clear separation between observation and theory; platforms need review signals that let a legitimate publisher find the disputed state.&lt;/p&gt;

&lt;p&gt;Further reading: &lt;a href="https://xlii.space/eng/malicious-software-on-google-ads/" rel="noopener noreferrer"&gt;the RACE case report&lt;/a&gt;, &lt;a href="https://support.google.com/adspolicy/answer/6015406?hl=en" rel="noopener noreferrer"&gt;Google Ads policy documentation&lt;/a&gt;, &lt;a href="https://support.google.com/webmasters/answer/9044101?hl=en" rel="noopener noreferrer"&gt;Search Console security issues&lt;/a&gt;, &lt;a href="https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution" rel="noopener noreferrer"&gt;Apple notarization documentation&lt;/a&gt;, and &lt;a href="https://transparencyreport.google.com/safe-browsing/overview" rel="noopener noreferrer"&gt;Google Safe Browsing status&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/when-safe-browsing-says-clean-but-google-ads-says-malware" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Coding Agents Do Not Test Like Experts: What Agentic Verification Actually Needs</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Tue, 08 Sep 2026 13:13:03 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/coding-agents-do-not-test-like-experts-what-agentic-verification-actually-needs-laj</link>
      <guid>https://dev.to/chenyuan20509/coding-agents-do-not-test-like-experts-what-agentic-verification-actually-needs-laj</guid>
      <description>&lt;p&gt;A large evaluation of coding agents produced an awkward result. Naming a testing technique did not reliably make the agents use that technique well. In the Zstd implementation study described by &lt;a href="https://danluu.com/agentic-testing/" rel="noopener noreferrer"&gt;Dan Luu&lt;/a&gt;, agents were given instructions for TDD, QuickCheck, property-based testing, fuzzing, differential testing, mutation testing, formal methods, and other tools. The default condition, with no extra testing instruction, performed above average. Several named techniques performed worse.&lt;/p&gt;

&lt;p&gt;The point is not that testing is useless. The point is that a test library name is not a testing method. Agents can produce tests that compile, run, and miss the bug that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable result is not a missing test command
&lt;/h2&gt;

&lt;p&gt;Coding agents already know that a finished implementation should have tests. They can create a test module, run the project command, repair a failing assertion, and report a green result. That workflow looks like verification from a distance. It often checks only whether the agent's implementation agrees with the agent's own assumptions.&lt;/p&gt;

&lt;p&gt;Luu's evaluation makes that gap visible. Agents did not simply ignore every instruction. They often used the requested library or wrote something that resembled the requested technique. The problem was the missing judgment underneath it. A property-based test with a weak generator is still a weak test. A formal proof about an irrelevant invariant does not protect the code path where the defect lives. A differential test that implements the same mistake twice creates agreement, not an oracle.&lt;/p&gt;

&lt;p&gt;That distinction matters more as agents take on larger changes. A human reviewer can ask whether a test attacks the risky assumption. An agent tends to optimize for a local signal: a command finished successfully, the assertion passed, or the requested tool appeared somewhere in the diff. The harness must make the stronger question visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the evaluation actually measured
&lt;/h2&gt;

&lt;p&gt;The primary evaluation reused a Rust implementation task for Zstd. It compared 26 prompt conditions, including Default, TDD, QuickCheck, property-based testing, fuzzing, differential testing, mutation testing, Lean 4, Verus, Alloy, and other formal or testing tools. Four additional skills were tested. Correctness came from hidden tests rather than from the tests the agent wrote for itself.&lt;/p&gt;

&lt;p&gt;The main graph averaged 80 runs for each condition and effort level and compared medium with higher-effort runs. The article also discusses an IMAP RFC evaluation. The exact ranking should not be treated as a universal league table. The task, model, harness, prompt wording, and available dependencies all affect the result. The author repeatedly warns that the observed differences are noisy and that agents often failed to apply a technique in a meaningful way.&lt;/p&gt;

&lt;p&gt;The broad pattern is more useful than the ordering. Default performed above average. At higher effort, fuzzing and property-based conditions did somewhat better than formal methods on average, while the medium-effort picture was mixed. The testing skills recommended by the model underperformed, while a small custom skill did better because it pushed agents toward examining likely mistakes instead of presenting a long tutorial.&lt;/p&gt;

&lt;p&gt;This is a study of minimal instructions, not a study of what an expert can achieve with Verus, QuickCheck, or TDD. It asks what happens when an agent receives a technique label and limited guidance. That is close to how many teams deploy skills today, which is why the limitation is also the practical lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why naming a technique is not using it
&lt;/h2&gt;

&lt;p&gt;QuickCheck was a clear example. The agents generally wrote simple smoke tests, used random inputs that often followed rejection paths, and checked few meaningful properties. In 63 of 160 QuickCheck runs discussed in the study, only one property was checked. The library was present, but the reasoning that makes property-based testing useful was absent.&lt;/p&gt;

&lt;p&gt;The formal tools showed a similar split. Verus agents often proved abstract arithmetic facts instead of properties tied to the implementation. Some proofs were effectively of the form &lt;code&gt;A =&amp;gt; A&lt;/code&gt;: valid, but not useful for finding a Zstd defect. Lean, Alloy, and related conditions also relied heavily on ordinary Rust tests while adding proofs or models that did not reach the risky behavior.&lt;/p&gt;

&lt;p&gt;TDD changed the workflow more visibly. Agents wrote about twice as many tests and created failing tests earlier, but the condition still underperformed. More test-first activity did not guarantee better cases. In the four-stream jump-table feature, agents often made all four streams identical. That is a legal fixture for a simple path, but it cannot reveal a bug that appears only when the streams differ.&lt;/p&gt;

&lt;p&gt;None of this is an indictment of the tools. It is an indictment of the assumption that a tool name contains its own operating knowledge. An expert knows how to choose generators, construct an oracle, shrink a failure, inspect a counterexample, and decide whether an invariant touches the risk. A prompt that says “use QuickCheck” supplies none of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference between test presence and test power
&lt;/h2&gt;

&lt;p&gt;Test presence is easy to count. Test power is harder to observe. A suite can contain hundreds of assertions and still leave the important behavior unconstrained.&lt;/p&gt;

&lt;p&gt;The Zstd examples show why. Agents noticed that bitstream reversal could be risky, then used palindromic inputs for a relevant test. Reversing a palindrome produces the same sequence, so an encode/decode implementation with the wrong order can pass. The test is related to the feature but powerless against the defect.&lt;/p&gt;

&lt;p&gt;The same problem appeared in tests for four Huffman streams. Making every stream identical exercises the shape of the input without exercising the relationship between distinct streams. A fixture that looks realistic can still erase the dimension where the bug occurs.&lt;/p&gt;

&lt;p&gt;Differential testing adds another warning. It works when independent implementations receive the same inputs and disagree. In the study, most agents that attempted it did not build two genuinely independent implementations. They wrote the same idea twice, allowing the same wrong assumption to appear in both versions. Agreement then became a false signal of correctness.&lt;/p&gt;

&lt;p&gt;A passing test proves only that one execution satisfied one assertion. It does not prove that the assertion represents the specification, that the fixture reaches the risky state, or that the oracle is independent from the code under test. Those are separate questions, and an agent needs a separate artifact for each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A verification loop an agent can follow
&lt;/h2&gt;

&lt;p&gt;A useful verification loop starts with a claim instead of a command. The agent states what must remain true, identifies the failure mode that would violate it, chooses an oracle, and records the evidence. A small artifact can make that sequence explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;verification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;claim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;decode(encode(data)) equals data for valid inputs&lt;/span&gt;
  &lt;span class="na"&gt;risk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitstream order can be reversed in one direction&lt;/span&gt;
  &lt;span class="na"&gt;targeted_cases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;empty input&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;one-symbol input&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;non-palindromic multi-block input&lt;/span&gt;
  &lt;span class="na"&gt;adversarial_case&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;distinct values in every stream&lt;/span&gt;
  &lt;span class="na"&gt;independent_oracle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reference decoder or specification&lt;/span&gt;
  &lt;span class="na"&gt;required_evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test result&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mutation result&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;failure log when a case is rejected&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The claim gives the agent a target that can be reviewed. The targeted cases connect the claim to boundaries and ordinary behavior. The adversarial case tries to falsify the agent's interpretation. The independent oracle prevents the implementation from grading its own homework. The evidence record makes it possible to inspect what the agent actually checked.&lt;/p&gt;

&lt;p&gt;The adversarial case should be shaped around the suspected failure, not chosen because it is convenient to serialize. For a stream-order bug, a non-palindromic input is more informative than another randomly generated input that happens to collapse to the same path. For a parser, malformed delimiters and ambiguous boundaries may matter more than another valid example.&lt;/p&gt;

&lt;p&gt;A small Rust test can express the difference without pretending to be a complete proof:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;distinct_streams_exercise_ordering&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;StreamSet&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nd"&gt;vec!&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="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="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&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="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reference_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test is stronger than a palindrome because each stream carries different structure. It still does not prove that the encoder is correct for every valid input. It only supplies one independent, reviewable check aimed at a particular failure mode.&lt;/p&gt;

&lt;p&gt;The loop should also have a stop condition. If a mutation survives, the agent must either add a case that kills it or explain why the mutation is outside the specification. A green command with surviving mutations is not a green verification result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn verification into an observable interface
&lt;/h2&gt;

&lt;p&gt;A prompt or skill can nudge an agent, but the environment decides what the agent can learn from failure. The evaluation found that agents could name risky areas such as FSE, Huffman coding, and bit readers without testing them effectively. That is a feedback problem as much as a reasoning problem.&lt;/p&gt;

&lt;p&gt;A harness should separate implementation context from verification context where practical. It should provide bounded tools, expose mutation results, preserve failing inputs, and make the independent oracle easy to call. It should also stop runaway work and attach each artifact to a change identifier. The agent then receives information that a longer instruction cannot provide.&lt;/p&gt;

&lt;p&gt;Here is a compact interface sketch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VerificationHarness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutation_tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fuzzer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&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;oracle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oracle&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;mutation_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutation_tool&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;fuzzer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fuzzer&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;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify&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;implementation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;specification&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;claim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;propose_claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_targeted_tests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;oracle_checks&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;oracle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;specification&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests do not check the specification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;mutation_score&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="n"&gt;mutation_tool&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="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tests&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;mutation_score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;important mutations survived&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;fuzz_result&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="n"&gt;fuzzer&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="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&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;fuzz_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;found_failure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Reject&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;fuzzer found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fuzz_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;case&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutation_score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mutation_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mutation score is not a correctness certificate. It is a pressure signal: tests that merely mirror the implementation should fail to kill useful mutations. Fuzzing is not a certificate either. It adds cases and failure modes that the agent did not choose by hand. The oracle check asks a different question again.&lt;/p&gt;

&lt;p&gt;Technique selection should follow the risk and the available oracle. Differential testing fits a protocol with a trusted reference implementation. Property-based testing fits a domain with a clear invariant and a useful generator. Fuzzing fits parsers and state machines with robust crash or semantic oracles. Mutation testing evaluates the tests themselves. Formal methods fit a specification that is precise enough to prove. TDD can structure feedback, but it does not replace hard-case design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What teams should change first
&lt;/h2&gt;

&lt;p&gt;Start with one high-risk path, not an attempt to impose every testing technique on every task. Write down the independent oracle before asking an agent to implement the path. If the oracle cannot be described, the team is not ready to treat a green agent-generated suite as strong evidence.&lt;/p&gt;

&lt;p&gt;Require an adversarial case tied to the likely defect. Do not accept “edge cases included” as a summary. Name the input dimension, state transition, permission boundary, or protocol ambiguity that the case exercises. A reviewer should be able to see why the case could distinguish a correct implementation from a plausible wrong one.&lt;/p&gt;

&lt;p&gt;Record verification artifacts with the change. Keep the claim, test rationale, adversarial input, oracle source, mutation result, and preserved failures. This turns review from “the agent says tests pass” into an inspection of the evidence. It also gives a later agent something concrete to revisit when a test fails in production.&lt;/p&gt;

&lt;p&gt;Measure escaped defects and holdout failures instead of generated test counts. The study's TDD condition wrote more tests without improving correctness. Counting files or assertions rewards activity, not constraint. Run holdout cases that the agent did not see, mutate the implementation, and compare results against a reference where one exists.&lt;/p&gt;

&lt;p&gt;The practical fix is not a longer prompt. It is a verification system that makes weak tests fail visibly, gives the agent an oracle it cannot rewrite, and leaves a reviewer enough evidence to judge the gap between tests that exist and tests that matter.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/coding-agents-do-not-test-like-experts-what-agentic-verification-actually-needs" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Rust Trait Objects Are Just a Pointer and a Vtable</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Sun, 06 Sep 2026 06:51:27 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/rust-trait-objects-are-just-a-pointer-and-a-vtable-400l</link>
      <guid>https://dev.to/chenyuan20509/rust-trait-objects-are-just-a-pointer-and-a-vtable-400l</guid>
      <description>&lt;p&gt;A trait object in Rust is a fat pointer. That statement is precise, not metaphorical. Every &lt;code&gt;&amp;amp;dyn Trait&lt;/code&gt;, &lt;code&gt;Box&amp;lt;dyn Trait&amp;gt;&lt;/code&gt;, and &lt;code&gt;*const dyn Trait&lt;/code&gt; occupies two machine words: one word holds the address of the concrete value, and the other holds the address of a vtable. The vtable is a static structure generated by the compiler for each concrete type that implements the trait. This layout is the entire mechanism behind dynamic dispatch in Rust.&lt;/p&gt;

&lt;p&gt;The Hacker News discussion around the article "Visualizing Rust's Vtables: How dyn Trait Works In Memory" surfaced a common point of confusion: developers coming from C++ expect the vtable pointer to live inside the object itself. In Rust, it does not. The vtable pointer travels with the reference, not with the data. This distinction changes how you reason about memory layout, object size, and API design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two words that explain a trait object
&lt;/h2&gt;

&lt;p&gt;A trait object is not a type in the same sense as a struct or an enum. It is a dynamically sized type (DST). The compiler does not know its size at compile time, so you cannot store a &lt;code&gt;dyn Trait&lt;/code&gt; directly on the stack or in a struct field without indirection. You must always use a pointer: &lt;code&gt;&amp;amp;dyn Trait&lt;/code&gt;, &lt;code&gt;Box&amp;lt;dyn Trait&amp;gt;&lt;/code&gt;, &lt;code&gt;Rc&amp;lt;dyn Trait&amp;gt;&lt;/code&gt;, or &lt;code&gt;*const dyn Trait&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The pointer itself is the key. A regular pointer in Rust, such as &lt;code&gt;&amp;amp;Circle&lt;/code&gt;, is a single machine word containing an address. A trait object pointer, such as &lt;code&gt;&amp;amp;dyn Draw&lt;/code&gt;, is two machine words. The first word points to the concrete data. The second word points to the vtable for that concrete type's implementation of the trait.&lt;/p&gt;

&lt;p&gt;This two-word representation is why &lt;code&gt;std::mem::size_of::&amp;lt;&amp;amp;dyn Draw&amp;gt;()&lt;/code&gt; returns 16 on a 64-bit platform while &lt;code&gt;std::mem::size_of::&amp;lt;&amp;amp;Circle&amp;gt;()&lt;/code&gt; returns 8. The data pointer and the vtable pointer together form what the Rust community calls a fat pointer.&lt;/p&gt;

&lt;p&gt;The fat pointer does not contain a length, unlike a slice fat pointer which stores a pointer and a length. For trait objects, the metadata is a vtable pointer, not a count. The &lt;code&gt;Pointee&lt;/code&gt; trait in the standard library formalises this: for &lt;code&gt;dyn Trait&lt;/code&gt;, the &lt;code&gt;Metadata&lt;/code&gt; associated type is &lt;code&gt;DynMetadata&amp;lt;dyn Trait&amp;gt;&lt;/code&gt;, which is a pointer to the vtable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What lives in the fat pointer
&lt;/h2&gt;

&lt;p&gt;The first component of a fat pointer is straightforward: it points to the concrete value. That value can be anywhere—stack, heap, or static memory. The pointer is raw address information, nothing more.&lt;/p&gt;

&lt;p&gt;The second component points to a vtable. The vtable is a static structure, typically placed in read-only data segments, that contains the function pointers for all the trait's methods, plus some additional metadata required by the language.&lt;/p&gt;

&lt;p&gt;The vtable layout is an implementation detail of the Rust compiler, not a stable language guarantee. However, the general structure is well understood from compiler source and documentation. A vtable begins with three entries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pointer to the &lt;code&gt;drop_in_place&lt;/code&gt; implementation for the concrete type. This is how &lt;code&gt;Box&amp;lt;dyn Trait&amp;gt;&lt;/code&gt; knows how to drop the value when it goes out of scope.&lt;/li&gt;
&lt;li&gt;The size of the concrete type, in bytes.&lt;/li&gt;
&lt;li&gt;The alignment of the concrete type, in bytes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After these three header entries, the vtable contains one function pointer for each method defined in the trait, in declaration order. For traits with supertraits, the vtable may also contain pointers to the supertrait vtables to support trait upcasting coercion.&lt;/p&gt;

&lt;p&gt;Each concrete type gets its own vtable for each trait it implements. If a type implements both &lt;code&gt;Draw&lt;/code&gt; and &lt;code&gt;Clone&lt;/code&gt;, the compiler generates a &lt;code&gt;Draw&lt;/code&gt; vtable and a separate &lt;code&gt;Clone&lt;/code&gt; vtable. When you coerce a &lt;code&gt;&amp;amp;Circle&lt;/code&gt; to &lt;code&gt;&amp;amp;dyn Draw&lt;/code&gt;, the fat pointer uses the &lt;code&gt;Draw&lt;/code&gt; vtable. When you coerce the same &lt;code&gt;&amp;amp;Circle&lt;/code&gt; to &lt;code&gt;&amp;amp;dyn Clone&lt;/code&gt;, it uses the &lt;code&gt;Clone&lt;/code&gt; vtable.&lt;/p&gt;

&lt;p&gt;All instances of the same concrete type share the same vtable. The vtable is per-(type, trait) pair, not per-instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a vtable without guessing
&lt;/h2&gt;

&lt;p&gt;The standard library documentation exposes &lt;code&gt;std::ptr::DynMetadata&lt;/code&gt; as the conceptual type for trait-object metadata, but the pointer-metadata APIs are still nightly-only in the current stable documentation. That distinction matters: the type is useful vocabulary for understanding what the compiler carries, but it is not a portable stable inspection interface. On stable Rust, &lt;code&gt;std::mem::size_of::&amp;lt;&amp;amp;dyn Draw&amp;gt;()&lt;/code&gt; and ordinary coercions let you reason about the representation without extracting metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Speak&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Speak&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Dog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"woof"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;trait_obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Speak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trait object reference: {} bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;size_of_val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;trait_obj&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"concrete reference: {} bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;size_of&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Dog&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a 64-bit target, the first line reports 16 and the second reports 8. The example measures the reference representation, not the size of the &lt;code&gt;Dog&lt;/code&gt; value. It is stable and safe, but it does not expose the vtable pointer or its function slots.&lt;/p&gt;

&lt;p&gt;Reading the actual function pointers from a vtable is not a stable operation. The vtable layout is not guaranteed across Rust versions or target platforms. Any attempt to read vtable slots by offset is a debugging experiment, not production code. Even comparing vtable pointer values is unreliable: the compiler may duplicate or merge vtables during code generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static dispatch and dynamic dispatch side by side
&lt;/h2&gt;

&lt;p&gt;Static dispatch, using generics, produces a separate monomorphised copy of the function for each concrete type. The compiler knows exactly which &lt;code&gt;draw&lt;/code&gt; implementation to call at each call site. There is no vtable, no fat pointer, and no runtime overhead for dispatch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Draw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Draw&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Circle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Circle"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Draw&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Square"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Static dispatch: monomorphised per type.&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;draw_statically&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Draw&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;shape&lt;/span&gt;&lt;span class="nf"&gt;.draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;draw_statically&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;draw_statically&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler generates &lt;code&gt;draw_statically::&amp;lt;Circle&amp;gt;&lt;/code&gt; and &lt;code&gt;draw_statically::&amp;lt;Square&amp;gt;&lt;/code&gt; as separate functions. Each function contains a direct call to the corresponding &lt;code&gt;draw&lt;/code&gt; implementation.&lt;/p&gt;

&lt;p&gt;Dynamic dispatch, using &lt;code&gt;dyn Trait&lt;/code&gt;, uses a single function that accepts a fat pointer and dispatches through the vtable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;draw_dynamically&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;shape&lt;/span&gt;&lt;span class="nf"&gt;.draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Draw&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&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;shape&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;draw_dynamically&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;draw_dynamically&lt;/code&gt; function is not monomorphised. There is one copy of the function. Each call to &lt;code&gt;shape.draw()&lt;/code&gt; loads the vtable pointer from the fat pointer, finds the method entry for &lt;code&gt;Draw::draw&lt;/code&gt;, and calls that function with the data pointer as the &lt;code&gt;self&lt;/code&gt; argument.&lt;/p&gt;

&lt;p&gt;Dynamic dispatch adds an indirect call and can limit inlining compared with a statically known concrete type. The actual cost depends on the surrounding code, cache behaviour, and whether the compiler can remove or specialise the abstraction. It should be measured in the workload that matters, not reduced to a universal number of loads.&lt;/p&gt;

&lt;p&gt;The choice between static and dynamic dispatch is a trade-off between optimisation opportunities and flexibility. Static dispatch enables inlining and devirtualisation when the compiler can see the concrete type, but requires compile-time knowledge of the participating types. Dynamic dispatch allows heterogeneous collections and runtime polymorphism, but gives up some of those opportunities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Object safety is an API boundary
&lt;/h2&gt;

&lt;p&gt;Not every trait can be used as a trait object. The compiler enforces a set of rules called object safety, also referred to in recent compiler versions as "dyn compatibility". These rules determine whether a trait can be converted to a &lt;code&gt;dyn Trait&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;A trait is dyn-compatible if its methods can be called through a trait object. The most important rules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Methods cannot have generic type parameters unless the method is restricted with &lt;code&gt;where Self: Sized&lt;/code&gt;. A vtable entry must represent one callable signature, while a generic method would require monomorphisation for each type argument.&lt;/li&gt;
&lt;li&gt;Methods cannot return &lt;code&gt;Self&lt;/code&gt; by value unless they are similarly restricted. A trait object does not know the erased concrete size needed for such a return.&lt;/li&gt;
&lt;li&gt;A method cannot use &lt;code&gt;Self&lt;/code&gt; in a position that requires the erased type's size, such as a by-value argument, unless the method is restricted with &lt;code&gt;where Self: Sized&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Associated constants are not dyn-compatible. Associated types are allowed when the trait object specifies the associated type, as in &lt;code&gt;dyn Iterator&amp;lt;Item = u8&amp;gt;&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dyn-compatible.&lt;/span&gt;
&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Safe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;do_another&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Not dyn-compatible: generic method.&lt;/span&gt;
&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;UnsafeGeneric&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;generic&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Not dyn-compatible: associated constant.&lt;/span&gt;
&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;UnsafeConst&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KIND&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Dyn-compatible when the object supplies Output.&lt;/span&gt;
&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;HasOutput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// A usable object type can be written as dyn HasOutput&amp;lt;Output = i32&amp;gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler will refuse to coerce a value to &lt;code&gt;&amp;amp;dyn UnsafeGeneric&lt;/code&gt; or &lt;code&gt;Box&amp;lt;dyn UnsafeConst&amp;gt;&lt;/code&gt;. A trait with an associated type is not automatically rejected; the object type may need to bind that type before it can be used.&lt;/p&gt;

&lt;p&gt;The object safety rules are an API boundary, not a performance optimisation. They exist because the compiler cannot generate one callable vtable entry for operations that still require compile-time type information. A vtable does not choose generic method instantiations or provide storage for a size-varying return. An associated type can work when the trait object fixes its value in the object type.&lt;/p&gt;

&lt;p&gt;The Rust reference explicitly defines object safety in terms of what operations can be performed on a trait object. A trait object permits "late binding" of methods, dispatched using vtables. If a method cannot be dispatched through a vtable, the trait is not object safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small memory probe in Rust
&lt;/h2&gt;

&lt;p&gt;You can observe the fat pointer layout directly using &lt;code&gt;std::mem::transmute&lt;/code&gt; or by casting to a raw pointer and reading the two words. This is an unsafe debugging technique, not a stable API. The following example demonstrates the layout on a 64-bit platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Item"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;trait_ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// A fat pointer is two words.&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"size of &amp;amp;dyn Identify: {} bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;size_of&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// On 64-bit: 16 bytes.&lt;/span&gt;

    &lt;span class="c1"&gt;// Unsafe inspection: read the two words of the fat pointer.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vtable_ptr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;transmute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trait_ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data pointer: {:p}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vtable pointer: {:p}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vtable_ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The data pointer points to the `item` variable.&lt;/span&gt;
    &lt;span class="c1"&gt;// The vtable pointer points to a static vtable for Item + Identify.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;transmute&lt;/code&gt; call is unsafe because it bypasses Rust's type system. The language does not promise a stable vtable ABI or a stable ordering for private vtable slots. This code is useful when investigating a particular compiler build, but it is not a general FFI contract. The old &lt;code&gt;core::raw::TraitObject&lt;/code&gt; pattern should not be treated as a supported public API.&lt;/p&gt;

&lt;p&gt;The pointer-metadata APIs describe the same split more explicitly, but they are still nightly-only in the current standard-library documentation. A nightly experiment can reconstruct a raw trait-object pointer from matching parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#![feature(ptr_metadata)]&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;inspect_again&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trait_ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trait_ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trait_ptr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_raw_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The metadata must belong to the same erased trait-object type.&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trait_ptr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reconstructed&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The functions are safe in the narrow sense that constructing the raw pointer does not dereference it, but a caller must still ensure that the data pointer and metadata describe a valid &lt;code&gt;Identify&lt;/code&gt; object before dereferencing the result. That is a compiler-experiment boundary, not a stable application interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules for using dyn Trait deliberately
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;dyn Trait&lt;/code&gt; when you need runtime polymorphism in a collection or when the set of types is not known at compile time. A &lt;code&gt;Vec&amp;lt;Box&amp;lt;dyn Draw&amp;gt;&amp;gt;&lt;/code&gt; can hold circles, squares, and triangles together. A &lt;code&gt;Vec&amp;lt;Circle&amp;gt;&lt;/code&gt; cannot.&lt;/p&gt;

&lt;p&gt;Avoid &lt;code&gt;dyn Trait&lt;/code&gt; when the concrete type is known at compile time and you do not need heterogeneous storage. Static dispatch through generics produces faster code and enables inlining.&lt;/p&gt;

&lt;p&gt;When designing a trait that you intend to use as a trait object, ensure object safety from the start. Generic methods and &lt;code&gt;Self&lt;/code&gt;-returning methods are the most common blockers. If you need a trait to be object safe and also support generic methods, consider splitting the trait into an object-safe core and a separate generic extension trait.&lt;/p&gt;

&lt;p&gt;The vtable pointer in a fat pointer is not a stable ABI. Different Rust versions or different compiler flags may change the vtable layout. Code that relies on a specific vtable offset is fragile and should be confined to unsafe debugging or specialised crates that track compiler internals.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DynMetadata&lt;/code&gt; API provides stable access to size and alignment. Use it when you need to know the size of the concrete type behind a trait object. Do not attempt to read the function pointer slots directly in production code.&lt;/p&gt;

&lt;p&gt;The cost of dynamic dispatch is two additional memory indirections per method call. In performance-critical code paths, consider using an enum over a fixed set of types instead of &lt;code&gt;dyn Trait&lt;/code&gt;. An enum allows static dispatch through pattern matching while still supporting heterogeneous values.&lt;/p&gt;

&lt;p&gt;Object safety is a property of the trait definition, not the implementation. You cannot make an object-unsafe trait object-safe by changing the implementation. The trait's method signatures must satisfy the object safety rules for any &lt;code&gt;dyn Trait&lt;/code&gt; to exist. If you control the trait, design it to be object safe. If you do not control the trait, use generics or wrapper types.&lt;/p&gt;

&lt;p&gt;The fat pointer representation of trait objects is one of Rust's most elegant design decisions. It separates the mechanism of polymorphism from the data itself, keeps objects small, and enables zero-cost abstractions where static dispatch is sufficient. A trait object is just a pointer and a vtable. Everything else follows from that fact.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/rust-trait-objects-are-just-a-pointer-and-a-vtable" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Inside the Sandbox Is Still Code Execution: Chromium CVE-2026-85046 for Agent Operators</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Sat, 05 Sep 2026 14:17:14 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/inside-the-sandbox-is-still-code-execution-chromium-cve-2026-85046-for-agent-operators-enm</link>
      <guid>https://dev.to/chenyuan20509/inside-the-sandbox-is-still-code-execution-chromium-cve-2026-85046-for-agent-operators-enm</guid>
      <description>&lt;p&gt;Is a renderer compromise only a containment failure, or is it already a breach of the agent's trust boundary? CVE-2026-85046 makes that contrast concrete. The bug is a type confusion flaw in Chromium's V8 engine. Google patched it in Chrome 152.0.7977.82 after confirming it was exploited in the wild. The NVD description is narrower than the headlines that followed: a crafted HTML page can run arbitrary code inside the sandbox. That sentence is easy to read as a relief. For an AI agent that browses the open web with a logged-in session, it is the opposite.&lt;/p&gt;

&lt;p&gt;A sandbox is a containment layer. It limits what a renderer process may do to the rest of the machine. A trust boundary is the set of actions the agent is allowed to take as the user. Those two lines are not the same. In-sandbox code execution crosses the second line without needing to cross the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the advisory actually says
&lt;/h2&gt;

&lt;p&gt;The NVD entry for CVE-2026-85046 names the component, the class of bug, the fixed build, and the impact. The component is V8. The class is type confusion, catalogued as CWE-843. The fixed build is Chrome 152.0.7977.82. The impact is remote code execution inside the sandbox from a crafted HTML page. Chromium rated the issue High. Public writeups credit Salvatore Gulizia, also known as Serotav, with the report.&lt;/p&gt;

&lt;p&gt;Type confusion is not a logic error in a web API. V8 represents JavaScript values with hidden classes and typed slots. A successful exploit creates a value under one type and later forces the engine to read those slots as another type. Once that happens, attacker-controlled JavaScript can treat a small integer as a pointer, or a pointer as a length. From there the usual memory-corruption path is available inside the renderer: arbitrary read, arbitrary write, then control of the instruction pointer in that process.&lt;/p&gt;

&lt;p&gt;The important qualifier is the process, not the machine. Chromium splits work across a browser process and one or more renderers. Site isolation puts different sites in different renderers when it can. V8 lives in the renderer, so the bug starts in the process that already has the page's DOM and the cookies that page is allowed to touch.&lt;/p&gt;

&lt;p&gt;"Inside the sandbox" describes the operating-system jail around that renderer. It does not mean the page, the session, or the agent remains honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  In-sandbox code execution is not a sandbox escape
&lt;/h2&gt;

&lt;p&gt;The sandbox is an operating-system policy around the renderer: restricted tokens and job objects on Windows, seatbelt on macOS, namespaces and seccomp on Linux. Those controls are meant to stop a hostile renderer from opening arbitrary files or talking to the rest of the desktop as an equal.&lt;/p&gt;

&lt;p&gt;CVE-2026-85046 does not remove that jail. Code that runs after the type confusion still runs as the renderer. It still sees the page and still speaks to the browser process through the same IPC channels a legitimate renderer uses.&lt;/p&gt;

&lt;p&gt;That is a different bug class from a sandbox escape. The same Chrome release also fixed CVE-2026-85050, an out-of-bounds write in WebGL on Android that NVD describes as code execution outside the sandbox. A full browser exploit chain often wants both stages: first a renderer primitive, then a second bug that breaks the jail. CVE-2026-85046 is the first stage. CVE-2026-85050, on the platforms where it applies, is the second.&lt;/p&gt;

&lt;p&gt;Treating "no sandbox escape" as "no incident" collapses the two stages into one. For Chrome's security team the distinction is real. For an agent whose job is to click and read as the user, the first stage already steals the product. The attacker does not need a new host process if the existing renderer can drive the same session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agent browsers inherit the same bug
&lt;/h2&gt;

&lt;p&gt;Playwright, Puppeteer, Chrome DevTools Protocol clients, and extension-based automation do not ship a private JavaScript engine. They start or attach to Chromium. The V8 in that binary is the V8 in that Chrome build. If the build is older than 152.0.7977.82, the advisory applies.&lt;/p&gt;

&lt;p&gt;There are at least three common ways an agent gets a browser, and they do not update together.&lt;/p&gt;

&lt;p&gt;The first is the user's daily Chrome. Auto-update may patch it on a schedule the operator does not control. That is the binary people mean when they say "I already updated Chrome."&lt;/p&gt;

&lt;p&gt;The second is Chrome for Testing, or the Chromium revision Playwright downloads into a cache directory. Teams pin that binary so CI is reproducible. Pinning last month's revision is a stability choice. It is also a decision to keep last month's V8.&lt;/p&gt;

&lt;p&gt;The third is attaching to an already running profile. Hermes-style "use my logged-in browser" automation, Playwright's channel attach, and some CDP scripts skip the pinned testing binary and speak to the browser that already has cookies. That path inherits whatever build the user actually launched. It also inherits the session.&lt;/p&gt;

&lt;p&gt;A patched desktop Chrome therefore does not repair a pinned testing binary. A patched testing binary does not repair an old Electron shell. An updated library package does not replace a browser downloaded once and left in a cache. The bug lives in the executable that runs V8, not in the Python that called &lt;code&gt;browser.new_page()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;MIN_CHROME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;152&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7977&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82&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;parse_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(\d+)\.(\d+)\.(\d+)\.(\d+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&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;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&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;chromium_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parse_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_patched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chromium_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&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;version&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refusing to start: browser version is unknown&lt;/span&gt;&lt;span class="sh"&gt;"&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;version&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_CHROME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refusing to start: Chromium %s is older than 152.0.7977.82&lt;/span&gt;&lt;span class="sh"&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;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&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;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;version&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;version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A pinned browser is a security decision
&lt;/h2&gt;

&lt;p&gt;Pinning a browser revision is not a missing update. It is a choice to prefer a known binary over a moving one. That choice is reasonable for a test suite that only opens fixtures on localhost. It is a poor default for an agent that follows links, opens email HTML, or visits sites chosen by a model.&lt;/p&gt;

&lt;p&gt;Playwright's install step downloads the Chromium revision that matches the package. Upgrading the Python package without re-running the install leaves the old binary in the cache. Docker tags have the same shape: a tag that was current in August is not a patch for a September V8 advisory.&lt;/p&gt;

&lt;p&gt;The other pinning trap is flags. &lt;code&gt;--no-sandbox&lt;/code&gt; is still common in containers because it makes Chrome start under root. That flag does not create CVE-2026-85046, but it removes the containment layer the advisory still relies on. &lt;code&gt;--disable-web-security&lt;/code&gt; and &lt;code&gt;--disable-site-isolation-trials&lt;/code&gt; widen what a hostile renderer can reach after it already runs code. A version gate that ignores flags is a version gate that lies.&lt;/p&gt;

&lt;p&gt;A production agent should treat the binary path, the four-part version, the launch flags, and the profile as one reviewable object. If any field is unknown, the process should not open a URL. Unknown is not "probably fine." Unknown is "the check did not run."&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;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="c1"&gt;# Reuses assert_patched() from the previous listing.
&lt;/span&gt;&lt;span class="n"&gt;FORBIDDEN_FLAGS&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;--no-sandbox&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;--disable-web-security&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;--disable-site-isolation-trials&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_launch_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;launch_flags&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="nb"&gt;str&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;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assert_patched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;forbidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FORBIDDEN_FLAGS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;launch_flags&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;forbidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refusing to start: forbidden flags %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&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;forbidden&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chromium %s accepted&lt;/span&gt;&lt;span class="sh"&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;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&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;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open_untrusted_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&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;browser_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;launch_flags&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="nb"&gt;str&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="nf"&gt;assert_launch_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;launch_flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refusing to open a non-http URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Containment is not the same as trust
&lt;/h2&gt;

&lt;p&gt;After a renderer compromise, the attacker has the same view the page had, plus the ability to ignore the page's own JavaScript and talk to the renderer internals. That is enough to read cookies the renderer is allowed to send, fill forms, click buttons, inspect the DOM, and intercept traffic that already terminates in that process.&lt;/p&gt;

&lt;p&gt;For a personal browsing agent the blast radius is the user's session. The agent was going to use that session anyway. The attacker now uses it first. Anything the renderer could already send, including cookies and filled forms, is in scope.&lt;/p&gt;

&lt;p&gt;For an attached profile the blast radius is larger still. The automation did not create a throwaway login. It borrowed the user's real Chrome. In-sandbox code execution then sits next to every other tab in that profile's renderer topology. Site isolation helps, but it is not a promise that a hostile origin cannot be opened, and it is not a promise that the agent's privileged tab is in a different process from the attacker's page.&lt;/p&gt;

&lt;p&gt;The sandbox still does useful work. It is why a renderer bug is not automatically a kernel bug, and why "steal the session" and "install a host implant" should stay different tickets. The mistake is to file the first one as "not an incident because the sandbox held."&lt;/p&gt;

&lt;p&gt;An agent is a program whose feature is to act. The renderer is where that acting happens. Control of the renderer is control of the feature. "Inside the sandbox" answers a different question: whether that control also reaches the rest of the disk as the host user.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a version check should prove
&lt;/h2&gt;

&lt;p&gt;A useful check answers four questions with evidence, not with a changelog.&lt;/p&gt;

&lt;p&gt;Which executable started? The path must be the path the agent will launch or attach to, not a &lt;code&gt;google-chrome --version&lt;/code&gt; from some other install on the same machine.&lt;/p&gt;

&lt;p&gt;Which four-part version did that executable print? Compare it as a tuple against 152.0.7977.82. String equality against a single build is too brittle, and a major-only check is too loose.&lt;/p&gt;

&lt;p&gt;Which flags were actually passed? Parse the launch argument list and fail closed on &lt;code&gt;--no-sandbox&lt;/code&gt;, &lt;code&gt;--disable-web-security&lt;/code&gt;, and &lt;code&gt;--disable-site-isolation-trials&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which profile is attached? A throwaway user-data-dir is a different decision from the person's daily profile. If the policy forbids attaching to the real profile, a running Chrome with that profile is a failed start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;browser_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;min_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;152.0.7977.82"&lt;/span&gt;
  &lt;span class="na"&gt;attach_user_profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;allowed_launch_flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--headless=new"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--disable-dev-shm-usage"&lt;/span&gt;
  &lt;span class="na"&gt;disallowed_launch_flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--no-sandbox"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--disable-web-security"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--disable-site-isolation-trials"&lt;/span&gt;
  &lt;span class="na"&gt;on_unknown_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;block&lt;/span&gt;
  &lt;span class="na"&gt;on_forbidden_flag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The policy is small on purpose. It does not detect exploits. It only refuses to start a browser whose version or flags the operator has not accepted, including when CI reuses an old cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary you can actually review
&lt;/h2&gt;

&lt;p&gt;You cannot review whether a random page contains a V8 exploit. You can review which Chromium binary the agent starts, whether that binary is at least 152.0.7977.82, whether the sandbox flags are intact, and whether the session is a disposable profile or the user's own Chrome.&lt;/p&gt;

&lt;p&gt;Those four facts belong in the same place as the rest of the agent's contract: a file, a startup assertion, and an exit status. They do not belong in a wiki reminder to "keep Chrome updated." The desktop auto-updater and the agent cache are different programs. Treating them as one program is how a patched laptop still runs a vulnerable renderer for the model.&lt;/p&gt;

&lt;p&gt;The sandbox remains worth keeping. It is the reason CVE-2026-85046 is not described as code execution on the host. Keep the flag that enables it. Keep site isolation.&lt;/p&gt;

&lt;p&gt;Then keep the other line in view. An agent that browses untrusted HTML hands a renderer a session and asks it to act. Code execution inside that renderer is already enough to steal cookies, drive the page, and act as the logged-in user. Chrome's security team will still care whether the jail held. The operator should care whether the agent still belonged to them. For this class of product, "inside the sandbox" is already a total loss of the session the agent was built to use.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/inside-the-sandbox-is-still-code-execution-chromium-cve-2026-85046-for-agent-operators" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Skill Is the Product: Making AI Coding Agents Obey Real Repositories</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Thu, 03 Sep 2026 11:33:07 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/the-skill-is-the-product-making-ai-coding-agents-obey-real-repositories-3ckn</link>
      <guid>https://dev.to/chenyuan20509/the-skill-is-the-product-making-ai-coding-agents-obey-real-repositories-3ckn</guid>
      <description>&lt;p&gt;A Hacker News post on September 2, 2026 linked Matt Pocock's public repository, &lt;a href="https://github.com/mattpocock/skills" rel="noopener noreferrer"&gt;Skills For Real Engineers&lt;/a&gt;. The repository is not a new agent runtime. It is a collection of small Markdown skills taken from the author's &lt;code&gt;.agents&lt;/code&gt; directory, with an installer for putting selected files into a project. The discussion around the post made the useful question sharper: which instructions belong in a general repository guide, and which deserve their own reusable workflow?&lt;/p&gt;

&lt;p&gt;That distinction matters once an agent edits a real codebase. A short prompt can suggest an approach. A repository guide can describe local rules. A skill can package a repeatable operation with its trigger, supporting files, and expected checkpoints. The last item is valuable only when the operation has a boundary that a human can inspect. Otherwise, a skill is just a longer prompt with a more impressive filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repository is part of the agent
&lt;/h2&gt;

&lt;p&gt;An agent does not arrive at a repository with the same assumptions as its author. It must learn the project’s names, tests, issue tracker, architecture, and definition of done. The README for Pocock’s collection describes this problem directly: agents are often asked to discover a project’s vocabulary as they work, then spend many words restating concepts that the project could have named once.&lt;/p&gt;

&lt;p&gt;The collection’s &lt;code&gt;CONTEXT.md&lt;/code&gt; example shows the practical fix. A project-specific phrase can replace a long explanation of a domain event. That is not decoration. It gives the agent a stable search term for functions, files, tests, and design notes. The repository becomes part of the skill’s input rather than a blank directory that the agent may interpret differently on every run.&lt;/p&gt;

&lt;p&gt;This does not mean that every local rule belongs in a skill. A global rule such as “run the formatter before committing” fits an &lt;code&gt;AGENTS.md&lt;/code&gt; or &lt;code&gt;CLAUDE.md&lt;/code&gt; file. A workflow such as “turn an ambiguous request into a spec, then create tickets” has a trigger, a sequence, and a visible result. That workflow is a better skill candidate. The test is simple: could a teammate invoke it by name and know what artifact should exist when it finishes?&lt;/p&gt;

&lt;h2&gt;
  
  
  What a skill actually contains
&lt;/h2&gt;

&lt;p&gt;Pocock’s repository uses ordinary Markdown files, grouped into directories such as &lt;code&gt;skills/engineering&lt;/code&gt; and &lt;code&gt;skills/productivity&lt;/code&gt;. Its README separates user-invoked skills from model-invoked skills. User-invoked entries include flows such as &lt;code&gt;/grill-me&lt;/code&gt;, &lt;code&gt;/to-spec&lt;/code&gt;, and &lt;code&gt;/implement&lt;/code&gt;. Model-invoked entries include reusable practices such as test-driven development, bug diagnosis, code review, and codebase design. The distinction is about who may start the flow, not about whether one kind of file is magically more intelligent.&lt;/p&gt;

&lt;p&gt;The repository also shows why a skill is different from a README. A README explains a collection and links to its members. A skill file describes the work to perform, the questions to ask, the files to consult, and the checks to complete. The surrounding repository supplies scripts, templates, reference documents, and installation metadata where needed. Together, these parts form an operational package.&lt;/p&gt;

&lt;p&gt;It is useful to keep the contract small. A skill needs a trigger, inputs, ordered actions, and an observable verification rule. Those fields do not claim to be the schema of Pocock’s repository. They are a local convention for teams that want to make a file-based workflow easier to review and test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add-health-route&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;add a health route&lt;/span&gt;
&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;handler_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;route_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;allowed_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;append_if_missing&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handler_file&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;@app.get("{{ route_path }}")&lt;/span&gt;
      &lt;span class="s"&gt;def health():&lt;/span&gt;
          &lt;span class="s"&gt;return {"status": "ok"}&lt;/span&gt;
&lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handler_file&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@app.get(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;route_path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example is deliberately boring. It names one file, accepts two values, performs one mutation, and checks one result. It does not promise to understand a whole web framework. That modesty makes review possible. A maintainer can ask whether the route belongs in that file, whether the decorator is correct, and whether the verification is strong enough before the agent runs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a narrow contract
&lt;/h2&gt;

&lt;p&gt;A broad instruction such as “improve this service” gives an agent too many legal interpretations. A narrow contract removes choices that do not belong to the task. It can state the permitted directories, the files that must already exist, the command that may run, and the result that proves completion. When an assumption is false, the skill should stop before mutation and report the missing condition.&lt;/p&gt;

&lt;p&gt;Narrow does not mean trivial. Pocock’s collection includes workflows for grilling through a plan, writing a test-driven change, diagnosing a difficult bug, and reviewing a diff. Each workflow is substantial, but each has a recognizable seam. &lt;code&gt;tdd&lt;/code&gt; is about a red-green-refactor loop. &lt;code&gt;diagnosing-bugs&lt;/code&gt; is about building a feedback loop around one hard bug. &lt;code&gt;code-review&lt;/code&gt; examines a bounded diff against standards and the originating specification.&lt;/p&gt;

&lt;p&gt;The contract should also say what the skill will not do. A route skill should not silently redesign authentication. A ticket skill should not publish a specification to an issue tracker without making the destination explicit. A test skill should not claim success because a file changed. Negative boundaries protect the repository from enthusiastic automation.&lt;/p&gt;

&lt;p&gt;The input contract is where safety begins. Reject absolute paths if the operation is meant to stay inside the repository. Reject a missing required value. Restrict enumerated options. Resolve defaults before the first write. These checks are cheap, and they turn an ambiguous request into a visible failure instead of a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the contract into executable checks
&lt;/h2&gt;

&lt;p&gt;Written instructions explain intent, but an exit status or an exact file check gives the agent something firmer to observe. The verification need not be elaborate. It can assert that a generated file exists, a required string appears once, a test command exits successfully, or a diff stays inside an allowed directory.&lt;/p&gt;

&lt;p&gt;A useful check tests the outcome rather than the action. “The append step ran” is weak evidence. “The route decorator appears in the intended file, the test passes, and no file outside the allowlist changed” is stronger. The more consequential the operation, the more the verification should inspect the external state that matters.&lt;/p&gt;

&lt;p&gt;The same rule applies to skills copied from a public collection. Installation is not verification. The README for &lt;code&gt;mattpocock/skills&lt;/code&gt; offers two paths: a Claude Code plugin or the &lt;code&gt;skills.sh&lt;/code&gt; installer, which can copy selected editable files into a repository. After installation, a team still needs to confirm that the chosen files are in the expected directory, that their triggers are reachable in the chosen agent, and that project-specific paths and terminology have been adapted. A copied workflow is a starting point, not proof that it fits.&lt;/p&gt;

&lt;p&gt;A small runner can make the local convention testable without introducing a framework. The important details are path confinement, input validation, bounded commands, and structured results. The runner below treats the YAML contract as data. It never passes a rendered command through a shell.&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;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SkillRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contract_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contract_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contract_path&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;repo_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;contract_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&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;stream&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;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&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;_inputs&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;provided&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="ow"&gt;in&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;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputs&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="nf"&gt;items&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;provided&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provided&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;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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;missing required input: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&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="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;allowed_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allowed_prefix&lt;/span&gt;&lt;span class="sh"&gt;"&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;allowed_prefix&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;str&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="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed_prefix&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;ValueError&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;invalid value for input: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&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="n"&gt;values&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_render&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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{ &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; }}&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_path&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&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;repo_path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&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;candidate&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="n"&gt;repo_path&lt;/span&gt; &lt;span class="ow"&gt;and&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;repo_path&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parents&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;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path leaves repository&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="n"&gt;candidate&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&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;provided&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;values&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;_inputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provided&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="ow"&gt;in&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;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;append_if_missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;path&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;_path&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="nf"&gt;_render&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;content&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;_render&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;_render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;values&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
                &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&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="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&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="n"&gt;repo_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                    &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&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;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phase&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;step&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;returncode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stderr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&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;ValueError&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;unsupported action: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="ow"&gt;in&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;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&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;path&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;_path&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="nf"&gt;_render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;expected&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;_render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;values&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;check&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;contains&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phase&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;verify&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;check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unnamed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phase&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;verify&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;This is not a general agent framework. It supports two file-level actions and one command action, and it returns early on a failed command or check. That is enough to demonstrate the property a skill needs: the procedure is explicit, and the result is not inferred from a confident paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep context local and explicit
&lt;/h2&gt;

&lt;p&gt;Context should be close to the workflow that consumes it. A repository-wide guide can define language, package manager, test command, and protected directories. A skill can point to the small set of files needed for its operation. A reference document can explain a domain term without forcing every task to reread the whole project.&lt;/p&gt;

&lt;p&gt;Pocock’s README makes this separation concrete through &lt;code&gt;CONTEXT.md&lt;/code&gt;, ADRs, and skills that update or use them. The point is not to put every fact into one enormous instruction file. Large instruction files become difficult to audit, and agents may spend attention on rules unrelated to the current change. A pointer to a focused document is often better than a duplicate explanation.&lt;/p&gt;

&lt;p&gt;Local context also improves portability. The public collection can be installed as editable files, but the README warns that a team should choose one installation philosophy rather than installing the same skills twice through different mechanisms. After installation, replace generic assumptions with the project’s actual issue tracker, documentation location, labels, and vocabulary. A skill copied unchanged is not automatically a team process.&lt;/p&gt;

&lt;p&gt;The context boundary should be visible in the contract. List the files the skill may read. List the paths it may write. Name commands and give them time limits. If a workflow needs a human decision, produce a proposal and stop at that seam. Do not hide a product decision inside a filesystem helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test failure paths, not demos
&lt;/h2&gt;

&lt;p&gt;The happy path proves that the example was arranged correctly. Failure tests prove that the boundary exists. For a file-writing skill, test a missing required input, a path outside the repository, an unsupported option, a command timeout, and a verification mismatch. The assertion should cover both the returned status and the absence of an unsafe mutation.&lt;/p&gt;

&lt;p&gt;A failure result should be useful to the next action. “Failed” is not enough. Include the phase, the named check, the return code, or the rejected input. Avoid copying an entire environment into the result; a short diagnostic is easier for a human and an agent to inspect.&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;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SkillRunner&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@app.get(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;contract&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;inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handler_file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&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;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;append_if_missing&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;path&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;{{ handler_file }}&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;content&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;@app.get(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&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;action&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;contains&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;path&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;{{ handler_file }}&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&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;test_successful_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skill.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;make_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SkillRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmp_path&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handler_file&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;app/routes.py&lt;/span&gt;&lt;span class="sh"&gt;"&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;result&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phase&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;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@app.get(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app/routes.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&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;test_failed_verification_is_reported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skill.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;make_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marker-that-is-not-written&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SkillRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmp_path&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handler_file&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;app/routes.py&lt;/span&gt;&lt;span class="sh"&gt;"&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;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&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="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phase&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;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check&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;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second test is intentionally unglamorous. The write happens, but the contract still reports failure because the stated outcome is absent. That distinction prevents a runner from confusing “the step executed” with “the task succeeded.” In a real repository, the verification would usually call the project’s existing tests rather than inventing a second test system.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small Python skill runner
&lt;/h2&gt;

&lt;p&gt;The runner’s most important design choice is what it refuses to do. It does not interpret free-form prose as a command. It does not accept a path that escapes the repository. It does not turn a missing input into a guessed default unless the contract declares that default. It does not report success until every verification entry passes.&lt;/p&gt;

&lt;p&gt;The command action is still a sharp tool. A fixed argument list is safer than a string assembled for a shell, but it can still delete data or contact an external service. Give the skill an allowlist of commands, use a short timeout, capture output, and run it in a temporary checkout when the operation is experimental. For deployment, billing, credentials, or irreversible migrations, make the human approval an explicit boundary instead of pretending that a local exit code proves the remote state.&lt;/p&gt;

&lt;p&gt;This is also where existing engineering skills help. Pocock’s collection treats test-driven development, diagnosis, architecture, and code review as separate disciplines that can be combined around a change. A local runner should not reproduce all of those practices. It should invoke the project’s established tools and verify the seam that belongs to the skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this approach stops helping
&lt;/h2&gt;

&lt;p&gt;A skill contract is a good fit when the task has a stable trigger, a small input surface, and a deterministic check. It is a poor fit for work whose correctness depends on an unresolved product decision, a broad architectural tradeoff, or a remote system that cannot be inspected from the repository. In those cases, the skill should gather evidence, produce a proposal, and stop before the decision point.&lt;/p&gt;

&lt;p&gt;The Hacker News comments on Pocock’s repository raise a fair boundary question: some practices could fit in a general &lt;code&gt;AGENTS.md&lt;/code&gt; or &lt;code&gt;CLAUDE.md&lt;/code&gt;, while a skill is more useful for a named workflow, a project-specific script, unusual domain knowledge, or a sequence that benefits from explicit gates. That is a better rule than treating every useful paragraph as a new command.&lt;/p&gt;

&lt;p&gt;The practical recommendation is to start with one narrow skill that touches a known seam. Define its inputs and refusal conditions. Reuse the repository’s own tests. Add failure cases before sharing the file. If the skill grows a long list of exceptions, stop adding branches and move the complex reasoning into a normal tool or a human review step. The product is the bounded, inspectable workflow. The agent is the caller that applies it to the repository in front of it.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/the-skill-is-the-product-making-ai-coding-agents-obey-real-repositories" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why uv Now Deduplicates Every Wheel in Its Cache (and How It Does It)</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Mon, 31 Aug 2026 15:51:22 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/why-uv-now-deduplicates-every-wheel-in-its-cache-and-how-it-does-it-2gh</link>
      <guid>https://dev.to/chenyuan20509/why-uv-now-deduplicates-every-wheel-in-its-cache-and-how-it-does-it-2gh</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: Duplicate Wheels in the Cache
&lt;/h2&gt;

&lt;p&gt;A developer working on a monorepo with two Python projects—a FastAPI web service and a data pipeline—notices something odd. Each project has a &lt;code&gt;requirements.txt&lt;/code&gt; file listing around 200 dependencies. When they run &lt;code&gt;uv pip install&lt;/code&gt; for the first project, uv downloads and extracts 200 wheels into its global cache. Then they run &lt;code&gt;uv pip install&lt;/code&gt; for the second project, and uv does the same thing again.&lt;/p&gt;

&lt;p&gt;But here's the kicker: these two projects share roughly 150 of the same dependencies. &lt;code&gt;pydantic==2.10.4&lt;/code&gt;, &lt;code&gt;httpx==0.27.2&lt;/code&gt;, &lt;code&gt;typing-extensions==4.12.2&lt;/code&gt;—the list goes on. Yet when they check their disk usage, they find that uv has stored 400 copies of extracted wheels on disk. The same &lt;code&gt;typing-extensions&lt;/code&gt; wheel, byte-for-byte identical, exists twice in the cache. They're effectively paying double the storage cost for a dependency they use in both projects.&lt;/p&gt;

&lt;p&gt;This isn't an edge case. In any Python environment with multiple projects, virtual environments, or CI pipelines, the same packages get pulled down and extracted repeatedly. The waste compounds quickly. A monorepo with 10 microservices might store 10 copies of &lt;code&gt;numpy&lt;/code&gt;, 10 copies of &lt;code&gt;pandas&lt;/code&gt;, and 10 copies of &lt;code&gt;scikit-learn&lt;/code&gt;—each taking up hundreds of megabytes.&lt;/p&gt;

&lt;p&gt;Why does this happen? Prior to the content-addressed cache feature, uv's cache stored extracted wheels in &lt;code&gt;archive-v0&lt;/code&gt; under randomly generated IDs. If the same wheel was reached through different cache entries—for example, fetched from two different indexes—uv stored duplicate copies because it identified extracts by their source rather than their contents.&lt;/p&gt;

&lt;p&gt;This identity-based approach is simple and fast—you can look up a package by name and version instantly. But it leads to significant redundancy when many projects share the same dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before: How uv's Cache Organized Data
&lt;/h2&gt;

&lt;p&gt;Before the content-addressed cache features landed, uv's cache had a two-tier structure. Downloaded wheels lived in the &lt;code&gt;wheels-v0&lt;/code&gt; or &lt;code&gt;wheels-v1&lt;/code&gt; bucket, keyed by a combination of the package name, version, and a hash of the wheel file itself. Extracted wheels lived in &lt;code&gt;archive-v0&lt;/code&gt;, and each extract got its own randomly generated directory ID.&lt;/p&gt;

&lt;p&gt;You can inspect your current uv cache location with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv cache &lt;span class="nb"&gt;dir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux and macOS, this typically outputs something like &lt;code&gt;$HOME/.cache/uv&lt;/code&gt;. On Windows, it's &lt;code&gt;%LOCALAPPDATA%\uv\cache&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you peek inside the &lt;code&gt;archive-v0&lt;/code&gt; directory, you'll see a bunch of seemingly random directory names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;uv cache &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/archive-v0/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a real system, this might show entries like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0VSKRv7KPsU6OVxKhRKyA/
3vlR8U66f3qKdzSrxDtyR/
gekN10oQ_Vp-g2TxEYqsD/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these opaque directories contains the full extracted contents of a single wheel. The IDs are randomly generated—they carry no relationship to the files inside. This means that if you install the same wheel from PyPI and then from a local directory, uv treats them as different cache entries and stores two copies of the extracted files.&lt;/p&gt;

&lt;p&gt;To see just how much duplication exists, you can use tools like &lt;code&gt;du&lt;/code&gt; to measure the total size of the cache and compare it against the unique file count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;uv cache &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;uv cache &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/archive-v0/ | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a project with many dependencies, the &lt;code&gt;archive-v0&lt;/code&gt; directory can contain tens of thousands of files. A significant portion of these are duplicates—the same &lt;code&gt;.py&lt;/code&gt; files, the same compiled extensions, the same native libraries, stored over and over again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content-Addressable Storage: A Brief Primer
&lt;/h2&gt;

&lt;p&gt;Content-addressable storage (CAS) is a pattern where data is stored and retrieved by its content hash rather than by a name or location. Instead of asking for "file &lt;code&gt;typing-extensions-4.12.2.dist-info&lt;/code&gt;," you ask for "the file whose BLAKE3 hash is &lt;code&gt;b7a3...&lt;/code&gt;." If two files have identical contents, they produce identical hashes, and the storage system returns the same object for both requests.&lt;/p&gt;

&lt;p&gt;This concept is not new. Git uses content-addressable storage for its object database. Every blob, tree, and commit is identified by its SHA-1 hash, which is why Git can detect duplicate files across your repository and only store them once. Docker layer caching works on a similar principle—if two images share the same base layer, they can reuse it without storing a second copy. npm deduplicates tarballs in its cache using content hashes, ensuring that if you install the same package in different projects, you only download it once.&lt;/p&gt;

&lt;p&gt;The key insight is that content-addressable storage turns the problem of "how do I avoid storing the same data twice" into a simple lookup: compute the hash of the content, check if an object with that hash already exists, and if so, reuse it. The hard part is managing the links—making sure that when you ask for the contents of a package, the system knows which hashed objects to assemble, and when a package is removed, the system can safely garbage-collect unreferenced objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  How uv's Content-Addressed Cache Works
&lt;/h2&gt;

&lt;p&gt;uv's content-addressed cache evolution happened in two stages. The foundational work landed in PR #19693, which introduced content-based directory hashes for entire extracted wheels. The idea was simple: instead of storing an extracted wheel under a randomly generated ID, compute a hash of the directory's entire contents, and store it under that hash. If two different wheels extract to the exact same directory tree, they'll produce the same hash, and uv can deduplicate them.&lt;/p&gt;

&lt;p&gt;The more significant optimization came in PR #21327, which moved deduplication from the wheel level to the file level. In this newer implementation, every file extracted from a wheel is stored individually in a &lt;code&gt;files-v0&lt;/code&gt; bucket, keyed by its BLAKE3 content hash. The original directory structure is preserved through hardlinks—the &lt;code&gt;archive-v0&lt;/code&gt; directory contains hardlinks pointing to the actual file objects in &lt;code&gt;files-v0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how the flow works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When uv needs to extract a wheel, it iterates through every file in the archive.&lt;/li&gt;
&lt;li&gt;For each file, it computes a BLAKE3 hash of the file's contents.&lt;/li&gt;
&lt;li&gt;It checks if an object with that hash already exists in the &lt;code&gt;files-v0&lt;/code&gt; bucket.&lt;/li&gt;
&lt;li&gt;If the object exists, uv creates a hardlink from the extracted wheel's location to the existing object.&lt;/li&gt;
&lt;li&gt;If the object doesn't exist, uv writes the file to the &lt;code&gt;files-v0&lt;/code&gt; bucket under its hash, then creates a hardlink to it.&lt;/li&gt;
&lt;li&gt;The extracted wheel directory in &lt;code&gt;archive-v0&lt;/code&gt; now contains hardlinks to shared file objects, with each object stored exactly once on disk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach preserves the existing installation pipeline—the wheel installation step still reads files from their expected paths in &lt;code&gt;archive-v0&lt;/code&gt;—but the underlying storage is now fully deduplicated.&lt;/p&gt;

&lt;p&gt;The PR benchmarks showed impressive savings. On a local machine with a typical Python cache, the file-level deduplication saved 545.2 MiB of disk space, or about 10% of the total cache. The optimization covered all payload files in the cache—not just executables and native libraries, but every &lt;code&gt;.py&lt;/code&gt;, &lt;code&gt;.so&lt;/code&gt;, &lt;code&gt;.dll&lt;/code&gt;, and data file.&lt;/p&gt;

&lt;p&gt;The performance impact was also carefully measured. Cold installs—where the cache is empty and files must be written for the first time—showed a slowdown of less than 4% on median. Warm installs, where the cache is already populated, showed no statistically significant difference. The team found this tradeoff worthwhile—a small performance hit on cold installs for a noticeable reduction in disk usage.&lt;/p&gt;

&lt;p&gt;The feature uses BLAKE3 as its hashing algorithm, which is known for its speed and parallelizability, making it well-suited for hashing many files during wheel extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling the Preview Feature
&lt;/h2&gt;

&lt;p&gt;The content-addressed cache features are currently behind preview flags. To use them, you need to explicitly opt in. uv provides several ways to enable preview features.&lt;/p&gt;

&lt;p&gt;First, you can enable the feature via the &lt;code&gt;--preview-features&lt;/code&gt; flag on individual commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--preview-features&lt;/span&gt; content-addressed-cache &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To enable it for all commands in a session, you can use the &lt;code&gt;UV_PREVIEW_FEATURES&lt;/code&gt; environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UV_PREVIEW_FEATURES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;content-addressed-cache
uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also enable preview features in your &lt;code&gt;uv.toml&lt;/code&gt; file or under &lt;code&gt;[tool.uv]&lt;/code&gt; in your &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.uv]&lt;/span&gt;
&lt;span class="py"&gt;preview-features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"content-addressed-cache"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the physical space accounting feature—which accounts for hardlinks when reporting cache size—you need the &lt;code&gt;cache-physical-space&lt;/code&gt; preview feature as well. You can enable multiple features simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv cache size &lt;span class="nt"&gt;--preview-features&lt;/span&gt; content-addressed-cache,cache-physical-space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or set the environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UV_PREVIEW_FEATURES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;content-addressed-cache,cache-physical-space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once enabled, uv will start using content-addressed storage for new wheel extractions. Existing cache entries will remain in their current format—uv doesn't automatically migrate old cache data to the new structure. You can either let new installs populate the new format gradually, or you can clear your cache and start fresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv cache clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that preview features are subject to change. uv explicitly warns that they may be modified or removed without notice, and they should not be relied upon in production environments until stabilized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the Savings
&lt;/h2&gt;

&lt;p&gt;To measure how much space your cache is actually using on disk—accounting for hardlinks—you need the &lt;code&gt;cache-physical-space&lt;/code&gt; preview feature. Without it, &lt;code&gt;uv cache size&lt;/code&gt; reports the apparent size of the cache, which counts the same file multiple times if it's hardlinked in different locations. With the physical space accounting, the command reports the actual disk usage.&lt;/p&gt;

&lt;p&gt;Run the following command to see your cache's physical space usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv cache size &lt;span class="nt"&gt;--preview-features&lt;/span&gt; content-addressed-cache,cache-physical-space &lt;span class="nt"&gt;--human&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--human&lt;/code&gt; flag displays the size in a human-readable format (e.g., &lt;code&gt;1.2 GiB&lt;/code&gt; instead of raw bytes).&lt;/p&gt;

&lt;p&gt;The actual savings you'll see depend on your workload. The benchmark in PR #21327 showed a 545.2 MiB reduction on a local machine, which was about 10% of the total cache. However, that benchmark was on a single machine with a specific set of packages. In practice, the savings could be higher or lower depending on how much overlap exists between your dependencies.&lt;/p&gt;

&lt;p&gt;For workloads with many large packages that share common files—like multiple machine learning frameworks that bundle the same native libraries—the savings could be significantly more. The per-file selection table from the PR shows the pattern: selecting only executables and native libraries saves 275.7 MiB across 3,336 files, while selecting all payload files saves 545.2 MiB across 134,222 files.&lt;/p&gt;

&lt;p&gt;To see the breakdown, you can also use system-level tools to compare the apparent size of the extracted tree against the actual disk usage of the object store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sb&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;uv cache &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/archive-v0   &lt;span class="c"&gt;# Apparent size: every hardlink counts full&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;  &lt;span class="si"&gt;$(&lt;/span&gt;uv cache &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/files-v0     &lt;span class="c"&gt;# Disk usage: each unique object once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between these numbers represents the space saved by file-level deduplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for CI and Local Development
&lt;/h2&gt;

&lt;p&gt;For local development, the primary benefit is reduced disk usage. If you work on many Python projects simultaneously, a smaller cache means you're less likely to run out of disk space on your development machine. It also means less SSD wear—hardlinking does not write new data to disk, so the cache's write amplification is lower.&lt;/p&gt;

&lt;p&gt;For CI pipelines, the impact is more nuanced. CI systems often cache the &lt;code&gt;~/.cache/uv&lt;/code&gt; directory between runs to avoid re-downloading packages. But the cache's &lt;code&gt;archive-v0&lt;/code&gt; directory contains many small files that take time to compress and decompress. With file-level deduplication, the cache contains fewer distinct file objects in &lt;code&gt;files-v0&lt;/code&gt;, which may improve compression ratios and reduce cache transfer times.&lt;/p&gt;

&lt;p&gt;However, the best practice for CI is still to cache only the downloaded wheels (&lt;code&gt;wheels-v1&lt;/code&gt;) and not the extracted archives (&lt;code&gt;archive-v0&lt;/code&gt;), since the archive can be regenerated from the wheels. The content-addressed cache doesn't change this recommendation—if anything, it makes it more important to be selective about what gets cached.&lt;/p&gt;

&lt;p&gt;For monorepo setups, where many projects share dependencies, the benefits compound. A single &lt;code&gt;typing-extensions&lt;/code&gt; wheel extracted once into &lt;code&gt;files-v0&lt;/code&gt; and hardlinked across 10 project caches would save 9 copies of the extracted files. Over hundreds of shared dependencies across dozens of projects, the savings can add up quickly.&lt;/p&gt;

&lt;p&gt;It's worth noting that this feature is still in preview. Both PR #19693 (wheel-level dedup, merged 2026-08-25) and PR #21327 (file-level dedup, merged 2026-08-31) are in the development branch and have been released under the &lt;code&gt;content-addressed-cache&lt;/code&gt; preview flag in uv 0.12.7. Early adopters should expect some rough edges, especially around cross-platform compatibility. The initial implementation was validated on Linux with the ext4 filesystem. Supporting macOS, Windows, and other filesystems may require additional work. The team has considered edge cases like reflink support on copy-on-write filesystems (e.g., Btrfs, ZFS, APFS), but these features are still under development.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/why-uv-now-deduplicates-every-wheel-in-its-cache-and-how-it-does-it" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Stand-In Is Not a Backup: Designing for a Full Cloud Outage</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Sat, 29 Aug 2026 11:57:42 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/a-stand-in-is-not-a-backup-designing-for-a-full-cloud-outage-2dga</link>
      <guid>https://dev.to/chenyuan20509/a-stand-in-is-not-a-backup-designing-for-a-full-cloud-outage-2dga</guid>
      <description>&lt;h2&gt;
  
  
  A full cloud outage is a different failure
&lt;/h2&gt;

&lt;p&gt;A cloud provider can fail in a way that leaves machines running but makes the system hard to operate. Deployment APIs, identity, DNS, service discovery, and queues may be unreachable. A backup image is not much help if the team cannot provision it or obtain credentials. This differs from an Availability Zone (AZ) failure, where the rest of the platform remains under normal control.&lt;/p&gt;

&lt;p&gt;A stand-in asks what must remain usable when the primary control plane is unavailable. It is a separate service for a few critical operations, with enough cached state to make bounded decisions. It is a second operating path with a smaller promise, not a restore point waiting to be rehydrated.&lt;/p&gt;

&lt;p&gt;The design brief changes. Multi-AZ and multi-region deployments reduce different risks; a stand-in needs its own failure domain and activation path. It may use another cloud or a separately managed environment, but it cannot depend on the primary control plane. &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/disaster-recovery.html" rel="noopener noreferrer"&gt;AWS's reliability guidance&lt;/a&gt; frames disaster recovery around recovery objectives and architecture. A stand-in adds a stricter question: can the recovery system operate while the primary is unreachable?&lt;/p&gt;

&lt;h2&gt;
  
  
  What Monzo Stand-in gets right
&lt;/h2&gt;

&lt;p&gt;Monzo's &lt;a href="https://monzo.com/blog/tolerating-full-cloud-outages-with-monzo-stand-in" rel="noopener noreferrer"&gt;engineering post&lt;/a&gt; describes Stand-in as separate backup banking infrastructure. The Primary Platform runs on Amazon Web Services, while Stand-in runs on Google Cloud Platform. The platforms have independent Kubernetes clusters and separate services. Stand-in exposes limited endpoints and a simplified app experience for card spending, cash withdrawal, bank transfers, balance and transaction views, and freezing or unfreezing cards.&lt;/p&gt;

&lt;p&gt;The important choice is separation. Monzo's post says that common operations, including card payment processing, are implemented independently in the two platforms. Stand-in is therefore not a copy of the core banking platform with another database connection. Different code reduces the chance that one software defect, deployment mistake, or assumption will take down both paths.&lt;/p&gt;

&lt;p&gt;The design also explains why strong consistency was not applied to every replicated value. If both platforms had to acknowledge every write, losing either could block updates. Monzo keeps the primary as the system of record and treats Stand-in decisions as temporary. Disaster recovery becomes a product boundary: preserve a few useful actions, and define what happens to everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Independence beats perfect replication
&lt;/h2&gt;

&lt;p&gt;A common response to cloud-provider risk is to replicate the entire deployment in a second region or cloud. A full replica can fit some recovery objectives, but it is expensive and complex. It also reproduces shared assumptions: a bad identity policy, Kubernetes upgrade, or shared DNS or release system can affect both copies.&lt;/p&gt;

&lt;p&gt;A stand-in chooses independence over feature parity. Instead of duplicating every service, the team identifies the smallest set of operations that must survive. For a bank, that may be payment processing and transfers. For a SaaS platform, it may be read-only access to critical data or the ability to start a failover process manually. The stand-in is a deliberately limited system, not a second edition of the whole product.&lt;/p&gt;

&lt;p&gt;Traditional active-passive recovery can still be useful, but its contract is to promote the same service with the same data model and APIs. A stand-in accepts a different API and reduced functionality when that separates failure modes. The goal is a working path while the primary is unreachable, not an invisible handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sync only the state you can tolerate
&lt;/h2&gt;

&lt;p&gt;During a network partition, a design cannot promise both strong consistency and availability for the same write. A stand-in needs a bounded stale view and a policy for each action. Monzo's post describes non-blocking, eventually consistent replication, immutable data copied from the Primary Platform, separate state created while Stand-in is active, and a durable queue of effects for recovery.&lt;/p&gt;

&lt;p&gt;Monzo says its minimal state includes balances, limited transaction history, card and account details, pots, and payees. It monitors replication lag rather than pretending the view is perfect. A smaller system should carry a monotonic version or source timestamp, reject older events, and alert when freshness crosses policy.&lt;/p&gt;

&lt;p&gt;A cached balance may be stale. A read-only view can show its last known value, while an irreversible write may require a conservative decline or review queue. Each accepted action needs an idempotency key and durable record so recovery can distinguish a new operation from a replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the emergency surface small
&lt;/h2&gt;

&lt;p&gt;A small emergency surface is easier to reason about, test, and operate under stress. Every additional service adds dependencies, configuration, and another way for activation to fail.&lt;/p&gt;

&lt;p&gt;This is a deliberate architectural constraint. For a payments system, the minimum useful surface might authorize a transaction from cached state and record the decision for later reconciliation. For an e-commerce platform, it might serve product listings from a static cache and let authenticated users view their order history. The exact boundary comes from the customer promise, not from the list of services already running in the primary platform.&lt;/p&gt;

&lt;p&gt;The stand-in should not include administrative or analytics features or expose the full primary API. Every endpoint is a potential defect; every synchronized table is a freshness and recovery problem. A boring service with a clear refusal mode is more useful than a broad service that fails halfway through an operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the switch as a product
&lt;/h2&gt;

&lt;p&gt;Activating a stand-in is not a simple DNS cutover. It changes the customer experience, the write path, and the recovery plan. Treat the switch as a product feature rather than an operational afterthought.&lt;/p&gt;

&lt;p&gt;The activation control should be explicit and reversible. An operator or independent health check can request the transition, but the stand-in should refuse activation when its cache is empty, sync lag is outside policy, or its dependencies are unhealthy. Record the decision and its reason in an audit log the primary cannot erase.&lt;/p&gt;

&lt;p&gt;Observability needs the same independence. Logs, metrics, traces, and alert delivery cannot depend only on the primary monitoring stack. Customer communication also belongs in the design: users may see fewer features or delayed transactions, and support staff need a visible state that explains why.&lt;/p&gt;

&lt;p&gt;During Stand-in operation, some values are stale, some writes are provisional, and some requests must be rejected. Making those rules visible is safer than routing traffic to a hidden copy and asking downstream services to infer what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the path before you need it
&lt;/h2&gt;

&lt;p&gt;A stand-in that has never exercised its activation path is an unverified assumption. Test promotion, degraded behavior, and return to the primary. A small matrix can run regularly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Property to verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stale snapshot&lt;/td&gt;
&lt;td&gt;The service rejects or limits an unsafe write and still serves the actions allowed by policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate event&lt;/td&gt;
&lt;td&gt;Replaying an event does not double-apply an effect or change a settled decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed promotion&lt;/td&gt;
&lt;td&gt;A missing dependency leaves the service in a known state and does not advertise partial availability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary recovery&lt;/td&gt;
&lt;td&gt;The stand-in drains new writes, emits its durable effects, and returns control without losing the audit trail.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fault injection should remove the dependencies the design claims not to need: block primary DNS, revoke primary identity permissions, disable a control-plane API, and interrupt sync. Run the tests against the real activation mechanism, not a test-only flag. &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/" rel="noopener noreferrer"&gt;Kubernetes readiness and liveness probes&lt;/a&gt; help with process health, but they do not prove fresh business state; that condition needs its own test and alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small reference implementation
&lt;/h2&gt;

&lt;p&gt;These examples illustrate a stand-in service. They are not production-ready and have not been tested in an outage. The service is a transaction authorizer with a bounded sync queue, explicit transitions, and a recovery record.&lt;/p&gt;

&lt;p&gt;Start with a queue that fails loudly when it is full. A bounded queue that silently evicts old snapshots can turn a known outage into an unknown data gap.&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;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandInState&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;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;IDLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;idle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;ACTIVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;DRAINING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draining&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;FAILED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountSnapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SyncQueue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&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;_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AccountSnapshot&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;max_items&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;put&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;snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AccountSnapshot&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="k"&gt;try&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;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&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;Full&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sync queue is full&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;def&lt;/span&gt; &lt;span class="nf"&gt;drain&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;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AccountSnapshot&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;snapshots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&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="n"&gt;snapshots&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_nowait&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;Empty&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;snapshots&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue rejects input when full, giving the sync worker an observable failure instead of discarding the oldest state. The snapshot version lets the consumer ignore delayed events that would move an account backward.&lt;/p&gt;

&lt;p&gt;The service owns its transitions and applies snapshots only when their versions advance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandInService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;_allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FAILED&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DRAINING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FAILED&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DRAINING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FAILED&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FAILED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDLE&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;__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="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;_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDLE&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;_accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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;AccountSnapshot&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&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="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;state&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="n"&gt;StandInState&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;_state&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;transition&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;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StandInState&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&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;_lock&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;target&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&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;_allowed&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;_state&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;RuntimeError&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;invalid transition: &lt;/span&gt;&lt;span class="si"&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;_state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTIVE&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&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;_accounts&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;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cannot activate without cached state&lt;/span&gt;&lt;span class="sh"&gt;"&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;_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&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;apply_snapshot&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;snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AccountSnapshot&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;bool&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;with&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;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current&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="n"&gt;_accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&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;current&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&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;_accounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&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;authorize_transaction&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;account_id&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;amount&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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&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;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&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;_state&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stand_in_not_active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&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;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;account&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="n"&gt;_accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_not_cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_frozen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insufficient_balance_cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The authorizer never calls the primary. The caller still needs an idempotency key and a reservation or ledger rule; this example only shows the availability decision. A real policy may decline more aggressively when the snapshot is old.&lt;/p&gt;

&lt;p&gt;The activation check should combine primary reachability with stand-in readiness. The probes must use paths that remain available when the primary control plane is not.&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;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Awaitable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Health&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;primary_up&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;cache_ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;sync_lag_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&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;decide_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StandInService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;probe_primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="n"&gt;Awaitable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="n"&gt;cache_ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;sync_lag_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_up&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;probe_primary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;cache_ready&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;cache_ready&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;sync_lag_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;sync_lag_ok&lt;/span&gt;&lt;span class="p"&gt;(),&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;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;primary_up&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;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTIVE&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;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DRAINING&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;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_ready&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync_lag_ok&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;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTIVE&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;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function refuses promotion when the cache or freshness check is unhealthy. Recovery moves to &lt;code&gt;DRAINING&lt;/code&gt; first, giving the write path a visible stopping point.&lt;/p&gt;

&lt;p&gt;After recovery, reconciliation should classify records before applying anything:&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;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Advice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_advices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;primary_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&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;already_applied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&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;advices&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="n"&gt;Advice&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&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="n"&gt;Advice&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="n"&gt;Advice&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="n"&gt;Advice&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;already_applied&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;review&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;advice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;advices&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;advice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;advice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;primary_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;duplicates&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;advice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;review&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;advice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;review&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;begin_rollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StandInService&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StandInState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DRAINING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deliberately auto-applies nothing. A duplicate is recorded, and an unseen advice goes to review or a separately designed idempotent step. A delayed payment is visible; a double-applied payment is harder to undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off is deliberate
&lt;/h2&gt;

&lt;p&gt;Designing for a full cloud outage means choosing which failure a service must tolerate. A full replica optimizes feature parity, but it can carry the same software, identity assumptions, and operational dependencies into the recovery environment. A stand-in optimizes independence and accepts a smaller promise.&lt;/p&gt;

&lt;p&gt;That promise has costs. Users see fewer features, some state is eventually consistent, and the team must reconcile provisional effects when the primary returns. The reward is a recovery path whose assumptions are visible: a separate platform, a bounded cache, explicit refusal modes, an independent switch, and tests that exercise the path before an emergency. Monzo's Stand-in is a concrete example of this design, but the pattern applies to any service where a short list of useful actions matters more than keeping the entire product online.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/a-stand-in-is-not-a-backup-designing-for-a-full-cloud-outage" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Serving Markdown to AI Agents Without Breaking HTTP Caches</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Thu, 27 Aug 2026 10:19:12 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/serving-markdown-to-ai-agents-without-breaking-http-caches-20b</link>
      <guid>https://dev.to/chenyuan20509/serving-markdown-to-ai-agents-without-breaking-http-caches-20b</guid>
      <description>&lt;h2&gt;
  
  
  The web already has the content
&lt;/h2&gt;

&lt;p&gt;The web already has the content. The challenge is not that AI agents cannot fetch a URL. The challenge is that the representation they receive, typically the same HTML a browser renders, is optimized for visual display, not for machine consumption. HTML pages are laden with navigation, scripts, styles, and layout markup that add token cost and dilute semantic signal for retrieval or summarization. The interesting engineering problem is serving a cleaner, more token-efficient representation from the same URL while keeping existing caches intact. Content negotiation via the &lt;code&gt;Accept&lt;/code&gt; header provides a standard mechanism, but it requires careful implementation to avoid fragmenting or poisoning caches.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Accept: text/markdown changes
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.rfc-editor.org/rfc/rfc9110#name-accept" rel="noopener noreferrer"&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/a&gt; request header tells a server which media types the client can process. Browsers typically send a broad value like &lt;code&gt;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&lt;/code&gt;. An AI agent that prefers Markdown can send &lt;code&gt;Accept: text/markdown, text/html;q=0.9,*/*;q=0.8&lt;/code&gt; or a simpler &lt;code&gt;Accept: text/markdown&lt;/code&gt;. This is a signal, not a command. The server is free to respond with &lt;code&gt;text/html&lt;/code&gt; if that is all it provides. The media type &lt;a href="https://www.rfc-editor.org/rfc/rfc7763" rel="noopener noreferrer"&gt;&lt;code&gt;text/markdown&lt;/code&gt;&lt;/a&gt; is registered for Markdown documents and is the appropriate type to use for this representation.&lt;/p&gt;

&lt;p&gt;What changes when a server supports this is the representation it returns. Instead of the full HTML page, the server responds with a Markdown document containing the core content. This is not a replacement for HTML; it is an alternative representation optimized for a specific use case. HTTP already has a mechanism for this kind of negotiation. The server decides which representation to serve based on the client's expressed preferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTTP contract clients and servers need
&lt;/h2&gt;

&lt;p&gt;Content negotiation relies on a few precise rules. When a server supports both HTML and Markdown representations for a resource, it must evaluate the client's &lt;code&gt;Accept&lt;/code&gt; header to choose the appropriate one. Parsing &lt;code&gt;Accept&lt;/code&gt; is not a simple substring match. It requires parsing the media types and their associated quality values (q-values) to determine the client's preference order. A client sending &lt;code&gt;text/markdown;q=0.8, text/html;q=1.0&lt;/code&gt; prefers HTML, so the server should choose HTML when it has that representation. The &lt;code&gt;*/*&lt;/code&gt; wildcard can match any media type, but a specific media range takes precedence over it.&lt;/p&gt;

&lt;p&gt;The response must then declare what it is serving via the &lt;code&gt;Content-Type&lt;/code&gt; header. For Markdown, this is &lt;code&gt;text/markdown; charset=utf-8&lt;/code&gt;. For HTML, it is &lt;code&gt;text/html; charset=utf-8&lt;/code&gt;. The server must not return one type while declaring another. A &lt;code&gt;Content-Type: text/html&lt;/code&gt; response to a request for &lt;code&gt;text/markdown&lt;/code&gt; is a contract violation, not a fallback strategy. If no available representation matches the client's constraints and the server is unwilling to supply a default, it can return &lt;code&gt;406 Not Acceptable&lt;/code&gt;, as defined by RFC 9110. It should not silently label HTML as Markdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching is part of the feature
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.rfc-editor.org/rfc/rfc9110#name-vary" rel="noopener noreferrer"&gt;&lt;code&gt;Vary: Accept&lt;/code&gt;&lt;/a&gt; response header is not optional here. Without it, a cache, whether a browser cache, a reverse proxy, or a CDN, might store the Markdown response for a URL and serve it to a subsequent request that expects HTML, or vice versa. The &lt;code&gt;Vary&lt;/code&gt; header tells the cache that the response varies based on the &lt;code&gt;Accept&lt;/code&gt; request header. The cache can then use that request-header value when deciding whether a stored response matches, keeping HTML and Markdown variants from being mixed.&lt;/p&gt;

&lt;p&gt;This is where the engineering gets precise. &lt;code&gt;Vary: Accept&lt;/code&gt; enables correct caching, but it can also cause cache fragmentation. Clients can send many different &lt;code&gt;Accept&lt;/code&gt; strings that describe roughly the same preference, leading to multiple cache entries for one URL and a lower hit rate. The cure is provider-specific cache-key policy or normalization, not an assumption that every CDN treats &lt;code&gt;Vary&lt;/code&gt; the same way. A good rollout checks the CDN's documentation and inspects real cache behavior with more than one header spelling.&lt;/p&gt;

&lt;p&gt;CDNs also have specific handling for the &lt;code&gt;Vary&lt;/code&gt; header. Akamai's &lt;a href="https://techdocs.akamai.com/property-mgr/docs/rm-vary-header" rel="noopener noreferrer"&gt;current documentation&lt;/a&gt; describes a policy in which arbitrary &lt;code&gt;Vary&lt;/code&gt; values can prevent caching unless the property is configured for them. Other providers document different behavior, and those policies change. Treat &lt;code&gt;Vary: Accept&lt;/code&gt; as an application contract and a CDN configuration item, not as a guarantee that every edge will cache the variants in the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small implementation that does not lie
&lt;/h2&gt;

&lt;p&gt;The simplest implementation involves checking the &lt;code&gt;Accept&lt;/code&gt; header in a middleware or request handler and serving the appropriate representation. Many frameworks have libraries for parsing &lt;code&gt;Accept&lt;/code&gt; headers; do not write a custom parser unless you enjoy handling edge cases. The route fragment below uses Express and the &lt;code&gt;accepts&lt;/code&gt; package. The content lookup is application-specific, but the negotiation and response headers are the part worth copying.&lt;/p&gt;

&lt;p&gt;This example uses Node.js with Express and the &lt;code&gt;accepts&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accepts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accepts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/article/:slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getContentForSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// application-specific lookup&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// The package handles q-values and media-range specificity.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&gt;res&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;406&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No acceptable representation is available.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/markdown; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markdown&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="nx"&gt;res&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The available list contains only representations that the origin can actually produce. That detail prevents a request for Markdown from being selected when no Markdown body exists. It also makes an unsupported &lt;code&gt;Accept&lt;/code&gt; choice visible as 406 instead of silently returning a mislabeled body.&lt;/p&gt;

&lt;p&gt;For a static documentation site, the same idea can be implemented with explicit &lt;code&gt;.html&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt; files. The NGINX configuration below deliberately supports one narrow policy: it chooses the Markdown file only when &lt;code&gt;text/markdown&lt;/code&gt; appears with no lower q-value. It is not a general &lt;code&gt;Accept&lt;/code&gt; parser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# http {} context. Each document has matching .html and .md files.&lt;/span&gt;
&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$http_accept&lt;/span&gt; &lt;span class="nv"&gt;$doc_file&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;default&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="s"&gt;.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;"~*^text/markdown&lt;/span&gt;&lt;span class="s"&gt;([[:space:]]*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="kn"&gt;[[:space:]]*q=1&lt;/span&gt;&lt;span class="s"&gt;([.]0*)?)?([[:space:]]*,|&lt;/span&gt;$&lt;span class="s"&gt;)"&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="s"&gt;.md&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/srv/site&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# /docs/guide -&amp;gt; /srv/site/docs/guide.html or /srv/site/docs/guide.md&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/docs/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;text/html&lt;/span&gt; &lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;text/markdown&lt;/span&gt; &lt;span class="s"&gt;md&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$doc_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Vary&lt;/span&gt; &lt;span class="s"&gt;Accept&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example is useful when the representation is already stored as a file. If the endpoint must honor every q-value combination, keep negotiation in application code or a tested edge function instead of growing an untested regular expression. Either way, preserve &lt;code&gt;Vary: Accept&lt;/code&gt; on successful responses and on errors generated by the variant route.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a separate Markdown URL is safer
&lt;/h2&gt;

&lt;p&gt;Content negotiation is elegant but not the only solution. Some sites may find it simpler or safer to publish their Markdown content at a separate, predictable URL. For example, &lt;code&gt;example.com/article.md&lt;/code&gt; or &lt;code&gt;example.com/markdown/article-slug&lt;/code&gt;. This approach avoids all the complexity of &lt;code&gt;Accept&lt;/code&gt; header parsing and &lt;code&gt;Vary&lt;/code&gt; header management.&lt;/p&gt;

&lt;p&gt;It is also more predictable for agents. An agent can fetch the Markdown representation directly without relying on the server correctly implementing content negotiation. This is a defensible choice, especially for documentation sites or content repositories that already manage content in Markdown. A separate URL eliminates the risk of cache fragmentation because the URL itself is the differentiator. There is no need for &lt;code&gt;Vary: Accept&lt;/code&gt;; the URL is the cache key. However, it also breaks the principle of one resource, one URL, and can lead to link rot if the Markdown URL is not treated as a canonical representation.&lt;/p&gt;

&lt;p&gt;The decision is an engineering trade-off. Content negotiation is more elegant and maintains a single canonical URL. A separate &lt;code&gt;.md&lt;/code&gt; URL is more robust and simpler to cache and test. For sites with existing, heavy CDN caching and complex request routing, the separate URL often proves safer to implement, as it sidesteps potential CDN quirks with &lt;code&gt;Vary&lt;/code&gt; handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test an AI-ready endpoint
&lt;/h2&gt;

&lt;p&gt;A testing checklist is essential for validating a content-negotiation endpoint. Run these requests against staging before production, and inspect both the status and the response headers.&lt;/p&gt;

&lt;p&gt;For a default HTML request, omit &lt;code&gt;Accept&lt;/code&gt; or use the broad value a browser sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://example.com/article | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type|Vary"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: &lt;code&gt;Content-Type: text/html; charset=utf-8&lt;/code&gt; and &lt;code&gt;Vary: Accept&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For an explicit Markdown request, ask for the representation directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: text/markdown"&lt;/span&gt; https://example.com/article | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type|Vary"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: &lt;code&gt;Content-Type: text/markdown; charset=utf-8&lt;/code&gt; and &lt;code&gt;Vary: Accept&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then test a q-value fallback. This client prefers HTML but can still process Markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: text/markdown;q=0.8, text/html;q=1.0"&lt;/span&gt; https://example.com/article | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type|Vary"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: &lt;code&gt;Content-Type: text/html; charset=utf-8&lt;/code&gt; and &lt;code&gt;Vary: Accept&lt;/code&gt;. The server should respect the higher q-value for HTML.&lt;/p&gt;

&lt;p&gt;Finally, ask for a type the server does not provide and inspect the cache headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; https://example.com/article
curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: text/markdown"&lt;/span&gt; https://example.com/article | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"Vary|CF-Cache-Status|X-Cache"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request should return &lt;code&gt;406 Not Acceptable&lt;/code&gt; when the server declines to supply a default. The second should show &lt;code&gt;Vary: Accept&lt;/code&gt; plus whatever cache status headers the provider documents. A passing curl command does not prove that a CDN is configured correctly. Make repeated requests with different &lt;code&gt;Accept&lt;/code&gt; strings and confirm that the edge serves the right variant without mixing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What adoption has to prove
&lt;/h2&gt;

&lt;p&gt;Adopting &lt;a href="https://acceptmarkdown.com/" rel="noopener noreferrer"&gt;&lt;code&gt;text/markdown&lt;/code&gt; content negotiation&lt;/a&gt; is a practical step, but it solves only one problem: delivering a cleaner representation to clients that request it. It does not make a site "AI-ready" by itself. The Markdown representation must be of high quality. It must strip out the cruft of templates and scripts while preserving the essential content hierarchy. The conversion process from HTML to Markdown is itself a separate engineering task, and a poorly generated Markdown document can be as useless as the raw HTML.&lt;/p&gt;

&lt;p&gt;The caching implications are real. A site that serves &lt;code&gt;Vary: Accept&lt;/code&gt; will see additional cache entries. This is the cost of doing business. The benefit is a better experience for AI agents and, potentially, lower token consumption and faster processing for the client. The adoption has to prove that the benefit outweighs the cost. For many content-heavy sites, the cost is negligible when a CDN normalizes &lt;code&gt;Accept&lt;/code&gt; headers. For others, the fragmentation could be a concern. The only way to know is to measure. Monitor cache hit ratios after enabling the feature. Watch for an increase in cache misses. If the hit ratio drops unacceptably, consider normalizing the &lt;code&gt;Accept&lt;/code&gt; header at the CDN level or using a separate Markdown URL instead. &lt;/p&gt;

&lt;p&gt;The web has always supported multiple representations of a resource. The &lt;code&gt;Accept&lt;/code&gt; header is a mature part of HTTP, not a new agent-only protocol. If you want to try this pattern, start with one content route, generate Markdown from the same source as HTML, implement the negotiation with a tested library, set &lt;code&gt;Vary: Accept&lt;/code&gt;, and observe the cache before expanding the rollout. If your CDN cannot give you a clear variant policy, use an explicit &lt;code&gt;.md&lt;/code&gt; URL instead.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/serving-markdown-to-ai-agents-without-breaking-http-caches" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>What Does an Agent Harness Actually Do? Building a Minimal One in Python</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Mon, 24 Aug 2026 08:07:19 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/what-does-an-agent-harness-actually-do-building-a-minimal-one-in-python-1c6l</link>
      <guid>https://dev.to/chenyuan20509/what-does-an-agent-harness-actually-do-building-a-minimal-one-in-python-1c6l</guid>
      <description>&lt;h2&gt;
  
  
  What Is a Harness, Really?
&lt;/h2&gt;

&lt;p&gt;You have used a coding agent. You type a prompt, it edits files, runs tests, maybe even opens a browser. But when you look inside the request, you are not sending your prompt directly to the model. Between you and the LLM sits a piece of infrastructure that does almost all of the work. That is the harness.&lt;/p&gt;

&lt;p&gt;An agent harness is the runtime scaffolding that turns a language model into an agent that can perform work. It drives model and tool calls, manages conversation state and context, applies approval policies, and keeps the agent progressing through multi-step tasks. In simpler terms: the harness is everything around the model. The model thinks; the harness acts.&lt;/p&gt;

&lt;p&gt;Think of the harness as the operating system for your agent. It holds the system prompt, which sets the agent’s role, behavior, and decision-making style. It maintains the message history. It decides when to stop. It turns the model’s request for a tool into an actual function call in your runtime. Without a harness, you have a chat endpoint. With a harness, you have something that can actually do things.&lt;/p&gt;

&lt;p&gt;The modern harness was not designed top-down from abstractions. It was born bottom-up out of coding agents solving real problems. Claude Code runs on one. DeepSeek Harness treats every capability—tools, skills, even the agent loop itself—as a swappable plugin, and it explicitly defines &lt;code&gt;Agent = Model + Harness&lt;/code&gt;. The harness is the layer that makes the model useful in the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Jobs of a Harness
&lt;/h2&gt;

&lt;p&gt;Every harness does four things. Nothing more, nothing less.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, it holds the system prompt.&lt;/strong&gt; That prompt is the agent’s job description. It tells the model who it is, what tools it has, and how it should behave. The user never sees it, but it shapes every response. Here is what one looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a coding assistant with access to these tools:
- read_file(path: str) -&amp;gt; str
- write_file(path: str, content: str) -&amp;gt; None
- run_shell(command: str) -&amp;gt; str

You have one job: help the user complete their task. When you need information,
call a tool. Do not guess. Do not make up file contents. When you have enough
information to answer, provide the final answer and stop.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Second, it manages the message history.&lt;/strong&gt; The harness keeps every user message, assistant response, and tool result in a single list. That list goes back to the model on every request. The harness does not just append; it trims, compresses, and summarizes when the context window gets tight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, it translates tool calls into real execution.&lt;/strong&gt; The model outputs a &lt;code&gt;tool_calls&lt;/code&gt; array. The harness parses that array, invokes the corresponding functions in your environment, collects the results, and sends them back as &lt;code&gt;role: "tool"&lt;/code&gt; messages. That round trip is the core of the harness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fourth, it enforces permissions.&lt;/strong&gt; The model can ask to run any shell command or delete any file. The harness decides whether to allow it, ask for approval, or block it entirely. Claude Code runs every tool call through a permission check before execution. Your harness should too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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;bool&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;tool_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;run_shell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rm -rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&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="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# block dangerous commands
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool_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;write_file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# only allow writing to current directory
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Agentic Loop, Line by Line
&lt;/h2&gt;

&lt;p&gt;The agentic loop is the reason–act–observe cycle that drives every step of an AI agent. The model generates a response, optionally invokes tools, observes the results, and loops until the task is done. That is it.&lt;/p&gt;

&lt;p&gt;Here is a complete, runnable single-file Python agent loop using the OpenAI API. It handles tools, dispatch, and the &lt;code&gt;role="tool"&lt;/code&gt; message round trip.&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&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;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&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="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;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;The weather in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is sunny, 72°F.&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;get_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&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="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;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;The time in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is 3:00 PM.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get current weather for a city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get current time for a timezone&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;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timezone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;tool_map&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;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_time&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;messages&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;role&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;content&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;You are a helpful assistant with tools.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;messages&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tool_choice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assistant_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="n"&gt;messages&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;assistant_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump&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;assistant_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&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;tool_call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;messages&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;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call_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;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;else&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assistant:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That loop does exactly what every harness does. It sends the conversation to the model. It checks whether the model requested tools. If yes, it executes them and appends the results as &lt;code&gt;role: "tool"&lt;/code&gt; messages. Then it loops. If no, it prints the answer and stops.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tool_calls&lt;/code&gt; field on the assistant message contains the model's requests. Each call has an &lt;code&gt;id&lt;/code&gt;, a function &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;arguments&lt;/code&gt; as a JSON string. The harness must send back a message with &lt;code&gt;role: "tool"&lt;/code&gt;, the same &lt;code&gt;tool_call_id&lt;/code&gt;, and the result as &lt;code&gt;content&lt;/code&gt;. That is the contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# When the model calls get_weather(city="Boston"), the assistant message looks like:
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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;call_abc123&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;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&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;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Boston&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Your harness runs get_weather("Boston") and sends back:
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call_id&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;call_abc123&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;content&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;The weather in Boston is sunny, 72°F.&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;h2&gt;
  
  
  Tools: Teaching the Model to Ask Instead of Guess
&lt;/h2&gt;

&lt;p&gt;The model does not call functions. It outputs a structured request to call a function. The harness executes the function and returns the result. That distinction matters because it means the harness, not the model, controls what actually runs.&lt;/p&gt;

&lt;p&gt;You define tools as a list of JSON schemas. Each tool has a name, a description, and a parameters schema. The description is critical. The model uses it to decide which tool to call and with what arguments. Write descriptions that are concrete and specific.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_user_by_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look up a user by their email address. Returns user ID, name, and role.&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;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s email address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The harness can also force a specific tool using &lt;code&gt;tool_choice&lt;/code&gt;. Set it to &lt;code&gt;{"type": "function", "function": {"name": "get_weather"}}&lt;/code&gt; to skip the model's reasoning and go straight to the tool. That is useful for routing or for tools that the model should always call first.&lt;/p&gt;

&lt;p&gt;Real harnesses expose many tools. Claude Code keeps its toolbox deliberately small—eighteen tools in the snapshot captured in late 2025, every one sitting behind a permission gate. OpenHarness ships with forty-three tools covering file I/O, shell, and more. The harness validates the arguments against the schema before executing the function. If the model sends malformed JSON or missing required fields, the harness rejects the call and returns an error message to the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_schema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_schema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&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;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&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;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&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;field&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&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;Missing required field: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Context: The Window Is a Budget
&lt;/h2&gt;

&lt;p&gt;Every request to the model has a context window. For GPT-4o, that is 128,000 tokens. That sounds like a lot until you start appending file contents, search results, and tool outputs. The harness must manage that budget.&lt;/p&gt;

&lt;p&gt;The harness does not just append every message forever. It tracks token counts. When the conversation gets too long, it has to decide what to drop. The simplest strategy is a sliding window: keep the system prompt, the last N user-assistant exchanges, and the most recent tool results. Drop everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trim_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Keep system prompt (index 0). Then keep the most recent messages
&lt;/span&gt;    &lt;span class="c1"&gt;# until we hit the token limit. This is a simplified version.
&lt;/span&gt;    &lt;span class="n"&gt;system_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&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="c1"&gt;# In practice, you would use tiktoken to count tokens.
&lt;/span&gt;    &lt;span class="c1"&gt;# For now, just keep the last 20 messages.
&lt;/span&gt;    &lt;span class="n"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;system_msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&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;trimmed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More sophisticated harnesses summarize old turns. They send a compressed version of the history back to the model. Some use a separate summarization model. Others just truncate. The choice depends on how much context your task requires.&lt;/p&gt;

&lt;p&gt;The harness also manages the context across tool calls. When the model calls a tool, the harness appends the result and immediately sends the updated message list back to the model. That means the model sees the tool output in the next request, not in the same request. The harness is the intermediary that makes that round trip possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# After executing a tool, the harness appends the result and loops.
# The next model request includes the tool result in the message history.
&lt;/span&gt;&lt;span class="n"&gt;messages&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;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call_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;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# The loop continues. The model now sees the tool output.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Permissions: The Harness Holds the Leash
&lt;/h2&gt;

&lt;p&gt;The model can ask to do anything. The harness decides what it is allowed to do. That is the permission system.&lt;/p&gt;

&lt;p&gt;Every tool call flows through a permission check before execution. The check can be automatic (allow or deny based on rules), interactive (ask the user for approval), or hybrid (allow some operations, ask for others). Claude Code gates every tool call behind its permission modes. DeepSeek Harness routes approvals through a swappable plugin, so you can replace the whole approval flow the way you swap any other component.&lt;/p&gt;

&lt;p&gt;The simplest permission system is a whitelist. Define which tools are allowed and which arguments are acceptable. Block everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED_TOOLS&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;read_file&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;write_file&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_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;BLOCKED_PATTERNS&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;rm -rf&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;sudo&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;chmod 777&lt;/span&gt;&lt;span class="sh"&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;permission_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&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;if&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_TOOLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&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;Tool &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not allowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool_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;run_shell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&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="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCKED_PATTERNS&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;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&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;Command contains blocked pattern: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The harness can also implement a confirmation flow. When the model requests a dangerous operation, the harness pauses, prompts the user, and only continues if the user approves. That is how coding agents avoid accidentally deleting your entire project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_with_approval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&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;tool_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;delete_file&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;Model wants to delete &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Approve? (y/n)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&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;y&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Operation cancelled by user&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;tool_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What We Skipped (and Why It Matters)
&lt;/h2&gt;

&lt;p&gt;The minimal harness above works, but it is not production-ready. Real harnesses handle a lot more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming.&lt;/strong&gt; Real harnesses start executing tools before the model finishes generating. That cuts latency. The model streams tool calls as it generates them; the harness kicks off the tool execution in parallel. Our loop waits for the full response before dispatching anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel tool calls.&lt;/strong&gt; The model can request multiple tools in one response. Our loop executes them sequentially. A real harness runs them concurrently and collects all results before sending the next request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retries and error handling.&lt;/strong&gt; Tools fail. The harness catches exceptions, formats them as error messages, and sends them back to the model. The model can then retry with corrected arguments or try a different approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Please try again with different arguments.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;State persistence.&lt;/strong&gt; The harness holds the conversation state in memory. If the process crashes, the state is lost. Real harnesses persist the message history, tool results, and progress to disk or a database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost control.&lt;/strong&gt; Every loop iteration costs money. The harness tracks token usage and can enforce a budget. If the agent exceeds the budget, the harness stops the loop and returns a partial result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;MAX_TOKENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_TOKENS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;
    &lt;span class="c1"&gt;# process response...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tool registration.&lt;/strong&gt; Our loop uses a hardcoded &lt;code&gt;tool_map&lt;/code&gt;. Real harnesses have a registry where tools are dynamically added, removed, and discovered. Plugins register their own tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build One Yourself
&lt;/h2&gt;

&lt;p&gt;You now know what a harness does. The next step is to build your own.&lt;/p&gt;

&lt;p&gt;Start with the loop from Section 3. Add a system prompt that defines your agent's personality and available tools. Add a few tools that matter to you: read a file, search the web, run a shell command. Add a permission check that blocks dangerous operations. Run it with a real task and watch the loop go.&lt;/p&gt;

&lt;p&gt;Then make it better. Add streaming. Add parallel tool calls. Add a token budget. Add persistence. Add a summarizer for long histories. Each addition teaches you something about how real harnesses work.&lt;/p&gt;

&lt;p&gt;The harness is not magic. It is a while loop, a message list, and a dispatch table. The model does the thinking. The harness does the work. Build one, and you will never look at a coding agent the same way again.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/what-does-an-agent-harness-actually-do-building-a-minimal-one-in-python" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Private AI Inference with Homomorphic Encryption: A Practical Guide to Computing on Encrypted Data</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Sat, 15 Aug 2026 12:23:41 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/private-ai-inference-with-homomorphic-encryption-a-practical-guide-to-computing-on-encrypted-data-3349</link>
      <guid>https://dev.to/chenyuan20509/private-ai-inference-with-homomorphic-encryption-a-practical-guide-to-computing-on-encrypted-data-3349</guid>
      <description>&lt;p&gt;In 2009, Craig Gentry proved that it is possible to compute on encrypted data without ever decrypting it, and the result was widely treated as a theoretical curiosity. Sixteen years later, homomorphic encryption has crossed from conference papers into production pipelines: banks screen transactions against encrypted watchlists, hospitals run diagnostic models on data that never leaves their custody, and in August 2026 Google announced private AI features built on the same primitives. The gap between "possible in theory" and "usable in practice" is still wide, but it is no longer an argument against trying. This guide walks through what homomorphic encryption actually computes, how the CKKS scheme turns encrypted vectors into a workable substrate for machine learning, and the cost model that decides whether a private inference pipeline is worth building at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Promise: Compute Without Reading
&lt;/h2&gt;

&lt;p&gt;Ordinary encryption has a hard property: a ciphertext reveals nothing about the plaintext. AES-CTR, ChaCha20, RSA — all of them scramble data so thoroughly that an attacker holding the ciphertext and a supercomputer cannot recover the message without the key. That property is also the problem. If a server stores customer data encrypted at rest, every query requires shipping the data (or the key) somewhere a human or a process can read it. The moment the data is decrypted for computation, the confidentiality boundary moves from the storage layer to the memory of whatever process is doing the work.&lt;/p&gt;

&lt;p&gt;Homomorphic encryption changes the terms. A homomorphic scheme is one where operations on ciphertexts correspond to operations on plaintexts: &lt;code&gt;Enc(a) ⊕ Enc(b) = Enc(a + b)&lt;/code&gt;. A server can add, multiply, and combine encrypted values and return the encrypted result, and the client — the only party holding the key — decrypts the final answer. The server learns nothing about the inputs, the intermediate values, or the output. For inference, this is the entire ballgame: the model owner never exposes weights, and the data owner never exposes the query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Plain Encryption Breaks Computation
&lt;/h2&gt;

&lt;p&gt;To see why this is hard, consider what AES does to a single byte. The S-box substitution and the ShiftRows/MixColumns rounds mix the input so completely that flipping one plaintext bit changes roughly half the output bits. That avalanche effect is what makes AES secure, and it is exactly what makes it unusable for computation. There is no way to run &lt;code&gt;a + b&lt;/code&gt; on AES ciphertexts because the algebraic structure of the ciphertext has no relationship to the algebraic structure of the plaintext.&lt;/p&gt;

&lt;p&gt;Fully homomorphic encryption takes the opposite approach. Instead of starting with a scrambling cipher and hoping arithmetic survives, it starts with an algebraic structure that naturally supports both operations. The classic construction works over polynomial rings: plaintexts are small polynomials, ciphertexts are pairs of larger polynomials, and addition and multiplication in the ring map to addition and multiplication of the encrypted messages. Security comes not from destroying structure but from noise — every operation grows a random error term that must stay small enough for decryption to still recover the message. Multiply too many times and the noise drowns the signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Gentry's Bootstrapping to CKKS
&lt;/h2&gt;

&lt;p&gt;Gentry's 2009 breakthrough had two parts. The first was the observation that a scheme with bounded noise can still be made unbounded: if the decryption circuit is shallow enough, the server can evaluate it homomorphically, producing a fresh ciphertext with reset noise. That process, bootstrapping, was the theoretical missing piece. The second part was a working scheme with a decryption circuit shallow enough to bootstrap. The catch was performance — early bootstrapping took minutes per operation.&lt;/p&gt;

&lt;p&gt;The decade after Gentry produced a family of practical schemes. BGV and BFV handle encrypted integers with exact arithmetic. TFHE (CGGI) works over encrypted bits and is fast enough for small circuits like database lookups and comparisons. And CKKS, published by Cheon, Kim, Kim, and Song in 2017, introduced approximate arithmetic over encrypted real numbers, with a noise budget that behaves like floating-point error. That last property is what made machine learning viable: neural networks already tolerate small numerical errors, so a scheme that treats noise as precision loss fits the workload instead of fighting it.&lt;/p&gt;

&lt;p&gt;CKKS also inherits a trick called SIMD packing from its predecessors. A single ciphertext in CKKS is not one number but a vector of hundreds of slots, and operations apply element-wise across the whole vector. A dot product — the inner loop of almost every inference step — becomes one ciphertext multiplication and a few rotations instead of hundreds of separate operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CKKS Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;The construction is a polynomial ring &lt;code&gt;R = Z[X] / (X^N + 1)&lt;/code&gt;, typically with &lt;code&gt;N = 4096&lt;/code&gt; or &lt;code&gt;8192&lt;/code&gt;. Plaintexts are polynomials of degree less than &lt;code&gt;N&lt;/code&gt; whose coefficients are the numbers you actually care about, scaled by a large factor and rounded. The scale factor is the scheme's version of fixed-point arithmetic: it sets how many bits of fractional precision survive a multiplication. After each multiply, the scale of the result grows, and the scheme provides a rescaling operation that brings it back down.&lt;/p&gt;

&lt;p&gt;Noise is the real constraint. Each addition adds noise linearly; each multiplication roughly squares it. The scheme allocates a noise budget at encryption time, and every operation spends some of it. When the budget hits zero, decryption produces garbage. This is why parameter selection matters more in FHE than in any other part of a machine learning stack: the ring degree sets the maximum vector size and the top of the noise budget, the scale factor sets precision, and the multiplication depth you need sets how many levels the chain must provide before bootstrapping becomes necessary.&lt;/p&gt;

&lt;p&gt;A minimal encrypted vector in Python, using a research-oriented binding, looks like this:&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;openfhe&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CKKSRNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SecurityLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ScalingTechnique&lt;/span&gt;

&lt;span class="c1"&gt;# A fresh CKKS context with 40 levels of multiplicative depth
&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CKKSRNS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SetMultiplicativeDepth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SetScalingModSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SetSecurityLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SecurityLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HEStd_128_classic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CKKSRNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenCryptoContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PKE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEYSWITCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LEVELEDSHE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ADVANCED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;KeyGen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EvalMultKeyGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# allow encrypted multiplication
&lt;/span&gt;&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EvalRotateKeyGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secretKey&lt;/span&gt;&lt;span class="p"&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MakePackedPlaintext&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire setup. &lt;code&gt;ct&lt;/code&gt; is now a ciphertext the server can store, transform, and return without ever seeing the values inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Minimal Private Inference Pipeline
&lt;/h2&gt;

&lt;p&gt;The classic private inference flow is split between a client that owns the data and a server that owns the model. The client encrypts its input, the server evaluates the model homomorphically, and the client decrypts the result. For a logistic regression — a single affine transform followed by a sigmoid — the encrypted computation is small enough to show in full:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encrypted_predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Encrypted dot product: one multiply, one add over packed slots
&lt;/span&gt;    &lt;span class="n"&gt;ct_z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EvalAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EvalMult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct_w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Approximate the sigmoid with a low-degree polynomial
&lt;/span&gt;    &lt;span class="c1"&gt;# because division and exp are not directly supported
&lt;/span&gt;    &lt;span class="n"&gt;sigmoid_approx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="mf"&gt;0.5&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.197&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ct_z&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.004&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EvalMult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct_z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct_z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ct_z&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;sigmoid_approx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details matter here. First, the sigmoid must be replaced by a polynomial approximation such as a Taylor or Chebyshev expansion, because CKKS supports only addition and multiplication. Second, the approximation degree is a direct trade against the noise budget: every extra multiply spends a level. Third, the whole vector of slots is processed in parallel, so one encrypted call classifies an entire batch of inputs, not a single sample.&lt;/p&gt;

&lt;p&gt;For deeper networks, the same recipe repeats layer by layer: convolution becomes a sum of shifted and multiplied ciphertexts, ReLU becomes a polynomial like &lt;code&gt;x^2&lt;/code&gt;-based approximation or a TFHE-style comparison, and pooling becomes rotations plus additions. The engineering problem is not expressing the model — it is keeping the depth inside the budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost Model
&lt;/h2&gt;

&lt;p&gt;Homomorphic inference is slow, and the honest framing is to say how slow and why. Ciphertexts are two or three orders of magnitude larger than the plaintexts they hold. Multiplication on a packed ciphertext is roughly a thousand to ten thousand times more expensive than the equivalent plaintext float operation, depending on ring degree and security level. Bootstrapping, when the noise budget runs out, costs on the order of tens of milliseconds to seconds per ciphertext — cheap enough to amortize over a packed batch, ruinous if applied per element.&lt;/p&gt;

&lt;p&gt;The practical consequence is that private inference shifts the bottleneck from model quality to arithmetic budget. A model that runs in 2 milliseconds on plaintext floats can take seconds homomorphically, and the gap is dominated by the number of multiplications per slot, not the model's parameter count. Architectures that are friendly to FHE are the ones that keep multiplicative depth low: shallow MLPs, quantized networks, models with polynomial activations. Deep transformer stacks with attention and softmax are the worst case, because attention is a softmax followed by matrix products — softmax division is not a native operation, and its polynomial replacement is expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Private Inference Makes Sense
&lt;/h2&gt;

&lt;p&gt;The honest answer is that homomorphic inference earns its cost in a narrow but real set of situations. The first is regulated data with a shared computation: hospitals collaborating on a model where patient records cannot leave each institution's boundary, or banks running joint fraud models over accounts they are legally barred from sharing. The second is API-based inference where the query itself is the secret — legal research, medical symptom triage, proprietary financial signals — and the client does not want the server to see what it is asking. The third is the emerging pattern behind the August 2026 announcements: consumer AI where the provider wants to process a user's data without being able to read it, as a product differentiator rather than a regulatory requirement.&lt;/p&gt;

&lt;p&gt;What homomorphic encryption does not buy you is protection against a malicious server. A server that controls the evaluation can still drop requests, return garbage, or measure timing and access patterns. FHE guarantees confidentiality of the data against the server's curiosity, not integrity of the result against the server's malice. Teams that need both must add zero-knowledge proofs or commit-and-reveal protocols on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Library
&lt;/h2&gt;

&lt;p&gt;The ecosystem has consolidated around a few serious options. Microsoft SEAL is the reference implementation of BFV and CKKS in C++, with Python bindings via the pybind11-based extensions; it is battle-tested but expects the caller to manage parameters. OpenFHE is the community successor, actively maintained, and adds BGV, TFHE, and a unified API across schemes. TenSEAL, built on SEAL, provides a Pythonic API for CKKS over PyTorch tensors, though its maintenance has slowed. Zama's Concrete implements TFHE with a compiler that takes plain Python functions and emits bootstrapped circuits, which suits smaller integer workloads like lookups and decision trees. For a new project, the practical default is OpenFHE for custom CKKS work, and Concrete when the target workload is small and integer-shaped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# OpenFHE exposes the same context pattern across schemes,
# so the pipeline above ports to BGV with two line changes:
&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BGVRNS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SetMultiplicativeDepth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BGVRNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenCryptoContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parameter selection itself is a black art that libraries are only beginning to automate. The rule of thumb is: choose the ring degree from the vector size and security level, choose the scaling modulus from the precision you need, then count the multiplicative depth of your exact model graph and add headroom for the approximations. Getting this wrong shows up not as a crash but as silent precision loss at the output — which makes an end-to-end test with known plaintexts the most important step in any FHE project.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Decision Framework for Your First Private Inference Pilot
&lt;/h2&gt;

&lt;p&gt;A useful heuristic before committing engineering time: if the plaintext model fits on a single machine and the deployment is a one-off computation, homomorphic encryption is probably the wrong tool — shipping the data under an agreement is simpler. If the computation is recurring, the inputs are sensitive, and the participants do not trust each other enough to share plaintexts, the calculus flips. Start with a single layer, measure the noise budget after every operation, and instrument the number of slots used per ciphertext. Most teams discover that the bottleneck is not the cryptographic primitives but the model's activation functions, and that replacing one ReLU with a polynomial buys more than a faster library ever could.&lt;/p&gt;

&lt;p&gt;The field is also moving faster than its reputation suggests. Ciphertext compression, GPU kernels for CKKS multiplication, and programmable bootstrapping have each cut the effective cost by an order of magnitude within the last few years. The 2009 result was a proof that encrypted computation is possible; the current state of the art is a proof that it is affordable for the workloads where confidentiality is actually worth money. For anyone building AI products on other people's data, that is a gap worth watching — and a pilot worth running.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/private-ai-inference-with-homomorphic-encryption-a-practical-guide-to-computing-on-encrypted-data" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why Your LLM Classifier Doesn't Need the Taxonomy: Hypothetical Classification with Embeddings</title>
      <dc:creator>Chen Yuan</dc:creator>
      <pubDate>Fri, 14 Aug 2026 14:47:39 +0000</pubDate>
      <link>https://dev.to/chenyuan20509/why-your-llm-classifier-doesnt-need-the-taxonomy-hypothetical-classification-with-embeddings-387d</link>
      <guid>https://dev.to/chenyuan20509/why-your-llm-classifier-doesnt-need-the-taxonomy-hypothetical-classification-with-embeddings-387d</guid>
      <description>&lt;p&gt;Classifying free-text queries into a fixed product taxonomy is one of the most common LLM workloads in production, and one of the most quietly expensive ones. A typical e-commerce catalog ships with 400 to 800 legal categories, each spelled out as a fully qualified path like &lt;code&gt;Furniture / Living Room Furniture / Coffee Tables &amp;amp; End Tables / Coffee Tables&lt;/code&gt;. Every time a search query needs a label, the whole vocabulary has to travel with the prompt: as a giant Pydantic &lt;code&gt;Literal&lt;/code&gt;, as a JSON schema, or as a few hundred lines of enum text. Tokens are not free, and neither is latency. The prompt grows, the small model you wanted to use starts misbehaving, and the request that should have cost a fraction of a cent now needs the biggest model on the roster just to keep the output valid.&lt;/p&gt;

&lt;p&gt;There is a cheaper pattern that most teams never consider. Instead of forcing the model to choose from the real taxonomy, you let a small, cheap model invent fake categories that sound plausible, and then resolve those hallucinations back into the real vocabulary with an embedding lookup. It sounds backwards. It works surprisingly well. This article walks through the idea, the implementation, the failure modes, and the measurements you should collect before trusting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Standard Answer: Structured Outputs and the Taxonomy Tax
&lt;/h2&gt;

&lt;p&gt;The conventional approach is structured outputs. You define the legal vocabulary as a type, hand it to the provider, and ask for a constrained decode:&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;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;

&lt;span class="n"&gt;FullyQualifiedClassifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Furniture / Bedroom Furniture / Beds &amp;amp; Headboards / Beds&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;Furniture / Living Room Furniture / Chairs &amp;amp; Seating / Accent Chairs&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;Rugs / Area Rugs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ... times 500
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueryClassification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Structured representation of a search query.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;classifications&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="n"&gt;FullyQualifiedClassifications&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Possible classifications for the product.&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;Constrained decoding guarantees that the answer is drawn from the legal set, which is a strong property. But it carries a hidden cost that grows with the catalog. The schema has to be sent with every request, so every call pays the full vocabulary in input tokens. Classification is a high-volume, low-value-per-request workload, which is exactly the wrong place to spend tokens.&lt;/p&gt;

&lt;p&gt;The second problem is that the guarantee is only as good as the model's willingness to stay inside the schema. Small models choke on a 500-element &lt;code&gt;Literal&lt;/code&gt;; they either truncate it, or they start hallucinating categories that were never in the list, which defeats the entire purpose. The teams I have watched hit this wall usually respond by upgrading the model, and the unit economics of the whole pipeline follow them upward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trick: Ask the Model to Make Things Up
&lt;/h2&gt;

&lt;p&gt;The key realization is that the model does not need to know the taxonomy to know what kind of thing a query is talking about. A query like "brown coffee table" is obviously a piece of living room furniture. The hard part is not understanding the query; it is mapping that understanding onto a specific node in a tree with hundreds of leaves.&lt;/p&gt;

&lt;p&gt;So stop sending the tree. Send an example of the &lt;em&gt;shape&lt;/em&gt; of a classification, and ask the model to invent new ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;hallucination_prompt&lt;/span&gt; &lt;span class="o"&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;
Your task is to create novel, never seen before furniture, home goods,
or hardware classifications that best fit a search query.

Product classifications look like:
Furniture / Living Room Furniture / Coffee Tables &amp;amp; End Tables / Coffee Tables
Décor &amp;amp; Pillows / Decorative Pillows &amp;amp; Blankets / Throw Pillows
Furniture / Bedroom Furniture / Dressers &amp;amp; Chests
Kitchen &amp;amp; Tabletop / Kitchen Organization / Food Storage &amp;amp; Canisters
Baby &amp;amp; Kids / Toddler &amp;amp; Kids Bedroom Furniture / Kids Beds

Here is the query to generate classifications for:
brown coffee table
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt now fits in a few hundred tokens instead of several thousand. The model has no legal vocabulary to violate, so it will happily produce a made-up path like &lt;code&gt;Furniture / Living Room / Tables / Coffee&lt;/code&gt;. That output is useless as a label by itself. But it is a precise description of the query's meaning, written in the same style as the real taxonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolving the Fake into the Real
&lt;/h2&gt;

&lt;p&gt;Now the problem becomes: given a hallucinated path, find the real category it points at. Embeddings make this a nearest-neighbor search instead of a string-matching problem, which matters because the fake path and the real path share almost no literal characters.&lt;/p&gt;

&lt;p&gt;The setup is small enough to live in memory. Embed every real category path once at startup with a compact sentence model, keep the vectors in a NumPy array or a tiny vector store, and embed the hallucinated path at request time. The label is the real category whose vector has the highest dot product with the fake one:&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;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;real_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;  &lt;span class="c1"&gt;# the full taxonomy, loaded once
&lt;/span&gt;&lt;span class="n"&gt;real_vectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real_paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalize_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hallucinated_path&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="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="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;hallucinated_path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;normalize_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real_vectors&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&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;real_paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a catalog of a few hundred categories, this is a single matrix-vector product, well under a millisecond even on a laptop CPU. The expensive part of the pipeline is now the tiny LLM call, and that is the whole point: you have moved the cost from a big model with a giant schema to a small model with a short prompt plus a local lookup that costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works: What Small Models Are Actually Good At
&lt;/h2&gt;

&lt;p&gt;The pattern sounds like it should fail, so it is worth being precise about why it does not. Small models are bad at constrained generation over large vocabularies: their attention degrades as the constraint set grows, and their sampling drifts into invalid outputs. But they are good at open-ended paraphrase and at writing text in the style of an example. Inventing a plausible category path for "brown coffee table" is a style-matching task, not a constraint-satisfaction task. It is exactly the kind of fluent generation that even a 1B-parameter model does reliably.&lt;/p&gt;

&lt;p&gt;The embedding model, meanwhile, is the component that actually holds the taxonomy. Sentence embeddings map both the fake path and the real paths into a space where meaning, not surface form, determines distance. &lt;code&gt;Furniture / Living Room / Tables / Coffee&lt;/code&gt; and &lt;code&gt;Furniture / Living Room Furniture / Coffee Tables &amp;amp; End Tables / Coffee Tables&lt;/code&gt; share almost no tokens, but they land close together because the embedding space was trained on paraphrases. Each component does the part it is good at, and neither needs to be strong at the other's job.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Cheap Models Are Enough, and When They Are Not
&lt;/h2&gt;

&lt;p&gt;The pattern is not a universal replacement for structured outputs; it is a trade. You should measure three failure classes before committing.&lt;/p&gt;

&lt;p&gt;The first is vocabulary drift. A small model inventing categories will sometimes produce a path that is semantically generic, like &lt;code&gt;Furniture / Tables&lt;/code&gt;, which resolves to whichever real node is closest in the embedding space. If your taxonomy has many tables, the nearest neighbor may be wrong even though the query was specific. This is the dominant failure mode, and its rate is a property of your taxonomy, not of the model.&lt;/p&gt;

&lt;p&gt;The second is resolution ambiguity. Some queries are genuinely under-specified: "black stand" could be a phone stand, a monitor stand, or a plant stand. The hallucinated path will be confidently wrong because the model had no way to know. This is not a bug in the pattern; structured outputs fail the same way, but they fail by returning a confidently wrong label with a schema-valid appearance.&lt;/p&gt;

&lt;p&gt;The third is taxonomy churn. If your catalog changes often, the embedding index must be rebuilt, and a newly added category is invisible until then. Rebuilding a few hundred embeddings takes seconds, so this is an operational detail rather than a blocker, but it needs to be part of the deploy pipeline.&lt;/p&gt;

&lt;p&gt;The pattern shines when the vocabulary is large, the queries are short, and you want the classification step cheap enough to run on every request without thinking about the bill. It is a poor fit when the taxonomy is tiny, when labels must be exact strings with no tolerance, or when you cannot tolerate any rate of misclassification and prefer the deterministic failure of constrained decoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails: Handling Unresolvable Hallucinations
&lt;/h2&gt;

&lt;p&gt;In practice you want a confidence floor on the resolution step, not just an argmax. The dot product between the hallucinated vector and its nearest real neighbor tells you how convinced the system is. A path that resolves at 0.45 cosine similarity is a guess; one that resolves at 0.85 is a lock. The cheap fix is a threshold with two fallback tiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.6&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;tuple&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;invent_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# small LLM, short prompt
&lt;/span&gt;    &lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# embedding lookup
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&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;real&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;run_structured_classification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below the threshold you can escalate to the structured-output path on a bigger model, or route to a human review queue. Because the cheap path handles the bulk of traffic, the escalation path only fires on the ambiguous fraction, and your average cost stays low even though the worst case still uses the expensive machinery. A second guardrail worth adding is a short denylist of generic leaves that are known to swallow unrelated queries, so a generic hallucination can be rejected before it resolves to a useless bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring Quality Without a Labeled Set
&lt;/h2&gt;

&lt;p&gt;The objection to this pattern is usually "how do I know it is accurate?" You can get surprisingly far without a hand-labeled test set. Sample a few hundred real search queries from your logs, run both the cheap path and the structured-output path on the same queries, and diff the labels. Disagreement does not mean the cheap path is wrong, but every disagreement is a case to eyeball. In practice the disagreements cluster into two piles: cases where the cheap path is semantically right and the schema path was forced into a wrong but valid leaf, and cases where the cheap path resolved to a generic neighbor. The first pile is evidence the pattern is working; the second pile tunes your threshold.&lt;/p&gt;

&lt;p&gt;Track the resolution score distribution as a metric. If the mean score drifts down over weeks, the model vendor changed something or your taxonomy drifted; either way you want to know before the misclassification rate moves. A small dashboard with three numbers, mean score, escalation rate, and disagreement rate against the structured path, is enough to run this pattern in production with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;The complete pipeline is short enough to hold in your head: a tiny model invents a category path from a prompt of a few hundred tokens, an embedding lookup resolves that path to a real leaf in under a millisecond, and a similarity threshold decides whether the answer ships or escalates. The taxonomy never travels with the request, the cheap model stays cheap, and the expensive machinery only sees the genuinely ambiguous cases.&lt;/p&gt;

&lt;p&gt;Hypothetical classification is one of those ideas that looks like a hack until you measure it. The vocabulary is the expensive part of classification, and the embedding model lets you carry it once, locally, instead of shipping it on every request. If your classification workload is currently paying the taxonomy tax on a big model, it is worth a weekend experiment: replace the schema with a hallucination prompt, add the lookup, and look at the disagreement rate. The numbers will tell you quickly whether the backwards idea is the right one.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://dispatch-blog.hashnode.dev/why-your-llm-classifier-doesn-t-need-the-taxonomy-hypothetical-classification-with-embeddings" rel="noopener noreferrer"&gt;Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
