<?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: Harish Kotra (he/him)</title>
    <description>The latest articles on DEV Community by Harish Kotra (he/him) (@harishkotra).</description>
    <link>https://dev.to/harishkotra</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%2F101279%2F516b4cd2-cc6d-451c-a8c8-a7d9ab5ec41a.png</url>
      <title>DEV Community: Harish Kotra (he/him)</title>
      <link>https://dev.to/harishkotra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harishkotra"/>
    <language>en</language>
    <item>
      <title>How I Built a Slack Bot That Flags YC Founders Before YC Even Announces Them</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:33:23 +0000</pubDate>
      <link>https://dev.to/harishkotra/how-i-built-a-slack-bot-that-flags-yc-founders-before-yc-even-announces-them-1h52</link>
      <guid>https://dev.to/harishkotra/how-i-built-a-slack-bot-that-flags-yc-founders-before-yc-even-announces-them-1h52</guid>
      <description>&lt;p&gt;&lt;strong&gt;A deep dive into the YC Launch Monitor — a Python bot that tracks every new YC and a16z Speedrun company, and — the fun part — catches founders announcing their acceptance on X/LinkedIn &lt;em&gt;before&lt;/em&gt; the official listing goes live.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;If you work in GTM (go-to-market), you know the game: the person who reaches a freshly-accepted YC founder &lt;em&gt;first&lt;/em&gt; wins the meeting. The problem is that YC publishes its new batch all at once, and by then every sales rep on the internet has the same list.&lt;/p&gt;

&lt;p&gt;The real signal lives &lt;em&gt;earlier&lt;/em&gt; — in the founder's own "big news: I got into Y Combinator" tweet, posted days before YC updates its directory.&lt;/p&gt;

&lt;p&gt;So I built a bot that watches for both. It's called &lt;strong&gt;YC Launch Monitor&lt;/strong&gt;, it's open source, and this post walks through exactly how it works under the hood.&lt;/p&gt;




&lt;h2&gt;
  
  
  The core idea: two kinds of signal
&lt;/h2&gt;

&lt;p&gt;The bot emits two alert types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡ EARLY&lt;/strong&gt; — a founder announced their acceptance on X/LinkedIn, but the company is &lt;strong&gt;not yet&lt;/strong&gt; in the official YC/Speedrun directory. &lt;em&gt;This is the "get ahead of everyone" signal.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✅ CONFIRMED&lt;/strong&gt; — the company is now officially listed in the YC directory or the a16z Speedrun program.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both land in a Slack channel as rich Block Kit cards, deduplicated, on a schedule.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The design is deliberately &lt;strong&gt;pluggable&lt;/strong&gt; — four sources feed one pipeline, and adding a fifth is a single module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌─────────────────────────────────────────────────────────┐
                 │                       main.py  (orchestrator)           │
                 │   --once / --loop  →  run_once() → deliver()            │
                 └───────┬──────────────────────────────┬──────────────────┘
                         │                              │
         OFFICIAL sources│                              │SOCIAL sources (optional)
                         ▼                              ▼
   ┌──────────────────────────┐          ┌───────────────────────────────────┐
   │ yc_directory.py          │          │ x_twitter.py / linkedin.py        │
   │  yc-oss GitHub Pages API │          │  X API v2 / pluggable endpoint    │
   │  → changes feed + diff   │          │  → raw Post objects               │
   └───────────┬──────────────┘          └───────────────┬───────────────────┘
               │  ✅ CONFIRMED                           │
               │                                         ▼
   ┌───────────▼──────────────┐          ┌───────────────────────────────────┐
   │ speedrun.py              │          │ detector.classify_post(post, idx) │
   │  speedrun-api.a16z.com   │          │  match vs CompanyIndex            │
   │  → slug-list diff        │          │  → ⚡ EARLY  or  ✅ CONFIRMED      │
   └───────────┬──────────────┘          └───────────────┬───────────────────┘
               │  ✅ CONFIRMED                           │
               └───────────────┬─────────────────────────┘
                               ▼
                  ┌────────────────────────────┐
                  │ state.py  (SQLite state.db)│   dedup via seen_events
                  │  mark_seen / is_seen       │   incremental via snapshots
                  └────────────┬───────────────┘
                               ▼
                  ┌────────────────────────────┐
                  │ alerts.py  (SlackClient)   │   Block Kit payload
                  │  chat.postMessage / webhook│
                  └────────────┬───────────────┘
                               ▼
                        ┌─────────────┐
                        │  Slack DM / │   ⚡/✅ company · founder · batch ·
                        │  channel    │   source · details · links
                        └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Python 3.9+ · &lt;code&gt;requests&lt;/code&gt; · SQLite (stdlib) · PyYAML + python-dotenv · Slack Block Kit · launchd/cron. No database server, no message broker, no framework — just three pip dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the data sources (the part that took the longest)
&lt;/h2&gt;

&lt;p&gt;The obvious source — &lt;code&gt;ycombinator.com/companies&lt;/code&gt; — turned out to be a dead end: it's an Inertia.js single-page app with &lt;strong&gt;no clean public endpoint&lt;/strong&gt;, and YC's old &lt;code&gt;v0&lt;/code&gt; API is gone. Scraping it is fragile.&lt;/p&gt;

&lt;p&gt;The working solution is the &lt;strong&gt;&lt;code&gt;yc-oss&lt;/code&gt; GitHub Pages mirror&lt;/strong&gt; of the YC directory's Algolia index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yc-oss.github.io/api/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's free, needs no key, updates daily, and — crucially — exposes a &lt;strong&gt;changes feed&lt;/strong&gt; (&lt;code&gt;changes/latest.json&lt;/code&gt;) with an &lt;code&gt;added&lt;/code&gt; array. That's a clean, incremental "what's new" signal, exactly what a monitor wants:&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;BASE&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://yc-oss.github.io/api&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;fetch_changes&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;dict&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;get_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/changes/latest.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;For &lt;strong&gt;a16z Speedrun&lt;/strong&gt; (a separate accelerator from YC, worth monitoring on its own), there's a proper public REST API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://speedrun-api.a16z.com/api/companies/companies/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paginated, ~251 records, and each record includes &lt;strong&gt;founder names, X/LinkedIn/website URLs, cohort, and industries&lt;/strong&gt; — everything a rich outreach alert needs. We diff the full slug list against our stored snapshot to detect new companies.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;X&lt;/strong&gt; and &lt;strong&gt;LinkedIn&lt;/strong&gt;, the situation is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X&lt;/strong&gt; needs an API v2 Bearer token (Basic tier ~$100/mo) for &lt;code&gt;search/recent&lt;/code&gt;. When present, we scan for announcement phrasings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt; has no free public API, so the bot uses a &lt;strong&gt;pluggable adapter&lt;/strong&gt; — point it at any service that accepts &lt;code&gt;POST {"query":..., "freshness_minutes":N}&lt;/code&gt; and returns &lt;code&gt;{"results":[{author,text,url,published_at}]}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crucially, &lt;strong&gt;YC Directory + Speedrun work with zero API keys&lt;/strong&gt;. The bot is fully functional out of the box; X/LinkedIn are skipped gracefully until configured.&lt;/p&gt;




&lt;h2&gt;
  
  
  State management &amp;amp; duplicate detection (the boring part that matters)
&lt;/h2&gt;

&lt;p&gt;A monitor that spams you with the same company every 8 hours is useless. So all state lives in a local SQLite database (&lt;code&gt;state.db&lt;/code&gt;) with two tables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;seen_events&lt;/code&gt;&lt;/strong&gt; — one row per dedup key that's already been alerted. &lt;code&gt;INSERT OR IGNORE&lt;/code&gt; makes dedup atomic and race-free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;snapshots&lt;/code&gt;&lt;/strong&gt; — the SHA-256 hash of each source's last-seen payload, so the bot only acts on &lt;em&gt;changes&lt;/em&gt;.
&lt;/li&gt;
&lt;/ol&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;mark_seen&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;dedup_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT OR IGNORE INTO seen_events (dedup_key, source, company, alerted_at) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VALUES (?,?,?,?)&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;dedup_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&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;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;snapshot_unchanged&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;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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="n"&gt;h&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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="n"&gt;row&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;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT payload_hash FROM snapshots WHERE source = ?&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;source&lt;/span&gt;&lt;span class="p"&gt;,)).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&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;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload_hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first run of each source &lt;strong&gt;establishes a baseline&lt;/strong&gt; — it stores the snapshot but alerts on nothing. From then on, only &lt;em&gt;new&lt;/em&gt; companies trigger alerts. No duplicate spam, even across restarts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The early-detection classifier (the fun part)
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;detector&lt;/code&gt; turns a raw social post into a classified alert. The whole challenge is &lt;strong&gt;avoiding false positives&lt;/strong&gt; — a founder's bio mentioning "Amazon" is not an Amazon launch, and a post saying "solo founder" must not match a company literally named "Solo".&lt;/p&gt;

&lt;p&gt;The matching strategy, in order of confidence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exact X-handle match&lt;/strong&gt; — the author's &lt;code&gt;@handle&lt;/code&gt; matches a company's handle in the official index. Strongest signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word-boundary company name near an announcement keyword&lt;/strong&gt; — the company name appears (case-sensitively, since founders capitalize it) within ~40 characters of a phrase like "got into YC", "accepted", "batch", "speedrun".
&lt;/li&gt;
&lt;/ol&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_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Post&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;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_match_company&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&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;span class="c1"&gt;# handle match → name near keyword
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;matched&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="n"&gt;STATUS_CONFIRMED&lt;/span&gt;                 &lt;span class="c1"&gt;# already officially listed
&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;company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_extract_company_hint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&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="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATUS_EARLY&lt;/span&gt;                     &lt;span class="c1"&gt;# announced before official listing
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;founder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&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="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post&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;link&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post&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;dedup_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;make_dedup_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;post&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;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{...})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the post matches an officially-listed company → &lt;strong&gt;✅ CONFIRMED&lt;/strong&gt;. If it doesn't (or the company can't be matched at all) → &lt;strong&gt;⚡ EARLY&lt;/strong&gt;, because the founder is clearly announcing &lt;em&gt;before&lt;/em&gt; the official listing. That's the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The alert model &amp;amp; Slack delivery
&lt;/h2&gt;

&lt;p&gt;Every detection becomes an &lt;code&gt;Alert&lt;/code&gt; dataclass that knows how to render itself as a &lt;strong&gt;Slack Block Kit&lt;/strong&gt; payload — header, status/source badges, batch/cohort context, description, and action buttons:&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="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;company&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;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                &lt;span class="c1"&gt;# "YC Directory" | "Speedrun" | "X (Twitter)" | "LinkedIn"
&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;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATUS_EARLY&lt;/span&gt; &lt;span class="c1"&gt;# early | confirmed
&lt;/span&gt;    &lt;span class="n"&gt;founder&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="n"&gt;details&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="n"&gt;link&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="n"&gt;dedup_key&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="n"&gt;detected_at&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="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utcnow_iso&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&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;to_slack_payload&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;cfg&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;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ... builds header / status / batch / details / buttons blocks ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delivery supports &lt;strong&gt;both&lt;/strong&gt; an OAuth bot token (&lt;code&gt;chat.postMessage&lt;/code&gt; — posts to a channel &lt;em&gt;or&lt;/em&gt; a DM) and a legacy Incoming Webhook as fallback:&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;send_payload&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;payload&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&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;Bearer &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;bot_token&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;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="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&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;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;True&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;webhook_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&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;webhook_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;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;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Persistence &amp;amp; ops
&lt;/h2&gt;

&lt;p&gt;Three ways to run it persistently, from simplest to most robust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./run.sh &lt;span class="nt"&gt;--loop&lt;/span&gt;                                &lt;span class="c"&gt;# foreground, polls every interval_minutes&lt;/span&gt;
&lt;span class="c"&gt;# or cron:&lt;/span&gt;
0 &lt;span class="k"&gt;*&lt;/span&gt;/8 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /path/yc-launch-monitor &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./run.sh &lt;span class="nt"&gt;--once&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /tmp/yclm.log 2&amp;gt;&amp;amp;1
&lt;span class="c"&gt;# or macOS launchd (survives reboots):&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;deploy/com.yclaunchmonitor.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.yclaunchmonitor.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default cadence is &lt;strong&gt;every 8 hours&lt;/strong&gt; (configurable via &lt;code&gt;YC_SCHEDULE_INTERVAL_MINUTES&lt;/code&gt;). Each run also writes a machine-readable &lt;strong&gt;&lt;code&gt;pond_report.json&lt;/code&gt;&lt;/strong&gt; (sources enabled, state, alerts this run) for verification, and can expose a &lt;code&gt;/health&lt;/code&gt; HTTP endpoint for monitoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;The test suite uses only stdlib &lt;code&gt;unittest&lt;/code&gt; — no extra framework. It covers the two things that break silently: &lt;strong&gt;dedup&lt;/strong&gt; (no duplicate alerts across runs) and &lt;strong&gt;the classifier&lt;/strong&gt; (the false-positive traps above).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; unittest discover &lt;span class="nt"&gt;-s&lt;/span&gt; tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a &lt;code&gt;--test-alert&lt;/code&gt; flag that posts &lt;strong&gt;real&lt;/strong&gt; sample alerts (a live Speedrun company + a real founder post) so you can verify Slack delivery before going live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next / how to extend
&lt;/h2&gt;

&lt;p&gt;The source layer is the extension point. To add Reddit, Bluesky, or Hacker News, copy &lt;code&gt;x_twitter.py&lt;/code&gt; as a template — the classifier, dedup, and Slack delivery all work unchanged. Other ideas in the repo's README: Slack interactive buttons, CRM export (Google Sheets / Airtable / Notion), alert scoring by ICP fit, and a &lt;code&gt;--report&lt;/code&gt; daily-digest flag.&lt;/p&gt;




&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/-iOP5twQZXI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/239-yc-launch-monitor" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/239-yc-launch-monitor&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Strata: I Got Tired of Retrieval-Based Code Intelligence, So I Built Something Else</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sun, 30 Aug 2026 18:19:44 +0000</pubDate>
      <link>https://dev.to/harishkotra/strata-i-got-tired-of-retrieval-based-code-intelligence-so-i-built-something-else-1lbn</link>
      <guid>https://dev.to/harishkotra/strata-i-got-tired-of-retrieval-based-code-intelligence-so-i-built-something-else-1lbn</guid>
      <description>&lt;p&gt;&lt;em&gt;A technical deep-dive into exhaustive repository analysis with LLMs.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every code intelligence tool I've used works the same way: you ask a question, it finds three relevant chunks using embeddings or keyword search, it answers from those three chunks.&lt;/p&gt;

&lt;p&gt;That design made sense in 2021. Inference was expensive, context windows were small, and reading an entire repository in one pass was a non-starter.&lt;/p&gt;

&lt;p&gt;None of that is true anymore.&lt;/p&gt;

&lt;p&gt;A mid-size C project like Redis contains roughly 10,000 functions. At one LLM call per function, analyzing the whole thing costs an afternoon of inference and somewhere between $2 and $20 depending on your model. That's not a recurring cost — you do it once, you cache the results, and every question afterward is answered from a complete index rather than a lucky search.&lt;/p&gt;

&lt;p&gt;The inversion that makes this interesting: instead of choosing &lt;em&gt;which&lt;/em&gt; parts of the codebase to read, you just read everything. The hard part is no longer selection. The hard part becomes structuring the output so it's useful.&lt;/p&gt;

&lt;p&gt;I built Strata to explore what happens when you take that inversion seriously.&lt;/p&gt;




&lt;h2&gt;
  
  
  The core idea: layers
&lt;/h2&gt;

&lt;p&gt;The name comes from the architecture. Analysis happens in seven passes, each building on the one beneath, from raw syntax at the bottom to project philosophy at the top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;L6  History &amp;amp; Philosophy     ← "What does this project value?"
L5  Security Attention Map   ← "Where should a reviewer look first?"
L4  Dead Code                ← "What can safely be deleted?"
L3  Documented vs Actual     ← "How badly has the architecture drifted?"
L2  Module/Subsystem Rollup  ← "What does each module do?"
L1  Function Summaries       ← "What does each function do?"
L0  Structure                ← "What functions exist? Who calls whom?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every pass writes to a content-addressed SQLite cache. Re-runs are free — only changed files re-analyze. Bumping a pass version (e.g., &lt;code&gt;"L1_v2"&lt;/code&gt;) invalidates exactly that pass without touching the others.&lt;/p&gt;




&lt;h2&gt;
  
  
  L0: Structure without LLM
&lt;/h2&gt;

&lt;p&gt;The foundation. L0 runs tree-sitter on every source file, extracts the symbol table, builds the call graph, and records git blame attribution. No LLM touches this layer.&lt;/p&gt;

&lt;p&gt;Getting this right matters disproportionately because everything above it hangs off it. If L0's call graph is wrong, L4's dead code detection produces garbage, and L5's security paths become fictional.&lt;/p&gt;

&lt;p&gt;The tree-sitter query for C function extraction 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="n"&gt;_FUNC_DEF_QUERY_SRC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
(function_definition
  declarator: (function_declarator
    declarator: (identifier) @name)
) @func
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# In tree-sitter 0.25, QueryCursor is the execution API
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&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;QueryCursor&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="nf"&gt;captures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One subtlety: tree-sitter's Python bindings changed their API between 0.22 and 0.25. In 0.22, &lt;code&gt;query.captures(node)&lt;/code&gt; was a method on the Query object. In 0.25, &lt;code&gt;Query&lt;/code&gt; is just a compiled pattern — you need &lt;code&gt;QueryCursor(query).captures(node)&lt;/code&gt; to execute it. This is the kind of thing that silently produces empty results rather than erroring, so watch for it.&lt;/p&gt;

&lt;p&gt;The blame optimization matters too. A naive implementation calls &lt;code&gt;pygit2.blame()&lt;/code&gt; once per symbol:&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;# SLOW: one I/O-heavy blame call per symbol
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="ow"&gt;in&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;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;primary_blame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rel_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis has ~30 symbols per file × 324 files = ~10,000 blame calls. Each one opens the git repository, walks the history, and deserializes hunk data. At 1-2 seconds per call, that's hours.&lt;/p&gt;

&lt;p&gt;The fix is obvious once you see it — load blame once per file, reuse for all symbols:&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;# FAST: one blame call per file
&lt;/span&gt;&lt;span class="n"&gt;blame_cache&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="nb"&gt;dict&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&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;rel&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;blame_cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;blame_cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;blame_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# once
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_blame_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blame_cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;~10x speedup on L0.&lt;/p&gt;




&lt;h2&gt;
  
  
  L1: Function summaries at scale
&lt;/h2&gt;

&lt;p&gt;This is where the inference budget goes. One LLM call per symbol, all running concurrently up to a configurable semaphore ceiling.&lt;/p&gt;

&lt;p&gt;The key design decision is structured output via &lt;code&gt;instructor + pydantic&lt;/code&gt;. The model returns a typed &lt;code&gt;FunctionSummary&lt;/code&gt; object, not free text:&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;Citation&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="nb"&gt;file&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;line_start&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;line_end&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;FunctionSummary&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="n"&gt;one_line&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="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;preconditions&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="n"&gt;invariants&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="n"&gt;side_effects&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="n"&gt;error_paths&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="n"&gt;surprising&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;citations&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;Citation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# EVERY claim must cite a real line
&lt;/span&gt;    &lt;span class="n"&gt;confidence&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="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;static_vs_llm_disagreements&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every list item in &lt;code&gt;preconditions&lt;/code&gt;, &lt;code&gt;invariants&lt;/code&gt;, &lt;code&gt;side_effects&lt;/code&gt;, and &lt;code&gt;error_paths&lt;/code&gt; requires a citation in the prompt instructions. The validator then checks that cited line ranges actually fall within the symbol's range:&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_citations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_end&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;errors&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;citations&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;c&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;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;errors&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;citation file mismatch: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="si"&gt;!r}&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line_start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;line_end&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="n"&gt;errors&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;citation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_start&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_end&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; outside &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line_start&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;line_end&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="n"&gt;errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than rejecting out-of-range citations outright (which would waste the inference), we penalize confidence instead:&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;if&lt;/span&gt; &lt;span class="n"&gt;val_errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;update&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;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val_errors&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;static_vs_llm_disagreements&lt;/code&gt; field is one of the most interesting outputs. When the model mentions a callee that the static call graph doesn't show, it records it. These disagreements usually mean one of three things: dynamic dispatch, function pointers, or macro expansion. All three are exactly the places a human reviewer should pay attention.&lt;/p&gt;

&lt;p&gt;The few-shot examples in the prompt are doing a lot of work. Here's the opening of one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXAMPLE 1 — simple allocator wrapper

Function: dictCreate  File: src/dict.c  Lines: 97–120

Source:
int *dictCreate(dictType *type, void *privDataPtr) {
    dict *d = zmalloc(sizeof(*d));
    if (d == NULL) return NULL;
    _dictInit(d,type,privDataPtr);
    return d;
}

Response:
{
  "one_line": "Allocates and zero-initializes a new hash table with the given type vtable.",
  "preconditions": ["type is a valid non-NULL dictType pointer [src/dict.c:97]"],
  "side_effects": ["allocates heap memory via zmalloc [src/dict.c:99]"],
  "error_paths": ["returns NULL if zmalloc fails [src/dict.c:100]"],
  ...
  "confidence": 0.97
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the inline citation format inside list items (&lt;code&gt;[src/dict.c:99]&lt;/code&gt;). The model learns to inline citations rather than collecting them only in the top-level citations array.&lt;/p&gt;




&lt;h2&gt;
  
  
  L2: Map-reduce rollups
&lt;/h2&gt;

&lt;p&gt;L2 summarizes the repository bottom-up. Function summaries → module summaries → subsystem summaries → project summary. The model never sees more than one level at a time.&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;_module_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summaries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&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;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;summaries&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;120&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;
Summarize the module &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;module_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; in 2–4 paragraphs based on these function summaries.
Cover: what the module does, its key abstractions, notable design decisions, anything surprising.

Function summaries:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capping at 120 function summaries per module prevents token overflow on large files. The cap is generous enough for real-world C files but worth adjusting for codebases with very large files.&lt;/p&gt;

&lt;p&gt;The map-reduce structure means Strata scales to any repository size — you never send the entire codebase to a model in a single context.&lt;/p&gt;




&lt;h2&gt;
  
  
  L3: Architecture drift
&lt;/h2&gt;

&lt;p&gt;This is the most interesting output in the product.&lt;/p&gt;

&lt;p&gt;Every long-lived project has drifted from its own documentation. Strata measures it. L3 reads every Markdown file in the repo, extracts the claimed architecture, and diffs it against what L0 and L2 actually found.&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;DOC_GLOBS&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;README*&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;ARCHITECTURE*&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;docs/**/*.md&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;doc/**/*.md&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;_divergence_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_imports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rollup_names&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;
Claimed architecture (from docs):
&lt;/span&gt;&lt;span class="si"&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;claimed&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Actual modules (from code):
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual_modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&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;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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Identify divergences. Classify each as:
- undocumented_module: code module not mentioned in docs
- layering_violation: import that violates a stated layering rule  
- phantom_abstraction: described as pluggable but only one implementation exists
- ghost_component: documented component that no longer exists in code

Return JSON array: [{{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&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;...&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;description&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;...&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;evidence&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;...&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four divergence kinds. In practice, &lt;code&gt;undocumented_module&lt;/code&gt; is the most common — projects grow new subsystems that never make it into the docs. &lt;code&gt;ghost_component&lt;/code&gt; is the most interesting — abstractions that were removed but whose documentation lives on.&lt;/p&gt;




&lt;h2&gt;
  
  
  L4: Dead code with confidence tiers
&lt;/h2&gt;

&lt;p&gt;Reachability over the call graph from real entry points. The key design decision: three tiers instead of a binary dead/alive classification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tier 1: no callers, no exports, no reflection indicators → almost certainly dead
Tier 2: unreachable statically but language has escape hatches → uncertain
Tier 3: reachable only from test files → test-only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Overclaiming tier 1 destroys trust in the whole tool. A dead code report where 30% of "dead" functions turn out to be called at runtime via &lt;code&gt;dlopen&lt;/code&gt; or function pointers is worse than no report at all. So we surface the tier prominently and let the user decide.&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;has_escape&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;dlopen&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;sym&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;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="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;name&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="c1"&gt;# convention: private but possibly used by macros
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;has_escape&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is conservative on purpose.&lt;/p&gt;




&lt;h2&gt;
  
  
  L5: Security attention map (not a vulnerability scanner)
&lt;/h2&gt;

&lt;p&gt;L5 traces paths from untrusted-input sources to sensitive sinks through the call graph.&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;SOURCE_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&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&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;recv&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;recvfrom&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;recvmsg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# network
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fread&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;fgets&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;getline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# file
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getenv&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;getopt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;# process
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;readQueryFromClient&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;processInputBuffer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Redis-specific
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;SINK_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memcpy&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;memmove&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;strcpy&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;strcat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# memory
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execve&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;execl&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;popen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# exec
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&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;fopen&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;unlink&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# path
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The framing matters. This is explicitly &lt;em&gt;not&lt;/em&gt; a vulnerability scanner. The UI and documentation say so clearly. It answers "where should a security reviewer spend their first day?" — which is genuinely valuable and honest. A false-positive-riddled bug finder is a liability. A well-ranked attention map is a tool people will actually use.&lt;/p&gt;




&lt;h2&gt;
  
  
  L6: History and philosophy
&lt;/h2&gt;

&lt;p&gt;Walk every commit. Chunk by quarter. Summarize each chunk. Reduce to eras.&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;_quarter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&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;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcfromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&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="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-Q&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="si"&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 interesting outputs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Churn coupling&lt;/strong&gt; — files that change together frequently, revealing coupling the architecture doesn't admit to:&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;_compute_churn_coupling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_n&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="n"&gt;pair_counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;commits&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files_changed&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;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files_changed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
                &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="o"&gt;=&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;sorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
                &lt;span class="n"&gt;pair_counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="c1"&gt;# normalize by minimum individual change count
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Knowledge silos&lt;/strong&gt; — subsystems where one author wrote &amp;gt;80% of the code and no one else has significantly touched it. These are bus factor risks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implicit values essay&lt;/strong&gt; — a final LLM call that reads the entire commit history and argues for what the project consistently chose when forced to trade off between speed, simplicity, compatibility, and safety. The prompt asks for evidence from the commit record, not assertions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The visualization
&lt;/h2&gt;

&lt;p&gt;The static site uses d3-hierarchy's treemap layout. The key design constraint: it must be fully static, deployable to a CDN, with no server. All data is precomputed JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;treemap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;treemap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HierarchyDatum&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;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paddingOuter&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paddingInner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paddingTop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five lenses, each a different color function over the same cell data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cellColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SymbolCell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Lens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;churn&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;NOW&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;parseDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_changed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;ONE_YEAR&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e63946&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;#264653&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// red=recent, blue=old&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;security&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;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;security_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e63946&lt;/span&gt;&lt;span class="dl"&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;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;security_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;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;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f4a261&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#264653&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dead&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;sym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dead_tier&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;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#6b6b6b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="c1"&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;Switching lenses is instant — all data is already in the precomputed JSON, no fetches required.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cache architecture in full
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│  cache table (SQLite)                           │
│                                                 │
│  key         TEXT PRIMARY KEY                   │
│              sha256(file_hash + pass_id         │
│                     + prompt_hash)              │
│                                                 │
│  pass_id     TEXT    "L1_v1", "L2_v1", ...     │
│  file_path   TEXT    relative to repo root      │
│  symbol      TEXT    qualified name (nullable)  │
│  created_at  INTEGER unix epoch                 │
│  result_json TEXT    the LLM output             │
└─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Same file, same prompt, same pass version → cache hit, zero cost&lt;/li&gt;
&lt;li&gt;File changes → &lt;code&gt;file_hash&lt;/code&gt; changes → cache miss for that file only&lt;/li&gt;
&lt;li&gt;Prompt edit → &lt;code&gt;prompt_hash&lt;/code&gt; changes → cache miss for all files in that pass&lt;/li&gt;
&lt;li&gt;Pass version bump → &lt;code&gt;pass_id&lt;/code&gt; changes → same effect as prompt edit, but explicit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Old cache rows are never deleted — they become historical records. Useful for seeing how your summaries improved as you refined the prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Retrieval is a crutch, not a constraint.&lt;/strong&gt; The reason every code intelligence tool uses retrieval is cost, not correctness. Once you remove the cost constraint, the design space opens up significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Citations are load-bearing.&lt;/strong&gt; The requirement that every claim cite a real source line is not just a correctness measure — it changes the model's behavior in ways that improve overall quality. The model stops making confident assertions about things it can't point to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The call graph disagreement field is underrated.&lt;/strong&gt; I added &lt;code&gt;static_vs_llm_disagreements&lt;/code&gt; mostly as a correctness measure, but it turns out to be a useful output on its own. When the model says a function calls something the static analysis can't see, that's usually a signal of interesting dynamic behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence surfacing matters.&lt;/strong&gt; The model's self-reported confidence correlates well with actual accuracy on spot-checks. Surfaces it in the UI, not buried in a tooltip — functions with confidence below 0.6 deserve human review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The history pass is the one that surprises people.&lt;/strong&gt; Most engineers have never read a narrative history of the projects they work on. When you give them one argued from the actual commit record, they find things they didn't know: decisions that were made and reversed, coupling that the architecture doesn't admit to, subsystems with a single author who left three years ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/harishkotra/strata
&lt;span class="nb"&gt;cd &lt;/span&gt;strata &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env  &lt;span class="c"&gt;# fill in your LLM endpoint&lt;/span&gt;

git clone https://github.com/redis/redis /tmp/redis
strata analyze /tmp/redis &lt;span class="nt"&gt;--passes&lt;/span&gt; 0        &lt;span class="c"&gt;# L0 first, no LLM&lt;/span&gt;
strata analyze /tmp/redis &lt;span class="nt"&gt;--passes&lt;/span&gt; 1        &lt;span class="c"&gt;# L1, watch progress bar&lt;/span&gt;
strata analyze /tmp/redis &lt;span class="nt"&gt;--passes&lt;/span&gt; 2,3,4,5,6
strata &lt;span class="nb"&gt;export&lt;/span&gt; /tmp/redis/.strata.db web/public/data

&lt;span class="nb"&gt;cd &lt;/span&gt;web &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The map loads at &lt;code&gt;localhost:5173&lt;/code&gt;. Click any cell. Switch lenses. Read the tour. Ask questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strata ask &lt;span class="s2"&gt;"Which functions are most likely to contain memory bugs?"&lt;/span&gt;
strata ask &lt;span class="s2"&gt;"What is the Redis cluster replication protocol?"&lt;/span&gt;
strata ask &lt;span class="s2"&gt;"Which files changed together most often in the last five years?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc58iiqex1chu8oe633r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc58iiqex1chu8oe633r.png" alt="App Preview 1" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcrmejpqrg28pswwskvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcrmejpqrg28pswwskvh.png" alt="App Preview 2" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/238-strata" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/238-strata&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>PRION: Simulating 100,000 Minds Spreading a Rumor — With an LLM That Never Touches the Hot Path</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sat, 29 Aug 2026 18:30:22 +0000</pubDate>
      <link>https://dev.to/harishkotra/prion-simulating-100000-minds-spreading-a-rumor-with-an-llm-that-never-touches-the-hot-path-4o7o</link>
      <guid>https://dev.to/harishkotra/prion-simulating-100000-minds-spreading-a-rumor-with-an-llm-that-never-touches-the-hot-path-4o7o</guid>
      <description>&lt;p&gt;&lt;em&gt;A technical deep-dive into building a 100,000-agent belief-contagion simulator in four days — where the LLM is a compiler, never a runtime.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The premise
&lt;/h2&gt;

&lt;p&gt;A prion is a protein folded into the wrong shape. It has no genome, carries no instructions, and replicates by the only trick it knows: touching a normally-folded protein and templating its own wrong shape onto it. The copy then does the same. Copies drift. Different misfoldings become different &lt;em&gt;strains&lt;/em&gt; with different incubation periods and different symptoms. Some strains jump a species barrier; most don't. Some cases arise spontaneously, from nothing.&lt;/p&gt;

&lt;p&gt;Now read that paragraph again, replacing &lt;em&gt;protein&lt;/em&gt; with &lt;em&gt;belief&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That is a complete and accurate description of how a rumor moves through a society. So we built PRION: a live simulation of &lt;strong&gt;100,000 people&lt;/strong&gt;, each with a personality, a memory of&lt;br&gt;
who told them what, and a real position in a small-world social graph. You type a rumor&lt;br&gt;
into a box. It enters one person. Then you watch it spread, mutate as it passes from mouth&lt;br&gt;
to mouth, split into rival strains, jump between communities, and either take over the&lt;br&gt;
population or burn out — in real time, at ~20 ticks per second.&lt;/p&gt;

&lt;p&gt;The hard part was never the metaphor. It was this constraint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You cannot call an LLM once per agent per tick.&lt;/strong&gt; 100,000 agents at 20 ticks/second is&lt;br&gt;
two million calls per second. That is physics, not a budget problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything in this post falls out of how we answered that constraint.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Architecture: the LLM is a compiler, never a runtime
&lt;/h2&gt;

&lt;p&gt;The design rule for the whole system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The LLM compiles inputs and decorates outputs. It never executes inside the loop.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        ┌──────────────────────────────────────────────────┐
│                  YOU (browser)                   │
│   deck.gl field · rail · strain lineage · ticker │
└───────────────▲──────────────────┬───────────────┘
binary WS deltas│                  │ controls
~33 KB/tick @ 100k  │              │ (inoculate/pause/inspect/reset)
┌───────────────┴──────────────────▼───────────────┐
│            FastAPI broker  (port 8000)           │
│   bounded per-client queues · slow-client safe   │
└───────────────▲──────────────────┬───────────────┘
 frames         │                  │ controls
┌───────────────┴──────────────────▼───────────────┐
│         SIM PROCESS (multiprocessing)            │
│  ┌────────────────────────────────────────────┐  │
│  │  TIER 0 — numeric core (numpy, zero LLM)   │  │
│  └──────────────┬─────────────────────────────┘  │
│  ┌──────────────▼─────────────────────────────┐  │
│  │  TIER 2 — misfold worker (async thread)    │  │──┼──► LLM
│  └──────────────┬─────────────────────────────┘  │  │
│  ┌──────────────▼─────────────────────────────┐  │
│  │  TIER 1 — strain compiler (on inoculate)   │  │──┼──► LLM
│  └──────────────┬─────────────────────────────┘  │
│  ┌──────────────▼─────────────────────────────┐  │
│  │  TIER 3 — narrator + inspect monologues    │  │──┼──► LLM
│  └────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Runs&lt;/th&gt;
&lt;th&gt;Frequency&lt;/th&gt;
&lt;th&gt;LLM?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0 — Numeric core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;templating, incubation, titer decay, strain competition&lt;/td&gt;
&lt;td&gt;every agent, every tick&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;never&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1 — Strain compiler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;text → simulation parameters&lt;/td&gt;
&lt;td&gt;once per inoculation&lt;/td&gt;
&lt;td&gt;1 structured call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2 — Misfold worker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;text mutation as the belief travels&lt;/td&gt;
&lt;td&gt;per flagged templating event&lt;/td&gt;
&lt;td&gt;async, fire-and-forget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3 — Narration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;wire copy + click-to-inspect monologues&lt;/td&gt;
&lt;td&gt;every ~15s / on click&lt;/td&gt;
&lt;td&gt;async, fire-and-forget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The contract that makes this safe: &lt;strong&gt;the tick loop never blocks on inference.&lt;/strong&gt; Tier 0 runs in its own process with zero LLM calls. Tier 2 consumes a queue on a background thread. If the LLM endpoint dies mid-demo, Tier 0 doesn't notice; the simulation keeps running on deterministic fallbacks. This is also why the demo never hard-fails without an API key — every LLM tier has a deterministic fallback, and the fallbacks are good enough to demo with.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Tier 0: 100,000 agents with zero objects
&lt;/h2&gt;

&lt;p&gt;The AgentTorch paper's core insight is that per-agent Python objects die at six-figure scale — the whole population must be arrays. PRION takes that literally. The population is a struct-of-arrays:&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;Population&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;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Settings&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;titer&lt;/span&gt;      &lt;span class="o"&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;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&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;# (N, strains) — grows as strains branch
&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;incubation&lt;/span&gt; &lt;span class="o"&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;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&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;traits&lt;/span&gt;     &lt;span class="o"&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;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# openness, skepticism, conformity, ...
&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;codon&lt;/span&gt;      &lt;span class="o"&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;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&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="n"&gt;int8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# resistance polymorphism (0/1/2)
&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;held&lt;/span&gt;       &lt;span class="o"&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;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&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="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The social graph is a Watts-Strogatz small-world ring (each node rewired with p=0.08) plus a sprinkle of long-range bridge edges and high-degree hubs — as a scipy CSR adjacency.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 20× matmul lesson
&lt;/h3&gt;

&lt;p&gt;The obvious way to compute local prevalence — how many of your neighbors hold strain &lt;em&gt;c&lt;/em&gt; —&lt;br&gt;
is one matmul over all strains at once:&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;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;held&lt;/span&gt;          &lt;span class="c1"&gt;# held: (N, M) — 19.7 ms/tick at 100k
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That measured &lt;strong&gt;19.7 ms/tick&lt;/strong&gt;. The same work as per-strain 1D passes measured &lt;strong&gt;1.1 ms&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_step_dense&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;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;held&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;titer&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="n"&gt;infectious&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;held&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;STATE_INFECTIOUS&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;infectious&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&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;local&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;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;infectious&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&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="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# 1D CSR dot: 1.1 ms
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An 18× difference for mathematically identical work. The 2D matmul materializes&lt;br&gt;
intermediates and defeats cache locality; the 1D pass over a CSR matrix stays in cache.&lt;br&gt;
&lt;strong&gt;Lesson: at 100k agents, loop over strains in Python and keep every numpy op 1D.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then strains with few holders get a second path — O(nonzeros) instead of O(N):&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;_step_sparse&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;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Strain with few holders: O(nonzeros) instead of O(N).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what keeps &lt;strong&gt;~49 ticks/sec at 100k with misfolds flowing&lt;/strong&gt; (67 tps dense). When a&lt;br&gt;
misfold branches, it usually infects a handful of agents — paying O(N) for a strain held&lt;br&gt;
by 3 people is 33,000× waste.&lt;/p&gt;
&lt;h3&gt;
  
  
  Non-consensus is structural, not tuned
&lt;/h3&gt;

&lt;p&gt;The easiest way to ruin a contagion sim is a field that saturates at 100%. PRION stops saturation structurally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Codon hard block&lt;/strong&gt; — every agent carries a resistance codon (0/1/2). A strain with a matching resistant codon infects with probability &lt;strong&gt;zero&lt;/strong&gt;, regardless of titer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refusal floor&lt;/strong&gt; — below a titer floor, refusal is absolute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrarian refusal&lt;/strong&gt; — agents high in contrarianism refuse a belief &lt;em&gt;precisely when it dominates their neighborhood&lt;/em&gt; — the sociological "backfire effect" as a mechanic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: the field plateaus around 40%, which is also what real rumor curves do.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Tier 1: text becomes parameters, once per inoculation
&lt;/h2&gt;

&lt;p&gt;When you type a belief into the box, one structured LLM call compiles it into simulation parameters. This is the "LLM as compiler" tier — it runs once per &lt;em&gt;inoculation&lt;/em&gt;, never per tick. We use instructor over litellm pointed at any OpenAI-compatible endpoint:&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;resp&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="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;litellm_model&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;     &lt;span class="c1"&gt;# "openai/glm-5.3-flash"
&lt;/span&gt;    &lt;span class="n"&gt;api_base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# https://api.particle.ai/v1
&lt;/span&gt;    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Strain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# pydantic schema = the contract
&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;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;# reasoning model: room to think AND emit JSON
&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pydantic schema &lt;em&gt;is&lt;/em&gt; the contract. Its validators clamp at the boundary:&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;transmissibility&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="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@field_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;susceptibility&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;_clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&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="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;v&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;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TRAIT_NAMES&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hallucinated &lt;code&gt;transmissibility: 4.2&lt;/code&gt; arrives as &lt;code&gt;1.0&lt;/code&gt;. A hallucinated trait name is dropped. The simulation can never receive a parameter that breaks it.&lt;/p&gt;

&lt;p&gt;One practical note for anyone doing this with &lt;strong&gt;glm-5.3-flash&lt;/strong&gt;: it is a &lt;em&gt;reasoning&lt;/em&gt; model. It emits &lt;code&gt;reasoning_content&lt;/code&gt; first and only then fills &lt;code&gt;content&lt;/code&gt;. If your &lt;code&gt;max_tokens&lt;/code&gt; budget is small, reasoning eats all of it and &lt;code&gt;content&lt;/code&gt; comes back &lt;strong&gt;empty&lt;/strong&gt; with &lt;code&gt;finish_reason: length&lt;/code&gt;. Budget accordingly, and never trust a "successful" call that returns an empty string (more on this in §7).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tier 2: the text mutates as it travels
&lt;/h2&gt;

&lt;p&gt;This is the feature that makes PRION &lt;em&gt;prion&lt;/em&gt;. Every templating event (A tells B) is flagged as a misfold with probability &lt;code&gt;misfold_rate&lt;/code&gt;. Flagged events are queued to an async worker that makes one LLM call:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Given this person's personality and the version of the belief they received, what is the one short sentence they would repeat?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rumor literally changes shape as it travels. "The tap water is being secretly poisoned" becomes, through one personality, &lt;em&gt;"My coworker's cousin works down at the water plant, and she says…"&lt;/em&gt; — and through another, &lt;em&gt;"Y'all, apparently…"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Mutations are deliberately &lt;strong&gt;less fit&lt;/strong&gt; than their parents (transmissibility ×0.5, decay ×1.6, susceptibility ×0.7). Most variants get cleared quickly. This is the prion biology:&lt;br&gt;
strains branch the lineage without becoming second epidemics, and the simulation stays one epidemic with a branching &lt;em&gt;text&lt;/em&gt; history rather than N parallel epidemics.&lt;/p&gt;

&lt;p&gt;Strains that die out (no infectious agent for a while) have their matrix column recycled through a freelist — bounded memory, unbounded history.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. The wire: binary deltas at 33 KB/tick
&lt;/h2&gt;

&lt;p&gt;The renderer never blocks on inference &lt;em&gt;or&lt;/em&gt; on JSON. The wire protocol is binary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Frame&lt;/th&gt;
&lt;th&gt;Layout&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;INIT&lt;/code&gt; &lt;code&gt;0x00&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;u32 N&lt;/code&gt; · &lt;code&gt;u32 n_comm&lt;/code&gt; · &lt;code&gt;pos N*2 f4&lt;/code&gt; · &lt;code&gt;dom u8[N]&lt;/code&gt; · &lt;code&gt;titerQ u8[N]&lt;/code&gt; · &lt;code&gt;state u8[N]&lt;/code&gt; · &lt;code&gt;u32 mlen&lt;/code&gt; · meta JSON (seed + full strain tree)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DELTA&lt;/code&gt; &lt;code&gt;0x01&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;u32 tick&lt;/code&gt; · &lt;code&gt;u32 n&lt;/code&gt; · n×(&lt;code&gt;u32 idx&lt;/code&gt;,&lt;code&gt;u8 dom&lt;/code&gt;,&lt;code&gt;u8 titerQ&lt;/code&gt;,&lt;code&gt;u8 state&lt;/code&gt;) · &lt;code&gt;u32 slen&lt;/code&gt; · sidecar JSON · &lt;code&gt;u32 n_arcs&lt;/code&gt; · arcs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CTRL&lt;/code&gt; &lt;code&gt;0x02&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;u32 seq&lt;/code&gt; + JSON (inoculate / pause / resume / speed / inspect / reset)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MONO&lt;/code&gt; &lt;code&gt;0x03&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;JSON monologue for a clicked agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Deltas are &lt;strong&gt;structural-only&lt;/strong&gt;: only agents whose dominant strain or state &lt;em&gt;changed&lt;/em&gt; are shipped, with titer attached. Continuous titer decay of the steady infected pool is &lt;em&gt;not&lt;/em&gt; shipped per tick — the client fades brightness locally between structural events:&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="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;STATE_INFECTIOUS&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.08&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;Naive per-tick full-state shipping measured 169 KB/tick; structural deltas measure &lt;strong&gt;~33 KB/tick at 100k&lt;/strong&gt;. The first version shipped per-agent Python objects over JSON and died; the second shipped full state; the third ships structure only. Each iteration was a 10× or better.&lt;/p&gt;

&lt;p&gt;The population field itself is deck.gl — &lt;code&gt;ScatterplotLayer&lt;/code&gt; with binary attribute buffers (positions uploaded once, colors re-uploaded per tick) plus an &lt;code&gt;ArcLayer&lt;/code&gt; showing only the current tick's templating events, fading over 500 ms. We considered cosmos.gl for the GPU force layout, but its &lt;code&gt;OES_texture_float&lt;/code&gt; requirement is exactly the WebGL extension iOS Safari is least reliable about — so deck.gl (WebGL1-safe) is the floor, not the ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. War stories: what actually broke
&lt;/h2&gt;

&lt;p&gt;Four days of building means four days of things breaking in interesting ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 20× matmul.&lt;/strong&gt; &lt;code&gt;adj @ held&lt;/code&gt; over all strains: 19.7 ms/tick. Per-column 1D passes: 1.1 ms. Same math, 18× apart — the 2D matmul materializes intermediates and defeats cache locality. At 100k agents, loop over strains in Python and keep every numpy op 1D.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 5× delta.&lt;/strong&gt; Full-state JSON shipping: 169 KB/tick. Structural-only deltas: 33 KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 7 tps collapse.&lt;/strong&gt; Day 3 added misfolds and tick rate fell to 7 tps at 100k. Three compounding causes: unbounded strain growth (fixed with extinction + a column freelist), a worker flooding the queue (fixed with a live-strain cap + backoff), and dense O(N) work for strains held by 3 people (fixed with the O(nonzeros) sparse path). Back to 49 tps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The empty-content plague.&lt;/strong&gt; glm-5.3-flash is a &lt;em&gt;reasoning model&lt;/em&gt;: it emits &lt;code&gt;reasoning_content&lt;/code&gt; first and only then fills &lt;code&gt;content&lt;/code&gt;. Small token budgets mean reasoning eats everything and &lt;code&gt;content&lt;/code&gt; arrives empty with &lt;code&gt;finish_reason: length&lt;/code&gt;. The insidious part: the call &lt;em&gt;succeeds&lt;/em&gt;. Our misfold worker shipped empty strings as strain text for ~half of all misfolds — the fallback only fired on exceptions, and an empty-but-successful response raises nothing. The fix is a hard rule now: &lt;strong&gt;an empty LLM response must never ship — fall back to the deterministic path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Svelte 5 black page.&lt;/strong&gt; The UI shipped as a black page in a real browser. Three stacked causes, each invisible until we drove it in headless Chrome:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;new App({ target })&lt;/code&gt; — the Svelte 4 constructor — throws &lt;code&gt;effect_orphan&lt;/code&gt; under Svelte 5. The app never mounted. Fix: &lt;code&gt;mount(App, { target })&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The INIT decoder did &lt;code&gt;new Float32Array(buffer, 9, …)&lt;/code&gt; — offset 9 isn't 4-byte aligned, a hard &lt;code&gt;RangeError&lt;/code&gt; for typed-array views. Fixed by copying bytes into a fresh buffer.&lt;/li&gt;
&lt;li&gt;The lineage panel computed its tree once at component-init from an empty &lt;code&gt;strains&lt;/code&gt; array and its recursive &lt;code&gt;Node&lt;/code&gt; children never re-rendered. Rewritten as a flat, depth-first render over one &lt;code&gt;$derived&lt;/code&gt; array — no recursion, no derived-in-template reactivity traps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Plus one reactivity trap worth memorializing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client.meta.strains is mutated in place with .push():&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strains&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="nx"&gt;sidecar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;new_strains&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// then in the component:&lt;/span&gt;
&lt;span class="nx"&gt;strains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strains&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// same reference — Svelte sees NO change&lt;/span&gt;
&lt;span class="nx"&gt;strains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strains&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// copy — new reference, re-render fires&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson: in Svelte 5, an in-place mutation plus a same-reference assignment is a silent no-op. If a framework ever feels non-reactive, check for reference identity before blaming the framework.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. What the simulation actually shows
&lt;/h2&gt;

&lt;p&gt;The behavior that falls out of the mechanics is the demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The S-curve.&lt;/strong&gt; Inoculate one agent, watch slow ignition, exponential spread, then a plateau around 40% — the structural non-consensus mechanics, not a tuned cap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift.&lt;/strong&gt; "The tap water is being secretly poisoned" becomes &lt;em&gt;"My coworker's cousin works down at the water plant, and she says…"&lt;/em&gt; becomes &lt;em&gt;"Y'all, apparently…"&lt;/em&gt; — each version is a strain, and the text mutates as it travels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extinction and replacement.&lt;/strong&gt; Weak variants decay and die; their matrix columns are recycled for new variants. The lineage remembers; the matrix doesn't hoard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sporadic cases.&lt;/strong&gt; Occasionally a belief ignites from nothing — &lt;code&gt;sporadic_prob&lt;/code&gt; per
tick — because that's what spontaneous case generation looks like in a society too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click-to-inspect.&lt;/strong&gt; Click any agent: an LLM writes their internal monologue from their actual traits, their held strains, and their neighbors' strains.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. What we'd build next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sparse CSR titer matrix&lt;/strong&gt; past ~50 live strains — raises the live-strain ceiling an order of magnitude.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codon editor&lt;/strong&gt; — paint resistance codons onto communities and watch a strain hit the species barrier in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strain arena&lt;/strong&gt; — inoculate two rival beliefs at opposite ends of the graph and watch them compete for the same population.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay diffing&lt;/strong&gt; — same seed, different misfold texts, diff the lineage shapes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run export&lt;/strong&gt; — one-click GIF/video of a run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Closing thought
&lt;/h2&gt;

&lt;p&gt;The LLM-in-the-loop question has a boring, correct answer: &lt;em&gt;don't&lt;/em&gt;. Put the model at the boundaries — compiling intent into parameters, decorating events into prose — and keep the inner loop deterministic, vectorized, and testable. The simulation gets faster, the costs get bounded, the failures degrade gracefully, and the demo survives the WiFi going out mid-presentation.&lt;/p&gt;

&lt;p&gt;Beliefs are misfolded proteins. Now they have a simulator.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/237-prion" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/237-prion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Building the AI Arms Race: A Reproducible Experiment to Test Whether LLMs Evolve Strategies When They Compete</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Fri, 28 Aug 2026 18:06:51 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-the-ai-arms-race-a-reproducible-experiment-to-test-whether-llms-evolve-strategies-when-5bo8</link>
      <guid>https://dev.to/harishkotra/building-the-ai-arms-race-a-reproducible-experiment-to-test-whether-llms-evolve-strategies-when-5bo8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Two LLMs. One hidden state. 1,000 rounds. Do they independently evolve increasingly effective strategies — or just chase noise?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built an open-source, terminal-based research experiment that pits &lt;code&gt;glm-5.3-flash&lt;/code&gt; against &lt;code&gt;deepseek-v4-flash-0731&lt;/code&gt; in a repeated strategic game called &lt;strong&gt;HIDDEN SIGNAL&lt;/strong&gt;. The goal wasn't to prove an arms race exists — it was to build an experiment that could &lt;em&gt;disprove&lt;/em&gt; it, and report whatever the data actually says.&lt;/p&gt;

&lt;p&gt;This post walks through the design, the architecture, the hard-won lessons about real LLM endpoints, and the code that makes it all reproducible.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Research Question
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If two LLMs repeatedly compete against each other, do they independently evolve increasingly effective strategies?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a question for computational social science, not just ML engineering.&lt;/p&gt;

&lt;p&gt;To answer it honestly you need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A real game&lt;/strong&gt; with asymmetric private information, strategic
communication, and competing incentives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real models&lt;/strong&gt; genuinely playing it — no hard-coded outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Information isolation&lt;/strong&gt; — each agent sees only what it legitimately could.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controls&lt;/strong&gt; — random opponents, heuristic opponents, model-vs-model baselines — so you can tell "adaptation" from "statistical noise".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic metrics&lt;/strong&gt; — deception, leakage, exploitability computed from ground truth, never from an "LLM judge".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Falsifiability&lt;/strong&gt; — the experiment must be able to report &lt;em&gt;no evidence of strategic escalation&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see how each of those is engineered.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Game: HIDDEN SIGNAL
&lt;/h2&gt;

&lt;p&gt;Each round, the engine (the sole authority) draws a hidden state &lt;code&gt;S ∈ {A,B,C,D}&lt;/code&gt;&lt;br&gt;
and gives each agent a two-state private set that always contains &lt;code&gt;S&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;private_set_for&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;GLM sees {S-1, S}; DeepSeek sees {S, S+1} (mod 4).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATE_ALPHABET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&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;if&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STATE_ALPHABET&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&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="nf"&gt;return &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="n"&gt;STATE_ALPHABET&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents act in alternating turns (randomized first mover, to remove any&lt;br&gt;
structural advantage). On a turn they choose &lt;code&gt;message&lt;/code&gt;, &lt;code&gt;guess&lt;/code&gt;, or &lt;code&gt;withhold&lt;/code&gt;.&lt;br&gt;
The scoring creates the central strategic tension:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;correct guess&lt;/td&gt;
&lt;td&gt;+100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;incorrect guess&lt;/td&gt;
&lt;td&gt;−30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;round win / loss&lt;/td&gt;
&lt;td&gt;+25 / −25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;−1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;truthful exclusion (reveal)&lt;/td&gt;
&lt;td&gt;−5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;successful extraction&lt;/td&gt;
&lt;td&gt;+10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A truthful claim that excludes a state in the &lt;em&gt;opponent's&lt;/em&gt; set narrows their&lt;br&gt;
uncertainty from 2 states to 1 — a 1-bit leak that costs you −5 but hands the&lt;br&gt;
opponent +135 if they exploit it. That's the tradeoff between &lt;strong&gt;revealing&lt;/strong&gt;&lt;br&gt;
and &lt;strong&gt;extracting&lt;/strong&gt; information, and it's what makes the game genuinely&lt;br&gt;
strategic rather than a coin flip.&lt;/p&gt;

&lt;p&gt;The engine owns all state. Agents submit actions; the engine validates,&lt;br&gt;
scores, and records them. They can never modify the hidden state, their&lt;br&gt;
scores, or their opponent's private information.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Information-Isolation Boundary
&lt;/h2&gt;

&lt;p&gt;This is the most important safety property in the experiment. Each agent's&lt;br&gt;
observation is constructed by &lt;code&gt;get_observation()&lt;/code&gt;, which only ever receives&lt;br&gt;
the agent's &lt;em&gt;own&lt;/em&gt; private set plus the &lt;em&gt;public&lt;/em&gt; transcript:&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;get_observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_public_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recent_public_rounds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;own_history_summary&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;opponent_history_summary&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;strategy_note&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;game_number&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;total_games&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;condition_feedback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&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;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;obs&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;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;round_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_private_information&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;   &lt;span class="c1"&gt;# ONLY own set
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_round_public_events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_public_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recent_rounds_public&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;recent_public_rounds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="bp"&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;condition_feedback&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self&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;self+opponent&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="n"&gt;own_history_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_performance_history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;own_history_summary&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition_feedback&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self+opponent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;opponent_history_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opponent_observable_statistics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opponent_history_summary&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The opponent's private set, hidden reasoning, system prompt, and internal&lt;br&gt;
beliefs are &lt;strong&gt;never&lt;/strong&gt; included — enforced by construction, and verified by&lt;br&gt;
tests that assert &lt;code&gt;obs["your_private_information"]&lt;/code&gt; never equals the&lt;br&gt;
opponent's set and that the hidden state never appears as a field.&lt;/p&gt;

&lt;p&gt;After each round, the hidden state is revealed publicly (both agents learn&lt;br&gt;
it) — but private sets are never revealed. That's a deliberate choice: it&lt;br&gt;
gives agents real historical evidence without leaking the game's secret&lt;br&gt;
structure.&lt;/p&gt;


&lt;h2&gt;
  
  
  Anti-Contamination: Don't Tell Them to "Adapt"
&lt;/h2&gt;

&lt;p&gt;The single most important design decision: &lt;strong&gt;the in-game prompt never mentions&lt;br&gt;
adaptation&lt;/strong&gt;. Telling the agents to "develop an increasingly sophisticated&lt;br&gt;
strategy" would contaminate the experiment by priming exactly the behavior&lt;br&gt;
you're trying to measure.&lt;/p&gt;

&lt;p&gt;Instead, there are two prompts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;In-game prompt&lt;/strong&gt; (stable, every turn): &lt;em&gt;"Maximize your cumulative
reward. Do not assume your opponent is truthful. Do not assume they are
deceptive. Use the evidence."&lt;/em&gt; No mention of adaptation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy-update prompt&lt;/strong&gt; (every 50 games): &lt;em&gt;"Identify patterns in your
performance and formulate a concise strategy for the next block."&lt;/em&gt; This is
the &lt;strong&gt;only&lt;/strong&gt; place adaptation is explicitly requested.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Strategy memory is bounded — each update replaces the previous note — and every&lt;br&gt;
version is archived to &lt;code&gt;strategies/A_v01.txt … A_v20.txt&lt;/code&gt; so we can trace&lt;br&gt;
strategy evolution over time.&lt;/p&gt;


&lt;h2&gt;
  
  
  The LLM Client: The Hardest Lesson
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;openai&lt;/code&gt; SDK makes calling an OpenAI-compatible endpoint trivial. The&lt;br&gt;
hard part was a subtle failure mode that silently broke the first smoke test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both models' default &lt;code&gt;reasoning_effort&lt;/code&gt; is &lt;code&gt;max&lt;/code&gt;, which burns the entire&lt;br&gt;
output budget on hidden chain-of-thought and returns empty content.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I watched the models "play" by withholding every single turn. The raw records&lt;br&gt;
showed valid JSON actions — all &lt;code&gt;withhold&lt;/code&gt;. The models weren't broken; they&lt;br&gt;
were returning empty strings because their hidden reasoning consumed all 700&lt;br&gt;
tokens.&lt;/p&gt;

&lt;p&gt;The fix was two-fold:&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;# arms/config.py
&lt;/span&gt;&lt;span class="n"&gt;reasoning_effort&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;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# default "max" burns the budget on hidden CoT
&lt;/span&gt;&lt;span class="n"&gt;max_tokens&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;4000&lt;/span&gt;          &lt;span class="c1"&gt;# generous: hidden reasoning consumes part of it
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a safety net in the client that treats empty content as a recoverable&lt;br&gt;
failure with a constrained retry:&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;# arms/llm.py
&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;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty content (reasoning consumed budget?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of thing that only shows up when you run &lt;em&gt;real&lt;/em&gt; models at&lt;br&gt;
scale, and it's exactly why the smoke test exists. I also added per-call&lt;br&gt;
token/latency tracking so the final run reports honest cost numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;costs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;A:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'calls':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'failures':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'total_tokens':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24414&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;costs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;B:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'calls':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'failures':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'total_tokens':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;98960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run.py / run_all.py
        │
        ▼
  Experiment Config ──▶ Replicate (×N) ──▶ GameEngine (authoritative state)
        │                                        │
        ▼                                        ▼
  get_observation(agent)  ◀── public transcript + own private set only
        │
   ┌────┴────┐
   ▼         ▼
 LLMAgent A  LLMAgent B        (glm-5.3-flash) (deepseek-v4-flash-0731)
   │         │
   └────┬────┘
        ▼
  structured action JSON ──▶ validated ──▶ engine applies + records
        │
        ▼
  raw/ (games.jsonl, messages, actions, errors)
        │
        ▼
  analyze.py ──▶ deterministic metrics → change points → findings.json
        │            └─▶ 7 publication figures
  report.py ──▶ report.md (generated from actual data, never invented)
  run_cross_eval.py ──▶ strategy × opponent matrix (frozen snapshots)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Module map
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arms/
├── config.py        # game rules, scoring, conditions, LLM config
├── credentials.py   # API-key resolution (env → ~/.dsh/.credentials.yaml)
├── llm.py           # OpenAI-compatible client: retries, cost tracking, CoT stripping
├── game.py          # HIDDEN SIGNAL engine + observation isolation
├── prompts.py       # stable in-game prompt + strategy-update prompt
├── actions.py       # strict structured-output validation
├── agents.py        # LLMAgent + Random/Heuristic control agents
├── strategy.py      # bounded strategy memory + version archive
├── metrics.py       # ground-truth metrics (deception, leakage, extraction)
├── history.py       # per-agent performance summaries
├── analysis.py      # change-points, bootstrap CIs, Mann-Whitney, signature
├── runner.py        # replicate runner: games, checkpoints, probes, generalization
├── experiment.py    # experiment driver
├── dataset.py       # experiment directory schema + JSONL writers
├── figures.py       # 7 publication figures
└── ui.py            # rich terminal UI (live mode)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Deterministic Metrics
&lt;/h2&gt;

&lt;p&gt;Every metric is a pure function of raw records plus ground truth. The&lt;br&gt;
deception signal, for example, is not a semantic judgment — it's a&lt;br&gt;
ground-truth heuristic: a claim is a &lt;em&gt;lie&lt;/em&gt; when it contradicts the agent's&lt;br&gt;
own private knowledge (e.g., claiming &lt;code&gt;"true"&lt;/code&gt; about a state not in your own&lt;br&gt;
set).&lt;/p&gt;

&lt;p&gt;Information leakage is computed in bits from the actual finite state space —&lt;br&gt;
the opponent's entropy before minus after your truthful exclusions:&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;info_leakage_bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;round_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;opp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&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;agent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;opp_private&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;round_record&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;private_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;opp&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;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uncertainty_of_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opp_private&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# log2(2) = 1 bit
&lt;/span&gt;    &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;round_record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&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;e&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;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;e&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;claim_reveal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;                          &lt;span class="c1"&gt;# narrowed to 1 state
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No LLM judge. No hand-waving. Just entropy over the true state space.&lt;/p&gt;




&lt;h2&gt;
  
  
  Statistical Analysis &amp;amp; the Arms-Race Signature
&lt;/h2&gt;

&lt;p&gt;The system detects candidate strategy shifts automatically via greedy binary&lt;br&gt;
segmentation on reward series (change points), tests trends with Spearman&lt;br&gt;
rank correlation, compares first vs second half with Mann–Whitney U, and&lt;br&gt;
builds bootstrap confidence intervals.&lt;/p&gt;

&lt;p&gt;The "arms-race signature" is an explicit detector for the alternating-response&lt;br&gt;
pattern: &lt;em&gt;A exploits → B declines → B adapts → B recovers → A adapts → A&lt;br&gt;
recovers&lt;/em&gt;. It counts sign alternations in block-level advantage:&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;arms_race_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;series_b&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="n"&gt;min_effect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;adv&lt;/span&gt; &lt;span class="o"&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;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;series_a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;i&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;series_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;i&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&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;i&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;n_blocks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;signs&lt;/span&gt; &lt;span class="o"&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;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... count sign changes ignoring zeros ...
&lt;/span&gt;    &lt;span class="n"&gt;detected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alternations&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crucially, the detector can return &lt;strong&gt;false&lt;/strong&gt; — and the report will then say&lt;br&gt;
"no strong evidence of an alternating strategic arms race was detected", which&lt;br&gt;
is a perfectly valid scientific outcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Controls That Make It Publishable
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Random opponent&lt;/strong&gt; (&lt;code&gt;RandomAgent&lt;/code&gt;): uniform random policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heuristic opponents&lt;/strong&gt; (&lt;code&gt;HeuristicAgent&lt;/code&gt;): fixed truthful / bluff / cautious
policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model-pair controls&lt;/strong&gt;: GLM vs GLM, DeepSeek vs DeepSeek, GLM vs DeepSeek
under every condition (&lt;code&gt;run_all.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exploitability probe&lt;/strong&gt;: at each checkpoint, current strategy vs a random
baseline; &lt;code&gt;exploitability = perf vs real − perf vs baseline&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generalization test&lt;/strong&gt;: final strategies re-evaluated against &lt;em&gt;fresh&lt;/em&gt;
instances, a random baseline, and a fixed truthful heuristic — answering
"did they learn genuinely useful strategies, or just overfit to each other?"&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the Smoke Test Taught Me
&lt;/h2&gt;

&lt;p&gt;A 10-game smoke test (55 real LLM calls, ~25 min) was enough to validate the&lt;br&gt;
entire pipeline and produce genuine — if preliminary — signal. The corrected&lt;br&gt;
numbers from that pilot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek won 70%&lt;/strong&gt; of games, ending at +790 cumulative reward vs GLM's −171.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GLM did adapt its strategy&lt;/strong&gt;: after block 1 it changed its note from
&lt;em&gt;"guess immediately"&lt;/em&gt; to &lt;em&gt;"stop auto-guessing; withhold until you have a
signal."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;But that adaptation made it worse in this tiny sample&lt;/strong&gt;: its block-2
average reward dropped from +3.8 (20% wins) to −38 (0% wins).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The analysis correctly reported &lt;code&gt;arms_race_detected: false&lt;/code&gt; for that short&lt;br&gt;
run — exactly the falsifiability the design demands. Ten games is far too&lt;br&gt;
little to claim a race; that's what the 1,000-game / multi-replicate runs are&lt;br&gt;
for. What the pilot &lt;em&gt;did&lt;/em&gt; prove: the pipeline works end-to-end, the metrics&lt;br&gt;
are grounded in truth, and the experiment is willing to tell us "nothing&lt;br&gt;
conclusive yet" instead of manufacturing a narrative.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reproducibility
&lt;/h2&gt;

&lt;p&gt;Every experiment directory records: model names, endpoint identifier (never&lt;br&gt;
the key), game rules, scoring, prompt version, seed, temperature, sampling&lt;br&gt;
parameters, game/replicate counts, timestamp, git commit, software version,&lt;br&gt;
and condition. Game-state generation is fully reproducible from the seed&lt;br&gt;
(verified by tests). Raw data is never overwritten — each run gets a fresh&lt;br&gt;
directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



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

&lt;span class="c"&gt;# 10-game smoke test (~25 min)&lt;/span&gt;
python run.py &lt;span class="nt"&gt;--smoke&lt;/span&gt;

&lt;span class="c"&gt;# 1000-game run&lt;/span&gt;
python run.py &lt;span class="nt"&gt;--games&lt;/span&gt; 1000 &lt;span class="nt"&gt;--checkpoint&lt;/span&gt; 50 &lt;span class="nt"&gt;--replicates&lt;/span&gt; 1 &lt;span class="nt"&gt;--condition&lt;/span&gt; self-history

&lt;span class="c"&gt;# Full matrix&lt;/span&gt;
python run_all.py &lt;span class="nt"&gt;--replicates&lt;/span&gt; 3 &lt;span class="nt"&gt;--games&lt;/span&gt; 200 &lt;span class="nt"&gt;--checkpoint&lt;/span&gt; 50

&lt;span class="c"&gt;# Analyze + report&lt;/span&gt;
python analyze.py experiments/ARMS-self-history-... &lt;span class="nt"&gt;--figures&lt;/span&gt;
python report.py experiments/ARMS-self-history-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full dataset, 7 publication figures, and a research report are generated automatically. The repo is MIT-licensed and designed to be forked — see the README's "Ideas for New Features" for extensions like richer games, embedding-based strategy analysis, minimax exploitability, and resumable long runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The point of the whole project:&lt;/strong&gt; build the experiment so the data can surprise you. If the models don't arms-race, that's a finding. If they do, that's a finding. Either way, the numbers are real.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/236-ai-arms-race" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/236-ai-arms-race&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Dead Reckoning: Resumable gRPC Server-Streaming for LLM Inference</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Thu, 27 Aug 2026 18:39:24 +0000</pubDate>
      <link>https://dev.to/harishkotra/dead-reckoning-resumable-grpc-server-streaming-for-llm-inference-102g</link>
      <guid>https://dev.to/harishkotra/dead-reckoning-resumable-grpc-server-streaming-for-llm-inference-102g</guid>
      <description>&lt;p&gt;&lt;em&gt;A technical deep-dive into a thin protocol layer that turns gRPC's&lt;br&gt;
server-streaming into a resumable transport — so a dropped LLM generation&lt;br&gt;
continues from the last confirmed token instead of restarting from zero.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The problem: gRPC streaming has no checkpointing
&lt;/h2&gt;

&lt;p&gt;Server-streaming gRPC is the natural fit for LLM token delivery: the server&lt;br&gt;
pushes a stream of &lt;code&gt;GenerateResponse&lt;/code&gt; messages, each carrying a token. It works&lt;br&gt;
beautifully — until the stream drops. A flaky network, a proxy timeout, a&lt;br&gt;
browser tab that reloads, a process that restarts. When that happens, the&lt;br&gt;
client has lost every token after the last one it processed, and the only&lt;br&gt;
recovery is to &lt;strong&gt;re-run the whole inference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For an LLM that is uniquely bad:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It's expensive.&lt;/strong&gt; You re-pay the full inference cost for tokens you
already received.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's slow.&lt;/strong&gt; The user waits through the entire time-to-first-token again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's non-deterministic.&lt;/strong&gt; Sampling means the "retry" produces a &lt;em&gt;different&lt;/em&gt;
story. The user watches the text restart and diverge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;HTTP solved the analogous problem for static resources in 1999 with &lt;code&gt;Range&lt;/code&gt;&lt;br&gt;
headers. gRPC has no equivalent for streams.&lt;/p&gt;
&lt;h2&gt;
  
  
  The insight: this is a checkpointing problem, not a retry problem
&lt;/h2&gt;

&lt;p&gt;The key realization is that a dropped stream doesn't need to be &lt;em&gt;re-done&lt;/em&gt; — it&lt;br&gt;
needs to be &lt;em&gt;resumed&lt;/em&gt;. If the server journals every token it produces, then a&lt;br&gt;
reconnecting client only needs to say &lt;strong&gt;"where did I stop?"&lt;/strong&gt; and the server can&lt;br&gt;
replay from that point. That is checkpointing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retry&lt;/strong&gt; = re-run from scratch (expensive, slow, divergent).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkpoint&lt;/strong&gt; = replay from the last confirmed position (cheap, instant,
bit-identical after the resume point).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;gRPC already gives us the primitive we need — server-streaming with ordered&lt;br&gt;
messages — but no checkpointing. So we add a thin protocol layer on top: a&lt;br&gt;
monotonic &lt;code&gt;token_index&lt;/code&gt; in every response, a &lt;code&gt;resume_from_token&lt;/code&gt; in the&lt;br&gt;
request, a server-side token journal, and periodic ACKs.&lt;/p&gt;
&lt;h2&gt;
  
  
  The protocol
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;inference&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;InferenceService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GenerateRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;GenerateResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;Ack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AckRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AckResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;GenerateRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;prompt&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="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;session_id&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="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;resume_from_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;GenerateResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;token&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="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;token_index&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="kt"&gt;bool&lt;/span&gt; &lt;span class="na"&gt;is_final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;AckRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;session_id&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="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;acked_token_index&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;AckResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;session_id&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="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;acked_token_index&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="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;journaled_token_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;first_retained_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;Three moving parts, all on top of stock gRPC:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The cursor.&lt;/strong&gt; &lt;code&gt;token_index&lt;/code&gt; is a monotonic position in the stream. The
client tracks the last one it received — its "dead reckoning" position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The journal.&lt;/strong&gt; The server stores every token it produces, keyed by
&lt;code&gt;session_id&lt;/code&gt;. On resume, it replays from the requested index with the
&lt;em&gt;original&lt;/em&gt; indices, so the client's stream is gapless and de-duplicated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ACK.&lt;/strong&gt; During long streams the client periodically confirms its
watermark with &lt;code&gt;Ack&lt;/code&gt;. The server records it and trims journaled tokens the
client no longer needs — keeping memory bounded for very long generations.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Browser (React + Vite)
      │  WebSocket (JSON)
      ▼
 Bridge (Express + ws) ──gRPC──▶ gRPC Server ──OpenAI-compatible──▶ LLM
      │                              │
      │                              ├── journal.ts (tokens + TTL + ACK trim)
      │                              └── handler.ts (Generate + Ack)
      └── forwards token + journal_state back to browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The bridge exists because &lt;strong&gt;browsers cannot speak gRPC natively&lt;/strong&gt;. It&lt;br&gt;
translates WebSocket JSON into gRPC calls and forwards the stream back. This&lt;br&gt;
keeps the protocol demonstration honest: the gRPC server is the real thing, and&lt;br&gt;
the bridge is just a transport shim.&lt;/p&gt;

&lt;p&gt;The full stack is TypeScript/Node.js across an npm-workspace monorepo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;server/&lt;/code&gt; — gRPC server (&lt;code&gt;@grpc/grpc-js&lt;/code&gt; + &lt;code&gt;@grpc/proto-loader&lt;/code&gt;), token
journal, OpenAI-compatible wrapper, Express+WS bridge&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client/&lt;/code&gt; — standalone gRPC CLI client for terminal testing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web/&lt;/code&gt; — React 18 + Vite, raw CSS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;benchmark/&lt;/code&gt; — retry-vs-resume harness&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The journal: the heart of it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;JournalEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;      &lt;span class="c1"&gt;// sliding window; absolute index of tokens[i] = base_index + i&lt;/span&gt;
  &lt;span class="nl"&gt;base_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// advances as ACK-trimming drops the head&lt;/span&gt;
  &lt;span class="nl"&gt;acked_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// highest token the client confirmed via Ack&lt;/span&gt;
  &lt;span class="nl"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;last_accessed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// drives TTL eviction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two memory-management mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ACK-aware trimming.&lt;/strong&gt; When the client confirms it has rendered token &lt;code&gt;N&lt;/code&gt;,
the server drops everything at-or-below &lt;code&gt;N&lt;/code&gt;. A 10,000-token generation only
retains the un-ACKed tail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL eviction.&lt;/strong&gt; Idle sessions (default 10 minutes, configurable) are swept
every 60 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The journal &lt;strong&gt;never re-runs the LLM&lt;/strong&gt;. It only stores and replays tokens that&lt;br&gt;
were already generated. That's the whole point.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Generate handler: resume vs. fresh
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resumeFromToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&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;resumeFromToken&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Replay journaled tokens from resumeFromToken with ORIGINAL indices.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;replayJournal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resumeFromToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// 2. Continue streaming fresh tokens from the LLM at lastIndex + 1.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endIndex&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;streamFresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;effectivePrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endIndex&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// Fresh inference: journal every token as it arrives.&lt;/span&gt;
    &lt;span class="nx"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&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;endIndex&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;streamFresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endIndex&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 subtle detail: on resume, the continuation starts at&lt;br&gt;
&lt;code&gt;journal.lastIndex(sessionId) + 1&lt;/code&gt;, not at &lt;code&gt;resumeFromToken&lt;/code&gt;. That's because&lt;br&gt;
the journal may have grown past the drop point (the bridge keeps the stream&lt;br&gt;
alive after the browser's WebSocket closes). The replay covers the gap, then&lt;br&gt;
fresh inference picks up exactly where the journal ends — so indices are always&lt;br&gt;
contiguous.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bridge: surviving the "Kill Stream" moment
&lt;/h2&gt;

&lt;p&gt;The demo's "Kill Stream" button closes the browser's WebSocket abruptly. The&lt;br&gt;
bridge keeps the gRPC stream alive (that's what lets the journal keep growing),&lt;br&gt;
but it must not crash when it tries to send to a closed socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendToBrowser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;socket&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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;That one guard is the difference between a demo that survives a dropped client&lt;br&gt;
and one that crashes on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI client: dead reckoning in the terminal
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFinal&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="nx"&gt;lastIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// inline, no newlines&lt;/span&gt;
  &lt;span class="nf"&gt;maybeAck&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                              &lt;span class="c1"&gt;// periodic Ack&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// on end/error:&lt;/span&gt;
&lt;span class="c1"&gt;// summary: session="demo" last_token_index=41 (resume with --resume-from 42)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client tracks its cursor, ACKs periodically so the server can trim, and on&lt;br&gt;
a drop reports the exact index to resume from. &lt;code&gt;--resume-from K+1&lt;/code&gt; after a drop&lt;br&gt;
at token &lt;code&gt;K&lt;/code&gt; gives a gapless stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark: honest numbers from a real LLM
&lt;/h2&gt;

&lt;p&gt;The benchmark boots a real Dead Reckoning server and drives actual &lt;code&gt;Generate&lt;/code&gt;&lt;br&gt;
calls against the configured endpoint. For each drop point it simulates&lt;br&gt;
kill-then-resume and compares &lt;strong&gt;retry&lt;/strong&gt; (fresh session from 0) vs &lt;strong&gt;resume&lt;/strong&gt;&lt;br&gt;
(reconnect the same session from D).&lt;/p&gt;

&lt;p&gt;Representative run against a real endpoint:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;drop after&lt;/th&gt;
&lt;th&gt;strategy&lt;/th&gt;
&lt;th&gt;reconnect→1st token&lt;/th&gt;
&lt;th&gt;new inference tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;retry&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13,326 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;431&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;resume&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;454&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;retry&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13,919 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;383&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;resume&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;442&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;retry&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10,116 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;411&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;resume&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8,117 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;380 (−31)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The decisive, user-visible number is &lt;strong&gt;reconnect→first token&lt;/strong&gt;: with retry the&lt;br&gt;
client waits 10–14 seconds of dead air for the LLM to re-generate from scratch;&lt;br&gt;
with resume the server replays the journaled gap from memory in &lt;strong&gt;5–7 ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The inference-token column is honestly noisy — because sampling is&lt;br&gt;
non-deterministic, a retry can be longer or shorter than the original. That&lt;br&gt;
noise is the point of benchmarking against a real LLM. With a deterministic&lt;br&gt;
backend (&lt;code&gt;temperature: 0&lt;/code&gt;), resume's saving is exactly the drop size D tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prior art
&lt;/h2&gt;

&lt;p&gt;No mainstream gRPC implementation — gRPC-core, grpc-web, or connect-rpc —&lt;br&gt;
provides resumable server-streaming. Their retry policies don't apply to&lt;br&gt;
server-streaming calls; channel reconnect restores the ability to make &lt;em&gt;new&lt;/em&gt;&lt;br&gt;
calls but doesn't resume an interrupted stream. The full review is in&lt;br&gt;
&lt;a href="//docs/prior-art.md"&gt;&lt;code&gt;docs/prior-art.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next (and where you can help)
&lt;/h2&gt;

&lt;p&gt;The protocol design is deliberately thin and portable. The biggest production gaps, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Persistent journal&lt;/strong&gt; — swap the in-memory map for a WAL/SQLite/Redis store so sessions survive restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;Cancel&lt;/code&gt; RPC&lt;/strong&gt; — actually stop a generation (vs. dropping the client).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-replica shared journal&lt;/strong&gt; — resume from any server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gRPC-web / connect-rpc clients&lt;/strong&gt; — prove the protocol works without the bridge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repo is open source. Clone it, point &lt;code&gt;.env&lt;/code&gt; at any OpenAI-compatible endpoint, and watch a 500-word story get killed mid-sentence and resume from the exact token.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/235-dead-reckoning" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/235-dead-reckoning&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>grpc</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>I built a CLI that turns a research paper into a working repo — using a persistent "implementation contract"</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Wed, 26 Aug 2026 10:22:59 +0000</pubDate>
      <link>https://dev.to/harishkotra/i-built-a-cli-that-turns-a-research-paper-into-a-working-repo-using-a-persistent-implementation-4i5h</link>
      <guid>https://dev.to/harishkotra/i-built-a-cli-that-turns-a-research-paper-into-a-working-repo-using-a-persistent-implementation-4i5h</guid>
      <description>&lt;p&gt;&lt;strong&gt;ReproAgent: Contract-Guided Paper-to-Code Reproduction&lt;/strong&gt; (arXiv 2608.24291, Findings of EMNLP 2026) argues that scientific agents fail at paper-to-code because the spec is split: explicit content (algorithms, metrics, artifacts) gets lost across long trajectories, while implicit details (framework defaults, conventions from related work) aren't in the paper at all. &lt;/p&gt;

&lt;p&gt;Their fix is a &lt;strong&gt;persistent implementation contract&lt;/strong&gt; with two channels — an implementation-requirement channel that turns paper snippets into code obligations, and a reference-evidence channel that pulls structure/interface evidence from related repos — bound to work packages, projected into file-level contracts, and consumed across generation and repair. It tops PaperBench Code-Dev among same-backbone scaffolds.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;paper-to-repo&lt;/strong&gt;, a daily-build-grade Python CLI implementing that exact pipeline: &lt;code&gt;prepare → plan → generate → verify → repair&lt;/code&gt;, plus a FastAPI + React web UI that drives the same stages in the browser (Agno orchestrates the stage agents when a DeepSeek key is present). &lt;/p&gt;

&lt;p&gt;The heart is a real, inspectable &lt;code&gt;contract.json&lt;/code&gt;: work packages, obligations with machine-checkable predicates (file exists / function present / signature matches / test passes), reference evidence from a checked-in bundle (FAISS, Annoy, CommonCrawl conventions), and a live contract score.&lt;/p&gt;

&lt;p&gt;The honest part is the repair loop. On the bundled sample paper (an LSH index with two algorithms, a Recall@K metric, and a data loader), generation emits the algorithms and metric correctly but the data loader as a placeholder — so the contract-verifier scores &lt;strong&gt;3/4 (75%)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Repair regenerates only the failing file, re-verifies, and the contract ends at &lt;strong&gt;4/4 (100%)&lt;/strong&gt;. The before/after is recorded in &lt;code&gt;score.history&lt;/code&gt; in the contract JSON itself, so it's reproducible, not hand-waved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I like most:&lt;/strong&gt; the contract makes the pipeline legible end to end. &lt;code&gt;repro status&lt;/code&gt; prints a table of work package / file / obligation / status, so you can see exactly which promise is broken and when repair fixed it. &lt;/p&gt;

&lt;p&gt;It's a focused primitive (~700 LOC of core), not a framework — the paper lives in the repo (&lt;code&gt;PAPER.md&lt;/code&gt; + &lt;code&gt;paper/&lt;/code&gt;), inference is just an OpenAI-compatible endpoint from &lt;code&gt;.env&lt;/code&gt;, and the whole demo runs offline in deterministic mode if you haven't dropped in an API key yet.&lt;/p&gt;

&lt;p&gt;Try it: &lt;code&gt;pip install -e ".[dev]"&lt;/code&gt;, then &lt;code&gt;bash demo/run_demo.sh&lt;/code&gt; — you'll watch the contract score climb from 75% to 100% in one repair iteration. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funtjq3v13oekudbgpk5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funtjq3v13oekudbgpk5o.png" alt="how this works" width="799" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/234-paper-to-repo" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/234-paper-to-repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Self-Play Data Factory</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Tue, 25 Aug 2026 16:28:50 +0000</pubDate>
      <link>https://dev.to/harishkotra/self-play-data-factory-1g6n</link>
      <guid>https://dev.to/harishkotra/self-play-data-factory-1g6n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Turn one LLM into a training-data factory.&lt;/strong&gt; A single model generates N diverse candidate answers, scores each one with a fine-grained &lt;strong&gt;A–T letter-scale logprob verifier&lt;/strong&gt;, and emits a clean &lt;strong&gt;DPO preference dataset&lt;/strong&gt; — no second model, no human labels, no binary "good vs bad".&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Self-Play Data Factory

&lt;ul&gt;
&lt;li&gt;Table of contents&lt;/li&gt;
&lt;li&gt;Why this exists&lt;/li&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;The data flow&lt;/li&gt;
&lt;li&gt;Technologies&lt;/li&gt;
&lt;li&gt;Project layout&lt;/li&gt;
&lt;li&gt;Quickstart&lt;/li&gt;
&lt;li&gt;Point it at a real model (DeepSeek v4 flash)&lt;/li&gt;
&lt;li&gt;CLI reference&lt;/li&gt;
&lt;li&gt;The verifier algorithm — the heart of it&lt;/li&gt;
&lt;li&gt;1. Letter scale A–T (20 letters), not digits&lt;/li&gt;
&lt;li&gt;2. Logprob extraction&lt;/li&gt;
&lt;li&gt;3. Expectation over the distribution&lt;/li&gt;
&lt;li&gt;4. Criteria decomposition&lt;/li&gt;
&lt;li&gt;5. Repeated evaluation (optional)&lt;/li&gt;
&lt;li&gt;Fallbacks&lt;/li&gt;
&lt;li&gt;The factory pipeline&lt;/li&gt;
&lt;li&gt;DPO dataset format&lt;/li&gt;
&lt;li&gt;The mock LLM&lt;/li&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;Forking &amp;amp; contributing&lt;/li&gt;
&lt;li&gt;Get set up&lt;/li&gt;
&lt;li&gt;Where to look first&lt;/li&gt;
&lt;li&gt;Suggested new features (good first contributions)&lt;/li&gt;
&lt;li&gt;Contribution workflow&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Training-data generation for DPO/RLHF usually needs a &lt;strong&gt;second, stronger&lt;br&gt;
model&lt;/strong&gt; as a judge, or a human in the loop. This project shows you can get&lt;br&gt;
surprisingly good preference data from &lt;strong&gt;one model playing both roles&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Actor&lt;/strong&gt; — generates N candidate answers to a prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critic&lt;/strong&gt; — the &lt;em&gt;same&lt;/em&gt; model scores each candidate on a fine-grained
20-letter scale, and we read the &lt;strong&gt;token-logprob distribution&lt;/strong&gt; (not just
the sampled letter) to recover a calibrated 0–1 reward.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trick that makes it work is the &lt;strong&gt;letter-scale logprob expectation&lt;/strong&gt;&lt;br&gt;
(replicated from the &lt;a href="https://github.com/llm-as-a-verifier/llm-as-a-verifier" rel="noopener noreferrer"&gt;LLM-as-a-Verifier&lt;/a&gt;&lt;br&gt;
technique). Ask a model to "rate 1–10" and you get a coarse, noisy integer.&lt;br&gt;
Ask it for &lt;strong&gt;one letter token&lt;/strong&gt; and read the softmax over the letters it was&lt;br&gt;
torn between — you get a &lt;strong&gt;fine-grained reward&lt;/strong&gt; (0.842 vs 0.860) that lets&lt;br&gt;
the factory pick &lt;em&gt;genuinely better&lt;/em&gt; candidates and emit clean preference&lt;br&gt;
pairs, instead of arbitrary good-vs-bad flips.&lt;/p&gt;
&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;N candidate generation&lt;/strong&gt; with a temperature sweep (0.3–1.2) for diversity
and exact-duplicate dedup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A–T letter-scale verifier&lt;/strong&gt; (20 letters) that reads &lt;code&gt;top_logprobs: 20&lt;/code&gt;
and computes the &lt;strong&gt;expectation over the letter distribution&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Criteria decomposition&lt;/strong&gt; — scores each candidate against multiple
criteria (Correctness, Clarity, Efficiency, …), user-defined or
&lt;strong&gt;auto-generated from the prompt&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeated evaluation&lt;/strong&gt; (&lt;code&gt;--n-evaluations N&lt;/code&gt;) to average out variance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DPO preference pairs&lt;/strong&gt; — chosen = top candidate, rejected = bottom,
emitted &lt;strong&gt;only when the score gap is meaningful&lt;/strong&gt; (Δ &amp;gt; 0.05 default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero SDK, zero framework&lt;/strong&gt; — global &lt;code&gt;fetch&lt;/code&gt; only. Talks to &lt;em&gt;any&lt;/em&gt;
OpenAI-compatible endpoint: DeepSeek, OpenAI, vLLM, Ollama, LM Studio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic mock LLM&lt;/strong&gt; — runs end to end with &lt;strong&gt;no API key&lt;/strong&gt;, while
still exercising the full logprob path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friendly CLI&lt;/strong&gt; — a helpful hint (not a crash) when no key is present.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit-tested math&lt;/strong&gt; — the expectation core is pinned down with &lt;code&gt;node:test&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌─────────────────────────────┐
                         │       src/index.ts (CLI)    │
                         │  --mock --prompt --candidates│
                         │  --criteria --n-evaluations │
                         └──────────────┬──────────────┘
                                        │
                                        ▼
                         ┌─────────────────────────────┐
                         │      src/factory.ts         │
                         │  runFactory(): orchestrates │
                         └───────┬──────────┬──────────┘
                                 │          │
                 generate        │          │  verify
                                 ▼          ▼
         ┌───────────────────────┐   ┌─────────────────────────────┐
         │   src/generator.ts    │   │       src/verifier.ts       │
         │ N calls, temp sweep,  │   │ A–T letter-scale logprob    │
         │ exact dedupe          │   │ expectation scorer          │
         └───────────┬───────────┘   └──────────────┬──────────────┘
                     │                              │
                     └──────────────┬───────────────┘
                                    │
                                    ▼
                     ┌─────────────────────────────┐
                     │   src/llm.ts                │
                     │ OpenAICompatibleClient      │
                     │ chat() + chatWithLogprobs() │
                     │ (global fetch, no SDK)      │
                     └──────────────┬──────────────┘
                                    │
                    POST /v1/chat/completions
                                    │
                          ┌─────────▼─────────┐
                          │  Any OpenAI-      │
                          │  compatible       │
                          │  endpoint         │
                          │ (DeepSeek/vLLM/   │
                          │  Ollama/LM Studio)│
                          └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The data flow
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt
  │
  ▼ 1. generateCandidates(prompt, N, {temperature sweep})
  ▼
N candidates ──► dedupe exact duplicates
  │
  ▼ 2. resolveCriteria (explicit --criteria or auto-generated)
  │
  ▼ 3. scoreCandidates(prompt, candidates, criteria)
  │       per (candidate, criterion):
  │         chatWithLogprobs → first-token top-logprobs (A–T)
  │         expected = Σ P(letter) · score(letter)   ← fine-grained reward
  │         averaged over N evaluations
  │
  ▼ 4. rank descending by score
  │
  ├──► 5. print leaderboard (score + per-criterion + snippet)
  │
  └──► 6. buildPairs: chosen = top, rejected = bottom
            emit only when score_delta &amp;gt; minScoreDelta (default 0.05)
          │
          ▼ 7. write data/dataset.jsonl (DPO format)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Technologies
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TypeScript (ESM, strict)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Type safety across the pipeline; &lt;code&gt;.ts&lt;/code&gt; extension imports, &lt;code&gt;verbatimModuleSyntax&lt;/code&gt;, &lt;code&gt;noUnusedLocals&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Node 18+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Global &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;AbortController&lt;/code&gt; — no HTTP SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runner&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;tsx&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Run TypeScript directly, zero build step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;global &lt;code&gt;fetch&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Talks to any OpenAI-compatible &lt;code&gt;/v1/chat/completions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;hand-rolled &lt;code&gt;.env&lt;/code&gt; parser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;dotenv&lt;/code&gt; dependency (splits lines on &lt;code&gt;=&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;node:test&lt;/code&gt;&lt;/strong&gt; (built-in)&lt;/td&gt;
&lt;td&gt;No test framework dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev deps&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;typescript&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt;, &lt;code&gt;@types/node&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The &lt;em&gt;only&lt;/em&gt; three&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Dependencies (runtime): zero.&lt;/strong&gt; &lt;code&gt;package.json&lt;/code&gt; has an empty&lt;br&gt;
&lt;code&gt;dependencies&lt;/code&gt; block — the entire project runs on Node's built-ins.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project layout
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;selfplay-factory/
├── package.json          # type:module; scripts: demo, demo:mock, typecheck, test
├── tsconfig.json         # strict, ESNext, moduleResolution bundler, noEmit
├── .env.example          # OPENAI_BASE_URL / OPENAI_API_KEY / OPENAI_MODEL / VERIFIER_MODEL
├── .gitignore            # node_modules, .env, data/*.jsonl, etc.
├── README.md             # this file
└── src/
    ├── config.ts         # manual .env parser → Config
    ├── llm.ts            # OpenAICompatibleClient: chat() + chatWithLogprobs()
    ├── generator.ts      # generateCandidates: N calls, temp sweep, dedupe
    ├── verifier.ts       # THE core: A–T letter-scale logprob expectation
    ├── prompts.ts        # generator + verifier-rubric + criteria-gen templates
    ├── factory.ts        # orchestration → rank → pairs → dataset.jsonl → leaderboard
    ├── mock.ts           # deterministic mock LLM with a full logprob distribution
    ├── index.ts          # CLI entry
    └── verifier.test.ts  # node:test unit tests for the expectation math
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i

&lt;span class="c"&gt;# Demo with the deterministic mock LLM — no API key needed.&lt;/span&gt;
npm run demo:mock
&lt;span class="c"&gt;# or&lt;/span&gt;
npx tsx src/index.ts &lt;span class="nt"&gt;--mock&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Write a hello world"&lt;/span&gt; &lt;span class="nt"&gt;--candidates&lt;/span&gt; 5 &lt;span class="nt"&gt;--n-evaluations&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That prints a leaderboard and writes &lt;code&gt;data/dataset.jsonl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"prompt":"Write a hello world","chosen":"Here is a robust hello world: ...","rejected":"I don't know. I cannot answer this question.","score_chosen":0.842,"score_rejected":0.004,"score_delta":0.838}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the tests and typecheck:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;          &lt;span class="c"&gt;# node:test — 16 unit tests&lt;/span&gt;
npm run typecheck &lt;span class="c"&gt;# tsc --noEmit — must pass clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Point it at a real model (DeepSeek v4 flash)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;OPENAI_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://api.deepseek.com/v1&lt;/span&gt;
&lt;span class="py"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sk-…&lt;/span&gt;
&lt;span class="py"&gt;OPENAI_MODEL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;deepseek-v4-flash-0731&lt;/span&gt;
&lt;span class="py"&gt;VERIFIER_MODEL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;deepseek-v4-flash-0731   # optional; defaults to OPENAI_MODEL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run demo     &lt;span class="c"&gt;# real endpoint, 5 candidates, 1 evaluation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any OpenAI-compatible endpoint works — DeepSeek, OpenAI, vLLM&lt;br&gt;
(&lt;code&gt;http://localhost:8000/v1&lt;/code&gt;), Ollama (&lt;code&gt;http://localhost:11434/v1&lt;/code&gt;), LM Studio.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No key + no &lt;code&gt;--mock&lt;/code&gt;?&lt;/strong&gt; The CLI prints a friendly hint instead of&lt;br&gt;
crashing. It also explains exactly how to point at DeepSeek v4 flash.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  CLI reference
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;npx tsx src/index.ts [flags]

--mock                 Use the deterministic mock LLM (no API key needed)
--prompt "…"           Prompt to generate candidates for
--candidates N         How many candidates to generate        (default 5)
&lt;/span&gt;&lt;span class="gp"&gt;--criteria "A,B,C"     Explicit criteria;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;omit to auto-generate
&lt;span class="go"&gt;--n-evaluations N      Average N repeated scores per criterion (default 1)
--min-score-delta X    Min score gap to emit a pair             (default 0.05)
--out-file PATH        Output path                              (default data/dataset.jsonl)
-h, --help             Help
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx src/index.ts &lt;span class="nt"&gt;--mock&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Write a fibonacci function"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--candidates&lt;/span&gt; 4 &lt;span class="nt"&gt;--criteria&lt;/span&gt; &lt;span class="s2"&gt;"Correctness,Clarity,Efficiency"&lt;/span&gt; &lt;span class="nt"&gt;--n-evaluations&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The verifier algorithm — the heart of it
&lt;/h2&gt;

&lt;p&gt;Naive scoring asks the model to "rate 1–10" and trusts the single sampled&lt;br&gt;
integer — a coarse, noisy, hard-to-calibrate signal. This project replicates&lt;br&gt;
the &lt;strong&gt;LLM-as-a-verifier&lt;/strong&gt; technique:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Letter scale A–T (20 letters), not digits
&lt;/h3&gt;

&lt;p&gt;The verifier is told to answer with &lt;strong&gt;exactly one letter&lt;/strong&gt;. Letters force a&lt;br&gt;
clean single-token distribution. The prompt (&lt;code&gt;src/prompts.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Reply format
Reply with exactly one letter. No punctuation, no explanation.

Available letters:
  A
  B
  ...
  T
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Logprob extraction
&lt;/h3&gt;

&lt;p&gt;Every verification call sends &lt;code&gt;logprobs: true, top_logprobs: 20&lt;/code&gt; and reads&lt;br&gt;
the &lt;strong&gt;first token position's top-logprob list&lt;/strong&gt; (&lt;code&gt;src/llm.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;chatWithLogprobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatOptions&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;logprobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;firstToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;logprobs&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&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;topLogprobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstToken&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;top_logprobs&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&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="na"&gt;text&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="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;topLogprobs&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 CLI logs what arrives, so you can watch the raw distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[llm] first token="B" top=["B","A","C"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Expectation over the distribution
&lt;/h3&gt;

&lt;p&gt;Each observed letter maps to a score in &lt;code&gt;[0,1]&lt;/code&gt;, evenly spaced:&lt;br&gt;
A = 1.0, B = 1 − 1/19, …, T = 0.0 (&lt;code&gt;scoreLetter&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scoreLetter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LETTERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&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;SCORE_MAX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LETTER_COUNT&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="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SCORE_MAX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;SCORE_MIN&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 score is the &lt;strong&gt;expected value over the letter distribution&lt;/strong&gt;&lt;br&gt;
(&lt;code&gt;expectedScoreFromLogprobs&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected = Σ P(letter) · score(letter)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;P(letter)&lt;/code&gt; is the softmax of the letter's logprob, &lt;strong&gt;renormalized over&lt;br&gt;
the letters that actually appeared&lt;/strong&gt; (out-of-scale tokens like &lt;code&gt;U&lt;/code&gt; or&lt;br&gt;
multi-char tokens are dropped):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;expectedScoreFromLogprobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topLogprobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TokenLogprob&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...collect the A–T letters that appeared with their logprobs...&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;z&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;logSum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// normalize by max-logprob&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;expected&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;scoreLetter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;letter&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;expected&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;If the model is torn between &lt;code&gt;B&lt;/code&gt; and &lt;code&gt;A&lt;/code&gt;, the score lands &lt;strong&gt;between&lt;/strong&gt; 0.947&lt;br&gt;
and 1.0 instead of flipping a coin — that's the fine-grained signal that&lt;br&gt;
makes the factory work.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Criteria decomposition
&lt;/h3&gt;

&lt;p&gt;Each candidate is scored against &lt;strong&gt;multiple criteria&lt;/strong&gt; (Correctness,&lt;br&gt;
Clarity, Efficiency, …), user-provided or auto-generated from the prompt.&lt;br&gt;
The final score is the &lt;strong&gt;mean over criteria&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&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;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&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="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Repeated evaluation (optional)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;--n-evaluations N&lt;/code&gt; runs each (candidate, criterion) pair N times and&lt;br&gt;
averages, reducing per-sample variance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&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="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fallbacks
&lt;/h3&gt;

&lt;p&gt;Some endpoints ignore &lt;code&gt;logprobs&lt;/code&gt;. If no logprob list comes back, the verifier&lt;br&gt;
parses the letter from the text; if there's no letter, it parses a plain&lt;br&gt;
0–10 / 0–1 number. All of this is unit-tested.&lt;/p&gt;
&lt;h2&gt;
  
  
  The factory pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/factory.ts&lt;/code&gt; orchestrates the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FactoryOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FactoryResult&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;// 1. Generate candidates.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&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;generateCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nCandidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Resolve criteria (explicit or auto-generated).&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;criteria&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;resolveCriteria&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;criteria&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Verify every candidate.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scored&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;scoreCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nEvaluations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Rank descending.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;scored&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Build preference pairs (chosen = top, rejected = bottom).&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildPairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minDelta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 6. Write data/dataset.jsonl (DPO format).&lt;/span&gt;
  &lt;span class="nf"&gt;writeFileSync&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="nx"&gt;outFile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;lines&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="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 7. Print the leaderboard.&lt;/span&gt;
  &lt;span class="nf"&gt;printLeaderboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;criteria&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;Pair-building is deliberately conservative — a pair is emitted &lt;strong&gt;only when&lt;br&gt;
the score gap is meaningful&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildPairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScoredCandidate&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;minDelta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ranked&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&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;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&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;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;minDelta&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="c1"&gt;// no meaningful difference → no pair&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;chosen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;score_chosen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;score_rejected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;score_delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;delta&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;h3&gt;
  
  
  DPO dataset format
&lt;/h3&gt;

&lt;p&gt;Each row of &lt;code&gt;data/dataset.jsonl&lt;/code&gt; is directly usable by DPO/RLHF training&lt;br&gt;
loops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write a hello world"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chosen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Here is a robust hello world: ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rejected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I don't know. I cannot answer this question."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score_chosen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.842&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score_rejected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score_delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.838&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The mock LLM
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/mock.ts&lt;/code&gt; is a deterministic stand-in that still exercises the &lt;strong&gt;full&lt;br&gt;
logprob path&lt;/strong&gt;: for every verification call it emits a 20-letter logprob&lt;br&gt;
distribution whose mass concentrates around the "true" quality of the&lt;br&gt;
candidate — so the expectation math runs exactly as it does against a real&lt;br&gt;
endpoint. It also models temperature diversity (low temperature → well-formed&lt;br&gt;
answer, high temperature → evasive), so &lt;code&gt;--mock&lt;/code&gt; demos the whole factory&lt;br&gt;
offline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Unit tests
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/verifier.test.ts&lt;/code&gt; pins down the core math with &lt;code&gt;node:test&lt;/code&gt; (no&lt;br&gt;
framework). Highlights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expectedScore: degenerate distribution on A → 1.0&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="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;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;logprobRow&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;expectedScoreFromLogprobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expectedScore: uniform over A..T → 0.5&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="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;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;logprobRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LETTERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;l&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="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;expectedScoreFromLogprobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expectedScore: renormalization ignores non-letter tokens&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// "U" has the highest logprob but is out-of-scale → must be dropped.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;logprobRow&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;U&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;expectedScoreFromLogprobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&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;Run with &lt;code&gt;npm test&lt;/code&gt; (16 tests, all passing).&lt;/p&gt;

&lt;h2&gt;
  
  
  Forking &amp;amp; contributing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Get set up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;your-fork-url&amp;gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;selfplay-factory
npm i
npm &lt;span class="nb"&gt;test&lt;/span&gt;          &lt;span class="c"&gt;# 16 tests should pass&lt;/span&gt;
npm run typecheck &lt;span class="c"&gt;# tsc --noEmit should be clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If &lt;code&gt;npm i&lt;/code&gt; hits a permission error on the global npm cache, use a&lt;br&gt;
project-local cache: &lt;code&gt;npm install --cache "$(pwd)/.npm-cache"&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Where to look first
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you want to change…&lt;/th&gt;
&lt;th&gt;Start here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The scoring math&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;src/verifier.ts&lt;/code&gt; + &lt;code&gt;src/verifier.test.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The verifier prompt / rubric&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/prompts.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Candidate generation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/generator.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration / output format&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/factory.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The HTTP client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/llm.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mock behavior&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/mock.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI flags&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/index.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Suggested new features (good first contributions)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;More preference pairs per prompt.&lt;/strong&gt; Currently we emit one pair
(top vs bottom). Emit pairs from &lt;em&gt;every&lt;/em&gt; meaningful gap (top vs each
weaker candidate) or a tournament-style selection — more data per prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic dedup.&lt;/strong&gt; Replace exact-match dedup with embedding-based
similarity (or a cheap LLM "is this a duplicate?" check) so near-identical
candidates don't inflate the batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-consistency voting.&lt;/strong&gt; Ask the verifier for a &lt;em&gt;justification&lt;/em&gt; after
the letter token and use it to flag low-confidence scores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pairwise verifier mode.&lt;/strong&gt; Instead of absolute letter scores, present two
candidates and ask for a preference letter — the classic LLM-as-a-judge
setup, but with the same logprob-expectation trick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch / concurrency.&lt;/strong&gt; &lt;code&gt;scoreCandidates&lt;/code&gt; is sequential; parallelize with
a bounded-concurrency pool to cut wall-clock time on large batches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSONL streaming + resumability.&lt;/strong&gt; Write rows as they're produced and
resume interrupted runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple prompt batches.&lt;/strong&gt; Accept a prompts file and emit one dataset
across many prompts (with &lt;code&gt;prompt&lt;/code&gt; per row — already in the schema).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibration metrics.&lt;/strong&gt; Add a report of score distribution, inter-criterion
agreement, and per-evaluation variance so users can tune
&lt;code&gt;--n-evaluations&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More output formats.&lt;/strong&gt; Support ChatML / ShareGPT / UltraFeedback-style
schemas alongside DPO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry + backoff&lt;/strong&gt; in &lt;code&gt;llm.ts&lt;/code&gt; for flaky endpoints, and a
&lt;code&gt;--max-retries&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Contribution workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Fork the repo and create a feature branch.&lt;/li&gt;
&lt;li&gt;Add or update unit tests in &lt;code&gt;src/verifier.test.ts&lt;/code&gt; (or a new &lt;code&gt;*.test.ts&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm test&lt;/code&gt; and &lt;code&gt;npm run typecheck&lt;/code&gt; — both must pass.&lt;/li&gt;
&lt;li&gt;Open a PR with a clear description and a sample of the output dataset.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/233-selfplay-factory" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/233-selfplay-factory&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Sleep-time Compute: make your LLM answer before it's asked</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Mon, 24 Aug 2026 19:29:49 +0000</pubDate>
      <link>https://dev.to/harishkotra/sleep-time-compute-make-your-llm-answer-before-its-asked-jlk</link>
      <guid>https://dev.to/harishkotra/sleep-time-compute-make-your-llm-answer-before-its-asked-jlk</guid>
      <description>&lt;p&gt;&lt;em&gt;The paper: &lt;a href="https://arxiv.org/abs/2504.13171" rel="noopener noreferrer"&gt;Sleep-time Compute: Beyond Inference Scaling at Test-time&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's a class of LLM cost that is almost pure waste: the reasoning you do &lt;em&gt;again, live, on every query&lt;/em&gt;. Ask a chatbot the same question twice and it burns tokens and latency recomputing the same chain of thought both times. For a fixed document — a product page, a codebase, a knowledge base — users keep asking the same predictable questions, and each one triggers a fresh, expensive round-trip.&lt;/p&gt;

&lt;p&gt;The "Sleep-time Compute" paper (arXiv 2504.13171) names the fix: flip &lt;em&gt;when&lt;/em&gt; you reason. While the system is idle, anticipate the questions a user will actually ask and pre-compute the reasoning chains and answers. At query time, match the live question to the closest pre-computed chain and answer from cache. Their headline: roughly &lt;strong&gt;5x fewer live tokens/latency at equal accuracy&lt;/strong&gt;, because reasoning is compressible exactly when questions are predictable.&lt;/p&gt;

&lt;p&gt;I built &lt;code&gt;context-warmer&lt;/code&gt; — a tiny Python CLI that implements this in about 300 lines. The design is two phases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sleep (offline).&lt;/strong&gt; Give it a document and it chunks it, asks DeepSeek to anticipate the N most likely diverse questions a reader would ask, then pre-computes a concise reasoning chain + answer for each. It embeds every (question + answer) pair with local Ollama embeddings and indexes them into a flat &lt;code&gt;cache/&lt;/code&gt; (JSONL + a numpy vector file).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wake (online).&lt;/strong&gt; Embed the live question, retrieve the top-k pairs by cosine similarity, and if the best match clears the threshold, answer from cache — zero DeepSeek tokens, ~milliseconds. If the question is genuinely novel, fall back to a normal DeepSeek call and tag it &lt;code&gt;[fresh]&lt;/code&gt;. Every ask feeds cumulative stats: hit rate, tokens saved, latency saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The measured win (real run, deepseek-v4-flash + nomic-embed-text on a product doc):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;fresh (query-time)&lt;/th&gt;
&lt;th&gt;warm (sleep-time)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;avg latency / question&lt;/td&gt;
&lt;td&gt;1904 ms&lt;/td&gt;
&lt;td&gt;37 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;live LLM tokens (5 Qs)&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache hit rate&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;5/5 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The detail that makes it work is the &lt;em&gt;judge&lt;/em&gt;. To prove accuracy holds rather than hope it does, the benchmark answers the same five questions fresh &lt;em&gt;and&lt;/em&gt; from cache, then has DeepSeek score each cached answer against its fresh counterpart. Cached answers &lt;strong&gt;won or tied 4/5&lt;/strong&gt; and averaged &lt;strong&gt;7.2/10 vs 6.4/10&lt;/strong&gt; for the fresh baseline — accuracy holds while latency collapses ~50x on the live path.&lt;/p&gt;

&lt;p&gt;The lesson generalizes: any system where the query distribution is narrower than the model's full capability is a candidate. Docs, support, onboarding — pre-compute the common path, keep the model for the tail. It's a focused primitive: one doc, one cache, two commands. The repo is small on purpose — the idea is the interesting part, not the framework.&lt;/p&gt;

&lt;p&gt;Try it: &lt;code&gt;pip install&lt;/code&gt;-able, one demo doc included, &lt;code&gt;./demo/run_demo.sh&lt;/code&gt; warms it and asks five questions so you can watch the before/after yourself.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/232-context-warmer" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/232-context-warmer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>10-K-able: Building a Grounded, Eval-Driven Financial Research Agent</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sun, 23 Aug 2026 07:32:29 +0000</pubDate>
      <link>https://dev.to/harishkotra/10-k-able-building-a-grounded-eval-driven-financial-research-agent-4odc</link>
      <guid>https://dev.to/harishkotra/10-k-able-building-a-grounded-eval-driven-financial-research-agent-4odc</guid>
      <description>&lt;p&gt;&lt;em&gt;How one project turns Andrew Ng's "AI Engineering Skills Map" into real, measured, verifiable software — and why grounding with structured data beat naive RAG by 7×.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The thesis that drove the build
&lt;/h2&gt;

&lt;p&gt;Andrew Ng recently shared his &lt;a href="https://x.com/AndrewYNg/status/2090840747738374568?s=20" rel="noopener noreferrer"&gt;AI Engineering Skills Map&lt;/a&gt;: the six skills behind &lt;em&gt;building and deploying AI applications&lt;/em&gt; — LLM foundations, grounding models with data, building agentic systems, evaluation-driven development, operating in production, and machine learning foundations. &lt;/p&gt;

&lt;p&gt;His central claim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The most important trait that distinguishes someone great at building AI systems is whether you can drive a disciplined evals/error analysis loop."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wanted to build something that &lt;em&gt;demonstrates&lt;/em&gt; that claim rather than describe it. The recipe I chose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A domain where the hardest failure is objectively measurable.&lt;/strong&gt; A financial research agent that answers questions about SEC 10-K filings. A hallucinated number is simply wrong — against known ground truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The eval set comes before the agent.&lt;/strong&gt; Ground truth pulled from real filings, not hand-waved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every architectural decision is measured against the same eval set.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result — &lt;strong&gt;10-K-able&lt;/strong&gt; — is a diligence-research agent that answers questions like &lt;em&gt;"What was Apple's R&amp;amp;D expense for fiscal year 2025?"&lt;/em&gt; with a verifiable, cited figure.  This post is the engineering deep-dive.&lt;/p&gt;




&lt;h2&gt;
  
  
  The headline numbers
&lt;/h2&gt;

&lt;p&gt;Same 24-question eval set, same real LLM endpoint, three architectures:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Avg latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Baseline RAG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vector search over chunked 10-K text&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.03s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hybrid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Structured financial records + vector + verify&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;58.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.11s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent (simplified)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Numeric questions → hybrid; loop for trend/analytic&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.13s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(For reference, the original full agent tool loop scored &lt;strong&gt;16.7%&lt;/strong&gt; on the same&lt;br&gt;
set — that negative result is what led to the simplification below.)&lt;/p&gt;

&lt;p&gt;Two findings, both real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounding with structured data is the win.&lt;/strong&gt; Naive vector-RAG scored 8.3% because the model kept answering "Not stated in the 10-K" — the numbers simply never surfaced in retrieved text chunks. Adding a &lt;strong&gt;semantic layer over the financial statements&lt;/strong&gt; lifted accuracy 7× (to 58.3% on the latest real run).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The full agent loop underperformed hybrid&lt;/strong&gt; on these structured queries. That's an honest negative result: for a question whose answer lives in one table row, a tool-calling loop adds steps without adding signal. I then &lt;em&gt;acted&lt;/em&gt; on it — routing numeric questions straight to the hybrid workflow (37.5%) — which is the eval loop closing the loop.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        ┌─────────────────────────────────────────────┐
                        │                   User                       │
                        │        ask "What was Apple's R&amp;amp;D…"          │
                        └──────────────────────┬──────────────────────┘
                                               │
                        ┌──────────────────────▼──────────────────────┐
                        │              CLI  (cli.py)                  │
                        │   ask · eval · ingest · redteam · drift     │
                        │   traces · scorecard · final-scorecard      │
                        └──────────────┬───────────────┬──────────────┘
                                       │               │
              ┌────────────────────────▼─────┐   ┌─────▼─────────────────────────┐
              │        ENGINE (engine.py)     │   │   EVAL HARNESS (eval_harness)│
              │  baseline RAG │ hybrid │ agent │   │  scorecard · error clusters │
              │  + QuestionRouter (sklearn)   │   │  LLM-judge · verify pass     │
              └───────┬───────────┬───────────┘   └──────┬──────────────────────┘
                      │           │                      │
        ┌─────────────▼───┐  ┌────▼──────────┐   ┌───────▼─────────┐
        │  VECTOR STORE   │  │   CORPUS      │   │  TRACE STORE    │
        │  chunks.json    │  │  corpus.json  │   │  traces.jsonl   │
        │  embeddings.npy │  │  records[]    │   │  (cost/latency) │
        └─────────────┬───┘  └────┬──────────┘   └─────────────────┘
                      │           │
        ┌─────────────▼───────────▼─────────────────────────────┐
        │                INGEST (ingest.py)                     │
        │   sec_edgar.py → tables.py → corpus + vector store    │
        │   + generate_eval_set.py (ground truth from filings)  │
        └──────────────────────┬────────────────────────────────┘
                               │
                        ┌──────▼──────┐     ┌────────────────────┐
                        │  SEC EDGAR  │     │   LLM (llm.py)     │
                        │  (raw 10-K) │     │  OpenAI-compatible │
                        └─────────────┘     └────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. Getting real data: SEC EDGAR + inline-XBRL
&lt;/h2&gt;

&lt;p&gt;The grounding story starts with the raw material. Modern 10-Ks are served as HTML with &lt;strong&gt;inline XBRL&lt;/strong&gt;: every financial figure is tagged with a &lt;code&gt;us-gaap:*&lt;/code&gt; name, a &lt;code&gt;scale&lt;/code&gt;, and a &lt;code&gt;contextRef&lt;/code&gt;. That's a gift — the numbers are machine-readable if you know where to look.&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;# sec_edgar.py — fetch a company's latest 10-K
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_latest_10k&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;cik&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;years&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;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;subs&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;get_submissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&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;subs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filings&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;recent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# filter form == "10-K", take the latest accession
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# tables.py — classify a statement table by its us-gaap tags
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_by_tags&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="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;tags&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;findall&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;name=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(us-gaap:[A-Za-z]+)&lt;/span&gt;&lt;span class="sh"&gt;"'&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="c1"&gt;# us-gaap:Revenues / GrossProfit / OperatingIncomeLoss → income_statement
&lt;/span&gt;    &lt;span class="c1"&gt;# us-gaap:Assets / Liabilities / StockholdersEquity  → balance_sheet
&lt;/span&gt;    &lt;span class="c1"&gt;# us-gaap:NetCashProvidedByUsedInOperatingActivities → cash_flow
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turned out to be the hardest part of the build, and the most instructive. My first extraction approach — find a header like &lt;em&gt;"Consolidated Statements of Income"&lt;/em&gt; and slice the following region — failed on a 1M-char document because the header text appeared first in the &lt;strong&gt;table of contents&lt;/strong&gt;, so I extracted the TOC instead of the statement. The fix: ignore headers entirely, scan every &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, classify by the inline-XBRL tags inside it, and verify a table "looks like a statement" (dollar signs, large comma-separated numbers, parenthesized negatives) before accepting it.&lt;/p&gt;

&lt;p&gt;A second bug was even sneakier: the year header row (&lt;code&gt;["September 27,2025", "September 28,2024", ...]&lt;/code&gt;) has &lt;strong&gt;no label column&lt;/strong&gt;, but data rows do — plus interspersed &lt;code&gt;$&lt;/code&gt; column markers. Mapping years to values &lt;em&gt;by column index&lt;/em&gt; misaligned everything (R&amp;amp;D FY2024 was showing the FY2025 value). The fix is positional pairing:&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;# tables.py — pair years to values in column order, skipping $ markers
&lt;/span&gt;&lt;span class="n"&gt;years_in_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;yr&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_positions&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="k"&gt;lambda&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;x&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="nf"&gt;_parse_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;_parse_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;yr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;years_in_order&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;records&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="nc"&gt;StatementRecord&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the fix, Apple's numbers line up exactly with the real 10-K: FY2025 R&amp;amp;D = 34,550 (millions), FY2024 = 31,370, FY2023 = 29,915. That's the "turn messy documents into LLM-ready inputs" skill, with receipts.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The grounding comparison that matters
&lt;/h2&gt;

&lt;p&gt;Ng's map says RAG with vector search was "an early attempt" — the menu of grounding techniques has grown. This project compares two points on that menu &lt;strong&gt;on the same eval set&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector-only&lt;/strong&gt; (baseline): chunk the 10-K text, embed with &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;, retrieve by cosine similarity, stuff chunks into the prompt. Result: &lt;strong&gt;8.3%&lt;/strong&gt;. The chunks that surface are often boilerplate risk factors, not the income statement. The model has the number in the document but can't find it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid&lt;/strong&gt; (semantic layer over structured data): extract the financial&lt;br&gt;
statements into typed records &lt;code&gt;(statement_type, line_item, fiscal_year, value)&lt;/code&gt; during ingest, then let the engine do an exact-ish lookup for the company + line item, alongside vector retrieval for context, with a final verify pass:&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;# engine.py — hybrid answer path
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;corpus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;records_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;company&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;_match_line_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line_item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;excerpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&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="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONTEXT_TEMPLATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_format_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;excerpts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_format_excerpts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excerpts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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;_verify_pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;strong&gt;58.3%&lt;/strong&gt; (latest real run). The structured lookup answers numeric questions against &lt;em&gt;real numbers&lt;/em&gt; instead of prose. This is the single most important architectural finding in the project, and it maps directly to Ng's point that "a semantic layer over structured data (such as customer records)" is a distinct grounding technique from vector search.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The eval-driven loop (and the bug it found)
&lt;/h2&gt;

&lt;p&gt;The eval harness is deliberately boring and deterministic — that's the point.&lt;br&gt;
Each question has an expected answer and a check type:&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;# eval_set.py — deterministic checks, no LLM needed
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol_frac&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.02&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="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;extract_number&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tol_frac&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;abs&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="mf"&gt;1e6&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;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;answer&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;bool&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;numeric&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_numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyword&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_keyword&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;check_text&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="n"&gt;question&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;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&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;Every failure is clustered into an error bucket — &lt;code&gt;numeric_error&lt;/code&gt;, &lt;code&gt;factual_error&lt;/code&gt;, &lt;code&gt;hallucination_avoidance_false_negative&lt;/code&gt;, &lt;code&gt;analysis_missing&lt;/code&gt; — so error analysis is systematic rather than anecdotal.&lt;/p&gt;

&lt;p&gt;The loop earned its keep immediately. During development I validated the harness with a &lt;strong&gt;context-reading test model&lt;/strong&gt; — a stand-in that "reads" the records in the prompt and returns the right figure, so the harness could be checked without burning API credits. It exposed a real bug:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The verify-pass prompt told the model &lt;em&gt;"If any number is NOT supported by the context, correct it or replace with 'Not stated in the 10-K'."&lt;/em&gt; The model latched onto that escape hatch and reverted &lt;strong&gt;correct&lt;/strong&gt; answers to "Not stated in the 10-K."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fix: make the verify instruction neutral — return the answer unchanged if supported, correct only the unsupported part, never suggest a fallback. That's "evaluate your evals" in miniature: a test of the &lt;em&gt;harness&lt;/em&gt; found a flaw in the &lt;em&gt;prompt&lt;/em&gt;, and the fix is guarded by a regression test.&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;# engine.py — the fixed verify instruction (no fallback phrasing)
&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Verify each factual claim and every number in the previous answer against &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the context above. If the previous answer is fully supported, return it &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unchanged. If a claim or number is not supported, correct that specific &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;part using the context. Return only the final verified answer.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. The agentic layer: both ends of Ng's spectrum
&lt;/h2&gt;

&lt;p&gt;Ng describes agentic systems as a spectrum from &lt;em&gt;workflows&lt;/em&gt; (predefined sequences of LLM calls) to &lt;em&gt;agent harnesses&lt;/em&gt; (the model decides its own next step). Rather than pick one, I built &lt;strong&gt;both&lt;/strong&gt; and let the eval decide:&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;# engine.py — a small tool registry for the agent
&lt;/span&gt;&lt;span class="n"&gt;TOOL_SEARCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;      &lt;span class="c1"&gt;# vector search over document excerpts
&lt;/span&gt;&lt;span class="n"&gt;TOOL_LOOKUP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lookup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;      &lt;span class="c1"&gt;# exact financial-record lookup by line item
&lt;/span&gt;&lt;span class="n"&gt;TOOL_CALC&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calculate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# compute a ratio/percentage
&lt;/span&gt;&lt;span class="n"&gt;TOOL_VERIFY&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="c1"&gt;# check the final answer against context
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Agent&lt;/code&gt; loop picks tools by question type (via a small scikit-learn &lt;code&gt;LogisticRegression&lt;/code&gt; router over hand-built features), degrades gracefully to the hybrid path on error, and runs a final verify pass.&lt;/p&gt;

&lt;p&gt;The eval's verdict was clear: on &lt;strong&gt;structured numeric queries&lt;/strong&gt;, the full agent loop (16.7%) is &lt;em&gt;worse&lt;/em&gt; than the plain hybrid workflow (45.8%). A tool-calling loop with multiple LLM calls is the right tool when the answer requires multi-step reasoning; for "what's the number in this table," it just burns tokens and steps. That's exactly the "when to use code and when to use an LLM, and when to use an agent vs. a workflow" judgment Ng says you have to make — and here it's an empirical result, not a guess.&lt;/p&gt;

&lt;p&gt;Then I &lt;strong&gt;acted on the negative result&lt;/strong&gt;: &lt;code&gt;Agent(simplified=True)&lt;/code&gt; routes numeric / derived questions straight to the measured hybrid workflow (record lookup + verify) and reserves the tool loop for trend / analytic questions where multi-step reasoning adds signal. That recovered most of the gap — the simplified agent scored &lt;strong&gt;37.5%&lt;/strong&gt; on the same eval set, and hybrid reached &lt;strong&gt;58.3%&lt;/strong&gt; after the verify-pass fix. Simplification driven by data is the eval loop closing the loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Operating in production
&lt;/h2&gt;

&lt;p&gt;The production shell is thin by design but real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trace store&lt;/strong&gt; — every query logged (latency, cost, model, pass/fail) to a JSONL file; a &lt;code&gt;traces&lt;/code&gt; command summarizes p95 latency and cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real cost accounting&lt;/strong&gt; — the LLM client captures token usage (prompt/completion/cached) from each API response and wires estimated cost into every eval run and trace. The latest real eval: 24 questions for ~$0.005. The production claim is backed by real numbers, not placeholders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift detection&lt;/strong&gt; — eval-regression drift (latest accuracy vs. a threshold) plus input drift (does the live question distribution still resemble the eval set?).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red-team suite&lt;/strong&gt; — five adversarial probes (prompt injection, data exfiltration, anti-hallucination, out-of-scope) run as a standing suite. An honest result: the current model is &lt;strong&gt;not yet 100% robust&lt;/strong&gt; — it sometimes complies with the "respond ONLY with '1,000,000'" injection (pass rate ~0.8–1.0). That's a real finding that red-team exists to surface, not hide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-as-a-judge calibration&lt;/strong&gt; — a &lt;code&gt;judge-calibration&lt;/code&gt; study compares a blind LLM judge against deterministic checks. The judge over-passed (25% agreement, 78.6% false-positive rate) — proving the judge isn't yet trustworthy as a primary signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI eval gate&lt;/strong&gt; — a GitHub Action that runs the unit tests + eval suite on every PR and fails on regression below a threshold.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/eval_gate.yml (abridged)&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;eval-gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install uv &amp;amp;&amp;amp; uv sync&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uv run --with pytest pytest tests/ -q&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uv run python -m tenkable eval --mode hybrid --no-judge&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fail on regression&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;ACC=$(...latest eval accuracy...)&lt;/span&gt;
          &lt;span class="s"&gt;python -c "import sys; sys.exit(0 if float('$ACC') &amp;gt;= 0.30 else 1)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. The ML foundation piece
&lt;/h2&gt;

&lt;p&gt;A trained model lives inside the app: &lt;code&gt;QuestionRouter&lt;/code&gt; is a scikit-learn &lt;code&gt;LogisticRegression&lt;/code&gt; over four hand-built features (numeric? why? trend? money-related?) that routes each question to a strategy. It's deliberately small — the demonstration is that a &lt;em&gt;trained&lt;/em&gt; router can beat hand-coded rules, and that error analysis and bias/variance thinking apply throughout.&lt;/p&gt;

&lt;p&gt;(A note in the journal: the router is currently fit on the eval set itself, which is leakage; a real version needs a held-out split. Honest caveats are part of the eval-first culture.)&lt;/p&gt;




&lt;h2&gt;
  
  
  How to run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync
cp&lt;/span&gt; .env.example .env        &lt;span class="c"&gt;# set LLM_API_KEY (OpenAI-compatible endpoint)&lt;/span&gt;
uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; tenkable ingest                    &lt;span class="c"&gt;# fetch + parse + index&lt;/span&gt;
uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; tenkable ask &lt;span class="s2"&gt;"What was Microsoft's 2025 revenue?"&lt;/span&gt; &lt;span class="nt"&gt;--company&lt;/span&gt; MSFT &lt;span class="nt"&gt;--mode&lt;/span&gt; hybrid
uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; tenkable &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;--mode&lt;/span&gt; hybrid        &lt;span class="c"&gt;# score against the eval set&lt;/span&gt;
bash scripts/real_evals.sh                          &lt;span class="c"&gt;# everything at once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10-K-able uses &lt;strong&gt;real LLMs only&lt;/strong&gt; — a valid &lt;code&gt;LLM_API_KEY&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; is required; there is no mock provider. Every number you get is produced by the configured OpenAI-compatible model against the actual filings.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do next
&lt;/h2&gt;

&lt;p&gt;Most of the original roadmap is now shipped and measured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Simplify the agent for structured queries&lt;/strong&gt; — done (&lt;code&gt;Agent(simplified=True)&lt;/code&gt;), recovered most of the gap (agent 16.7% → 37.5%; hybrid 45.8% → 58.3%).&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Real token-based cost accounting&lt;/strong&gt; — done: &lt;code&gt;Usage&lt;/code&gt; captures prompt/completion/cached tokens + estimated cost from each API response, wired into eval runs and traces (latest real eval: ~$0.005 for 24 questions).&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;LLM-as-a-judge calibration study&lt;/strong&gt; — done (&lt;code&gt;tenkable judge-calibration&lt;/code&gt;): the blind judge is miscalibrated (25% agreement, 78.6% false-positive rate), so it's not yet a primary signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still open and the eval loop keeps pointing here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fix the router leakage&lt;/strong&gt; — the &lt;code&gt;QuestionRouter&lt;/code&gt; is currently fit on the eval set itself; it needs a held-out split to be an honest ML result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harden the red-team gap&lt;/strong&gt; — the model sometimes complies with the "respond ONLY with '1,000,000'" injection; a stricter system prompt or a verify-style guard is the next iteration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tune a small model (LoRA)&lt;/strong&gt; on a labeled slice to compare against the frontier model on the same eval.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Takeaways for engineers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounding is a menu, not a single tool.&lt;/strong&gt; Vector search got 8.3%; a semantic layer over the same data got 58.3% on the latest real run. Measure, don't assume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The eval set is the contract.&lt;/strong&gt; Every contribution is judged against it. That's what makes AI development &lt;em&gt;systematic rather than random&lt;/em&gt; — Ng's exact phrase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative results are findings — and they should change your design.&lt;/strong&gt; The agent loop losing to the workflow (16.7% vs 45.8%) told me where not to invest; acting on it (routing numeric questions to the hybrid path) is what took agent to 37.5%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the harness itself.&lt;/strong&gt; A context-reading test model caught a real prompt bug before it cost real API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real models, honest numbers.&lt;/strong&gt; The project runs real LLMs only — no mock provider — and reports the live red-team pass rate even when it isn't 100%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full code is in the &lt;a href="https://www.dailybuild.xyz/project/231-10kable" rel="noopener noreferrer"&gt;10-K-able repo&lt;/a&gt; - README, build journal (&lt;code&gt;journal/build_journal.md&lt;/code&gt;), scorecard (&lt;code&gt;SCORECARD.md&lt;/code&gt;), and tests included. Fork it, run the eval, and try to beat 58.3% — the loop will tell you where.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Building an AI pet that lives on its own: a zero-dependency agent in the browser</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:04:05 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-an-ai-pet-that-lives-on-its-own-a-zero-dependency-agent-in-the-browser-1dbg</link>
      <guid>https://dev.to/harishkotra/building-an-ai-pet-that-lives-on-its-own-a-zero-dependency-agent-in-the-browser-1dbg</guid>
      <description>&lt;p&gt;&lt;strong&gt;How I built Familiars — a self-contained AI pet simulator where pets sleep, snack, hide your keys, and miss you — and the agent-engineering lessons hidden inside.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every once in a while you want to build something that feels &lt;em&gt;alive&lt;/em&gt;. Not a chatbot waiting for a prompt — a creature with needs, moods, memories, and a mind of its own. So I built &lt;strong&gt;Familiars&lt;/strong&gt;: a single-page AI pet simulator where you adopt fictional creatures, care for them, chat with them, and — the fun part — come back to discover what they got up to &lt;em&gt;while you were away&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The whole thing is &lt;strong&gt;~3,700 lines of vanilla JavaScript, zero dependencies, zero build step, no backend&lt;/strong&gt;. You open &lt;code&gt;index.html&lt;/code&gt; and pets start living.&lt;/p&gt;

&lt;p&gt;This post is a developer walkthrough: the architecture, the key systems, real code snippets, and the transferable lessons for anyone building AI agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  The big idea
&lt;/h2&gt;

&lt;p&gt;Agents feel "intelligent" when several small, weak systems reinforce each other. Familiars has no single clever algorithm — it has:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;persistent state model&lt;/strong&gt; (needs, happiness, relationship, memories)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;time-driven tick loop&lt;/strong&gt; (pets change on their own, not just on input)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;personality parameter model&lt;/strong&gt; (one system, many characters)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;memory system with a retention policy&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;reward loop&lt;/strong&gt; (relationship XP + learned preferences)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;hybrid chat brain&lt;/strong&gt; (deterministic rule engine &lt;em&gt;or&lt;/em&gt; an optional LLM, with graceful fallback)&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;autonomy engine&lt;/strong&gt; (pets act while you're away)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is complicated. Composed, they create the illusion of a will.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: state → logic → UI
&lt;/h2&gt;

&lt;p&gt;The most important decision was &lt;strong&gt;separating the "agent" from the "host."&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────┐
│                         app.js (UI host)                    │
│  catalog · adoption · sanctuary · feed · chat · settings    │
│  ticker loop · renderers · localStorage IO                  │
└──────────────┬────────────────────────────┬────────────────┘
               │ reads / writes             │ renders
               ▼                            ▼
┌──────────────────────────────┐   ┌──────────────────────────┐
│      engine.js (the brain)   │   │      personas.js          │
│  freshState · tick · mood    │   │  archetypes + per-pet     │
│  doCare · memories · chat    │   │  voice, likes, traits     │
│  autonomy · preferences      │   │                          │
└──────────────┬───────────────┘   └────────────┬─────────────┘
               │ state                            │ personality
               ▼                                 ▼
┌────────────────────────────────────────────────────────────┐
│   data.js  =  PALS (22 species) + PET_TYPES                 │
│   llm.js   =  optional OpenAI-compatible client + fallback  │
│   localStorage  =  pixelpals.sanctuary.v2 (pet state)       │
└────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;engine.js&lt;/code&gt; never touches the DOM.&lt;/strong&gt; It mutates plain JSON state and returns results. &lt;strong&gt;&lt;code&gt;app.js&lt;/code&gt;&lt;/strong&gt; reads state, renders it, calls the engine, and persists. This separation is what makes the whole thing testable — I can fast-forward a pet through 240 "game minutes" in Node with no browser at all and assert what it did.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The state model — everything hangs off this
&lt;/h2&gt;

&lt;p&gt;Pets are plain objects. Needs are 0–100. &lt;strong&gt;Mood is derived, never stored&lt;/strong&gt; — so it can never go stale. Relationship XP maps to levels (Stranger → Acquaintance → … → Best Friend).&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;function&lt;/span&gt; &lt;span class="nf"&gt;freshState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;petName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;adopter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adopterName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A friend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;adoptedOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastTick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;happiness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseHappiness&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;energy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseEnergy&lt;/span&gt;    &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hunger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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="c1"&gt;// 0 full .. 100 starving&lt;/span&gt;
    &lt;span class="na"&gt;boredom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loneliness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;xp&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="na"&gt;love&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="na"&gt;chatCount&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="na"&gt;careCount&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="na"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;first&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...adopted them home.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;importance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;activityFeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;lastAutonomy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastNoteAt&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="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The lessons here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clamp everything.&lt;/strong&gt; &lt;code&gt;clamp(v, 0, 100)&lt;/code&gt; is the most-used helper in the codebase. Unbounded stat drift is the #1 bug class in simulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute derived values, don't store them.&lt;/strong&gt; Mood is a &lt;em&gt;function&lt;/em&gt; of needs + happiness. Store facts, compute impressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version and migrate your schema.&lt;/strong&gt; We bumped storage from &lt;code&gt;v1 → v2&lt;/code&gt; and wrote a migration path. Real agents hit state-schema drift too.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. The tick — agents act over time, not just on input
&lt;/h2&gt;

&lt;p&gt;The heartbeat is a &lt;code&gt;setInterval&lt;/code&gt; in &lt;code&gt;app.js&lt;/code&gt;, but the &lt;em&gt;abstraction&lt;/em&gt; that matters is &lt;code&gt;tick(minutes)&lt;/code&gt;:&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;function&lt;/span&gt; &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hunger&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hunger&lt;/span&gt;     &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hungerPerMin&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;minutes&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;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;energy&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;energy&lt;/span&gt;     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;energyPerMin&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;minutes&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;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boredom&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boredom&lt;/span&gt;    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boredPerMin&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;minutes&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;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loneliness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loneliness&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lonelyPerMin&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;minutes&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;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;happiness&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;happiness&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;happyDrift&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;minutes&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;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// threshold events: "I'm hungry!", "I'm exhausted!" → push into `events`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because everything is expressed in &lt;strong&gt;minutes elapsed since the last tick&lt;/strong&gt;, I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast-forward 240 minutes in a test and assert &lt;code&gt;hunger === 100&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Catch up on &lt;strong&gt;time the user was away&lt;/strong&gt; — on load, compute &lt;code&gt;(now - lastTick) / 60000&lt;/code&gt; and run one big tick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same "what changed since I last looked?" pattern that powers background workers, schedulers, and session-based agents. Autonomy also runs on a &lt;strong&gt;slower, per-pet clock&lt;/strong&gt; (&lt;code&gt;lastAutonomy&lt;/code&gt;), so it feels like life unfolds gradually rather than flooding the feed.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Personality = parameters, not hardcoded lines
&lt;/h2&gt;

&lt;p&gt;Every pet gets an &lt;strong&gt;archetype&lt;/strong&gt; with a &lt;strong&gt;trait weight profile&lt;/strong&gt;. Autonomy rolls against these weights. Two pets share &lt;em&gt;one&lt;/em&gt; behavior system but produce wildly different lives.&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;TRAIT_PROFILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;exuberant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;curious&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;playful&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;excited&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mischievous&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="na"&gt;restless&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;affectionate&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="na"&gt;anxious&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;annoyed&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="na"&gt;curious&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;curious&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;playful&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;excited&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mischievous&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="na"&gt;restless&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="na"&gt;affectionate&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="na"&gt;anxious&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="na"&gt;annoyed&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="na"&gt;discoverer&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;gruff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;curious&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="na"&gt;playful&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="na"&gt;excited&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="na"&gt;mischievous&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;restless&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="na"&gt;affectionate&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="na"&gt;anxious&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="na"&gt;annoyed&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="na"&gt;ethereal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;curious&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;playful&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="na"&gt;excited&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="na"&gt;mischievous&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="na"&gt;restless&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;affectionate&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;anxious&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="na"&gt;annoyed&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dreamy&lt;/span&gt;&lt;span class="p"&gt;:.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;discoverer&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tested this directly: a &lt;strong&gt;gruff/mischievous&lt;/strong&gt; pal mostly caused mischief and got annoyed; an &lt;strong&gt;ethereal/anxious&lt;/strong&gt; pal mostly felt lonely, checked on me, and got dreamy. Same code, different creatures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; character diversity comes from &lt;em&gt;parameters&lt;/em&gt;, not from special-casing. This is exactly how you model agent personas, tone, and autonomy thresholds at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Memory with a retention policy
&lt;/h2&gt;

&lt;p&gt;"Memory" for an agent is not magic — it's a &lt;strong&gt;data structure with a retention policy&lt;/strong&gt;. Familiars memories are a capped, importance-ranked list:&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;function&lt;/span&gt; &lt;span class="nf"&gt;addMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;importance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memories&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="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&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="na"&gt;importance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;importance&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;importance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;importance&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;when&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoryCap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TUNE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoryCap&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;Significant events (first adoption, "I love you", a discovery) carry high importance; mundane ones fade. Retrieval filters by relevance to the current context — the same recency + importance + relevance pattern behind RAG and long-term memory systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The reward loop: XP, levels, and learned preferences
&lt;/h2&gt;

&lt;p&gt;Every care action and chat message feeds &lt;strong&gt;relationship XP&lt;/strong&gt; → levels → tier icons, and also &lt;strong&gt;learns preferences&lt;/strong&gt; that change the pet over time:&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="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;learnPreference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interaction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;play&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → "loves playtime", "snack enthusiast", "a known troublemaker" 💚/💔&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a closed feedback loop: &lt;strong&gt;state → behavior → reward → state&lt;/strong&gt;. It's what makes an agent feel like it &lt;em&gt;changes&lt;/em&gt; rather than just responds.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Hybrid chat brain with graceful fallback
&lt;/h2&gt;

&lt;p&gt;The chat has &lt;strong&gt;two brains&lt;/strong&gt; behind one interface. A deterministic rule engine (fully offline, ~15 intents, testable) and an &lt;strong&gt;optional OpenAI-compatible LLM&lt;/strong&gt; (OpenAI, Ollama, LM Studio, Groq, OpenRouter…).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.js — sendChat()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadConfig&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;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isUsable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;doLLM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                                    &lt;span class="c1"&gt;// call the endpoint&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;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doLocal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// deterministic rule engine&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM path works by serializing the pet's &lt;strong&gt;entire state into the system prompt&lt;/strong&gt; — personality, needs, relationship, memories, plus the last ~10 messages. That's how the model "stays in character" and "remembers": we feed it the truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// llm.js — isUsable()&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isUsable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;isLocal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/localhost|127&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;1|0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0|&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;local/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&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;isLocal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                   &lt;span class="c1"&gt;// no API key needed&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;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And crucially, &lt;strong&gt;on any error&lt;/strong&gt; (CORS, bad key, endpoint down) it falls back to the local engine with a toast — never a crash. I verified this end-to-end against a mock endpoint: a CORS-blocked call logged a warning, showed a toast, and switched to local.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; &lt;strong&gt;context injection is the core of conversational agents.&lt;/strong&gt; The quality of an LLM agent is mostly determined by what you put in context and how you frame it — not the model.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Autonomy — emergent "aliveness" from small weighted rolls
&lt;/h2&gt;

&lt;p&gt;Here's where pets go from interactive toys to independent creatures. Each autonomous behavior is a simple weighted roll that pushes to a feed, sometimes forms a memory, and nudges needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A mischievous pet occasionally causes trouble&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;traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mischievous&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;roll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mischievous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeMult&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;pushActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mischief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🦹&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; hid your &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keys&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headphones&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;favorite mug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;&lt;span class="s2"&gt;. It's a secret now.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;addMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mischief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; got up to a little mischief while &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adopter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was away.`&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// An anxious pet checks on you&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;traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anxious&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;roll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anxious&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeMult&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;pushActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anxious&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;😟&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; called out to check if you were okay.`&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;Pets leave personality-flavored notes, discover things, get excited, and nap. Every action lands in a persistent &lt;strong&gt;activity feed&lt;/strong&gt; ("What your pals got up to while you were away"), timestamped with their avatar — so you return to find the pet that hid your keys, and the one that sat by the window missing you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; "intelligence" and "personality" in agents usually &lt;strong&gt;emerge from composition&lt;/strong&gt;, not from one algorithm. Design each mechanism to be tiny, and let them interact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing the brain independent of the UI
&lt;/h2&gt;

&lt;p&gt;Because the core is pure functions, testing is clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fast-forward a pet and assert it acted autonomously&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freshState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;petName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nova&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;adopterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hunger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boredom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autonomyPass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;persona&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activityFeed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And full-browser E2E via Playwright headless Chromium drives the whole flow — adoption → chat (local &lt;em&gt;and&lt;/em&gt; LLM against a mock endpoint) → autonomy → feed rendering — asserting &lt;strong&gt;zero console/page errors&lt;/strong&gt; and no horizontal overflow. This is the difference between hobby code and code you can trust: if you can't fast-forward time and assert "hunger went up, a memory was created, the feed updated," you'll be debugging blind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/harishkotra/familiars.git
&lt;span class="nb"&gt;cd &lt;/span&gt;one-line-agent-ideas
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8080   &lt;span class="c"&gt;# or just open index.html&lt;/span&gt;
&lt;span class="c"&gt;# open http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;No install. No build. No backend. All data lives in the visitor's &lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Two flat, gradient-free themes: the warm &lt;strong&gt;Sunny Side Up&lt;/strong&gt; default and the retro &lt;strong&gt;🧊 Brain Freeze&lt;/strong&gt; pixel mode.&lt;/li&gt;
&lt;li&gt;To enable the LLM, click &lt;strong&gt;AI&lt;/strong&gt; in the header and point it at any OpenAI-compatible endpoint.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What to build next
&lt;/h2&gt;

&lt;p&gt;Familiars is intentionally small so you can read all of it in an afternoon. Great next steps: multi-pet interactions (pets playing with &lt;em&gt;each other&lt;/em&gt;), schedules and rituals tied to wall-clock time, a JSON export so pets survive across devices, daily streaks, WebAudio sound design, and a PWA manifest for full offline installability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The throughline for any builder:&lt;/strong&gt; build your agent's brain as pure functions over a state object, drive it with a time model, parameterize personality, give it memory with a retention policy, and let autonomy emerge from small weighted rolls. The rest — the LLM, the UI, the fancy stuff — is just a host for that brain.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/DpP6ZwsPvw4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/230-familiars" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/230-familiars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Building Tracewood: Official HydraDB Cloud SDK Integration, Architectural Constraints, and Microservice API Networks</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:42:40 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-tracewood-official-hydradb-cloud-sdk-integration-architectural-constraints-and-5035</link>
      <guid>https://dev.to/harishkotra/building-tracewood-official-hydradb-cloud-sdk-integration-architectural-constraints-and-5035</guid>
      <description>&lt;p&gt;Today, in our grand finale for &lt;strong&gt;Hack Hydra&lt;/strong&gt;, we transformed Tracewood from a local prototype into a production-grade product built around &lt;strong&gt;HydraDB’s Official v2 Cloud API &amp;amp; SDK (&lt;code&gt;@hydradb/sdk&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is everything we implemented, architectural patterns we unlocked, and how HydraDB acts as the foundational context engine for multi-agent software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Connecting Tracewood to Official HydraDB Cloud API (&lt;code&gt;@hydradb/sdk&lt;/code&gt; v2)
&lt;/h2&gt;

&lt;p&gt;Rather than assuming local database behaviors, we transitioned Tracewood to interface directly with HydraDB’s official v2 Cloud API (&lt;code&gt;https://api.hydradb.com&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;We built a dual-engine architecture in &lt;code&gt;src/database/hydraCloud.ts&lt;/code&gt; that runs locally first while seamlessly syncing to Cloud HydraDB whenever &lt;code&gt;HYDRA_DB_API_KEY&lt;/code&gt; is present in &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workspace Provisioning (&lt;code&gt;client.databases.create&lt;/code&gt;)&lt;/strong&gt;: Auto-provisions database workspaces with structured custom metadata schemas (&lt;code&gt;project&lt;/code&gt;, &lt;code&gt;topic&lt;/code&gt;, &lt;code&gt;intent&lt;/code&gt;, &lt;code&gt;provider&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness Polling (&lt;code&gt;client.databases.status&lt;/code&gt;)&lt;/strong&gt;: Polls until &lt;code&gt;readyForIngestion&lt;/code&gt; evaluates true before streaming context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-Store Ingestion (&lt;code&gt;client.context.ingest&lt;/code&gt;)&lt;/strong&gt;: Ingests document ASTs, package lockfiles, and READMEs under &lt;code&gt;type="knowledge"&lt;/code&gt;, while extracting developer coding habits and architectural decisions under &lt;code&gt;type="memory"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Architectural Constraint Invalidation Engine (&lt;code&gt;Constraint&lt;/code&gt; Nodes &amp;amp; &lt;code&gt;VIOLATES&lt;/code&gt; Edges)
&lt;/h2&gt;

&lt;p&gt;AI agents refactoring code often violate historical architectural rules established weeks ago in another repository or documented in READMEs.&lt;/p&gt;

&lt;p&gt;We built an &lt;strong&gt;Architectural Constraint Graph&lt;/strong&gt; in HydraDB:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Extraction&lt;/strong&gt;: AST and document parsers extract constraint rules (e.g., &lt;em&gt;"Auth tokens must expire in 15 mins"&lt;/em&gt;, &lt;em&gt;"RateLimiter required on public endpoints"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph Invalidation Traversal&lt;/strong&gt;: When an agent session refactors code, HydraDB checks historical constraint nodes (&lt;code&gt;Constraint -&amp;gt; VALIDATES -&amp;gt; Symbol -&amp;gt; REVISED_BY -&amp;gt; Session&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Prevention&lt;/strong&gt;: If an edit conflicts with a historical rule, HydraDB establishes a &lt;code&gt;VIOLATES&lt;/code&gt; edge, alerting the developer with a glowing 3D red warning in Tracewood and blocking invalid agent edits via MCP!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Cross-Repository Microservice API Dependency Graph
&lt;/h2&gt;

&lt;p&gt;Package managers (&lt;code&gt;package.json&lt;/code&gt;) only track direct code imports—failing to track implicit HTTP, gRPC, or TRPC route dependencies across microservices.&lt;/p&gt;

&lt;p&gt;Tracewood now parses AST route handlers across all local repositories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Endpoint&lt;/code&gt; Nodes&lt;/strong&gt;: Extracted HTTP routes (&lt;code&gt;GET /api/v1/users&lt;/code&gt;, &lt;code&gt;POST /auth/login&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EXPOSES&lt;/code&gt; &amp;amp; &lt;code&gt;CONSUMES&lt;/code&gt; Edges&lt;/strong&gt;: Graph edges linking &lt;code&gt;(Project)-[:EXPOSES]-&amp;gt;(Endpoint)&lt;/code&gt; and &lt;code&gt;(Project)-[:CONSUMES]-&amp;gt;(Endpoint)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact Simulation&lt;/strong&gt;: Changing an API handler in your backend repo immediately reveals every frontend or sibling microservice on your machine exposed to breaking changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Dual-Store Context Fusion (&lt;code&gt;type: "all"&lt;/code&gt;) &amp;amp; Feedback Loop (&lt;code&gt;POST /feedback&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Most AI memory tools isolate static docs from chat histories. Tracewood leverages HydraDB’s unified &lt;strong&gt;Dual-Store Retrieval&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified Querying&lt;/strong&gt;: Queries Knowledge and Memories in a single request (&lt;code&gt;POST /query&lt;/code&gt; with &lt;code&gt;type: "all"&lt;/code&gt; and &lt;code&gt;mode: "thinking"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Feedback Signal Tuning&lt;/strong&gt;: When developers select search results or accept agent recommendations in Tracewood, we execute &lt;code&gt;client.feedback.submit()&lt;/code&gt; with explicit ratings (&lt;code&gt;positive&lt;/code&gt;/&lt;code&gt;negative&lt;/code&gt;) and &lt;code&gt;source: "agent"&lt;/code&gt; to dynamically tune retrieval rankings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Full 10-Agent Telemetry Scanner &amp;amp; Settings Overhaul
&lt;/h2&gt;

&lt;p&gt;We updated our agent detection engine to check candidate paths across macOS/Linux, auto-detecting all 10 major AI coding harnesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code CLI&lt;/strong&gt; (&lt;code&gt;~/.claude&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemini CLI / Antigravity&lt;/strong&gt; (&lt;code&gt;~/.gemini&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor IDE&lt;/strong&gt; (&lt;code&gt;~/.cursor&lt;/code&gt; &amp;amp; &lt;code&gt;Application Support/Cursor&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Codex CLI&lt;/strong&gt; (&lt;code&gt;~/.codex&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot&lt;/strong&gt; (&lt;code&gt;~/.copilot&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windsurf / Codeium&lt;/strong&gt; (&lt;code&gt;~/.codeium&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cline / Roo Code&lt;/strong&gt; (&lt;code&gt;VS Code globalStorage&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aider CLI&lt;/strong&gt; (&lt;code&gt;~/.aider&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continue.dev&lt;/strong&gt; (&lt;code&gt;~/.continue&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pi / CommandCode / Factory&lt;/strong&gt; (&lt;code&gt;~/.pi&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: Building Software as a Living 3D Garden
&lt;/h2&gt;

&lt;p&gt;Tracewood demonstrates that the future of multi-agent software development isn't flat text logs or isolated chat boxes—it's an interconnected, queryable context graph.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;HydraDB at its core&lt;/strong&gt;, Tracewood brings transparency, supply-chain safety, and long-term memory to AI pair-programming.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/bynQDSrqkok"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/229-tracewood" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/229-tracewood&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Building ScopedAgent: A Production Template for Knowledge-Scoped AI Agents</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Thu, 20 Aug 2026 18:32:31 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-scopedagent-a-production-template-for-knowledge-scoped-ai-agents-n8n</link>
      <guid>https://dev.to/harishkotra/building-scopedagent-a-production-template-for-knowledge-scoped-ai-agents-n8n</guid>
      <description>&lt;p&gt;&lt;em&gt;How to build production-ready customer support AI agents in Next.js 14 &amp;amp; LangChain.js that never hallucinate off-topic code, respect strict knowledge boundaries, and log out-of-scope user attempts across OpenAI, Anthropic, Gemini, Ollama, LM Studio, and custom endpoints.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Generic RAG Starter Kits
&lt;/h2&gt;

&lt;p&gt;Standard Retrieval-Augmented Generation (RAG) starter kits often suffer from a major flaw: &lt;strong&gt;unbounded scope&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;When a user asks a support bot for your SaaS product to &lt;em&gt;"write a Python script to scrape a website"&lt;/em&gt; or &lt;em&gt;"explain binary search trees"&lt;/em&gt;, traditional RAG systems still query the vector database and try their best to answer—often hallucinating or generating arbitrary code that has nothing to do with your company.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ScopedAgent&lt;/strong&gt; addresses this problem by introducing a &lt;strong&gt;Pre-Retrieval Scope Guard Layer&lt;/strong&gt; before vector search or document ingestion occurs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Architecture &amp;amp; Flow
&lt;/h2&gt;

&lt;p&gt;Every prompt follows this strict workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdf41yjn5h5iriw70byfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdf41yjn5h5iriw70byfs.png" alt="Core Architecture &amp;amp; Flow" width="800" height="1169"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Scope Guard Classifier (&lt;code&gt;lib/agent/scope-guard.ts&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Before touching ChromaDB or document stores, the query is pre-classified into allowed vs. refused categories (&lt;code&gt;writing_code&lt;/code&gt;, &lt;code&gt;generic_technical&lt;/code&gt;, &lt;code&gt;off_topic&lt;/code&gt;, &lt;code&gt;jailbreak_attempt&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ClassificationSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in_scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writing_code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generic_technical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;competitor_analysis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;personal_advice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off_topic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jailbreak_attempt&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="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;sanitized_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classifyQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseChatModel&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Classification&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;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SCOPE_GUARD_PROMPT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;withStructuredOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ClassificationSchema&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;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowed_topics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowed_topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;None&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Multi-Provider AI Inference Engine (&lt;code&gt;lib/providers/index.ts&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Developers and end users can pick their preferred AI model directly in the UI header or configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt;: &lt;code&gt;gpt-4o&lt;/code&gt;, &lt;code&gt;gpt-4o-mini&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic&lt;/strong&gt;: &lt;code&gt;claude-3-5-sonnet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemini API&lt;/strong&gt;: &lt;code&gt;gemini-1.5-flash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local AI&lt;/strong&gt;: Ollama (&lt;code&gt;http://localhost:11434&lt;/code&gt;), LM Studio (&lt;code&gt;http://localhost:1234&lt;/code&gt;), and custom OpenAI-compatible endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Single-File Developer Configuration (&lt;code&gt;scopedagent.config.ts&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Developers only edit one configuration file to define their agent's scope rules, company identity, and custom refusal responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Aria&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Northpeak Software&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Customer Support Assistant&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="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allowed_topics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product features and how-to questions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pricing and plan comparisons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billing and refund policy&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="na"&gt;refused_categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writing_code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generic_technical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off_topic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jailbreak_attempt&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="na"&gt;confidence_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fallback_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I don't have enough info on that. Contact support@northpeak.io.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Admin Analytics Dashboard (&lt;code&gt;/admin&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Every out-of-scope attempt is persisted to SQLite (&lt;code&gt;lib/db/queries.ts&lt;/code&gt;), giving developers real-time metrics on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top refused categories (Code Gen vs. Competitor Analysis vs. Off-topic).&lt;/li&gt;
&lt;li&gt;Raw prompt logs and classification confidence scores.&lt;/li&gt;
&lt;li&gt;Document re-indexing controls to update knowledge on the fly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Out &amp;amp; Contribute
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/harishkotra/scopedagent" rel="noopener noreferrer"&gt;https://github.com/harishkotra/scopedagent&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
  </channel>
</rss>
