<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss-styles.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>Nayana’s Blog</title>
    <description/>
    <link>https://nayanachandrika99.github.io/</link>
    <lastBuildDate>Thu, 09 Jul 2026 03:50:37 GMT</lastBuildDate>
    <author>Nayana Gadde</author>
    <item>
      <title>A Multimodal Stochastic Medical Tutor: A Systems Build Story</title>
      <link>https://nayanachandrika99.github.io/posts/a-multimodal-stochastic-medical-tutor-a-systems-build-story/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/a-multimodal-stochastic-medical-tutor-a-systems-build-story/</guid>
      <description>A multimodal medical tutor that uses a deterministic runtime to orchestrate stochastic controller decisions—combining image tools, retrieval, and student modeling—improved through SFT, RL, and playbook optimization.</description>
      <pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 13:30:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/a-multimodal-stochastic-medical-tutor-a-systems-build-story/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;A multimodal medical tutor that uses a deterministic runtime to orchestrate stochastic controller decisions&amp;#x2014;combining image tools, retrieval, and student modeling&amp;#x2014;improved through SFT, RL, and playbook optimization.&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; January 12, 2026 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://drive.google.com/file/d/1UdihdepyYgDsWbvpIC3nq53O1fcmKn_n/view?usp=sharing&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;A Systems Build Story&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x2554;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2557;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                              &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;     &amp;#x2502;  Image  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; Retrieve &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; Decide  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; Tutor/Answer &amp;#x2502;     &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;     &amp;#x2502;    +    &amp;#x2502;      &amp;#x2502;  (text   &amp;#x2502;      &amp;#x2502; (tools, &amp;#x2502;      &amp;#x2502;   (probes,   &amp;#x2502;     &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;     &amp;#x2502;Question &amp;#x2502;      &amp;#x2502; +image)  &amp;#x2502;      &amp;#x2502; actions)&amp;#x2502;      &amp;#x2502;hints,answer) &amp;#x2502;     &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                             &amp;#x2502;                                &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                             &amp;#x25bc;                                &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                          &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                      &amp;#x2502;  Tool Exec &amp;#x2502;                          &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                      &amp;#x2502;(zoom,seg,  &amp;#x2502;                          &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                      &amp;#x2502; retrieve&amp;#x2026;) &amp;#x2502;                          &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                          &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                              &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;               A   M U L T I M O D A L   M E D I C A L   T U T O R.           &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                              &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x255a;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x255d;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;TL;DR&lt;/h3&gt;&lt;p&gt;I built a multimodal medical tutoring system that reads medical images, retrieves supporting knowledge from text and image databases, uses visual tools to gather evidence, teaches Socratically with calibrated probes and hints, and produces a final answer only when the student is ready.&lt;/p&gt;&lt;p&gt;The system is stochastic by design. At each decision point, the tutor samples actions under uncertainty&amp;#x2014;which tool to use, which question to ask, whether to reveal the answer. The stochasticity lives in the controller&amp;apos;s sampling; the surrounding runtime is deterministic. Those decisions are improved through three complementary mechanisms: SFT for reliable action formatting, RL for tool-use behavior, and ACE-style playbook optimization for fast iteration on recurring mistakes.&lt;/p&gt;&lt;p&gt;This is a long-form technical build story. I wrote it for engineers and recruiters who want to understand not just what the system does, but how it was designed and why.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The problem I actually solved&lt;/h3&gt;&lt;p&gt;&amp;quot;Medical VQA&amp;quot; and &amp;quot;medical tutoring&amp;quot; look similar on the surface. Both start with an image and a question. But tutoring adds requirements that most VQA systems never address.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   MEDICAL VQA                         MEDICAL TUTORING                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;                         &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Image + Question                    Image + Question                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;         &amp;#x2502;                                   &amp;#x2502;                               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;         &amp;#x25bc;                                   &amp;#x25bc;                               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                        &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;  Model   &amp;#x2502;                        &amp;#x2502;  Ask student &amp;#x2502;&amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x2510;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                        &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;        &amp;#x2502;                                     &amp;#x2502;           &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;        &amp;#x25bc;                                     &amp;#x25bc;           &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                        &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;  Answer  &amp;#x2502;                        &amp;#x2502;  Assess      &amp;#x2502;   &amp;#x2502; (loop until      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                        &amp;#x2502;  response    &amp;#x2502;   &amp;#x2502;  ready)          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                              &amp;#x2502;           &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                              &amp;#x25bc;           &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                       &amp;#x2502; Hint / Probe &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                       &amp;#x2502; / Reveal     &amp;#x2502;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;A tutor has to ask what the student thinks before explaining. It has to escalate hints based on performance rather than revealing immediately. It has to use tools when the image is ambiguous instead of guessing. It has to use retrieval when medical knowledge is needed, and it has to separate observation from diagnosis. Most importantly, every decision the system makes should be inspectable&amp;#x2014;which tool was called, what it returned, and how it changed the next step.&lt;/p&gt;&lt;p&gt;I built a system that treats tutoring as a control problem, not a single model call. The goal wasn&amp;apos;t just accuracy. I wanted a system that is consistent in how it reasons, inspectable when it fails, and trainable when I want to change behavior. This is why the final system looks more like a small operating system than a prompt-and-model demo.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The system at a glance&lt;/h3&gt;&lt;p&gt;The system has two interacting loops. At runtime, it behaves like a state machine that iterates until it produces an instructional response&amp;#x2014;a probe, a hint, a micro-lesson&amp;#x2014;or a final answer. Offline, multiple improvement channels operate on the same underlying runtime traces: SFT trains the controller to emit structured tutoring actions reliably, RL trains tool-use behavior with intermediate rewards, and ACE evolves a playbook of behavioral rules to reduce failure modes without retraining.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x2554;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2557;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                              ONLINE RUNTIME                                   &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2560;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2563;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                               &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2502; Pre-Retrieve  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;   Decide   &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; Tool Exec &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; Answer/Respond &amp;#x2502;   &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2502; (gather ctx)  &amp;#x2502;    &amp;#x2502; (policy)   &amp;#x2502;    &amp;#x2502; (1 tool)  &amp;#x2502;    &amp;#x2502;   (terminal)   &amp;#x2502;   &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                             &amp;#x2502;                 &amp;#x2502;                               &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                             &amp;#x2502;&amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                               &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                             &amp;#x2502;    (loop back)                                  &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                               &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2560;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2563;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;           &amp;#x25b2;                                            &amp;#x25b2;                      &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;           &amp;#x2502;  traces + state snapshots                  &amp;#x2502;  playbook rules      &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;           &amp;#x2502;                                            &amp;#x2502;                      &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2560;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2563;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                            OFFLINE IMPROVEMENT                                &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2560;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2563;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                               &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2502;      SFT        &amp;#x2502;  &amp;#x2502;       RL        &amp;#x2502;  &amp;#x2502;           ACE               &amp;#x2502;    &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2502; (action format, &amp;#x2502;  &amp;#x2502; (tool-use       &amp;#x2502;  &amp;#x2502; (playbook rules,            &amp;#x2502;    &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2502;  reliability)   &amp;#x2502;  &amp;#x2502;  rewards)       &amp;#x2502;  &amp;#x2502;  fast iteration)            &amp;#x2502;    &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2551;                                                                               &amp;#x2551;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x255a;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x255d;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This separation matters because it keeps the runtime stable, keeps training focused, and makes improvements measurable. The runtime never changes during training&amp;#x2014;only the policies that run inside it.&lt;/p&gt;&lt;p&gt;When I say &amp;quot;stochastic,&amp;quot; I mean something specific. At decision points, the controller can sample different actions under uncertainty: tool versus probe versus hint versus answer. But the runtime around it is deterministic and constrained&amp;#x2014;schemas, gates, routing, budgets. This combination gives you both creativity when the model needs it and predictability when the system needs it.&lt;/p&gt;&lt;p&gt;Every design decision in this system is downstream of a few constraints I couldn&amp;apos;t ignore. Raw retrieval and raw tool outputs blow up your prompt, so I needed a context budget. A tutor can&amp;apos;t take two minutes per turn and still feel interactive, so I needed a latency budget. Tool-using systems fail in dozens of new ways&amp;#x2014;parsing errors, missing dependencies, wrong modalities, empty retrieval, repeated tools&amp;#x2014;so I needed explicit failure handling. If I can&amp;apos;t replay a run, I can&amp;apos;t improve it reliably, so I needed reproducibility. And I needed hard constraints that prevent &amp;quot;answer dumping&amp;quot; even when the policy is stochastic.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The backbone: one state object, one traceable story&lt;/h3&gt;&lt;p&gt;The most important decision I made was that everything flows through one explicit state object.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                              AgentState                                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  messages: List[Message]          &amp;#x25c0;&amp;#x2500;&amp;#x2500; conversation history (multimodal)     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  image: ImageRef | None           &amp;#x25c0;&amp;#x2500;&amp;#x2500; current image reference               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; RETRIEVAL                                                             &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   retrieval_hits: List[RetrievalItem]    &amp;#x25c0;&amp;#x2500;&amp;#x2500; full structured results  &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   retrieval_summary: str                 &amp;#x25c0;&amp;#x2500;&amp;#x2500; compact for prompts      &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; TOOLS                                                                 &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   tool_calls: List[ToolCall]             &amp;#x25c0;&amp;#x2500;&amp;#x2500; what was requested       &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   tool_results: List[ToolResult]         &amp;#x25c0;&amp;#x2500;&amp;#x2500; what came back           &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   artifact_refs: List[ArtifactRef]       &amp;#x25c0;&amp;#x2500;&amp;#x2500; persisted outputs        &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; TUTORING                                                              &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   student_profile: StudentProfile        &amp;#x25c0;&amp;#x2500;&amp;#x2500; novice/medium/expert     &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   session_state: SessionState            &amp;#x25c0;&amp;#x2500;&amp;#x2500; attempts, performance    &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   hint_level: int                        &amp;#x25c0;&amp;#x2500;&amp;#x2500; escalation tracking      &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  errors: List[str]                &amp;#x25c0;&amp;#x2500;&amp;#x2500; captured, not hidden                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  route: str                       &amp;#x25c0;&amp;#x2500;&amp;#x2500; deterministic control flow            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If you want a tool-using system to be reliable, you need to be able to answer questions like: What did the model see at this step? Why did it call this tool? What did the tool return? What changed after that? So the runtime carries a single state object with conversation messages, the image reference, retrieval hits and a compact retrieval summary, tool calls and tool results, artifact references for persisted outputs, errors captured rather than hidden, tutoring fields for the student model and session progress, and a routing field that makes control flow deterministic.&lt;/p&gt;&lt;p&gt;This design has two practical benefits. First, I can debug failures as data rather than intuition. Second, I can run offline loops&amp;#x2014;SFT, RL, ACE&amp;#x2014;that learn from the same state and trace structure.&lt;/p&gt;&lt;p&gt;In early iterations, I tried the &amp;quot;just append tool results to the prompt&amp;quot; approach. It breaks in predictable ways. Retrieval results are verbose, so the prompt bloats. Tool outputs are heterogeneous, so formatting becomes inconsistent. The model begins to lose the thread of the task&amp;#x2014;a phenomenon often called context rot. Making state explicit and structured was the turning point that made the system scalable.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The runtime loop&lt;/h3&gt;&lt;p&gt;The runtime is intentionally simple: pre-retrieve to gather context, decide to choose the next action, execute exactly one tool if requested and persist outputs, then either teach (probe, hint, micro-lesson) or produce a final answer.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; init_state(user_input, image)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;state.retrieval &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pre_retrieve(state)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; True&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    action &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; decide(state)          &lt;/span&gt;&lt;span&gt;# stochastic: model-driven policy&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; action.type &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;TOOL&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tool_exec(action, state)   &lt;/span&gt;&lt;span&gt;# deterministic tool handler&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.with_tool_result(action, result)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        continue&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; action.type &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;TUTOR&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.with_tutor_message(apply_gates(action, state))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        break&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; action.type &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;ANSWER&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.with_answer(run_solver(state))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        break&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Where the &amp;quot;agent&amp;quot; feeling comes from is the decide step&amp;#x2014;model-driven policy&amp;#x2014;but the system remains stable because routing is deterministic. Two details matter in practice: one tool per turn, which prevents tool spam, keeps traces readable, and makes the policy learnable; and explicit termination, meaning every run ends with a coherent &amp;quot;what happens next.&amp;quot;&lt;/p&gt;&lt;p&gt;Without hard boundaries, a stochastic controller can get into loops. Zoom, zoom, zoom with slightly different bounding boxes each time. Retrieve, retrieve with the same query returning the same hits. Web search after web search because it &amp;quot;feels&amp;quot; like progress. The runtime constraints are not just for neatness&amp;#x2014;they prevent the most common agent failure: doing busy work until the budget runs out.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Why I split &amp;quot;controller&amp;quot; from &amp;quot;solver&amp;quot;&lt;/h3&gt;&lt;p&gt;I treat &amp;quot;what to do next&amp;quot; as a fundamentally different problem from &amp;quot;produce the best medical answer.&amp;quot;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                         CONTROLLER(S)                               &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   Input: state (image, history, retrieval, student signals)         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   Output: next action                                               &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     &amp;#x2022; TOOL: { name, args }                                          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     &amp;#x2022; TUTOR: { type: probe | hint | microlesson | ... }             &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     &amp;#x2022; ANSWER: trigger solver                                        &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   Trained via: SFT (format), RL (behavior), ACE (rules)             &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                              &amp;#x2502;                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                              &amp;#x2502; &amp;quot;ready to answer&amp;quot;                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                              &amp;#x25bc;                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                           SOLVER                                    &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   Input: state (image, retrieval, tool artifacts, question)         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   Output: final medical answer                                      &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     &amp;#x2022; answer text                                                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     &amp;#x2022; confidence                                                    &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     &amp;#x2022; OR: &amp;quot;insufficient evidence&amp;quot; (triggers retry path)             &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   Model: MedGemma (vision-language)                                 &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The controller decides what to do next&amp;#x2014;tool call versus tutor action versus answer. The solver produces the final medical answer once enough evidence exists. This split buys me faster iteration on policy behavior, cleaner debugging since I can distinguish policy errors from solver errors, and the ability to improve tool-use without destabilizing answer generation.&lt;/p&gt;&lt;p&gt;Tutoring systems have an uncomfortable truth: sometimes the right output is &amp;quot;I don&amp;apos;t have enough evidence.&amp;quot; I explicitly preserve that as a valid outcome and build a retry/repair path around it. When a solver is forced to always answer, it will hallucinate. When it can refuse, the rest of the system can respond by collecting more evidence.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Tools: evidence gathering with artifacts&lt;/h3&gt;&lt;p&gt;In tool-using systems, the failure mode is almost always the same. Tools become just strings in a prompt. Results get inlined as huge blobs. Context collapses. The &amp;quot;agent&amp;quot; starts hallucinating tool outputs.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                            TOOL REGISTRY                                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;    zoom     &amp;#x2502;  &amp;#x2502;   enhance   &amp;#x2502;  &amp;#x2502;   segment   &amp;#x2502;  &amp;#x2502;   image_findings    &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; bbox crop   &amp;#x2502;  &amp;#x2502; upscale     &amp;#x2502;  &amp;#x2502; MedSAM2     &amp;#x2502;  &amp;#x2502; describe features   &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; + padding   &amp;#x2502;  &amp;#x2502; + sharpen   &amp;#x2502;  &amp;#x2502; mask gen    &amp;#x2502;  &amp;#x2502; in region           &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  retrieve   &amp;#x2502;  &amp;#x2502; web_browser &amp;#x2502;  &amp;#x2502;              ocr                      &amp;#x2502;&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; &amp;#x2502;&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; query local &amp;#x2502;  &amp;#x2502; fallback    &amp;#x2502;  &amp;#x2502; extract text from image regions       &amp;#x2502;&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; database    &amp;#x2502;  &amp;#x2502; web search  &amp;#x2502;  &amp;#x2502;                                       &amp;#x2502;&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                            TOOL EXECUTION FLOW                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   ToolCall                        ToolResult                                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;               &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; name: &amp;quot;zoom&amp;quot; &amp;#x2502;               &amp;#x2502; ok: true                             &amp;#x2502;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; args: {      &amp;#x2502;    &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;    &amp;#x2502; summary: &amp;quot;Zoomed into upper-left&amp;#x2026;&amp;quot;   &amp;#x2502;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   bbox_2d,   &amp;#x2502;   (execute)   &amp;#x2502; artifact_refs: [                     &amp;#x2502;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   padding    &amp;#x2502;               &amp;#x2502;   &amp;quot;artifact://zoom_abc123.png&amp;quot;       &amp;#x2502;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; }            &amp;#x2502;               &amp;#x2502; ]                                    &amp;#x2502;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;               &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                          &amp;#x2502;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                          &amp;#x25bc;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                 &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                 &amp;#x2502; ARTIFACT STORE  &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                 &amp;#x2502; (images, masks, &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                 &amp;#x2502;  text files)    &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                 &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;I built tools as first-class operations. Tools have schemas. They return structured outputs and a human-readable summary. Tool outputs are persisted as artifacts&amp;#x2014;images, masks, text&amp;#x2014;referenced by ID or path instead of being dumped into the prompt.&lt;/p&gt;&lt;p&gt;The design principle is simple: summaries are for prompts, artifacts are for truth. The summary goes into the controller&amp;apos;s context; the artifact gets persisted for humans and offline evaluators. This avoids a classic failure mode where a system becomes &amp;quot;prompt-native&amp;quot; and loses its grounding in actual data.&lt;/p&gt;&lt;p&gt;Tools fail for boring reasons: no image present, missing optional dependency, malformed bounding box, empty crop after clamping, retrieval index unavailable, network keys missing for web search. These boring failures destroy a demo if you don&amp;apos;t capture them cleanly. I made tool failure a first-class output so the system can log it, surface it, and recover without crashing the run.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Retrieval: multimodal fusion under a context budget&lt;/h3&gt;&lt;p&gt;Retrieval in this system has two jobs: return evidence in the form of text snippets and image provenance, and produce a compact, controller-friendly representation.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                       MULTIMODAL RETRIEVAL PIPELINE                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                           &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                           &amp;#x2502;    Query      &amp;#x2502;                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                           &amp;#x2502; (text+image)  &amp;#x2502;                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                           &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                   &amp;#x2502;                                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x25bc;              &amp;#x25bc;              &amp;#x25bc;                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;             &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;             &amp;#x2502;   BM25    &amp;#x2502;  &amp;#x2502;   Text    &amp;#x2502;  &amp;#x2502;   Image   &amp;#x2502;                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;             &amp;#x2502;  (sparse) &amp;#x2502;  &amp;#x2502;  Vectors  &amp;#x2502;  &amp;#x2502;  Vectors  &amp;#x2502;                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;             &amp;#x2502;           &amp;#x2502;  &amp;#x2502;  (dense)  &amp;#x2502;  &amp;#x2502;  (dense)  &amp;#x2502;                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;             &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   &amp;#x2502;              &amp;#x2502;              &amp;#x2502;                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   &amp;#x2502;   ranked     &amp;#x2502;   ranked     &amp;#x2502;   ranked                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   &amp;#x2502;   results    &amp;#x2502;   results    &amp;#x2502;   results                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   &amp;#x2502;              &amp;#x2502;              &amp;#x2502;                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                  &amp;#x25bc;                                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x2502;   RRF Fusion    &amp;#x2502;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x2502; (rank-based     &amp;#x2502;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x2502;  aggregation)   &amp;#x2502;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                 &amp;#x2502;                                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2534;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x25bc;                         &amp;#x25bc;                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;          &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;          &amp;#x2502; retrieval_hits  &amp;#x2502;      &amp;#x2502;   retrieval_summary     &amp;#x2502;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;          &amp;#x2502; (full structs)  &amp;#x2502;      &amp;#x2502;   (compact for prompt)  &amp;#x2502;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;          &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;I use multimodal retrieval: text retrieval with BM25 plus dense vectors, image retrieval with dense vectors, fused via a rank-based aggregation strategy so one modality doesn&amp;apos;t steamroll the other. Crucially, the controller does not get everything. It gets top hits as structured data and a short summary string that fits comfortably in a prompt.&lt;/p&gt;&lt;p&gt;This is a real engineering constraint. If you dump raw retrieved documents into the prompt, you will destroy the agent&amp;apos;s ability to think.&lt;/p&gt;&lt;p&gt;I&amp;apos;m strict about retrieval order: prefer the local database before web, use web only when the database is thin or missing key facts. This policy shows up in three places: in the prompt policy telling the model what to do, in runtime constraints defining what tools exist and when they&amp;apos;re appropriate, and in RL shaping that penalizes web calls when database confidence is high. The same principle is reinforced by multiple layers rather than relying on a single prompt instruction.&lt;/p&gt;&lt;p&gt;Naively merging text and image retrieval can backfire. Dense similarity can return superficially similar but clinically irrelevant items. BM25 can overweight short keyword overlaps. Image retrieval can &amp;quot;look right&amp;quot; but be wrong in context. Fusion helped, but the bigger win was controlling what the controller sees: a small number of hits plus a compact summary that doesn&amp;apos;t drown out the question.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Student modeling: personalization without pretending I solved cognition&lt;/h3&gt;&lt;p&gt;I intentionally avoided unsupported &amp;quot;we solved knowledge tracing&amp;quot; claims.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                            STUDENT MODELING                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                       StudentProfile                                &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   level: novice | medium | expert                                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   (set at session start, influences scaffolding depth)              &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                       SessionState                                  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   attempt_count: int           &amp;#x25c0;&amp;#x2500;&amp;#x2500; how many tries so far            &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   performance: correct | partial | wrong | none                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   misconceptions: List[str]    &amp;#x25c0;&amp;#x2500;&amp;#x2500; detected errors in reasoning     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   consecutive_wrong: int       &amp;#x25c0;&amp;#x2500;&amp;#x2500; escalation trigger               &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                    Tutoring State Machine                           &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   hint_level: 0 &amp;#x2500;&amp;#x2500;&amp;#x25b6; 1 &amp;#x2500;&amp;#x2500;&amp;#x25b6; 2 &amp;#x2500;&amp;#x2500;&amp;#x25b6; 3                                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                  &amp;#x2502;                                  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                  &amp;#x25bc;                                  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                            (reveal allowed)                         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   reveal_gate: closed until attempt_count &amp;gt; 0 AND                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                 (performance == correct OR hint_level == max)       &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;My student modeling is built around what a system can actually observe during a session: a student profile indicating novice, medium, or expert level; attempt count; performance signals like correct, partial, or wrong; misconceptions when detectable; and a tutoring state machine that tracks hint level escalation and reveal gating.&lt;/p&gt;&lt;p&gt;This delivers visible behavior differences immediately. Novices get more probing and scaffolding. Experts get faster convergence and higher-level prompts. The system refuses to dump the answer on turn zero. That last point is not a prompt trick&amp;#x2014;it&amp;apos;s enforced by gates.&lt;/p&gt;&lt;p&gt;For a tutoring system, you can either build a long-horizon mastery estimator early or build a robust session-level tutor policy first. I chose the second path because it produces visible, demoable behavior quickly, it&amp;apos;s easier to verify, and it creates the scaffolding you need for knowledge tracing later: structured student signals, explicit state, traceable decisions.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Socratic tutor policy: actions, not essays&lt;/h3&gt;&lt;p&gt;Instead of generating free-form tutoring transcripts, I modeled tutoring decisions as one action per step: ASK_PROBE, HINT at levels 1 through 3, MICROLESSON, QUIZ, REQUEST_TOOL, REVEAL_ANSWER, or SAFETY_REFUSE.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         TUTOR ACTION SPACE                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  ASK_PROBE      &amp;quot;What do you observe in the image?&amp;quot;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  HINT (1-3)     Progressively more specific guidance                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  MICROLESSON    Brief teaching moment on a concept the student is missing   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  QUIZ           Follow-up question to check understanding                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  REQUEST_TOOL   &amp;quot;I need to examine this region closer&amp;quot;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  REVEAL_ANSWER  Give the answer (only after gates allow it)                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  SAFETY_REFUSE  &amp;quot;This requires clinical judgment&amp;#x2014;consult a physician&amp;quot;       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          GATING LAYER                                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Controller &amp;#x2500;&amp;#x2500;&amp;#x25b6; [proposed action] &amp;#x2500;&amp;#x2500;&amp;#x25b6; GATE CHECK &amp;#x2500;&amp;#x2500;&amp;#x25b6; [actual action]       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   REVEAL_GATE: Block reveal if attempt_count == 0                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   ASSESSMENT_GATE: Convert reveal to hint if performance != correct         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;That&amp;apos;s a design choice that pays off everywhere. It&amp;apos;s easy to validate, easy to log, easy to train with SFT, and easy to optimize with RL and ACE.&lt;/p&gt;&lt;p&gt;Even if a controller tries to reveal too early, runtime gates can override. No reveal until an attempt exists. Wrong or partial performance converts &amp;quot;reveal&amp;quot; into calibrated hints. Repeated tool calls within a single step are discouraged. This is how I guarantee the system behaves like a tutor even under stochastic policies.&lt;/p&gt;&lt;p&gt;If you&amp;apos;re tutoring from a medical image, it&amp;apos;s dangerously easy for a system to skip straight to a diagnosis or answer choice. I explicitly keep a pathway that produces objective findings without committing to a diagnosis. This supports tutoring behaviors like &amp;quot;Describe what you see first&amp;quot; or &amp;quot;Which finding most narrows the differential?&amp;quot; or &amp;quot;What would you expect next?&amp;quot; This observation-versus-diagnosis distinction is one of the most important tutor-versus-solver differences in the entire system.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;SFT: training the controller to output valid tutoring actions&lt;/h3&gt;&lt;p&gt;SFT is about reliability: consistent action formatting, fewer invalid outputs, better alignment to the action schema. The training target is not a perfect tutor transcript. The target is the next action.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                      SFT: ACTION-ONLY CONTROLLER TRAINING                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   INPUT:                                                                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; student_profile: { level: &amp;quot;novice&amp;quot; }                                &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; kc_tags: [&amp;quot;cardiac&amp;quot;, &amp;quot;imaging&amp;quot;, &amp;quot;tamponade&amp;quot;]                        &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; history: [                                                          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   { role: &amp;quot;user&amp;quot;, content: &amp;quot;What&amp;apos;s wrong with this chest X-ray?&amp;quot; }, &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   { role: &amp;quot;assistant&amp;quot;, content: &amp;quot;What features do you observe?&amp;quot; },  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   { role: &amp;quot;user&amp;quot;, content: &amp;quot;The heart looks big?&amp;quot; }                 &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; ]                                                                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; tool_summaries: [ &amp;quot;Zoomed into cardiac silhouette region&amp;quot; ]         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; assessment: { performance: &amp;quot;partial&amp;quot;, misconceptions: [] }          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   TARGET OUTPUT:                                                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; {                                                                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;quot;action&amp;quot;: &amp;quot;HINT&amp;quot;,                                                 &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;quot;level&amp;quot;: 1,                                                       &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;quot;content&amp;quot;: &amp;quot;Good observation. Can you describe the shape of       &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;              the cardiac silhouette more specifically?&amp;quot;             &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; }                                                                   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Training Method: LoRA (lightweight adaptation)                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If the runtime expects a structured action and the model outputs a 2,000-token lecture instead, you can&amp;apos;t gate or route safely. So I trained the controller on examples that include student profile, coarse knowledge-component tags, conversation history, tool summaries, student assessment signals, and the desired next tutoring action. This is the same structure used at runtime&amp;#x2014;the training distribution matches the control interface.&lt;/p&gt;&lt;p&gt;The goal was to make the controller reliably output valid structured actions that the runtime can gate and route. The format is action-only, JSON-shaped outputs with one decision per step. Training used lightweight adaptation via LoRA so I could iterate quickly.&lt;/p&gt;&lt;p&gt;What changed after SFT: fewer invalid outputs, more consistent hint calibration across levels, and less style drift into answer-dumping. The runtime became a stable interface, and the controller became something I could upgrade without rewriting the system.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;RL: training tool-use as sequential decision making&lt;/h3&gt;&lt;p&gt;Tool use is not a single decision. It&amp;apos;s a sequence: decide to zoom, observe, decide to retrieve, observe, decide to answer. If you only reward the final answer, tool learning becomes painfully sparse.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    RL: TOOL-USE AS SEQUENTIAL DECISION MAKING               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Episode: One medical case/question                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; State&amp;#x2080;  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2500;&amp;#x2500;&amp;#x2502; Action&amp;#x2080; &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2500;&amp;#x2500;&amp;#x2502; State&amp;#x2081;  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2500;&amp;#x2500;&amp;#x2502; Action&amp;#x2081; &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x25b6; ...        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; (init)  &amp;#x2502;      &amp;#x2502; (zoom)  &amp;#x2502;      &amp;#x2502; (after  &amp;#x2502;      &amp;#x2502;(retrieve)&amp;#x2502;           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2502;  zoom)  &amp;#x2502;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         &amp;#x2502;           &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;           &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         &amp;#x25bc;                                 &amp;#x25bc;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2502;Reward&amp;#x2080;  &amp;#x2502;                       &amp;#x2502;Reward&amp;#x2081;  &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2502;+0.1     &amp;#x2502;                       &amp;#x2502;+0.2     &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2502;(curious)&amp;#x2502;                       &amp;#x2502;(relevant)&amp;#x2502;           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          REWARD SHAPING                                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   CURIOSITY REWARD: +reward when image tool produces meaningfully new view  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   ANTI-REPEAT PENALTY: -penalty when same tool called with same/similar args&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   RETRIEVAL QUALITY: +reward when retrieval returns relevant evidence       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   SAFE FALLBACK: -penalty when web called AND local DB has high-conf hits   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   TERMINAL REWARD: +reward for correct answer,-penalty for incorrect/timeout&amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;So I added intermediate rewards that make tool-use learnable. Curiosity reward gives positive signal when an image tool produces a meaningfully new view. Anti-repeat penalty discourages repeated identical tool calls. Retrieval shaping rewards queries that return relevant evidence. Safe fallback shaping discourages web calls when the local database has high-confidence hits.&lt;/p&gt;&lt;p&gt;This turns tool-use learning from &amp;quot;pray the gradient finds it&amp;quot; into a tractable training signal.&lt;/p&gt;&lt;p&gt;I treated RL as an engineering problem rather than a label. One episode is one case/question. Actions are tool calls or terminal answers. Observations include the current view of the image via artifacts plus compact textual context. Termination happens when an answer is produced, the tool budget is exhausted, or there&amp;apos;s an unrecoverable parse failure.&lt;/p&gt;&lt;p&gt;What changed after RL: the planner learned to &amp;quot;look first&amp;quot; instead of guessing, retrieval became the default knowledge source, and tool sequences became shorter and more purposeful. Tool use stopped being a brittle prompt behavior and became a policy that can improve with data and rewards.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;ACE: playbooks that evolve without retraining&lt;/h3&gt;&lt;p&gt;Not every failure requires gradient updates. Many failures are policy mistakes that can be captured as explicit rules: use retrieval before web, if the image is ambiguous zoom first, don&amp;apos;t exceed one tool per turn, if you failed to parse a tool call simplify output formatting.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   ACE: AGENTIC CONTEXT ENGINEERING                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                        ACE LOOP                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                                                                     &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;#x2502;   Run    &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; Analyze  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  Curate  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  Update  &amp;#x2502;   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;#x2502; samples  &amp;#x2502;     &amp;#x2502; failures &amp;#x2502;     &amp;#x2502;   ops    &amp;#x2502;     &amp;#x2502; playbook &amp;#x2502;   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;        &amp;#x25b2;                                                  &amp;#x2502;         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;        &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;                      (next iteration)                               &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                           PLAYBOOK EXAMPLE                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   # Tool Policy Rules                                                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   1. Always use `retrieve` before `web_browser`                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   2. If the image region is ambiguous, use `zoom` first                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   3. One tool per turn maximum                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   4. If tool parsing fails, simplify output format                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   5. When evidence is insufficient, request a tool&amp;#x2014;do not guess             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;ACE gives me a clean loop: run the system on samples, analyze failures including tool failures, update a playbook, inject the playbook into controller prompts on the next run.&lt;/p&gt;&lt;p&gt;This is powerful because it is fast to iterate, transparent since the playbook is readable, and complementary to SFT and RL since it patches behavior while training catches up.&lt;/p&gt;&lt;p&gt;ACE is my &amp;quot;fast patch lane.&amp;quot; Instead of waiting for a training cycle, I can run the system on a batch, detect systematic failure modes like tool parsing errors, tool ordering mistakes, and retrieval misuse, and evolve a playbook that steers the controller away from repeating those mistakes.&lt;/p&gt;&lt;p&gt;What changed after ACE iterations: fewer tool-policy violations, better ordering with retrieve before web, and better behavior under uncertainty where the system requests a tool when evidence is unclear. It turns repeated mistakes into explicit, reviewable rules and gives the system a path to improve even when training is expensive.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Observability: if you can&amp;apos;t debug it, it&amp;apos;s not done&lt;/h3&gt;&lt;p&gt;Every time I&amp;apos;ve seen an &amp;quot;agent&amp;quot; fail in the wild, the root cause is the same: nobody can reconstruct what happened.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          OBSERVABILITY STACK                                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                              Runtime Execution                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                    &amp;#x2502;                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x25bc;                       &amp;#x25bc;                       &amp;#x25bc;                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;  Trace Events   &amp;#x2502;    &amp;#x2502;   Tool Cards    &amp;#x2502;    &amp;#x2502;   Artifacts     &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; &amp;#x2022; step_id       &amp;#x2502;    &amp;#x2502; &amp;#x2022; tool_name     &amp;#x2502;    &amp;#x2502; &amp;#x2022; artifact_id   &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; &amp;#x2022; state_before  &amp;#x2502;    &amp;#x2502; &amp;#x2022; arguments     &amp;#x2502;    &amp;#x2502; &amp;#x2022; type          &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; &amp;#x2022; action        &amp;#x2502;    &amp;#x2502; &amp;#x2022; summary       &amp;#x2502;    &amp;#x2502; &amp;#x2022; path          &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; &amp;#x2022; observation   &amp;#x2502;    &amp;#x2502; &amp;#x2022; ok/error      &amp;#x2502;    &amp;#x2502; &amp;#x2022; metadata      &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; &amp;#x2022; state_after   &amp;#x2502;    &amp;#x2502; &amp;#x2022; duration_ms   &amp;#x2502;    &amp;#x2502;                 &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; &amp;#x2022; timestamp     &amp;#x2502;    &amp;#x2502;                 &amp;#x2502;    &amp;#x2502;                 &amp;#x2502;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x2502;                      &amp;#x2502;                      &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                   &amp;#x25bc;                                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x2502;     Trace Store     &amp;#x2502;                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                   &amp;#x2502;                                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x25bc;                      &amp;#x25bc;                      &amp;#x25bc;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;  Deterministic  &amp;#x2502;   &amp;#x2502;     Audit       &amp;#x2502;   &amp;#x2502;  Targeted Improvement   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;     Replay      &amp;#x2502;   &amp;#x2502;  (provenance)   &amp;#x2502;   &amp;#x2502;  (feed into SFT/RL/ACE) &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;So I built the system to emit per-step trace events with state before and after, action, and observation. It emits tool cards with arguments, summary, and errors. It emits retrieval hits. And it persists artifacts like images, masks, and text outputs.&lt;/p&gt;&lt;p&gt;This makes it possible to replay failures deterministically, audit whether a claim is evidence-backed, and do targeted improvements instead of random prompt tweaks.&lt;/p&gt;&lt;p&gt;The most important debugging feature is replayability. When an agent fails, there are always two questions: what happened, and what should change. Replayability answers the first question with certainty. And once you can replay, you can build the second half of the system: targeted improvements through SFT, RL shaping, or ACE playbook updates.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The decisions that shaped the system&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;One tool per turn.&lt;/strong&gt; It&amp;apos;s a constraint that makes everything else possible. It bounds latency. It bounds context. It makes policy learning easier. And it keeps traces readable.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Controller is not solver.&lt;/strong&gt; This is what lets the system be both a stable medical answerer and a continuously improving tutor. I can iterate on policy behavior without destabilizing answer generation.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Student modeling is session-grounded.&lt;/strong&gt; I&amp;apos;m modeling what I can observe reliably in-session: attempts, correctness, misconceptions when detectable, plus profile-based priors. It&amp;apos;s enough to produce observable personalization without over-claiming.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Optimize with multiple knobs.&lt;/strong&gt; I didn&amp;apos;t bet everything on one method. SFT makes outputs reliable. RL makes tool-use behavior learnable. ACE patches recurring mistakes quickly. Together, they form a robust improvement stack.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The training recipe&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         THE BUILD RECIPE                                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Step A          Step B          Step C          Step D                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;Baseline&amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; State &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;Retriev&amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; Tools &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;        &amp;#x2502;       &amp;#x2502;+Traces&amp;#x2502;       &amp;#x2502;  al   &amp;#x2502;       &amp;#x2502;+Artif &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;      &amp;#x2502;               &amp;#x2502;               &amp;#x2502;               &amp;#x2502;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;      &amp;#x25bc;               &amp;#x25bc;               &amp;#x25bc;               &amp;#x25bc;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;quot;Can we        &amp;quot;Can we        &amp;quot;Can we        &amp;quot;Can we                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   measure?&amp;quot;      debug?&amp;quot;        ground?&amp;quot;       inspect?&amp;quot;                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Step E          Step F          Step G          Step H                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;Student&amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  SFT  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  RL   &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  ACE  &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;Model+ &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; Gates &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;       &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;      &amp;#x2502;               &amp;#x2502;               &amp;#x2502;               &amp;#x2502;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;      &amp;#x25bc;               &amp;#x25bc;               &amp;#x25bc;               &amp;#x25bc;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;quot;Can we        &amp;quot;Is output      &amp;quot;Can policy    &amp;quot;Can we fix                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   tutor?&amp;quot;        reliable?&amp;quot;      improve?&amp;quot;      fast?&amp;quot;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   Core Principle: Make the runtime a stable interface first&amp;#x2014;then            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   optimize behavior against that interface.                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If I had to compress the build into one principle, it would be this: make the runtime a stable interface first, then optimize behavior against that interface.&lt;/p&gt;&lt;p&gt;Most agent projects invert this. They start by tuning prompts and policies, then add tooling later. That works for a demo, but it collapses once you add multiple tools, retrieval, tutoring behaviors, and long multi-turn traces. I ended up with a recipe that deliberately moves in the opposite direction.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step A: establish a measurable baseline.&lt;/strong&gt; Before adding tools or retrieval, I started with the simplest solvable loop: image plus question to answer. This isn&amp;apos;t because it&amp;apos;s the end goal. It&amp;apos;s because you need a control group. Once you have a baseline, you can answer questions like: did retrieval actually help, or just add latency? Did tools improve evidence gathering, or just increase failure surface? Did tutoring gates improve pedagogy, or just prevent answers?&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step B: define the system boundary as explicit state plus traces.&lt;/strong&gt; The next step is not &amp;quot;add more capability.&amp;quot; It&amp;apos;s &amp;quot;make behavior observable.&amp;quot; I converted the runtime into an explicit state machine where every step has a before-state, an action, an observation, and an after-state. This is the point where the project stops being prompt engineering and becomes systems engineering. Once traces exist, failures become data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step C: introduce retrieval as a first-class dependency.&lt;/strong&gt; Retrieval adds value, but it also adds a context budget problem. The key pattern that made retrieval usable was storing full hits as structured evidence but injecting only a compact summary into controller prompts. This keeps the controller thinking on the actual question rather than being drowned in text.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step D: add tools with artifact discipline.&lt;/strong&gt; Tools are only useful if they&amp;apos;re trustworthy. The design that worked was tools returning a short summary for prompts and persisting outputs as artifacts. Prompts stay small. Humans can audit what happened. Offline loops can score behavior without re-running everything.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step E: turn the solver into a tutor.&lt;/strong&gt; At this point, the system can answer. But tutoring requires control. I treated student modeling as session-grounded signals: student profile, attempt count, performance grade, misconceptions when detectable, and tutoring state for hint escalation. Then I enforced pedagogy with hard gates: don&amp;apos;t reveal on turn zero, wrong or partial turns become hints and probes, and objective findings can be separated from diagnosis.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step F: SFT for reliability.&lt;/strong&gt; Once the runtime interface is stable, SFT becomes straightforward. The model is trained to output the next structured action, conditioned on the same information the runtime uses. The effect is not &amp;quot;the model got smarter.&amp;quot; The effect is fewer invalid actions, less formatting drift, and more predictable control flow.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step G: RL for behavior.&lt;/strong&gt; Tool use is sequential, and terminal-only rewards are too sparse. The recipe that worked was adding step shaping rewards that encode the behaviors the system needs: curiosity reward when visual tools create a new informative view, penalties for repeats and tool spam, reward retrieval when it produces relevant evidence, penalize web usage when local retrieval is clearly sufficient.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step H: ACE for fast iteration.&lt;/strong&gt; Even with training, some failures are not model skill problems&amp;#x2014;they&amp;apos;re policy mistakes that recur. Ordering mistakes like web before retrieval. Repeated formatting failures. Avoidable tool misuse. ACE turns these into explicit, reviewable rules that get injected into the controller context.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Failures that forced the architecture&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Context collapse.&lt;/strong&gt; The model forgets the question after large retrieval or tool outputs, or starts repeating generic answers. The fix: compact summaries for prompts, artifacts for everything else, and strict budgets like one tool per turn.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Tool spam and busy work.&lt;/strong&gt; Multiple tools called without changing the outcome. Repeating the same action with minor variations. The fix: bounded tool calls, repeat penalties in RL, and routing that forces progress.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Brittle formatting.&lt;/strong&gt; Malformed tool calls. Tool outputs that can&amp;apos;t be parsed. Controllers drifting into unstructured text. The fix: action schemas, action-only training targets, and system-level gates that sanitize unsafe decisions.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Answer dumping.&lt;/strong&gt; The system gives the correct option immediately. The fix: reveal gates and assessment gates, tutor actions as the primary interface, and a separation between objective findings and diagnosis.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Closing&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    WHAT THIS ARCHITECTURE ENABLES                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502; Multi-turn  &amp;#x2502;   &amp;#x2502;    Under    &amp;#x2502;   &amp;#x2502;  With tools &amp;#x2502;   &amp;#x2502; Debuggable  &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2502;  tutoring   &amp;#x2502;   &amp;#x2502; uncertainty &amp;#x2502;   &amp;#x2502;&amp;amp; retrieval  &amp;#x2502;   &amp;#x2502;&amp;amp; improvable &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;          &amp;#x2502;                 &amp;#x2502;                 &amp;#x2502;                 &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;          &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                            &amp;#x25bc;                 &amp;#x25bc;                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                  &amp;#x2502;   Deterministic Runtime Interface     &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                  &amp;#x2502;   (state, tools, traces, gates)       &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                      &amp;#x2502;                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x25bc;                 &amp;#x25bc;                 &amp;#x25bc;                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x2502;    SFT    &amp;#x2502;     &amp;#x2502;    RL     &amp;#x2502;     &amp;#x2502;    ACE    &amp;#x2502;              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x2502; (format)  &amp;#x2502;     &amp;#x2502; (behavior)&amp;#x2502;     &amp;#x2502; (rules)   &amp;#x2502;              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         &amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                              A TUTOR,                                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          NOT A CHATBOT                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         &amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;&amp;#x2550;                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This architecture is designed to make a difficult problem tractable: multi-turn tutoring, under uncertainty, with tools and retrieval, while staying debuggable and improvable.&lt;/p&gt;&lt;p&gt;The core idea is simple. Treat the runtime as a stable interface&amp;#x2014;state, tools, traces, gates&amp;#x2014;then improve behavior through multiple complementary mechanisms: SFT for reliable formatting, RL for learned tool-use policies, and ACE playbooks for fast behavioral fixes. That structure is what makes the system behave like a tutor rather than a single-shot chatbot.&lt;/p&gt;&lt;hr&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Building an Adaptive Tutoring System with RL and Knowledge Tracing</title>
      <link>https://nayanachandrika99.github.io/posts/building-an-adaptive-tutoring-system-with-rl-and-knowledge-tracing/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/building-an-adaptive-tutoring-system-with-rl-and-knowledge-tracing/</guid>
      <description>Combining knowledge tracing, reinforcement learning, and multi-agent orchestration to pick the right question for each learner.</description>
      <pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 08:03:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/building-an-adaptive-tutoring-system-with-rl-and-knowledge-tracing/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;Combining knowledge tracing, reinforcement learning, and multi-agent orchestration to pick the right question for each learner.&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; January 7, 2026 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;The Problem: Static Question Banks Don&amp;apos;t Work&lt;/h3&gt;&lt;p&gt;Every student learns differently. Some need more practice on fractions, others struggle with word problems. While many platforms use spaced repetition or Item Response Theory (IRT) to estimate difficulty, truly optimizing the &lt;em&gt;sequence&lt;/em&gt; for maximum learning efficiency remains a hard open problem.&lt;/p&gt;&lt;p&gt;The result? Students waste time on questions they&amp;apos;ve already mastered, skip over gaps in their understanding, and also lose motivation when difficulty spikes unexpectedly.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What if we could select the optimal next question for each student at each moment?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This post walks through how I built an adaptive tutoring system that does exactly that.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Transferring to Any Domain&lt;/h3&gt;&lt;p&gt;Before diving into the technical details, here&amp;apos;s the key insight: &lt;strong&gt;this system is domain-agnostic&lt;/strong&gt;. It works for math, medicine, law, programming&amp;#x2014;any domain with structured practice content.&lt;/p&gt;&lt;h4&gt;What You Need&lt;/h4&gt;&lt;p&gt;To deploy this system in a new domain, you need three data sources:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        REQUIRED DATA SOURCES                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  1. QUESTION BANK                                                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; Question ID (unique identifier)                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; Question Text (stem, choices, correct answer)                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; Knowledge Concepts (topics/skills the question tests)        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x2514;&amp;#x2500;&amp;#x2500; [Optional] Difficulty estimate, time estimate                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  2. INTERACTION LOGS                                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; User ID                                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; Question ID                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; Correct/Incorrect                                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; Timestamp                                                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x2514;&amp;#x2500;&amp;#x2500; [Optional] Time spent, hints used                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  3. KNOWLEDGE CONCEPT (KC) TAXONOMY                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; KC ID                                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x251c;&amp;#x2500;&amp;#x2500; KC Name/Description                                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     &amp;#x2514;&amp;#x2500;&amp;#x2500; [Optional] Prerequisites, hierarchy                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Minimum Viable Dataset&lt;/h4&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt; Component &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; Minimum Size &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; Recommended &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; Questions &lt;/td&gt;&lt;td&gt; 500+ &lt;/td&gt;&lt;td&gt; 5,000+ &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Interactions &lt;/td&gt;&lt;td&gt; 100K+ &lt;/td&gt;&lt;td&gt; 1M+ &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Users &lt;/td&gt;&lt;td&gt; 1K+ &lt;/td&gt;&lt;td&gt; 10K+ &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; KCs &lt;/td&gt;&lt;td&gt; 20+ &lt;/td&gt;&lt;td&gt; 100+ &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;The more interaction data you have, the better the KT model will calibrate. With less data, you can still use the heuristic scorer (Part 2) without training RL (Part 3).&lt;/p&gt;&lt;h4&gt;Data Transformation Pipeline&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Raw Content &amp;#x2192; Embeddings &amp;#x2192; KT Training &amp;#x2192; RL Training &amp;#x2192; Deployment&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Step 1: Generate Embeddings&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Run a pretrained language model (BERT, sentence-transformers) on your question text  &lt;/li&gt;&lt;li&gt; Output: 768-dim vector per question  &lt;/li&gt;&lt;li&gt; Also embed KC descriptions to get KC embeddings  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 2: Map Questions to KCs&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; If you have manual labels, use them  &lt;/li&gt;&lt;li&gt; If not, you can cluster question embeddings and label clusters as KCs  &lt;/li&gt;&lt;li&gt; Or use an LLM to auto-annotate (e.g., &amp;quot;What skills does this question test?&amp;quot;)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 3: Format Interaction Logs&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Required schema: &lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;user_id, question_id, correct (0/1), timestamp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt; Sort by user, then by timestamp to get sequences  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 4: Train KT Model&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Train the LSTM on your interaction sequences  &lt;/li&gt;&lt;li&gt; Calibrate to predict KC-level mastery  &lt;/li&gt;&lt;li&gt; Output: Model checkpoint (~10MB)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 5: (Optional) Train RL Policy&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Use the trained KT model as the environment  &lt;/li&gt;&lt;li&gt; Simulate student trajectories and optimize for learning gain  &lt;/li&gt;&lt;li&gt; This step is optional&amp;#x2014;the heuristic scorer works without it  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Example: Medical Licensure&lt;/h4&gt;&lt;p&gt;For a USMLE prep platform:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Questions&lt;/strong&gt;: Board-style vignettes (text + answer choices)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;KCs&lt;/strong&gt;: Medical topics (cardiology, nephrology, pharmacology, etc.)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Interactions&lt;/strong&gt;: Student practice history from your platform  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Embeddings&lt;/strong&gt;: Run BioBERT or PubMedBERT on question text  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The same architecture applies&amp;#x2014;only the content changes.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;System Architecture: Three Specialized Agents&lt;/h3&gt;&lt;p&gt;I split the system into three agents. Each one has a single job:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    SESSION ORCHESTRATOR                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  Coordinates the full answer &amp;#x2192; update &amp;#x2192; recommend cycle          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2022; Safety fallbacks &amp;#x2022; Audit logging &amp;#x2022; Session lifecycle          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;         &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2534;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;         &amp;#x25bc;                                     &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;              &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  KNOWLEDGE MODELER  &amp;#x2502;              &amp;#x2502;        PLANNER          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  (CalibrationQDKT)  &amp;#x2502;              &amp;#x2502;  (Heuristic + RL)       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;              &amp;#x251c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2524;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; &amp;#x2022; LSTM hidden state &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; &amp;#x2022; Candidate filtering   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; &amp;#x2022; Mastery vectors   &amp;#x2502;  p(correct)  &amp;#x2502; &amp;#x2022; Learning gain scoring &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; &amp;#x2022; Temporal decay    &amp;#x2502;  uncertainty &amp;#x2502; &amp;#x2022; SAC/DQN RL policy     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;              &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Why this decomposition?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Knowledge Modeler&lt;/strong&gt;: Owns the student state. Answers &amp;quot;What does this student know?&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Planner&lt;/strong&gt;: Owns question selection. Answers &amp;quot;What should we teach next?&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;: Owns the session. Ensures safety, logging, and graceful fallbacks.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h3&gt;Part 1: Knowledge Tracing with CalibrationQDKT&lt;/h3&gt;&lt;h4&gt;The Idea: Student State as LSTM Hidden State&lt;/h4&gt;&lt;p&gt;We model a student&amp;apos;s knowledge as the hidden state of an LSTM that has processed their entire interaction history. Each interaction (question + response) updates the hidden state.&lt;/p&gt;&lt;p&gt;But first&amp;#x2014;where does this LSTM come from?&lt;/p&gt;&lt;h4&gt;Training the KT Model&lt;/h4&gt;&lt;p&gt;The knowledge tracing model is trained in two stages:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Stage 1: Base LSTM Training&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The base model uses an &lt;strong&gt;LSTM (Long Short-Term Memory) architecture&lt;/strong&gt; inspired by Deep Knowledge Tracing (DKT). Unlike traditional DKT that learns simple ID embeddings, this model takes &lt;strong&gt;pretrained BERT (Bidirectional Encoder Representations from Transformers) embeddings&lt;/strong&gt; (768-dim) as input to capture the semantic content of each question. The LSTM processes these sequences to produce a 300-dimensional hidden state representing the student&amp;apos;s knowledge.&lt;/p&gt;&lt;p&gt;Key hyperparameters:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;768-dim embeddings&lt;/strong&gt; (BERT, frozen)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;300-dim hidden state&lt;/strong&gt; (the student knowledge representation)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;200 epochs&lt;/strong&gt; on XES3G5M  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Stage 2: KC Calibration&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The base LSTM predicts correctness on individual questions. But we want &lt;strong&gt;KC-level (Knowledge Concept)&lt;/strong&gt; mastery. The calibration stage freezes the LSTM and trains a small projection layer that maps the hidden state to KC predictions. This uses the KC embeddings (also 768-dim BERT vectors) as targets.&lt;/p&gt;&lt;p&gt;After calibration, the model can predict mastery for any KC embedding&amp;#x2014;not just questions it saw during training. This is what makes it useful for RL: the policy can query &amp;quot;what&amp;apos;s the student&amp;apos;s mastery on KC X?&amp;quot; without enumerating all questions.&lt;/p&gt;&lt;h4&gt;LSTM Update Step&lt;/h4&gt;&lt;p&gt;Now, back to inference. Each interaction updates the hidden state:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# From src/agents/knowledge_modeler/inference.py&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; update_state&lt;/span&gt;&lt;span&gt;(self, user_id: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, interaction: StudentInteraction):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Get current LSTM state&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    h, c, lstm_out &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;._state_cache[user_id]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Encode interaction&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    question_emb &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.embeddings.get_question_embedding(interaction.question_id)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; torch.tensor([[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; interaction.correct &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;]])&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Update LSTM&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    response_emb &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.model.net.correctness_encoding(response.long())&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    x &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; question_emb.unsqueeze(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; response_emb&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    new_lstm_out, (new_h, new_c) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.model.net.lstm_layer(x, (h, c))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    self&lt;/span&gt;&lt;span&gt;._state_cache[user_id] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (new_h, new_c, new_lstm_out)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Predicting Correctness&lt;/h4&gt;&lt;p&gt;Given a student&amp;apos;s hidden state &amp;#x24;h&amp;#x24; and a question embedding &amp;#x24;e_q&amp;#x24;, we predict the probability of correct response:&lt;/p&gt;&lt;p&gt;&amp;#x24;&amp;#x24;P(\text{correct} \mid h, e_q) = \sigma(W \cdot [h \| e_q] + b)&amp;#x24;&amp;#x24;&lt;/p&gt;&lt;p&gt;where &amp;#x24;[h \| e_q]&amp;#x24; is the concatenation of the 300-dim hidden state with the 768-dim question embedding.&lt;/p&gt;&lt;h4&gt;Temporal Decay: Students Forget&lt;/h4&gt;&lt;p&gt;Knowledge decays over time. We model forgetting with exponential decay:&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;mt=m0&amp;#x22c5;0.5t/&amp;#x3c4;m_t = m_0 \cdot 0.5^{t / \tau}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0.&lt;/span&gt;&lt;span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;where:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m0m_0&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the original mastery  &lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;tt&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is days since last practice  &lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;\tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the half-life (default: 7 days)  &lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# From src/agents/knowledge_modeler/decay.py&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; _decay_factor&lt;/span&gt;&lt;span&gt;(self, last_practiced: datetime, now: datetime) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    elapsed_days &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (now &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; last_practiced).total_seconds() &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 86400.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; 0.5&lt;/span&gt;&lt;span&gt; **&lt;/span&gt;&lt;span&gt; (elapsed_days &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.default_half_life_days)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This allows the system to identify &amp;quot;rusty&amp;quot; knowledge concepts that need refreshing.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Part 2: Expected Learning Gain Scoring&lt;/h3&gt;&lt;h4&gt;The Key Insight: Counterfactual Simulation&lt;/h4&gt;&lt;p&gt;To pick the best question, we simulate what would happen if the student answered it. The key insight: &lt;strong&gt;treat the KT model as an environment&lt;/strong&gt; you can query.&lt;/p&gt;&lt;p&gt;For each candidate question &lt;span&gt;&lt;span&gt;&lt;span&gt;qq&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Predict &lt;span&gt;&lt;span&gt;&lt;span&gt;p=P(correct&amp;#x2223;h,eq)p = P(\text{correct} \mid h, e_q)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;P&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;correct&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt; Simulate &amp;quot;what if correct?&amp;quot; &amp;#x2192; get new state &amp;#x24;s^+&amp;#x24;  &lt;/li&gt;&lt;li&gt; Simulate &amp;quot;what if incorrect?&amp;quot; &amp;#x2192; get new state &amp;#x24;s^-&amp;#x24;  &lt;/li&gt;&lt;li&gt; Compute expected learning gain:  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;&amp;#x24;&amp;#x24;\mathbb{E}[\Delta] = p \cdot \Delta^+ + (1-p) \cdot \Delta^-&amp;#x24;&amp;#x24;&lt;/p&gt;&lt;p&gt;where &amp;#x24;\Delta^+ = \text{gain}(s^+) - \text{gain}(s)&amp;#x24; and &amp;#x24;\Delta^- = \text{gain}(s^-) - \text{gain}(s)&amp;#x24;.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;         [Current State s]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;           /           \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    (Correct?)       (Incorrect?)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;       /                 \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[State s+]           [State s-]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |                   |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  Gain(s+)            Gain(s-)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      \\                   /&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    Weighted Expected Gain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# From src/agents/planner/scorer.py&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; score_question&lt;/span&gt;&lt;span&gt;(self, user_id: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, question_id: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; QuestionScore:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Step 1: Predict p(correct)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    p_correct, uncertainty &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.km.predict_correct(user_id, question_id)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Step 2: Counterfactual simulation&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    state_if_correct &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.km.simulate_update(user_id, question_id, &lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    state_if_incorrect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.km.simulate_update(user_id, question_id, &lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Step 3: Compute gains&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    gain_if_correct &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.km.compute_mastery_gain(current_state, state_if_correct)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    gain_if_incorrect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.km.compute_mastery_gain(current_state, state_if_incorrect)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Step 4: Expected learning gain&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    expected_gain &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; p_correct &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; gain_if_correct &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; p_correct) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; gain_if_incorrect&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; QuestionScore(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        question_id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;question_id,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        expected_learning_gain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;expected_gain,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        p_correct&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;p_correct,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        ...&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Zone of Proximal Development (ZPD) Bonus&lt;/h4&gt;&lt;p&gt;The Zone of Proximal Development is a concept from educational psychology (Vygotsky, 1978). It refers to the sweet spot where a task is challenging enough to promote learning but not so hard that the learner gives up.&lt;/p&gt;&lt;p&gt;In our context, we operationalize ZPD as a probability range. If a student has a 95% chance of getting a question right, they probably already know the material&amp;#x2014;low learning value. If they have a 10% chance, they&amp;apos;ll likely fail and get frustrated. The sweet spot is somewhere in the middle.&lt;/p&gt;&lt;p&gt;I use the range &lt;strong&gt;[0.55, 0.75]&lt;/strong&gt; based on the &amp;quot;desirable difficulty&amp;quot; literature. The bonus function is:&lt;/p&gt;&lt;p&gt;&amp;#x24;&amp;#x24;\text{ZPD Bonus} = \begin{cases} &lt;br&gt; w \cdot (1 - \frac{|p - 0.65|}{0.1}) &amp;amp; \text{if } 0.55 \leq p \leq 0.75 \\ &lt;br&gt; 0 &amp;amp; \text{otherwise} &lt;br&gt; \end{cases}&amp;#x24;&amp;#x24;&lt;/p&gt;&lt;p&gt;This gives maximum bonus at &amp;#x24;p = 0.65&amp;#x24; (the center of the range) and tapers linearly to zero at the edges. Questions outside the range get no bonus.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Without ZPD targeting, the scorer tends to recommend very hard questions (high learning delta) or very easy questions (high confidence). Neither is optimal for sustained engagement.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Part 3: Reinforcement Learning for Question Selection&lt;/h3&gt;&lt;h4&gt;Why RL?&lt;/h4&gt;&lt;p&gt;The scoring approach in Part 2 is greedy&amp;#x2014;it picks the question with the highest immediate expected gain. But learning is a multi-step process. A question that seems suboptimal now might set up better learning opportunities later.&lt;/p&gt;&lt;p&gt;Reinforcement learning can optimize for long-term outcomes. Instead of maximizing immediate gain, we train a policy to maximize cumulative learning over an entire session (or even longer horizons).&lt;/p&gt;&lt;h4&gt;Formulating the MDP&lt;/h4&gt;&lt;p&gt;We cast question selection as a Markov Decision Process:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;State&lt;/strong&gt;: The 300-dim LSTM hidden state. This is a learned representation of everything the student has done so far.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Action&lt;/strong&gt;: Which question to recommend next. This is where it gets interesting (see below).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reward&lt;/strong&gt;: Learning gain after the student answers. We measure this as the change in predicted correctness across a sample of questions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Transition&lt;/strong&gt;: Deterministic&amp;#x2014;the KT model tells us exactly how the state changes given a question and response.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;The Action Space Problem&lt;/h4&gt;&lt;p&gt;With 7,600+ questions, a naive discrete action space is huge. Two approaches:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Discrete (DQN)&lt;/strong&gt;: Train Q-values for each question. Works but doesn&amp;apos;t generalize to unseen questions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Continuous (SAC)&lt;/strong&gt;: Output a 768-dim embedding vector, then find the nearest question. Generalizes better and can handle new questions at test time.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;I went with SAC (Soft Actor-Critic) because the continuous action space maps naturally to the semantic embedding space:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; SACActorSelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RLPolicySelector&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; select_qid&lt;/span&gt;&lt;span&gt;(self, km, user_id, candidates, embedding_store):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Get student state&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        _, _, lstm_out &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; km._state_cache[user_id]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Actor outputs embedding (not question ID!)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt; torch.no_grad():&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            action_emb &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;._actor(lstm_out)  &lt;/span&gt;&lt;span&gt;# [1, 768]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Find nearest question in candidate set&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        candidate_embs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; torch.stack([&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            embedding_store.get_question_embedding(qid)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            for&lt;/span&gt;&lt;span&gt; qid &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; candidates&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        ])&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        similarities &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; F.cosine_similarity(action_emb, candidate_embs)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        best_idx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; similarities.argmax().item()&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; candidates[best_idx]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The actor learns to output an &amp;quot;ideal question embedding&amp;quot;&amp;#x2014;a point in semantic space representing what the student should practice next. We then find the actual question closest to that point.&lt;/p&gt;&lt;h4&gt;Training Details&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Algorithm&lt;/strong&gt;: SAC with automatic entropy tuning  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Critic&lt;/strong&gt;: Uses the same LSTM architecture to predict Q-values  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Batch size&lt;/strong&gt;: 256 trajectories  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Training&lt;/strong&gt;: ~50k episodes on simulated students (replay from real interaction logs)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Gamma&lt;/strong&gt;: 0.99 (we care about long-term outcomes)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The KT model acts as the environment&amp;#x2014;we can simulate thousands of students without real human interaction.&lt;/p&gt;&lt;h4&gt;Exam-Score Reward: Beyond KC-Level Metrics&lt;/h4&gt;&lt;p&gt;The obvious reward is KC-level mastery delta: how much did the student&amp;apos;s predicted mastery on the practiced KC improve?&lt;/p&gt;&lt;p&gt;But this is noisy. A single question might barely move KC mastery, making the reward signal sparse and hard to learn from.&lt;/p&gt;&lt;p&gt;I extended this with an &lt;strong&gt;&amp;quot;exam-score&amp;quot; reward&lt;/strong&gt;:&lt;/p&gt;&lt;p&gt;&amp;#x24;&amp;#x24;R = \frac{1}{|Q_{\text{sample}}|} \sum_{q \in Q_{\text{sample}}} \left[ P(\text{correct} \mid s&amp;apos;, e_q) - P(\text{correct} \mid s, e_q) \right]&amp;#x24;&amp;#x24;&lt;/p&gt;&lt;p&gt;Instead of measuring improvement on one KC, we measure improvement in predicted correctness across a &lt;em&gt;sample&lt;/em&gt; of questions. This is more like what we actually care about: overall exam performance.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Probe Split&lt;/strong&gt;: To avoid overfitting, we split questions into:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Train set&lt;/strong&gt;: Used for reward computation during training  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Probe set&lt;/strong&gt;: Held out entirely, used only for evaluation  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This prevents the policy from gaming the reward by memorizing which questions are in the sample.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Part 4: Multi-Objective Scoring&lt;/h3&gt;&lt;h4&gt;Beyond Pure Learning Gain&lt;/h4&gt;&lt;p&gt;If you only optimize for learning gain, the system does weird things:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Recommends the same hard topic repeatedly (high delta, but frustrating)  &lt;/li&gt;&lt;li&gt; Ignores topics the student should cover for an exam  &lt;/li&gt;&lt;li&gt; Never gives &amp;quot;easy wins&amp;quot; to build confidence  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Real tutoring has multiple objectives. The challenge is combining them sensibly.&lt;/p&gt;&lt;h4&gt;The Five Objectives&lt;/h4&gt;&lt;p&gt;I use a weighted linear combination:&lt;/p&gt;&lt;p&gt;&amp;#x24;&amp;#x24;\text{Score} = w_L \cdot L + w_C \cdot \text{Cov} + w_S \cdot S + w_{\text{Conf}} \cdot \text{Conf} + w_R \cdot R&amp;#x24;&amp;#x24;&lt;/p&gt;&lt;p&gt;Each objective is normalized to [0, 1]:&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt; Objective &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; What It Measures &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; How It&amp;apos;s Computed &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;L&lt;/strong&gt; (Learning Gain) &lt;/td&gt;&lt;td&gt; Expected knowledge improvement &lt;/td&gt;&lt;td&gt; Counterfactual simulation (Part 2) &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cov&lt;/strong&gt; (Coverage) &lt;/td&gt;&lt;td&gt; Blueprint/topic completion &lt;/td&gt;&lt;td&gt; % of required topics not yet practiced &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;S&lt;/strong&gt; (Speed) &lt;/td&gt;&lt;td&gt; Preference for shorter questions &lt;/td&gt;&lt;td&gt; Inverse of estimated time-to-complete &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Conf&lt;/strong&gt; (Confidence) &lt;/td&gt;&lt;td&gt; Questions in the ZPD range &lt;/td&gt;&lt;td&gt; ZPD bonus function &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;R&lt;/strong&gt; (Remediation) &lt;/td&gt;&lt;td&gt; Focus on weak/rusty areas &lt;/td&gt;&lt;td&gt; Overlap with flagged weak KCs &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Coverage&lt;/strong&gt; is particularly important for exam prep. If the student needs to cover 10 topics and has only seen 3, we should prioritize the missing 7 even if they&amp;apos;re not the highest-gain options.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Speed&lt;/strong&gt; helps when the student is frustrated or running low on time. Shorter questions provide quicker feedback loops.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Remediation&lt;/strong&gt; targets KCs that the decay model flagged as &amp;quot;rusty&amp;quot; or that the student has historically struggled with.&lt;/p&gt;&lt;h4&gt;Adaptive Weights by Mode&lt;/h4&gt;&lt;p&gt;The weights aren&amp;apos;t fixed&amp;#x2014;they depend on the session mode:&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt; Mode &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; &amp;#x24;w_L&amp;#x24; &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; &amp;#x24;w_C&amp;#x24; &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; &amp;#x24;w_S&amp;#x24; &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; &amp;#x24;w_{\text{Conf}}&amp;#x24; &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; &amp;#x24;w_R&amp;#x24; &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; Learning &lt;/td&gt;&lt;td&gt; 0.4 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.2 &lt;/td&gt;&lt;td&gt; 0.2 &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Exam &lt;/td&gt;&lt;td&gt; 0.2 &lt;/td&gt;&lt;td&gt; 0.5 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Review &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.1 &lt;/td&gt;&lt;td&gt; 0.0 &lt;/td&gt;&lt;td&gt; 0.7 &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Learning mode&lt;/strong&gt;: Maximize learning, with some confidence and remediation  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Exam mode&lt;/strong&gt;: Heavy coverage weight&amp;#x2014;make sure all topics are hit  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Review mode&lt;/strong&gt;: Focus almost entirely on weak areas  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Behavioral Adjustments&lt;/h4&gt;&lt;p&gt;The weights also adapt to real-time signals:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; infer_weights&lt;/span&gt;&lt;span&gt;(self, session: SessionSummary) -&amp;gt; AdaptiveWeights:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; session.mode &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;exam&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        weights.w_coverage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.5&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        weights.w_learning &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.2&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    elif&lt;/span&gt;&lt;span&gt; session.mode &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;review&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        weights.w_remediation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.7&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        weights.w_learning &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Behavioral adjustments&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; signals.skip_rate &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 0.3&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        weights.w_speed &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 0.2&lt;/span&gt;&lt;span&gt;  # Frustrated student &amp;#x2192; easier questions&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; signals.accuracy &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; 0.4&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        weights.w_confidence &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 0.15&lt;/span&gt;&lt;span&gt;  # Struggling &amp;#x2192; more ZPD targeting&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; weights.normalize()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If the student is skipping a lot of questions (frustration signal), we increase the weight on speed and confidence. If accuracy drops, we stay closer to the ZPD.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Part 5: Quality Gates and LLM Supervision&lt;/h3&gt;&lt;h4&gt;Why Gates?&lt;/h4&gt;&lt;p&gt;RL policies can behave unexpectedly, especially early in training or on out-of-distribution states. The SAC actor might output an embedding that maps to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; A question the student just answered (repeat)  &lt;/li&gt;&lt;li&gt; A question way too hard or too easy  &lt;/li&gt;&lt;li&gt; A question on a topic that&amp;apos;s not relevant to the session goals  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Rather than trust the policy blindly, we add a layer of deterministic quality checks.&lt;/p&gt;&lt;h4&gt;The Gate Pipeline&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[RL Policy] &amp;#x2500;&amp;#x2500;&amp;#x25b6; (Action Embedding)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                [Nearest Neighbor]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                [Quality Gates] &amp;#x2500;&amp;#x2500;(Fail)&amp;#x2500;&amp;#x2500;&amp;#x25b6; [Refinement]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                   (Pass)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                 [Selected Question]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The RL policy proposes a question. The gates check if it&amp;apos;s acceptable. If not, we either accept it anyway (logging the failure) or trigger refinement.&lt;/p&gt;&lt;h4&gt;The Four Gates&lt;/h4&gt;&lt;p&gt;Each gate is a simple boolean check:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. No Recent Repeats&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Don&amp;apos;t recommend a question the student answered in the last N interactions  &lt;/li&gt;&lt;li&gt; Window size is configurable (default: 20 questions)  &lt;/li&gt;&lt;li&gt; Prevents the &amp;quot;drill on the same item&amp;quot; failure mode  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;2. KC Overlap&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; The selected question must cover at least one of the target KCs  &lt;/li&gt;&lt;li&gt; Target KCs come from session goals or the remediation system  &lt;/li&gt;&lt;li&gt; Prevents wandering off-topic  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;3. ZPD Range&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; The estimated &amp;#x24;p(\text{correct})&amp;#x24; must be within [0.4, 0.85]  &lt;/li&gt;&lt;li&gt; Wider than the ZPD bonus range&amp;#x2014;this is a hard constraint, not a soft preference  &lt;/li&gt;&lt;li&gt; Prevents questions that are almost certainly too hard or too easy  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;4. Minimum Learning Gain&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Expected learning gain must exceed a threshold (default: 0.01)  &lt;/li&gt;&lt;li&gt; Filters out questions that the KT model predicts will have minimal impact  &lt;/li&gt;&lt;li&gt; Catches cases where the nearest neighbor is semantically close but pedagogically useless  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Supervisor Logic&lt;/h4&gt;&lt;p&gt;When gates fail, what do we do? Two options:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Accept anyway&lt;/strong&gt;: Log the failure and let the RL proposal through. Useful for exploration and collecting data on edge cases.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Trigger refinement&lt;/strong&gt;: Re-run the scorer with stricter constraints and pick the best passing candidate.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;The decision is made by a &amp;quot;supervisor&amp;quot;&amp;#x2014;either a simple heuristic or an LLM:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# RL proposes a question&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;rl_qid &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; policy_selector.select_qid(km, user_id, candidates, embeddings)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Evaluate quality gates&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;gate_results &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; evaluate_candidate(rl_qid, constraints)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Supervisor decides&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; supervisor.run_refinement &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; gate_results.all_passed:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Re-rank with stricter constraints&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    refined_qid &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; run_refinement(scorer, stricter_constraints)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Accept RL proposal even if gates fail&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    final_qid &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rl_qid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Heuristic vs. LLM Supervisor&lt;/h4&gt;&lt;p&gt;The heuristic supervisor uses simple rules:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Always refine if the repeat gate fails  &lt;/li&gt;&lt;li&gt; Accept ZPD failures if confidence is high  &lt;/li&gt;&lt;li&gt; Log everything  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The LLM supervisor (optional) can reason about context:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &amp;quot;The student has been struggling&amp;#x2014;let&amp;apos;s accept this easier question even though it&amp;apos;s below ZPD&amp;quot;  &lt;/li&gt;&lt;li&gt; &amp;quot;This question failed KC overlap but it&amp;apos;s a good bridge to the target topic&amp;quot;  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In practice, the heuristic works well. The LLM adds flexibility but also latency and cost.&lt;/p&gt;&lt;h4&gt;Why This Pattern?&lt;/h4&gt;&lt;p&gt;This &amp;quot;RL proposes, rules dispose&amp;quot; pattern has a few benefits:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Safety&lt;/strong&gt;: Hard rules catch obviously bad recommendations  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Debuggability&lt;/strong&gt;: You can see exactly which gate failed and why  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Graceful degradation&lt;/strong&gt;: If RL is broken, the system falls back to heuristic scoring  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Exploration&lt;/strong&gt;: By sometimes accepting failures, we collect data on edge cases  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h3&gt;Results &amp;amp; Demo&lt;/h3&gt;&lt;p&gt;I built a Rich TUI demo to visualize the full pipeline:&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/dd3f0ad4-208d-4491-bc2e-c67590769e94/demo_tui.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/dd3f0ad4-208d-4491-bc2e-c67590769e94/demo_tui.webp&quot; alt&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The demo shows:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Student snapshot (mastery, weak KCs, recent accuracy)  &lt;/li&gt;&lt;li&gt; Candidate funnel (7,652 &amp;#x2192; filtered &amp;#x2192; scored)  &lt;/li&gt;&lt;li&gt; Top recommendations with score breakdowns  &lt;/li&gt;&lt;li&gt; RL embedding space visualization  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h3&gt;What I Learned&lt;/h3&gt;&lt;h4&gt;1. Treat the KT model as an RL environment&lt;/h4&gt;&lt;p&gt;This was the biggest conceptual shift. The knowledge tracing model isn&amp;apos;t just a predictor&amp;#x2014;it&amp;apos;s a &lt;em&gt;simulator&lt;/em&gt;. You can ask it &amp;quot;what if the student answered this question correctly?&amp;quot; and get a new state back. That turns question selection into planning, and planning is what RL is good at.&lt;/p&gt;&lt;h4&gt;2. Counterfactual simulation is surprisingly cheap&lt;/h4&gt;&lt;p&gt;I expected simulating &amp;quot;correct&amp;quot; and &amp;quot;incorrect&amp;quot; outcomes for every candidate would be slow. It&amp;apos;s not. The LSTM forward pass is fast, and you&amp;apos;re not backpropagating. Scoring 200 candidates takes ~50ms on a laptop CPU.&lt;/p&gt;&lt;h4&gt;3. Multi-objective scoring matters more than I thought&lt;/h4&gt;&lt;p&gt;My first version just maximized expected learning gain. It kept recommending hard questions because those had the highest delta. Students got frustrated.&lt;/p&gt;&lt;p&gt;Adding coverage (don&amp;apos;t repeat topics), speed (shorter questions when struggling), and confidence (stay in the ZPD) made the recommendations feel sensible. The weights are simple rules, not learned&amp;#x2014;but they work.&lt;/p&gt;&lt;h4&gt;4. RL needs guardrails&lt;/h4&gt;&lt;p&gt;The SAC policy sometimes outputs embeddings that map to weird questions&amp;#x2014;repeats, or items way outside the student&amp;apos;s level. I added hard quality gates (no repeats, KC overlap, ZPD range) that run &lt;em&gt;after&lt;/em&gt; RL selection. If the policy picks something bad, we either accept it anyway (and log it) or re-rank with stricter constraints.&lt;/p&gt;&lt;p&gt;This &amp;quot;RL proposes, rules dispose&amp;quot; pattern kept the system usable while the policy was still learning.&lt;/p&gt;&lt;h4&gt;5. Cold-start is solved by embeddings&lt;/h4&gt;&lt;p&gt;New students have no history, so the LSTM is at its default state. But you still have the question embeddings. You can recommend based on target KCs, difficulty proxies, or just sample randomly. The system degrades gracefully.&lt;/p&gt;&lt;h4&gt;6. Latency budget drove architecture choices&lt;/h4&gt;&lt;p&gt;Everything runs in a single forward pass per candidate. No database lookups in the hot path&amp;#x2014;embeddings are preloaded. The full pipeline (filter &amp;#x2192; score &amp;#x2192; select) runs under 100ms for 200 candidates. That&amp;apos;s fast enough for interactive use.&lt;/p&gt;&lt;h4&gt;7. The reward function is the hard part&lt;/h4&gt;&lt;p&gt;KC-level mastery delta seemed like the obvious reward. But it&amp;apos;s noisy&amp;#x2014;small state changes produce small rewards, and the policy has trouble learning. The exam-score reward (improvement on a sampled set of questions) was more stable, but required careful train/probe splits to avoid leaking evaluation into training.&lt;/p&gt;&lt;h4&gt;8. Domain transfer is just data&lt;/h4&gt;&lt;p&gt;The architecture doesn&amp;apos;t care if questions are about math, medicine, or law. You need:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Question embeddings (run BERT on your content)  &lt;/li&gt;&lt;li&gt; KC annotations (can be automated with LLMs)  &lt;/li&gt;&lt;li&gt; Interaction logs (student &amp;#xd7; question &amp;#xd7; correct/incorrect)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Swap those in, retrain the KT model, and the rest of the pipeline works.&lt;/p&gt;&lt;h4&gt;What I&amp;apos;d do differently&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Start with heuristics&lt;/strong&gt;: The greedy scorer (no RL) was good enough for 80% of cases. I should have validated it more before jumping to RL.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Log everything&lt;/strong&gt;: I added audit logging late. Should have done it from day one.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Smaller action space&lt;/strong&gt;: SAC in 768-dim embedding space works, but discrete actions (DQN over question indices) might converge faster for small question banks.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h3&gt;References&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2507.11060&quot; target=&quot;_blank&quot;&gt;Personalized Exercise Recommendation with Semantically-Grounded Knowledge Tracing&lt;/a&gt; (NeurIPS 2025)  &lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/ai4ed/XES3G5M&quot; target=&quot;_blank&quot;&gt;XES3G5M Dataset&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://tianshou.org/&quot; target=&quot;_blank&quot;&gt;Tianshou RL Library&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/pykt-team/pykt-toolkit&quot; target=&quot;_blank&quot;&gt;pyKT Knowledge Tracing Toolkit&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;Built on math education data (XES3G5M), but the architecture works for any domain&amp;#x2014;medical, law, whatever. Just swap in your question bank and embeddings.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/&quot;&gt;home&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>When Regex Meets Fuzzy Matching: Building a Medical Document Classifier That Knows Who&apos;s Who</title>
      <link>https://nayanachandrika99.github.io/posts/when-regex-meets-fuzzy-matching-building-a-medical-document-classifier-that-knows-whos-who/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/when-regex-meets-fuzzy-matching-building-a-medical-document-classifier-that-knows-whos-who/</guid>
      <description>*Automating multi-patient fax sorting with OCR, entity extraction, and weighted clustering*</description>
      <pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Jan 11 2026 09:02:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <category>healthcare</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/when-regex-meets-fuzzy-matching-building-a-medical-document-classifier-that-knows-whos-who/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;*Automating multi-patient fax sorting with OCR, entity extraction, and weighted clustering*&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; December 10, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/healthcare/&quot;&gt; healthcare &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Medical offices still run on faxes. Every day, insurance companies, labs, and specialists send multi-patient PDF batches&amp;#x2014;100 pages, 15 patients, all jumbled together. Staff spend hours manually sorting pages by squinting at headers, cross-referencing MRNs, and hoping they don&amp;apos;t mix up John Smith #1 with John Smith #2.&lt;/p&gt;&lt;p&gt;Inspired by &lt;a href=&quot;https://tennr.com/&quot; target=&quot;_blank&quot;&gt;Tennr&amp;apos;s&lt;/a&gt; approach to medical document automation, I built a document processing pipeline that ingests multi-patient PDFs, extracts identifiers from each page, clusters them into patient profiles, and outputs per-patient PDFs with confidence scores. The core insight: patient identity isn&amp;apos;t a binary classification&amp;#x2014;it&amp;apos;s a &lt;em&gt;weighted similarity problem&lt;/em&gt; across multiple identifier types.&lt;/p&gt;&lt;p&gt;Here&amp;apos;s what I learned building it.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Problem: One PDF, Many Patients&lt;/h3&gt;&lt;p&gt;A typical incoming fax looks like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Pages 1-3&lt;/strong&gt;: Lab results for Mary Johnson, MRN 123456  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Page 4&lt;/strong&gt;: Prescription for Robert Garcia, DOB 05/15/1980  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pages 5-7&lt;/strong&gt;: Referral for Mary Johnson (same patient, no MRN this time)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pages 8-12&lt;/strong&gt;: Mixed records, some with phone numbers only  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The challenges stack up quickly. Some pages have MRNs, others only have name and DOB. Scanned documents introduce OCR noise&amp;#x2014;typos, artifacts, and formatting variations. And then there&amp;apos;s ambiguity: when you see &amp;quot;M. Johnson&amp;quot; on page 6, is that Mary Johnson or someone else entirely?&lt;/p&gt;&lt;p&gt;Manual sorting fails at scale. Rule-based automation using exact string matching fails on real-world data. I needed something smarter&amp;#x2014;a system that could reason about &lt;em&gt;degrees of similarity&lt;/em&gt; across multiple identifier types and make probabilistic judgments about page ownership.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Architecture: A Six-Stage Pipeline&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          DOCUMENT INGESTION                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                                                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; Multi-Patient&amp;#x2502;  100+ pages, 10+ patients                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; PDF Input    &amp;#x2502;  mixed labs, prescriptions, referrals                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                                                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          PREPROCESSING LAYER                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Page Extractor    &amp;#x2502;      &amp;#x2502;  OCR Processor     &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Poppler/pdf2image &amp;#x2502;      &amp;#x2502;  Tesseract (default)&amp;#x2502;                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  DPI: 200          &amp;#x2502;      &amp;#x2502;  OlmOCR (optional)  &amp;#x2502;                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x2502; raw text + word positions&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          ENTITY EXTRACTION                                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Entity Extractor (Regex-based)                                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;            &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502;  Name    &amp;#x2502;  &amp;#x2502;   MRN    &amp;#x2502;  &amp;#x2502;   DOB    &amp;#x2502;  &amp;#x2502;  Phone   &amp;#x2502;            &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502; &amp;quot;Patient:&amp;#x2502;  &amp;#x2502; &amp;quot;MRN:    &amp;#x2502;  &amp;#x2502; &amp;quot;DOB:    &amp;#x2502;  &amp;#x2502; &amp;quot;(555)   &amp;#x2502;            &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502;  John..&amp;quot; &amp;#x2502;  &amp;#x2502;  123456&amp;quot; &amp;#x2502;  &amp;#x2502;  1/1/80&amp;quot; &amp;#x2502;  &amp;#x2502;  123-..&amp;quot; &amp;#x2502;            &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;            &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x2502; identifiers + confidence&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          PATIENT CLUSTERING                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;         &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Fuzzy Matcher       &amp;#x2502;         &amp;#x2502;  Entity Linker       &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;       &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;       &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  RapidFuzz scoring   &amp;#x2502;         &amp;#x2502;  Anchor-based        &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Per-entity strategy &amp;#x2502;         &amp;#x2502;  patient clustering  &amp;#x2502;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;         &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x2502; patient profiles&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          PAGE ASSIGNMENT                                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Page Assigner (Weighted Scoring)                                   &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                                                                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Score = 0.4&amp;#xd7;name + 0.3&amp;#xd7;mrn + 0.2&amp;#xd7;dob + 0.1&amp;#xd7;phone                  &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                                                                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                 &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502; Assigned    &amp;#x2502;  &amp;#x2502; Ambiguous   &amp;#x2502;  &amp;#x2502; Unassigned  &amp;#x2502;                 &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502; (conf &amp;#x2265;60%) &amp;#x2502;  &amp;#x2502; (close race)&amp;#x2502;  &amp;#x2502; (conf &amp;lt;60%) &amp;#x2502;                 &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                 &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                          &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          OUTPUT GENERATION                                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Document Splitter &amp;#x2502;      &amp;#x2502;  Outputs:          &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  &amp;#x2022; patient_001.pdf &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  PyPDF2            &amp;#x2502;      &amp;#x2502;  &amp;#x2022; patient_002.pdf &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                    &amp;#x2502;      &amp;#x2502;  &amp;#x2022; metadata.json   &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                    &amp;#x2502;      &amp;#x2502;  &amp;#x2022; unassigned.pdf  &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The pipeline is modular by design. The &lt;strong&gt;Page Extractor&lt;/strong&gt; renders PDF pages to images at configurable DPI using Poppler. The &lt;strong&gt;OCR Processor&lt;/strong&gt; runs text extraction with pluggable backends&amp;#x2014;Tesseract by default, but swappable for OlmOCR or custom handlers.&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;Entity Extractor&lt;/strong&gt; runs regex patterns over OCR output to detect four identifier types: patient names, Medical Record Numbers (MRNs), dates of birth, and phone numbers. Each extracted identifier carries a confidence score based on pattern match quality.&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;Fuzzy Matcher&lt;/strong&gt; and &lt;strong&gt;Entity Linker&lt;/strong&gt; work together to cluster page-level identifiers into patient profiles. Fuzzy matching determines whether &amp;quot;Mary Johnson&amp;quot; on page 3 is the same person as &amp;quot;MARY JOHNSON&amp;quot; on page 7, while the linker builds coherent patient clusters using anchor-based logic.&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;Page Assigner&lt;/strong&gt; scores each page against all known patient clusters using weighted entity matching, flagging ambiguous cases for human review. Finally, the &lt;strong&gt;Document Splitter&lt;/strong&gt; generates per-patient PDFs with accompanying metadata.&lt;/p&gt;&lt;p&gt;Each stage is independently testable. The orchestrator wires them together but delegates all logic to the components.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Lesson 1: Not All Identifiers Are Equal&lt;/h3&gt;&lt;p&gt;My first approach weighted all identifiers equally. A name match counted the same as an MRN match. That was wrong.&lt;/p&gt;&lt;p&gt;MRNs are &lt;em&gt;unique identifiers&lt;/em&gt;&amp;#x2014;if two pages share an MRN, they&amp;apos;re almost certainly the same patient. A 95% MRN match is nearly definitive evidence. Names, in contrast, are not unique at all&amp;#x2014;there might be three Robert Garcias in the practice. A perfect name match by itself proves little.&lt;/p&gt;&lt;p&gt;DOBs fall in between. They&amp;apos;re unique per patient but commonly appear across multiple pages for the same person. Phone numbers are similar&amp;#x2014;moderately unique, but patients might share family phone numbers or update their contact information.&lt;/p&gt;&lt;p&gt;The fix was &lt;strong&gt;weighted scoring&lt;/strong&gt; where each identifier type contributes proportionally to its reliability:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;weights &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;name&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;,   &lt;/span&gt;&lt;span&gt;# High coverage, low uniqueness&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;mrn&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;,    &lt;/span&gt;&lt;span&gt;# Low coverage, high uniqueness&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;dob&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;,    &lt;/span&gt;&lt;span&gt;# Moderate on both&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;phone&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.1&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;# Least reliable&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; identifier &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; page.identifiers:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    match_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; fuzzy_match(identifier, patient)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    total_score &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; match_score &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; weights[identifier.kind]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Why is name weight highest despite being least unique? Because names appear on &lt;em&gt;almost every page&lt;/em&gt;, while MRNs don&amp;apos;t. A name match alone means little, but when you combine name + DOB + phone, you get strong signal even when MRN is absent. The weights encode how much &lt;em&gt;incremental confidence&lt;/em&gt; each identifier type provides.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Domain knowledge should inform feature importance. Don&amp;apos;t assume all signals are equal&amp;#x2014;weight them based on reliability and frequency of occurrence.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 2: Fuzzy Matching Requires Per-Entity Strategy&lt;/h3&gt;&lt;p&gt;Exact string matching fails immediately on real OCR output. &amp;quot;MARY JOHNSON&amp;quot; might come through as &amp;quot;MARY J0HNSON&amp;quot; (zero instead of O) or &amp;quot;Mary  Johnson&amp;quot; (extra space) or &amp;quot;Johnson, Mary&amp;quot; (reordered). You need fuzzy matching, but not all fuzzy matching is the same.&lt;/p&gt;&lt;p&gt;I used RapidFuzz, but with &lt;em&gt;different comparison strategies per entity type&lt;/em&gt;:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; rapidfuzz &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; fuzz&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; score_name&lt;/span&gt;&lt;span&gt;(a: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, b: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Token sort handles &amp;quot;John Smith&amp;quot; vs &amp;quot;Smith, John&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; fuzz.token_sort_ratio(normalize(a), normalize(b)) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 100.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; score_dob&lt;/span&gt;&lt;span&gt;(a: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, b: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Dates must match exactly after normalization&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; normalize_date(a) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; normalize_date(b) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; score_phone&lt;/span&gt;&lt;span&gt;(a: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, b: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Partial matching handles missing area codes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; fuzz.partial_ratio(digits_only(a), digits_only(b)) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 100.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;For names, token sort ratio handles word reordering, so &amp;quot;John Smith&amp;quot; matches &amp;quot;Smith, John&amp;quot; correctly. MRNs get stricter treatment&amp;#x2014;after stripping non-alphanumeric characters, I expect near-exact matches. DOBs require &lt;em&gt;exact&lt;/em&gt; match after normalization, handling format variations like &amp;quot;5/15/1980&amp;quot;, &amp;quot;05-15-80&amp;quot;, and &amp;quot;5.15.1980&amp;quot;. Phone numbers use partial matching to catch missing area codes.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Fuzzy matching isn&amp;apos;t one-size-fits-all. Each entity type has different semantics&amp;#x2014;names tolerate reordering, dates require exact match after normalization, phone numbers can be partial.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 3: Clustering Is Position-Aware&lt;/h3&gt;&lt;p&gt;Here&amp;apos;s a subtle problem: what happens when a page has multiple patients&amp;apos; information?&lt;/p&gt;&lt;p&gt;Consider a referral letter. The referring doctor mentions their patient (the subject of the referral), but also references a patient at the receiving clinic for context. Naive clustering treats all identifiers on a page as belonging to one patient&amp;#x2014;which would incorrectly merge two distinct people.&lt;/p&gt;&lt;p&gt;The solution is &lt;strong&gt;anchor-based clustering&lt;/strong&gt;. MRNs are designated as &amp;quot;anchors&amp;quot;&amp;#x2014;high-confidence identifiers that seed patient clusters. Other identifiers attach to the nearest preceding anchor on the same page:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; idx, identifier &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; enumerate&lt;/span&gt;&lt;span&gt;(page.identifiers):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; identifier.kind &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;mrn&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # MRNs are anchors&amp;#x2014;create or match to a cluster&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        cluster &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; find_or_create_cluster(identifier)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        anchor_positions.append((idx, cluster))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Supportive identifiers attach to nearest anchor&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        nearest_anchor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; find_nearest_anchor(idx, anchor_positions)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        attach_to_cluster(identifier, nearest_anchor)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This models how medical documents are typically structured: patient identifiers appear in blocks, with the MRN or header coming first. So when a page has MRN 123456 followed by &amp;quot;Mary Johnson&amp;quot;, then later MRN 789012 followed by &amp;quot;Robert Garcia&amp;quot;&amp;#x2014;the two names attach to their respective anchors rather than merging into one confused patient.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Document structure matters. When clustering entities, consider their position on the page, not just their content.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 4: Ambiguity Should Be Flagged, Not Hidden&lt;/h3&gt;&lt;p&gt;Early versions of the assigner tried to be &amp;quot;helpful&amp;quot; by assigning every page to &lt;em&gt;some&lt;/em&gt; patient, even when confidence was low. That&amp;apos;s dangerous in healthcare&amp;#x2014;a misassigned prescription is worse than an unassigned one. The wrong routing could delay critical treatment or violate HIPAA by exposing records to the wrong patient&amp;apos;s file.&lt;/p&gt;&lt;p&gt;I added explicit ambiguity detection with two layers:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [(patient, score_page(page, patient)) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; patient &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; patients]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;scores.sort(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;=lambda&lt;/span&gt;&lt;span&gt; x: x[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;reverse&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;top_patient, top_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; scores[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;manual_review &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; False&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Layer 1: Low confidence &amp;#x2192; unassigned&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; top_score &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; MIN_CONFIDENCE&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;# default 0.6&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; PageAssignment(&lt;/span&gt;&lt;span&gt;patient_id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;manual_review&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Layer 2: Close race &amp;#x2192; flag for review&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(scores) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; (top_score &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; scores[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;][&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; AMBIGUITY_MARGIN&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    manual_review &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; True&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; PageAssignment(&lt;/span&gt;&lt;span&gt;patient_id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;top_patient, &lt;/span&gt;&lt;span&gt;manual_review&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;manual_review)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The output now has three explicit categories:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Assigned with high confidence&lt;/strong&gt;: Automatically routed, no human intervention needed  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Assigned with manual review&lt;/strong&gt;: Routed but flagged for human verification  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Unassigned&lt;/strong&gt;: Low confidence, requires human classification  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The CLI and API both surface these metrics prominently. You can track your unassigned rate over time and tune thresholds accordingly.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: In high-stakes domains, &amp;quot;I don&amp;apos;t know&amp;quot; is a valid output. Systems that hide uncertainty are systems that create liability.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 5: Make OCR Pluggable from Day One&lt;/h3&gt;&lt;p&gt;Tesseract is the default OCR engine, and it&amp;apos;s good enough for most scanned documents. But some PDFs have handwriting, complex table layouts, or degraded quality (fax-of-a-fax quality) that Tesseract struggles with.&lt;/p&gt;&lt;p&gt;Rather than hardcoding Tesseract, I built backend pluggability into the OCR processor. The configuration accepts three modes: &lt;code&gt;tesseract&lt;/code&gt; (the default), &lt;code&gt;olmocr&lt;/code&gt; (for integration with newer vision-language models), or a custom module path like &lt;code&gt;my_module:ocr_function&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The custom mode uses Python&amp;apos;s import machinery to load any callable that conforms to the expected interface&amp;#x2014;accepting an image and returning extracted text with optional bounding boxes. This means you can experiment with cloud OCR APIs, newer open-source models, or domain-specific extractors without touching the core pipeline code.&lt;/p&gt;&lt;p&gt;Switching backends is a single environment variable change. The pipeline doesn&amp;apos;t care where the text comes from&amp;#x2014;it just needs text.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: When building pipelines, make the most likely-to-change components pluggable. You don&amp;apos;t need to implement alternatives on day one&amp;#x2014;just ensure swapping is painless when you do need them.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 6: The Config Decides Everything&lt;/h3&gt;&lt;p&gt;Medical workflows vary enormously. Some clinics need 95% confidence before auto-routing; others accept 60%. Some receive clean PDFs from modern systems; others get fourth-generation photocopy faxes. A one-size-fits-all threshold would fail for at least half of use cases.&lt;/p&gt;&lt;p&gt;I exposed &lt;em&gt;every&lt;/em&gt; tunable as an environment variable. Entity matching thresholds control how similar two values must be to match: 85% for names, 95% for MRNs, exact match for DOBs, 90% for phones. Page assignment has its own knobs: minimum confidence for assignment, ambiguity margin for flagging, and whether to allow unassigned pages at all.&lt;/p&gt;&lt;p&gt;The entity weights&amp;#x2014;how much each identifier type contributes to the overall score&amp;#x2014;are also configurable. A clinic that always has MRNs on every page might weight them higher; one that never uses MRNs might set that weight to zero.&lt;/p&gt;&lt;p&gt;There&amp;apos;s a &lt;code&gt;--show-settings&lt;/code&gt; CLI flag that dumps the current configuration before processing. This makes debugging configuration issues trivial and ensures operators always know what thresholds are active.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Hardcoded thresholds are tech debt. Every magic number should have a config knob, even if you only expose it to power users.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;What I&amp;apos;d Do Differently&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. Add layout-aware extraction&lt;/strong&gt;: Medical forms have predictable structure&amp;#x2014;headers, patient info blocks, tables, signature lines. A layout model (like LayoutLM or DONUT) could extract identifiers more reliably than regex over raw OCR text, especially for structured forms where position matters.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Use contextual embeddings for entity disambiguation&lt;/strong&gt;: When two &amp;quot;Robert Garcia&amp;quot; entries exist in the system, semantic embeddings of the surrounding context could help determine if they&amp;apos;re the same person. &amp;quot;Robert Garcia, cardiology patient, MRN 123456&amp;quot; is different from &amp;quot;Robert Garcia, referred for dermatology&amp;quot; even if the name matches.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. Implement active learning&lt;/strong&gt;: Every page flagged for manual review is a labeling opportunity. Human corrections could feed back into improved extraction patterns, clustering thresholds, and even regex refinements over time.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. Add document type classification&lt;/strong&gt;: Knowing that a page is a &amp;quot;lab result&amp;quot; vs &amp;quot;prescription&amp;quot; vs &amp;quot;referral&amp;quot; would improve entity expectations and weighting. Lab results almost always have MRNs; referral letters often mention multiple patients; prescriptions might only have name and DOB.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Key Takeaways&lt;/h3&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Weight identifiers by reliability&lt;/strong&gt;: MRNs are more trustworthy than names&amp;#x2014;your scoring should reflect this proportionally  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Per-entity fuzzy matching&lt;/strong&gt;: Names need token sorting, dates need exact match after normalization, phones can be partial&amp;#x2014;don&amp;apos;t use one algorithm for all  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Position-aware clustering&lt;/strong&gt;: When multiple patients appear on one page, use anchors to assign surrounding identifiers correctly  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Surface uncertainty explicitly&lt;/strong&gt;: Unassigned and ambiguous outputs are safer than false confidence in high-stakes domains  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Make the right things configurable&lt;/strong&gt;: Thresholds, weights, and backends should be environment-driven, not hardcoded  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;This project is open source and designed for high-volume medical office workflows. The test suite runs with mocked OCR by default, with real Tesseract available via environment flag.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Graph-Augmented RAG: Beyond Vector Similarity for Private Equity Research</title>
      <link>https://nayanachandrika99.github.io/posts/graph-augmented-rag-beyond-vector-similarity-for-private-equity-research/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/graph-augmented-rag-beyond-vector-similarity-for-private-equity-research/</guid>
      <description>How combining knowledge graphs with embeddings outperforms simple semantic search</description>
      <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 08:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/graph-augmented-rag-beyond-vector-similarity-for-private-equity-research/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;How combining knowledge graphs with embeddings outperforms simple semantic search&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; December 3, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Traditional RAG retrieves documents based on how similar they are to your query. That works fine for general Q&amp;amp;A, but it falls apart in specialized domains where &lt;em&gt;relationships&lt;/em&gt; matter as much as &lt;em&gt;content&lt;/em&gt;.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Consider private equity deal research. An investor asks: &amp;quot;Find precedents for a US healthcare roll-up with aggressive add-on strategy.&amp;quot; A pure vector search might return deals with similar descriptions, but miss the platform that acquired 12 add-ons&amp;#x2014;crucial context buried in structural relationships, not text.&lt;/p&gt;&lt;p&gt;I built &lt;strong&gt;DealGraph&lt;/strong&gt;, a graph-augmented RAG agent that fuses semantic embeddings with a knowledge graph. The result: a hybrid retrieval system where structural features&amp;#x2014;sector membership, add-on relationships, exit events&amp;#x2014;augment pure text similarity.&lt;/p&gt;&lt;p&gt;Here&amp;apos;s what I learned.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Problem: Relationships Matter&lt;/h3&gt;&lt;p&gt;Private equity research isn&amp;apos;t just about finding similar text. It&amp;apos;s about finding &lt;em&gt;precedents&lt;/em&gt;: deals that share strategic patterns, not just vocabulary.&lt;/p&gt;&lt;p&gt;Consider two deals:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Deal A&lt;/strong&gt;: Description mentions &amp;quot;healthcare consolidation&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Deal B&lt;/strong&gt;: Description is vague, but the graph shows 8 add-on acquisitions in healthcare  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;A cosine similarity search ranks Deal A higher. But Deal B is the better precedent&amp;#x2014;it demonstrated the actual roll-up strategy the investor is researching.&lt;/p&gt;&lt;p&gt;The solution: &lt;strong&gt;make relationships first-class features&lt;/strong&gt;.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Architecture: A Three-Layer Pipeline&lt;/h3&gt;&lt;p&gt;DealGraph orchestrates retrieval through three coordinated layers:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                         User Query                               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;quot;Find precedents for US healthcare roll-up with add-ons&amp;quot;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                            &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   RETRIEVAL LAYER                                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;          &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  FAISS Index     &amp;#x2502;          &amp;#x2502;    NetworkX Graph          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  (Embeddings)    &amp;#x2502;          &amp;#x2502;    (Relationships)         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                  &amp;#x2502;          &amp;#x2502;                            &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Query &amp;#x2192; Vector  &amp;#x2502;          &amp;#x2502;  Deal &amp;#x2500;&amp;#x252c;&amp;#x2500; IN_SECTOR &amp;#x2500;&amp;#x2500;&amp;#x2192;    &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Top-K by cosine &amp;#x2502;          &amp;#x2502;        &amp;#x251c;&amp;#x2500; ADDON_TO &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2192;    &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;          &amp;#x2502;        &amp;#x2514;&amp;#x2500; EXITED_VIA &amp;#x2500;&amp;#x2192;    &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x2502;                    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                        &amp;#x25bc;                                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              Candidate Pool + Graph Features                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                         &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    RANKING LAYER                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  Gradient Boosting Ranker (DealRanker)                 &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;                                                         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  Features: text_similarity, sector_match, num_addons,  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;           has_exit, degree, region_match, is_platform  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;                                                         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  Training: Reverse-query generation (LLM synthesizes   &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;            queries for known-good deals)               &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                         &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                   REASONING LAYER                                &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  DSPy-Optimized Deal Reasoner                          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;                                                         &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  Outputs:                                               &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  &amp;#x2022; Precedent selection (JSON)                          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  &amp;#x2022; Playbook levers (strategic patterns)                &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  &amp;#x2022; Risk themes                                          &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2502;  &amp;#x2022; Executive narrative                                  &amp;#x2502;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                         &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                  Structured Analysis&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Each layer solves a specific problem:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Retrieval&lt;/strong&gt;: Cast a wide net with hybrid search  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Ranking&lt;/strong&gt;: Learn what &amp;quot;relevance&amp;quot; means from training data  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reasoning&lt;/strong&gt;: Synthesize precedents into actionable insights  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;Lesson 1: Model Relationships as a Graph&lt;/h3&gt;&lt;p&gt;The core insight: deals aren&amp;apos;t documents, they&amp;apos;re &lt;em&gt;entities with relationships&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;I modeled the deal universe as a typed &lt;code&gt;MultiDiGraph&lt;/code&gt;:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Node types&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;NODE_TYPES&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Sector&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Region&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Event&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Snippet&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Edge types capture relationships&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;EDGE_TYPES&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;IN_SECTOR&amp;quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Sector&amp;quot;&lt;/span&gt;&lt;span&gt;),    &lt;/span&gt;&lt;span&gt;# Deal belongs to sector&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;IN_REGION&amp;quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Region&amp;quot;&lt;/span&gt;&lt;span&gt;),    &lt;/span&gt;&lt;span&gt;# Deal in geographic region&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;ADDON_TO&amp;quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;),       &lt;/span&gt;&lt;span&gt;# Add-on acquired by platform&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;EXITED_VIA&amp;quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Event&amp;quot;&lt;/span&gt;&lt;span&gt;),    &lt;/span&gt;&lt;span&gt;# Exit event (IPO, M&amp;amp;A)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;DESCRIBED_IN&amp;quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;&amp;quot;Deal&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Snippet&amp;quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;# Textual evidence&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This structure enables queries that pure text search can&amp;apos;t answer:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &amp;quot;Find all platforms with 5+ add-ons in healthcare&amp;quot;  &lt;/li&gt;&lt;li&gt; &amp;quot;Show deals that exited via IPO in the last 3 years&amp;quot;  &lt;/li&gt;&lt;li&gt; &amp;quot;Find add-ons to Platform X in adjacent sectors&amp;quot;  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The graph becomes a source of &lt;em&gt;features&lt;/em&gt;, not just navigation.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Lesson 2: Extract Graph Features for Ranking&lt;/h3&gt;&lt;p&gt;The key innovation: graph topology becomes a feature vector for ML ranking.&lt;/p&gt;&lt;p&gt;For each candidate deal, I compute structural features:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;FEATURE_NAMES&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;text_similarity&amp;quot;&lt;/span&gt;&lt;span&gt;,      &lt;/span&gt;&lt;span&gt;# From embeddings&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;sector_match&amp;quot;&lt;/span&gt;&lt;span&gt;,         &lt;/span&gt;&lt;span&gt;# Query mentions same sector?&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;region_match&amp;quot;&lt;/span&gt;&lt;span&gt;,         &lt;/span&gt;&lt;span&gt;# Query mentions same region?&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;num_addons&amp;quot;&lt;/span&gt;&lt;span&gt;,           &lt;/span&gt;&lt;span&gt;# How many add-ons acquired&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;has_exit&amp;quot;&lt;/span&gt;&lt;span&gt;,             &lt;/span&gt;&lt;span&gt;# Successful exit?&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;degree&amp;quot;&lt;/span&gt;&lt;span&gt;,               &lt;/span&gt;&lt;span&gt;# Graph connectivity&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;is_platform&amp;quot;&lt;/span&gt;&lt;span&gt;,          &lt;/span&gt;&lt;span&gt;# Platform vs add-on&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;sector_degree&amp;quot;&lt;/span&gt;&lt;span&gt;,        &lt;/span&gt;&lt;span&gt;# How connected is the sector&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;region_degree&amp;quot;&lt;/span&gt;&lt;span&gt;,        &lt;/span&gt;&lt;span&gt;# How connected is the region&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;text_graph_alignment&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;# Do text and graph agree?&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;These features capture things embeddings miss. A deal might have a generic description but 10 recorded add-ons&amp;#x2014;that&amp;apos;s crucial signal.&lt;/p&gt;&lt;p&gt;The heuristic ranking weights these explicitly:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; compute_relevance_score&lt;/span&gt;&lt;span&gt;(candidate):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    features &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; candidate.graph_features&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Text similarity (40%)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    text_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; features[&lt;/span&gt;&lt;span&gt;&amp;apos;text_similarity&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.4&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Sector match (20%)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    sector_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; features[&lt;/span&gt;&lt;span&gt;&amp;apos;sector_match&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.2&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Region match (15%)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    region_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; features[&lt;/span&gt;&lt;span&gt;&amp;apos;region_match&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.15&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Add-on activity (10%)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    addon_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; min&lt;/span&gt;&lt;span&gt;(features[&lt;/span&gt;&lt;span&gt;&amp;apos;num_addons&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 3.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # ... other features&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; text_score &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; sector_score &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; region_score &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; addon_score&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;But hand-tuning weights is fragile. That&amp;apos;s where ML comes in.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Lesson 3: Train a Ranker on Synthetic Query-Deal Pairs&lt;/h3&gt;&lt;p&gt;The challenge: I don&amp;apos;t have labeled relevance data. No one has annotated thousands of &amp;quot;query &amp;#x2192; relevant deals&amp;quot; pairs for private equity.&lt;/p&gt;&lt;p&gt;The solution: &lt;strong&gt;reverse-query generation&lt;/strong&gt;. Instead of labeling data manually, I use an LLM to synthesize queries for known deals:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; generate_reverse_queries&lt;/span&gt;&lt;span&gt;(deal_cluster, llm):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    Given a cluster of similar deals, generate realistic&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    queries that would surface these deals as relevant.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    prompt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    You are a private equity analyst. Given these deals:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span&gt;format_deals(deal_cluster)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    Generate 3 realistic search queries an investor&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    might use to find deals like these.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    queries &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; llm.generate(prompt)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; queries&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This creates training pairs:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Positive&lt;/strong&gt;: (generated query, deals in cluster)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Negative&lt;/strong&gt;: (generated query, random deals from other clusters)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The gradient boosting model learns which feature combinations predict relevance:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; DealRanker&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; __init__&lt;/span&gt;&lt;span&gt;(self):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; GradientBoostingRegressor(&lt;/span&gt;&lt;span&gt;random_state&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; fit&lt;/span&gt;&lt;span&gt;(self, X: np.ndarray, y: np.ndarray):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;quot;&amp;quot;&amp;quot;X = feature vectors, y = relevance scores&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.model.fit(X, y)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; rank&lt;/span&gt;&lt;span&gt;(self, candidates: List[CandidateDeal]):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        X &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; build_feature_matrix(candidates)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.model.predict(X)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        ranked &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sorted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            zip&lt;/span&gt;&lt;span&gt;(candidates, scores),&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            key&lt;/span&gt;&lt;span&gt;=lambda&lt;/span&gt;&lt;span&gt; pair: pair[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            reverse&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; [RankedDeal(c, score, rank) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; rank, (c, score) &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; enumerate&lt;/span&gt;&lt;span&gt;(ranked, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The trained ranker outperforms both pure embedding search and hand-tuned heuristics.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Lesson 4: Optimize Prompts Systematically with DSPy&lt;/h3&gt;&lt;p&gt;The reasoning layer isn&amp;apos;t just a static prompt&amp;#x2014;it&amp;apos;s a &lt;strong&gt;DSPy module&lt;/strong&gt; that can be optimized.&lt;/p&gt;&lt;p&gt;DSPy treats prompts as programs with learnable components. The MIPRO optimizer generates prompt variants and evaluates them against a composite metric:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Composite evaluation metric&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; composite_metric&lt;/span&gt;&lt;span&gt;(example, prediction):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        0.4&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; precision_at_k(prediction.precedents, example.gold_precedents, &lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        0.3&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; llm_judge_playbook_quality(prediction.playbook_levers) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        0.3&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; llm_judge_narrative_coherence(prediction.narrative_summary)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; score&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The optimizer runs ~500 LLM calls to find better prompts. Results on my benchmark:&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt; Metric &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; Naive Prompt &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; MIPRO-Optimized &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; Improvement &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; Precision@3 &lt;/td&gt;&lt;td&gt; 0.42 &lt;/td&gt;&lt;td&gt; 0.68 &lt;/td&gt;&lt;td&gt; +62% &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Playbook Quality &lt;/td&gt;&lt;td&gt; 0.55 &lt;/td&gt;&lt;td&gt; 0.72 &lt;/td&gt;&lt;td&gt; +31% &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Narrative Coherence &lt;/td&gt;&lt;td&gt; 0.61 &lt;/td&gt;&lt;td&gt; 0.78 &lt;/td&gt;&lt;td&gt; +28% &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Composite Score&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0.52&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0.73&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;+40%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;The key insight: &lt;strong&gt;prompts are artifacts that should be versioned, evaluated, and optimized&lt;/strong&gt;&amp;#x2014;not artisanal text you tweak by hand.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Runtime loads optimized version automatically&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;reasoner &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DealReasonerModule()&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;reasoner.load(&lt;/span&gt;&lt;span&gt;&amp;quot;prompts/deal_reasoner/v2_optimized.json&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Falls back to naive baseline if optimization hasn&amp;apos;t run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;Lesson 5: Fail Loudly, Don&amp;apos;t Degrade Silently&lt;/h3&gt;&lt;p&gt;A tempting pattern: wrap everything in try/catch and return empty results on failure.&lt;/p&gt;&lt;p&gt;Don&amp;apos;t do this.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# BAD: Silent degradation&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; reasoner(&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;query, &lt;/span&gt;&lt;span&gt;candidate_deals&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;deals_json)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; Exception&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&amp;quot;precedents&amp;quot;&lt;/span&gt;&lt;span&gt;: [], &lt;/span&gt;&lt;span&gt;&amp;quot;narrative&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;quot;Unable to analyze.&amp;quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# GOOD: Fail loudly&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; reasoner(&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;query, &lt;/span&gt;&lt;span&gt;candidate_deals&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;deals_json)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; Exception&lt;/span&gt;&lt;span&gt; as&lt;/span&gt;&lt;span&gt; e:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    raise&lt;/span&gt;&lt;span&gt; DealReasonerError(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;Deal reasoning failed: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Silent failures hide bugs. In production, I&amp;apos;d rather see an error than serve garbage that looks like a valid response.&lt;/p&gt;&lt;p&gt;This extends to model loading:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Model loading with graceful fallback (good)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; ranker_model_exists():&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    ranker &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DealRanker.load(&lt;/span&gt;&lt;span&gt;&amp;quot;models/deal_ranker_v1.pkl&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    ranker &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; HeuristicRanker()  &lt;/span&gt;&lt;span&gt;# Explicit fallback&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    logger.warning(&lt;/span&gt;&lt;span&gt;&amp;quot;Using heuristic ranker - ML model not found&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# But NOT silent fallback during inference&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ranker.predict_scores(X)  &lt;/span&gt;&lt;span&gt;# This should throw on failure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;The Architecture Decision: NetworkX for V1&lt;/h3&gt;&lt;p&gt;A common question: why not Neo4j or a &amp;quot;real&amp;quot; graph database?&lt;/p&gt;&lt;p&gt;For V1 with &amp;lt;1000 nodes, NetworkX is the right choice:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Zero infrastructure&lt;/strong&gt;: No database to deploy  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Fast iteration&lt;/strong&gt;: Graph structure can change without migrations  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Rich algorithms&lt;/strong&gt;: NetworkX has excellent graph algorithms out of the box  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Good enough&lt;/strong&gt;: In-memory traversal is plenty fast at this scale  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The migration triggers are clear:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Graph size exceeds 10K nodes  &lt;/li&gt;&lt;li&gt; Query latency becomes unacceptable (&amp;gt;1s)  &lt;/li&gt;&lt;li&gt; Need for persistence across restarts  &lt;/li&gt;&lt;li&gt; Multi-user concurrent access  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Until then, keep it simple.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;What I&amp;apos;d Do Differently&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. Use a vector database with filtering&lt;/strong&gt;: FAISS is fast but doesn&amp;apos;t support metadata filtering. I filter post-hoc, which is wasteful. Pinecone or Weaviate would let me push filters into the index.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Add temporal reasoning&lt;/strong&gt;: Deals have dates. A 2010 precedent might be less relevant than a 2023 one. The graph should encode temporal relationships.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. Implement online learning&lt;/strong&gt;: As users interact with results, their clicks are implicit relevance labels. The ranker should learn from this feedback.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. Build evaluation into the pipeline&lt;/strong&gt;: I added benchmarks late. Starting with evaluation infrastructure would have caught issues earlier.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Key Takeaways&lt;/h3&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Relationships are features&lt;/strong&gt;: Knowledge graphs aren&amp;apos;t just for navigation&amp;#x2014;extract structural features and feed them to ML models.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hybrid retrieval beats pure embeddings&lt;/strong&gt;: Text similarity is necessary but not sufficient. Combine it with domain-specific signals.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Synthesize training data when labels don&amp;apos;t exist&lt;/strong&gt;: Reverse-query generation creates high-quality training pairs without manual annotation.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Treat prompts as code&lt;/strong&gt;: Version them, optimize them with metrics, and A/B test before deploying.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Keep infrastructure simple&lt;/strong&gt;: Start with in-memory solutions. Migrate when you hit actual scale limits, not imagined ones.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;DealGraph is available on GitHub. The synthetic data generator creates a realistic deal universe for testing, and the full training pipeline runs on commodity hardware.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/&quot;&gt;home&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Mini-Town: What I Learned from an Abandoned Agent Simulation</title>
      <link>https://nayanachandrika99.github.io/posts/mini-town-what-i-learned-from-an-abandoned-agent-simulation/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/mini-town-what-i-learned-from-an-abandoned-agent-simulation/</guid>
      <description>An experimental project that taught me more by failing than it would have by succeeding</description>
      <pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 03:35:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/mini-town-what-i-learned-from-an-abandoned-agent-simulation/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;An experimental project that taught me more by failing than it would have by succeeding&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; November 30, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Mini-Town was an ambitious experiment that I eventually abandoned. The goal was to build an agent-based simulation where autonomous characters&amp;#x2014;powered by LLMs&amp;#x2014;would perceive their environment, form memories, reflect on experiences, and generate plans. Instead of hand-tuning prompts, I would use DSPy&amp;apos;s GEPA optimizer to automatically improve the agents&amp;apos; &amp;quot;brains&amp;quot; based on collected data.&lt;/p&gt;&lt;p&gt;The project reached a functional state. Agents moved around a 2D world, scored observations for importance, stored memories with vector embeddings, and generated daily plans. I got a 17% improvement in observation quality using compiled prompts versus hand-written ones. But the complexity grew faster than the value, and I ultimately set it aside.&lt;/p&gt;&lt;p&gt;This post isn&amp;apos;t a tutorial on how to build what I built. It&amp;apos;s a collection of the things I learned along the way&amp;#x2014;lessons that transferred to other projects and shaped how I think about LLM-based systems.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Original Idea&lt;/h3&gt;&lt;p&gt;The project emerged from a simple question: can you treat LLM prompts as programs that can be automatically optimized?&lt;/p&gt;&lt;p&gt;When you hand-write a prompt, you&amp;apos;re making guesses about what wording will produce the behavior you want. &amp;quot;Rate this observation from 1-10 based on importance&amp;quot; might work, but &amp;quot;Evaluate the significance of this event on a scale of 1-10, where 10 means life-changing and 1 means completely mundane&amp;quot; might work better. Or worse. You don&amp;apos;t know until you try, and trying is expensive and slow.&lt;/p&gt;&lt;p&gt;DSPy offers an alternative. Instead of writing prose instructions and hoping for the best, you define a typed signature&amp;#x2014;a formal specification of what inputs the LLM receives and what outputs it should produce. The framework then uses training examples to search for prompts that produce correct outputs. It&amp;apos;s treating prompt engineering as an optimization problem rather than an art form.&lt;/p&gt;&lt;p&gt;The approach is compelling because it scales differently than manual iteration. If you have 50 training examples and can afford 1000 LLM calls during optimization, the optimizer can explore far more prompt variations than a human could in a week. And because the optimization is metric-driven, it&amp;apos;s less susceptible to the subtle biases humans bring to prompt writing.&lt;/p&gt;&lt;p&gt;Mini-Town was the testbed for this idea. Build a simulation with multiple agents, log their cognitive outputs, use those logs as training data, and compile improved prompts. The hypothesis: compiled agents would behave more consistently and intelligently than uncompiled ones, and we could measure the difference.&lt;/p&gt;&lt;p&gt;The simulation itself was inspired by Stanford&amp;apos;s &amp;quot;Generative Agents&amp;quot; paper&amp;#x2014;the one with the AI town where characters formed relationships, remembered conversations, and threw parties. I wasn&amp;apos;t trying to replicate that research; I was using the simulation as a vehicle to test prompt optimization. The town was scaffolding. The real experiment was the compilation.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;What Got Built&lt;/h3&gt;&lt;p&gt;The architecture had three main components, each representing a different layer of the system.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          SIMULATION ENVIRONMENT                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  2D World (Grid)   &amp;#x2502;      &amp;#x2502;  Agent Population                         &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502;      &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;                        &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; Locations       &amp;#x2502;&amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  Alice, Bob, Maria, ...                   &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; Entities        &amp;#x2502;      &amp;#x2502;  Each running cognitive loop              &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; Time (ticks)    &amp;#x2502;      &amp;#x2502;                                           &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                  &amp;#x2502; perceive environment&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                  &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          COGNITIVE LOOP (per agent)                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; PERCEIVE &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  SCORE   &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  STORE   &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; REFLECT  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  PLAN  &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;          &amp;#x2502;    &amp;#x2502;          &amp;#x2502;    &amp;#x2502;          &amp;#x2502;    &amp;#x2502;          &amp;#x2502;    &amp;#x2502;        &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; Detect   &amp;#x2502;    &amp;#x2502; Rate 1-10&amp;#x2502;    &amp;#x2502; Embed +  &amp;#x2502;    &amp;#x2502; Synthesize&amp;#x2502;    &amp;#x2502; Daily  &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; nearby   &amp;#x2502;    &amp;#x2502; importance&amp;#x2502;    &amp;#x2502; save to  &amp;#x2502;    &amp;#x2502; insights &amp;#x2502;    &amp;#x2502; schedule&amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; entities &amp;#x2502;    &amp;#x2502; (DSPy)   &amp;#x2502;    &amp;#x2502; memory   &amp;#x2502;    &amp;#x2502; (DSPy)   &amp;#x2502;    &amp;#x2502; (DSPy) &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;               &amp;#x2502;               &amp;#x2502;               &amp;#x2502;               &amp;#x2502;      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;               &amp;#x2502;               &amp;#x2502;               &amp;#x2502;               &amp;#x2502;      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;               &amp;#x25bc;               &amp;#x25bc;               &amp;#x25bc;               &amp;#x25bc;      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;                DSPy Modules                          &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510; &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;  &amp;#x2502; ScoreImportance&amp;#x2502;  &amp;#x2502; ReflectOnDay  &amp;#x2502;  &amp;#x2502; MakePlan&amp;#x2502; &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;  &amp;#x2502; (typed sig)    &amp;#x2502;  &amp;#x2502; (typed sig)   &amp;#x2502;  &amp;#x2502; (typed) &amp;#x2502; &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518; &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;                                                      &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2502;  GEPA Optimizer: Compiles signatures &amp;#x2192; better prompts&amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;       &amp;#x2502;         &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          MEMORY SYSTEM (DuckDB + VSS)                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Weighted Retrieval: score = &amp;#x3b1;&amp;#xd7;relevance + &amp;#x3b2;&amp;#xd7;recency + &amp;#x3b3;&amp;#xd7;importance   &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                                                                         &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                 &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502; Relevance   &amp;#x2502;    &amp;#x2502; Recency     &amp;#x2502;    &amp;#x2502; Importance  &amp;#x2502;                 &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502; (cosine     &amp;#x2502;    &amp;#x2502; (time decay &amp;#x2502;    &amp;#x2502; (score at   &amp;#x2502;                 &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2502;  similarity)&amp;#x2502;    &amp;#x2502;  function)  &amp;#x2502;    &amp;#x2502;  storage)   &amp;#x2502;                 &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                 &amp;#x2502; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518; &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  Embeddings: all-MiniLM-L6-v2 (384-dim, local)                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;The Cognitive Loop&lt;/h4&gt;&lt;p&gt;Each agent ran a perceive-score-store-reflect-plan cycle that mimicked the approach from the Generative Agents paper, adapted for DSPy&amp;apos;s typed program model.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Perception&lt;/strong&gt;: On every simulation tick (every 2 seconds), agents would detect nearby entities. If Alice walked past Bob, Bob&amp;apos;s perception system would register &amp;quot;Alice is nearby&amp;quot; as a raw observation. The perception range was configurable&amp;#x2014;agents could &amp;quot;see&amp;quot; anything within a certain radius.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Scoring&lt;/strong&gt;: Raw observations aren&amp;apos;t all equally important. Seeing a tree is mundane. Seeing someone collapse is urgent. The scorer was a DSPy module that took an observation and the agent&amp;apos;s current context (their personality, goals, recent thoughts) and produced an importance score from 1-10. High scores triggered downstream processing; low scores were stored but didn&amp;apos;t prompt reflection.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;: Every scored observation went into the memory database. The storage included not just the text and score, but also a vector embedding and a timestamp. This enabled the retrieval system to find memories by meaning, not just by recency.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Reflection&lt;/strong&gt;: When accumulated importance exceeded a threshold, the agent would pause to reflect. The reflector was another DSPy module that took recent memories, retrieved semantically similar older memories, and synthesized a high-level insight. &amp;quot;I&amp;apos;ve noticed Maria keeps mentioning her garden&amp;quot; might become &amp;quot;Maria is passionate about gardening&amp;#x2014;I should ask her about it.&amp;quot; Reflections themselves were stored as high-importance memories.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Planning&lt;/strong&gt;: Periodically (configurable, but typically once per simulated &amp;quot;day&amp;quot;), agents would generate a schedule. The planner received the agent&amp;apos;s goal, relevant memories, and any pending invitations, and produced a time-blocked plan: &amp;quot;7am: wake up, 8am: go to cafe, 10am: visit Maria&amp;apos;s garden, 12pm: lunch at diner.&amp;quot;&lt;/p&gt;&lt;p&gt;The loop ran continuously. Every tick, agents perceived. Interesting perceptions got scored. Accumulating importance triggered reflection. Time passing triggered planning. Actions came from plans. The result was agents that appeared to make decisions, though everything was actually just LLM calls wrapped in state management.&lt;/p&gt;&lt;h4&gt;The Memory System&lt;/h4&gt;&lt;p&gt;Memory in agent simulations is deceptively complex. Humans don&amp;apos;t just remember things; they remember things that are relevant to their current situation, weighted by how recent they are and how important they seemed when they happened. Implementing that requires more than a list sorted by timestamp.&lt;/p&gt;&lt;p&gt;I used DuckDB with the Vector Similarity Search (VSS) extension. DuckDB gave me SQL for structured queries, and VSS gave me approximate nearest-neighbor search over embedding vectors. The combination let me implement weighted retrieval:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;score = &amp;#x3b1; &amp;#xd7; relevance + &amp;#x3b2; &amp;#xd7; recency + &amp;#x3b3; &amp;#xd7; importance&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Relevance&lt;/strong&gt; was cosine similarity between the query embedding (what the agent was currently thinking about) and the memory embedding. This made semantic recall possible. An agent thinking about &amp;quot;parties&amp;quot; could retrieve a memory containing &amp;quot;Maria&amp;apos;s get-together&amp;quot; even though the word &amp;quot;party&amp;quot; never appeared.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Recency&lt;/strong&gt; was an exponential decay based on how long ago the memory was formed. Recent memories scored higher. The decay rate was tunable; faster decay made agents forgetful, slower decay gave them longer attention spans.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Importance&lt;/strong&gt; was the score assigned at storage time. Salient memories&amp;#x2014;the ones that felt significant when they happened&amp;#x2014;remained accessible longer than mundane observations.&lt;/p&gt;&lt;p&gt;The weights (&amp;#x3b1;, &amp;#x3b2;, &amp;#x3b3;) controlled the behavior. High &amp;#x3b1; meant agents prioritized semantic relevance (useful for focused tasks). High &amp;#x3b2; meant agents focused on recent events (useful for reactive behavior). High &amp;#x3b3; meant agents surfaced important moments regardless of recency (useful for long-term relationship tracking).&lt;/p&gt;&lt;p&gt;Getting these weights right was one of the harder engineering problems in the project. I&amp;apos;ll return to this later.&lt;/p&gt;&lt;h4&gt;The DSPy Modules&lt;/h4&gt;&lt;p&gt;Each cognitive function was implemented as a DSPy program with a typed signature. Signatures define the inputs and outputs formally, which lets the optimizer understand what it&amp;apos;s trying to improve.&lt;/p&gt;&lt;p&gt;The scorer signature looked like this:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ScoreImportance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dspy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Signature&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;Score how important an observation is to this agent.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    observation: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dspy.InputField(&lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;What the agent just observed&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    agent_personality: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dspy.InputField(&lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;The agent&amp;apos;s personality traits&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    agent_goal: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dspy.InputField(&lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;The agent&amp;apos;s current goal&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    recent_context: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dspy.InputField(&lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;What the agent was just thinking about&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    score: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dspy.OutputField(&lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;Importance score from 1-10&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    reasoning: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dspy.OutputField(&lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;Why this observation matters or doesn&amp;apos;t&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The reflector and planner had similar structures. Each defined what went in and what came out, with human-readable descriptions that helped both the optimizer and the developer understand the expected behavior.&lt;/p&gt;&lt;p&gt;The baseline implementation wrapped these signatures in &lt;code&gt;dspy.ChainOfThought&lt;/code&gt;, which prompted the LLM to reason step-by-step before producing outputs. Chain of thought improved accuracy on the importance scoring task, at the cost of additional latency and tokens.&lt;/p&gt;&lt;p&gt;The compiled implementation used GEPA-optimized prompts that achieved similar or better accuracy with fewer tokens. More on compilation in the next section.&lt;/p&gt;&lt;h4&gt;The Frontend&lt;/h4&gt;&lt;p&gt;The frontend was a Next.js application that connected to the backend via WebSocket. It rendered a 2D map showing agent positions, displayed speech bubbles when agents &amp;quot;spoke&amp;quot; their plans or observations, and provided a diagnostic panel for inspecting agent state.&lt;/p&gt;&lt;p&gt;The visual representation was mostly for debugging. Watching an agent walk in circles told me its pathfinding was broken. Watching an agent stand still told me its LLM calls were timing out. Watching two agents never interact told me the perception radius was too small.&lt;/p&gt;&lt;p&gt;Most development happened headless (running the backend with logging and no frontend), but the visual mode was invaluable for the moments when logs didn&amp;apos;t explain what was happening.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Compilation Experiment&lt;/h3&gt;&lt;p&gt;The core experiment was simple: could GEPA find better prompts than I could write by hand?&lt;/p&gt;&lt;p&gt;GEPA (Generalized Evolutionary Prompt Acceleration) is a DSPy optimizer that uses evolutionary strategies to search for improved prompts. You provide a training set of input-output examples (called &amp;quot;seeds&amp;quot;), a metric function that scores outputs, and a search budget. GEPA generates prompt variations, evaluates them against the training set, and iteratively improves toward the best-performing variants.&lt;/p&gt;&lt;p&gt;The process took about 15 minutes on a T4 GPU (Google Colab free tier). Running 80 optimization iterations over 40 seed examples, GEPA evaluated roughly 3000 prompt variants to find the best-performing one. The result was a compiled scorer that outperformed my hand-written baseline.&lt;/p&gt;&lt;p&gt;In a controlled 5-minute A/B test:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Uncompiled agents generated 38 observations, avg quality score 0.67  &lt;/li&gt;&lt;li&gt; Compiled agents generated 53 observations, avg quality score 0.78  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;That&amp;apos;s a 17% improvement in observation quality and 39% more observations in the same time. The compiled scorer was also 46% faster in isolation tests (0.46s vs 0.85s latency) because the optimized prompt was more concise.&lt;/p&gt;&lt;p&gt;The reflection and planning modules couldn&amp;apos;t be compiled as easily because their outputs were harder to evaluate. What makes a &amp;quot;good&amp;quot; reflection? The question is inherently subjective. I could score importance reasonably consistently (a 7 is more significant than a 3), but I couldn&amp;apos;t score reflections or plans reliably enough to train an optimizer.&lt;/p&gt;&lt;p&gt;This asymmetry&amp;#x2014;some cognitive functions are easy to evaluate, others are hard&amp;#x2014;limited how far compilation could take the project.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Why I Abandoned It&lt;/h3&gt;&lt;p&gt;Several factors converged to make the project not worth continuing.&lt;/p&gt;&lt;h4&gt;The Evaluation Problem&lt;/h4&gt;&lt;p&gt;How do you measure whether an agent simulation is &amp;quot;good&amp;quot;?&lt;/p&gt;&lt;p&gt;I defined metrics. Observation quality was scored by an LLM judge. Plan coherence was measured by whether the plan&amp;apos;s activities matched the agent&amp;apos;s stated goals. Event attendance was measured by whether agents showed up to parties they&amp;apos;d been invited to.&lt;/p&gt;&lt;p&gt;But these were proxies for something I couldn&amp;apos;t directly measure: whether the agents were interesting to watch. The 17% improvement in observation quality didn&amp;apos;t translate into 17% more interesting behavior. It was an improvement on a metric that didn&amp;apos;t capture what mattered.&lt;/p&gt;&lt;p&gt;The fundamental problem with open-ended simulations is that &amp;quot;good behavior&amp;quot; is defined by human intuition, not by formal criteria. When Alice decides to visit the park, is that good or bad? It depends on what Alice has been doing, what her goals are, who else is at the park, what time of day it is, and&amp;#x2014;critically&amp;#x2014;what the observer finds interesting. I couldn&amp;apos;t formalize any of that.&lt;/p&gt;&lt;p&gt;I tried using &amp;quot;town score,&amp;quot; a weighted combination of observation quality, reflection depth, plan coherence, and event attendance. The town score improved with compilation. But watching an agent with a high town score felt the same as watching an agent with a medium town score. The metric didn&amp;apos;t correlate with the experience.&lt;/p&gt;&lt;h4&gt;The Cost Problem&lt;/h4&gt;&lt;p&gt;Even using cheap models (Llama 3.2 on &lt;a href=&quot;http://together.ai/&quot; target=&quot;_blank&quot;&gt;Together.ai&lt;/a&gt; at &amp;#x24;0.20 per million tokens), the API costs added up quickly when running multiple agents continuously.&lt;/p&gt;&lt;p&gt;Each tick involved multiple LLM calls: scoring observations, occasionally reflecting, checking if plans needed updating. With 5 agents and a 2-second tick, the simulation made roughly 250 LLM calls per minute. At ~500 tokens per call (including prompt and response), that was 125,000 tokens per minute, or about &amp;#x24;1.50 per hour.&lt;/p&gt;&lt;p&gt;That sounds cheap, but I needed sustained simulation to generate training data. GEPA optimization required a corpus of logged examples, which meant running the simulation for hours to collect data before I could even start the optimization process. Then I needed to run the simulation again to A/B test the results. Development required many such cycles.&lt;/p&gt;&lt;h4&gt;The Complexity Problem&lt;/h4&gt;&lt;p&gt;The cognitive loop introduced cascade failures that were difficult to debug and impossible to prevent.&lt;/p&gt;&lt;p&gt;If the scorer ran slowly (maybe the API was throttled, maybe the model was overloaded), observations accumulated faster than they could be processed. The observation queue grew. Old observations became stale. Reflections based on stale observations were nonsensical.&lt;/p&gt;&lt;p&gt;If reflection failed (maybe the LLM produced an unparseable response), the agent&amp;apos;s accumulated importance wasn&amp;apos;t reset. It would try to reflect on the same observations again next tick, fail again, and enter an infinite loop of failed reflections.&lt;/p&gt;&lt;p&gt;If planning failed, the agent defaulted to random walking. But random walking generated more observations, which triggered more scoring and more reflection, which increased load on the LLM, which made planning more likely to fail due to rate limits.&lt;/p&gt;&lt;p&gt;Each component&amp;apos;s failure propagated to everything downstream. Debugging required understanding the interaction of five different subsystems, each with its own state and failure modes. I spent more time building monitoring and debugging tools than I spent building the actual simulation.&lt;/p&gt;&lt;h4&gt;The Ambiguity Problem&lt;/h4&gt;&lt;p&gt;Agent behavior in open-ended simulations is inherently ambiguous. This made creating training data painful.&lt;/p&gt;&lt;p&gt;GEPA requires seed examples: input-output pairs where the output is the &amp;quot;correct&amp;quot; answer. For the scorer, I could produce these. &amp;quot;Observation: Maria invited me to her party. Score: 8. Reason: Social invitation with future commitment.&amp;quot; That&amp;apos;s defensible.&lt;/p&gt;&lt;p&gt;But what about edge cases? &amp;quot;Observation: I see a tree.&amp;quot; Is that a 1 or a 2? Depends on whether the agent cares about nature. &amp;quot;Observation: Bob walked past without saying hello.&amp;quot; Is that significant? Depends on whether Bob usually says hello. &amp;quot;Observation: The sun is setting.&amp;quot; Is that important? Depends on whether the agent needs to get home before dark.&lt;/p&gt;&lt;p&gt;I created an annotation guide to standardize scoring, then had a friend score some examples independently to check agreement. Cohen&amp;apos;s kappa was 0.65&amp;#x2014;substantial agreement, but not perfect. The residual disagreement meant the training data had noise, which limited how much GEPA could improve.&lt;/p&gt;&lt;p&gt;Better annotation would have required more annotators, more examples, and a more rigorous process. At that point, I was building a labeling pipeline, not an agent simulation.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;What I Learned&lt;/h3&gt;&lt;p&gt;Despite abandoning the project, it shaped how I approach LLM systems now.&lt;/p&gt;&lt;h4&gt;Structure Your Outputs from the Start&lt;/h4&gt;&lt;p&gt;Early versions of the cognitive loop logged free-form text. &amp;quot;Agent observed something interesting&amp;quot; tells you nothing when you&amp;apos;re trying to debug why an agent walked into a wall.&lt;/p&gt;&lt;p&gt;Later versions logged structured data:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Observation content (what the agent saw)  &lt;/li&gt;&lt;li&gt; Timestamp (when it happened)  &lt;/li&gt;&lt;li&gt; Importance score (how significant it was)  &lt;/li&gt;&lt;li&gt; Scoring latency (how long the LLM took)  &lt;/li&gt;&lt;li&gt; Failure reason (if scoring failed, why)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Structured outputs made debugging possible. When an agent started behaving erratically, I could query the logs: &amp;quot;Show me all observations with score &amp;gt; 7 from the last 5 minutes.&amp;quot; I could trace causality: &amp;quot;This high-importance observation triggered reflection at 10:42, which produced this insight, which affected the plan generated at 10:45.&amp;quot;&lt;/p&gt;&lt;p&gt;Structured outputs also made training data collection tractable. Each logged item had the fields GEPA expected: input (observation + context) and output (score + reasoning). I didn&amp;apos;t have to post-process logs into training examples; they were already in the right format.&lt;/p&gt;&lt;p&gt;If I&amp;apos;d started structured from day one, I would have saved days of refactoring. Every project I&amp;apos;ve started since then begins with a schema for the data it will log.&lt;/p&gt;&lt;h4&gt;Measure Latency Before Complexity&lt;/h4&gt;&lt;p&gt;I measured latency on Day 2 specifically to avoid a trap: building a complex system that couldn&amp;apos;t run in real time.&lt;/p&gt;&lt;p&gt;The process was simple. I wrapped every LLM call in a timing function:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; def&lt;/span&gt;&lt;span&gt; timed_llm_call&lt;/span&gt;&lt;span&gt;(func, &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;args, &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;kwargs):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    start &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; time.perf_counter()&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; func(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;args, &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;kwargs)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    elapsed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; time.perf_counter() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    latency_tracker.record(func.&lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;, elapsed)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;After a few minutes of simulation, I had a latency distribution. The scorer&amp;apos;s p50 was 0.8 seconds; p95 was 1.4 seconds. The reflector&amp;apos;s p50 was 1.2 seconds; p95 was 2.1 seconds.&lt;/p&gt;&lt;p&gt;The simulation tick was 2 seconds. That meant the cognitive loop had to complete its per-tick work (scoring + maybe reflecting) in under 2 seconds, or the simulation would lag. With scoring alone taking 1.4 seconds at p95, I had 0.6 seconds of headroom. Reflection was out of the question on every tick; it would have to happen on a slower schedule.&lt;/p&gt;&lt;p&gt;This constraint informed every design decision. I couldn&amp;apos;t add a second LLM call per tick. I couldn&amp;apos;t make prompts longer (more tokens = more latency). I couldn&amp;apos;t use larger models. The latency ceiling was load-bearing.&lt;/p&gt;&lt;p&gt;In later projects, I measure latency first, before adding features. You can always add complexity; you can&amp;apos;t always remove latency.&lt;/p&gt;&lt;h4&gt;Compilation Works, but Training Data Is Hard&lt;/h4&gt;&lt;p&gt;GEPA did what it promised: given training examples, it found better prompts. The 17% improvement was real. The optimized prompts were shorter and faster. The mechanism works.&lt;/p&gt;&lt;p&gt;But creating training examples required human judgment about what &amp;quot;correct&amp;quot; meant, and that judgment was expensive to exercise consistently. For the scorer, I spent three hours annotating 40 seed examples. Each example required reading an observation, imagining the agent&amp;apos;s context, deciding on a score, and writing a rationale. Forty examples was enough to show improvement but not enough to reach the optimizer&amp;apos;s full potential. GEPA&amp;apos;s documentation suggests 200+ examples for best results.&lt;/p&gt;&lt;p&gt;Generating 200 examples at 4 minutes per example would take 13 hours. That&amp;apos;s a full weekend of labeling. And if my scoring criteria evolved during that time (which they would, because annotating forces you to clarify your standards), earlier examples would be inconsistent with later ones.&lt;/p&gt;&lt;p&gt;For tasks with clear correctness criteria (classification, extraction, structured output), DSPy compilation is worth the effort. You can generate training data programmatically or collect it from production usage. But for tasks where correctness is subjective (open-ended generation, creative writing, behavioral simulation), the training data problem dominates everything else.&lt;/p&gt;&lt;h4&gt;Memory Retrieval Is Surprisingly Finicky&lt;/h4&gt;&lt;p&gt;The vector search used a weighted combination: relevance (semantic similarity), recency (time decay), and importance (score at storage time). Getting the weights right required extensive tuning.&lt;/p&gt;&lt;p&gt;In early tests, I set the weights to &amp;#x3b1;=1.0, &amp;#x3b2;=0.0, &amp;#x3b3;=0.0&amp;#x2014;pure relevance. The system seemed to work. Agents retrieved memories that were semantically related to their current situation. Great.&lt;/p&gt;&lt;p&gt;Then I tested a scenario where an agent needed to remember what happened earlier that day. Pure relevance failed. The agent retrieved a semantically similar but weeks-old memory instead of the relevant recent one. I needed recency.&lt;/p&gt;&lt;p&gt;Then I tested a scenario where an agent needed to remember an important conversation from last month. With high recency weight, that memory was suppressed by mundane recent observations. I needed importance.&lt;/p&gt;&lt;p&gt;Different scenarios needed different weights:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Emergencies: high relevance (&amp;#x3b1;=0.7), find anything about this topic  &lt;/li&gt;&lt;li&gt; Social planning: high recency (&amp;#x3b2;=0.6), what happened today  &lt;/li&gt;&lt;li&gt; Long-term relationships: high importance (&amp;#x3b3;=0.7), filter out noise  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I built a grid search to find optimal weights per scenario type, but the scenarios themselves were hard to define rigorously. The retrieval system worked, but tuning it was a research project in itself.&lt;/p&gt;&lt;p&gt;The lesson: test retrieval with adversarial examples, not just the happy path. If you only test with data where relevance and recency correlate (recent events that happen to be semantically related), you&amp;apos;ll think your system works when it&amp;apos;s actually ignoring half its inputs.&lt;/p&gt;&lt;h4&gt;Provider Flexibility Saves Projects&lt;/h4&gt;&lt;p&gt;The project nearly stalled on Day 5 when Groq&amp;apos;s free tier hit rate limits mid-experiment.&lt;/p&gt;&lt;p&gt;I was running an A/B comparison: 20 minutes of uncompiled simulation, then 20 minutes of compiled simulation, then compare the logs. Halfway through the uncompiled run, Groq started returning 429 errors. Rate limited. The remaining observations went unscored, ruining the comparison.&lt;/p&gt;&lt;p&gt;The fix was switching to &lt;a href=&quot;http://together.ai/&quot; target=&quot;_blank&quot;&gt;Together.ai&lt;/a&gt;, which had no rate limits on their serverless models. But I&amp;apos;d hardcoded Groq as the provider. DSPy was configured to use Groq. The API key was a Groq key. Changing providers required refactoring the configuration to support multiple backends.&lt;/p&gt;&lt;p&gt;I added a config file:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;llm&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  provider&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;together&lt;/span&gt;&lt;span&gt;  # or groq, or openai&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  model&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;meta-llama/Llama-3.2-3B-Instruct-Turbo&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  api_key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;#x24;{TOGETHER_API_KEY}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;And a dispatch function:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; configure_dspy&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; load_config()&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; config.llm.provider &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;together&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        lm &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; dspy.LM(&lt;/span&gt;&lt;span&gt;&amp;quot;together_ai/&amp;quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; config.llm.model)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    elif&lt;/span&gt;&lt;span&gt; config.llm.provider &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &amp;quot;groq&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        lm &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; dspy.LM(&lt;/span&gt;&lt;span&gt;&amp;quot;groq/&amp;quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; config.llm.model)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # ... etc&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    dspy.configure(&lt;/span&gt;&lt;span&gt;lm&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;lm)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;After that, switching providers was a one-line config change. The experiment restarted in minutes.&lt;/p&gt;&lt;p&gt;Every project I build now has provider abstraction from day one. The free tier that works today will hit limits tomorrow. The model that&amp;apos;s fastest now will be deprecated next month. The only constant is change, and the only defense is abstraction.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;What Remains&lt;/h3&gt;&lt;p&gt;The code still runs. The frontend renders agents moving through a 2D world. The cognitive loop still scores observations, stores memories, and generates plans. The compiled scorer still outperforms the baseline.&lt;/p&gt;&lt;p&gt;But I don&amp;apos;t work on it anymore. The lessons transferred to other projects:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; The voice agent optimization system uses similar memory structures for storing failed calls  &lt;/li&gt;&lt;li&gt; The fraud detection system uses structured failure logging to enable downstream analysis  &lt;/li&gt;&lt;li&gt; The deal research agent uses provider abstraction for resilient LLM access  &lt;/li&gt;&lt;li&gt; Everything I build now measures latency before adding complexity  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Mini-Town was the laboratory where I figured out how to build those things. The experiment failed to produce a compelling simulation, but it succeeded at teaching me how to build robust LLM systems.&lt;/p&gt;&lt;p&gt;Sometimes the value of a project isn&amp;apos;t in what it produces. It&amp;apos;s in what it teaches you.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Technical Details&lt;/h3&gt;&lt;p&gt;For those interested in the specifics:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Backend&lt;/strong&gt;: FastAPI (Python 3.11), DuckDB with VSS extension for vector search  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js/React with TailwindCSS, WebSocket for real-time updates  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;LLM&lt;/strong&gt;: &lt;a href=&quot;http://together.ai/&quot; target=&quot;_blank&quot;&gt;Together.ai&lt;/a&gt;&amp;apos;s Llama 3.2-3B-Instruct-Turbo (serverless, ~&amp;#x24;0.20/M tokens)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;DSPy&lt;/strong&gt;: GEPA optimizer for prompt compilation, ChainOfThought for base modules  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Embeddings&lt;/strong&gt;: all-MiniLM-L6-v2 (384-dimensional, runs locally)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Compilation budget&lt;/strong&gt;: 40 seed examples, 80 GEPA iterations, ~15 minutes on T4 GPU  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The 17% improvement came from comparing observation quality scores (LLM-judged) between compiled and uncompiled runs over 5-minute simulations with 3 agents. The compiled scorer was tested in isolation showing 46% latency reduction (0.46s vs 0.85s).&lt;/p&gt;&lt;p&gt;Total development time was approximately 5 days of focused work, spread over several weeks. Total API cost was under &amp;#x24;5, mostly consumed during the final A/B testing phase.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;Mini-Town is archived on GitHub. The code works for what it was built to do&amp;#x2014;it&amp;apos;s just not something I&amp;apos;m building further.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>When VLMs Need a Human Safety Net: Building Document Extraction with Confidence Calibration</title>
      <link>https://nayanachandrika99.github.io/posts/when-vlms-need-a-human-safety-net-building-document-extraction-with-confidence-calibration/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/when-vlms-need-a-human-safety-net-building-document-extraction-with-confidence-calibration/</guid>
      <description>How I designed a medical document processing system where the model knows when to ask for help</description>
      <pubDate>Wed, 19 Nov 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 03:36:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/when-vlms-need-a-human-safety-net-building-document-extraction-with-confidence-calibration/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;How I designed a medical document processing system where the model knows when to ask for help&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; November 19, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;p&gt;Healthcare document processing has a fundamental constraint: you can&amp;apos;t be wrong. A misread medication dosage or transposed patient ID isn&amp;apos;t a minor error&amp;#x2014;it&amp;apos;s a potential safety incident. Yet vision-language models, for all their power, are confidently wrong with uncomfortable frequency.&lt;/p&gt;&lt;p&gt;Inspired by &lt;a href=&quot;https://tennr.com/&quot; target=&quot;_blank&quot;&gt;Tennr&amp;apos;s&lt;/a&gt; approach to healthcare automation, I built a document extraction platform that embraces this reality. The core insight: &lt;strong&gt;the model&amp;apos;s job isn&amp;apos;t just to extract data&amp;#x2014;it&amp;apos;s to know when it shouldn&amp;apos;t be trusted&lt;/strong&gt;. When confidence is low, the system routes to human review. When humans correct errors, those corrections feed back into model improvement.&lt;/p&gt;&lt;p&gt;Here&amp;apos;s what I learned building it.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Problem: Models Don&amp;apos;t Know What They Don&amp;apos;t Know&lt;/h3&gt;&lt;p&gt;Vision-language models like olmOCR can extract structured data from complex medical forms with impressive accuracy. But they have a fatal flaw: their confidence scores are poorly calibrated. A model that outputs 90% confidence should be right 90% of the time&amp;#x2014;but in practice, these models are often overconfident on novel inputs and underconfident on routine ones.&lt;/p&gt;&lt;p&gt;This creates two failure modes:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Overconfidence&lt;/strong&gt;: The model extracts &amp;quot;150 mg&amp;quot; when the handwriting actually shows &amp;quot;180 mg&amp;quot;, reports 95% confidence, and the error sails through to production.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Underconfidence&lt;/strong&gt;: The model correctly extracts a clearly printed name, reports 70% confidence, and sends it to human review unnecessarily&amp;#x2014;wasting reviewer time.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;The solution isn&amp;apos;t better models. It&amp;apos;s &lt;strong&gt;calibration&lt;/strong&gt;: learning to transform raw model outputs into reliable confidence scores, and using those scores to make intelligent routing decisions.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Architecture: Confidence-Driven Routing&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          DOCUMENT INGESTION                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; PDF/Image    &amp;#x2502;      &amp;#x2502; Preprocessing&amp;#x2502;      &amp;#x2502; Layout       &amp;#x2502;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; Upload       &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; Enhancement  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; Detection    &amp;#x2502;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; (API/CLI)    &amp;#x2502;      &amp;#x2502; (denoise,    &amp;#x2502;      &amp;#x2502; (regions,    &amp;#x2502;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;              &amp;#x2502;      &amp;#x2502;  deskew)     &amp;#x2502;      &amp;#x2502;  tables)     &amp;#x2502;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;               &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                      &amp;#x2502; regions&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                      &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          VLM INFERENCE LAYER                                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  olmOCR-2-7B-MLX   &amp;#x2502;      &amp;#x2502;  Self-Consistency Engine               &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;                 &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Quantized VLM     &amp;#x2502;      &amp;#x2502;  k=1 (text) / k=3 (mixed) / k=5 (hard) &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  MLX acceleration  &amp;#x2502;      &amp;#x2502;  Majority voting + agreement ratio     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                 &amp;#x2502; predictions + raw confidence&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                 &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          CALIBRATION LAYER                                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Temperature Scaling Calibrator                                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                                                                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  raw_confidence &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; logit &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; scale by T &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; calibrated_conf    &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;                                                                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Per-field calibration: T_medication=1.5, T_name=0.9, T_date=0.8  &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                  &amp;#x2502; calibrated confidence&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                  &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          ROUTING DECISION                                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2502;  Confidence &amp;#x2265; threshold?    &amp;#x2502;                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2502;  (per-field: 85%-98%)       &amp;#x2502;                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                    &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                               &amp;#x2502;                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x2502;                &amp;#x2502;                &amp;#x2502;                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;              &amp;#x25bc; YES            &amp;#x25bc; MAYBE          &amp;#x25bc; NO                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510; &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510; &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; AUTOPILOT         &amp;#x2502; &amp;#x2502; FLAG FOR REVIEW &amp;#x2502; &amp;#x2502; HUMAN REVIEW      &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;       &amp;#x2502; &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;      &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; Direct export     &amp;#x2502; &amp;#x2502; Auto-assign but &amp;#x2502; &amp;#x2502; Queue for manual  &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; to EHR/database   &amp;#x2502; &amp;#x2502; mark for verify &amp;#x2502; &amp;#x2502; annotation        &amp;#x2502;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518; &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518; &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                          &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                          &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          HUMAN-IN-THE-LOOP                                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Streamlit UI      &amp;#x2502;      &amp;#x2502;  Active Learning Selector              &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502;      &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;                  &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Priority queue    &amp;#x2502; &amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  Uncertainty &amp;#xd7; 0.5 +                   &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Keyboard shortcuts&amp;#x2502;      &amp;#x2502;  Diversity &amp;#xd7; 0.3 +                     &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Context display   &amp;#x2502;      &amp;#x2502;  Value &amp;#xd7; 0.2                           &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Audit trail       &amp;#x2502;      &amp;#x2502;                                        &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;            &amp;#x2502; corrections                                                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          FEEDBACK LOOP                                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Dataset Manager   &amp;#x2502;      &amp;#x2502;  Calibration       &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502;      &amp;#x2502;  Monitor           &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Version annotations&amp;#x2502; &amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502;  Refit temperature &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Trigger retraining&amp;#x2502;      &amp;#x2502;  every N samples   &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Observability: MLflow (experiments) + Prometheus + Grafana        &amp;#x2502;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Documents flow through &lt;strong&gt;preprocessing&lt;/strong&gt; (image enhancement, layout detection) into the &lt;strong&gt;VLM inference layer&lt;/strong&gt;, where olmOCR-2-7B-MLX extracts structured fields. The &lt;strong&gt;self-consistency engine&lt;/strong&gt; runs multiple inferences for difficult regions, using agreement ratio as a confidence signal.&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;calibration layer&lt;/strong&gt; transforms raw model outputs into reliable probabilities using temperature scaling&amp;#x2014;learned separately per field type from human correction data. The &lt;strong&gt;routing layer&lt;/strong&gt; then decides: autopilot (high confidence), flag-for-review (borderline), or human review (low confidence).&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;human-in-the-loop&lt;/strong&gt; layer provides a Streamlit-based review interface with priority queuing and keyboard shortcuts. The &lt;strong&gt;active learning selector&lt;/strong&gt; chooses which samples are most valuable to annotate.&lt;/p&gt;&lt;p&gt;Finally, the &lt;strong&gt;feedback loop&lt;/strong&gt; closes the circle: corrections update the calibrator, trigger dataset versioning, and feed into model retraining. The entire system is instrumented with MLflow for experiments and Prometheus/Grafana for operational metrics.&lt;/p&gt;&lt;p&gt;The target: &lt;strong&gt;autopilot handles 70%+ of documents&lt;/strong&gt; without human intervention, while maintaining accuracy guarantees on the remaining 30% through targeted review.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Lesson 1: Self-Consistency Beats Single Inference&lt;/h3&gt;&lt;p&gt;My first approach used a single inference pass: run the VLM once, take the output, use the logit-based confidence. The problem? Single-pass confidence is noisy. The same input might produce different outputs on different runs, but a single inference can&amp;apos;t reveal that instability.&lt;/p&gt;&lt;p&gt;The fix is &lt;strong&gt;self-consistency sampling&lt;/strong&gt;: run inference k times with different random seeds, then use majority voting to select the consensus answer. The agreement ratio becomes a confidence signal&amp;#x2014;if 5 out of 5 samples agree, you have high confidence; if it&amp;apos;s 3-2, something is ambiguous.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; def&lt;/span&gt;&lt;span&gt; run_self_consistency&lt;/span&gt;&lt;span&gt;(image, prompt, adapter, k&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Run k parallel inferences with different seeds&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    predictions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; asyncio.gather(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        run_inference(image, prompt, &lt;/span&gt;&lt;span&gt;seed&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;i) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt;(k)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    ])&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Majority voting: serialize predictions, count occurrences&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    serialized &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [json.dumps(p, &lt;/span&gt;&lt;span&gt;sort_keys&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; p &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; predictions]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    most_common, count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Counter(serialized).most_common(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    agreement_ratio &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; count &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; json.loads(most_common), agreement_ratio&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The intuition: if the model is uncertain, different seeds will produce different outputs. If the model is confident &lt;em&gt;and correct&lt;/em&gt;, all samples will converge. Self-consistency surfaces the former while confirming the latter.&lt;/p&gt;&lt;p&gt;There&amp;apos;s a cost tradeoff: k=3 means 3x inference time. For simple text fields, k=1 is fine. For complex regions (handwriting, tables, checkboxes), k=5 is worth the latency. The system uses region type to dynamically select k.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Single-pass confidence is unreliable. Self-consistency sampling reveals model uncertainty that logits alone hide.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 2: Raw Probabilities Aren&amp;apos;t Probabilities&lt;/h3&gt;&lt;p&gt;Even with self-consistency, raw confidence scores are miscalibrated. A model might report 85% confidence and be right 95% of the time&amp;#x2014;or report 85% and be right only 70% of the time. Without knowing which, you can&amp;apos;t set reliable routing thresholds.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Temperature scaling&lt;/strong&gt; solves this. You collect a validation set of (confidence, is_correct) pairs, then learn a single temperature parameter T that rescales logits:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; TemperatureScaling&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; fit&lt;/span&gt;&lt;span&gt;(self, confidences, correctness):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Convert confidences to logits&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        logits &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.log(confidences &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; confidences))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Optimize T to minimize negative log-likelihood&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        def&lt;/span&gt;&lt;span&gt; nll&lt;/span&gt;&lt;span&gt;(T):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            scaled &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; np.exp(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;logits &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; T))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt;np.mean(correctness &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; np.log(scaled) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                           (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; correctness) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; np.log(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; scaled))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; minimize(nll, &lt;/span&gt;&lt;span&gt;x0&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;bounds&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[(&lt;/span&gt;&lt;span&gt;0.1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;)])&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.temperature &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; result.x[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; calibrate&lt;/span&gt;&lt;span&gt;(self, confidence):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        logit &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.log(confidence &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; confidence))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; np.exp(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;logit &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.temperature))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;After calibration, if the model says 95%, it&amp;apos;s right 95% of the time (&amp;#xb1;calibration error). This makes threshold-based routing meaningful: &amp;quot;send to review if confidence &amp;lt; 95%&amp;quot; actually means something.&lt;/p&gt;&lt;p&gt;The calibrator retrains continuously. Every human correction is a calibration sample. A &lt;code&gt;CalibrationMonitor&lt;/code&gt; accumulates samples and refits temperature every N predictions, tracking Expected Calibration Error (ECE) over time.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Raw model confidence is a ranking signal, not a probability. Temperature scaling transforms it into something you can actually trust for decision-making.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 3: Human Corrections Are Your Most Valuable Data&lt;/h3&gt;&lt;p&gt;Every document routed to human review is an opportunity. When a reviewer corrects a field, you get ground truth on a case the model found difficult. That&amp;apos;s exactly the data you need to improve.&lt;/p&gt;&lt;p&gt;But not all corrections are equally valuable. A typo fix on an easy case teaches less than a structural correction on a hard one. &lt;strong&gt;Active learning&lt;/strong&gt; selects which samples to prioritize for annotation based on their expected learning impact.&lt;/p&gt;&lt;p&gt;The selection strategy combines three signals:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; select_annotation_batch&lt;/span&gt;&lt;span&gt;(predictions):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Uncertainty: low-confidence samples are more informative&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    uncertainty_scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; np.array([p[&lt;/span&gt;&lt;span&gt;&amp;apos;confidence&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; p &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; predictions])&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Diversity: cluster samples, prefer underrepresented areas&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    diversity_scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; compute_cluster_distances(predictions)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Value: high-difficulty, common patterns have more impact&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    value_scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; predictions[&lt;/span&gt;&lt;span&gt;&amp;apos;difficulty&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.6&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                   predictions[&lt;/span&gt;&lt;span&gt;&amp;apos;frequency&amp;apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.4&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Weighted combination&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; uncertainty_scores &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             0.3&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; diversity_scores &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             0.2&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; value_scores)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; top_k_indices(total, &lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;batch_size)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Uncertainty sampling alone leads to annotating outliers that don&amp;apos;t generalize. Diversity sampling ensures coverage. Value scoring weights toward patterns that appear frequently&amp;#x2014;fixing a common error type helps more than fixing a rare one.&lt;/p&gt;&lt;p&gt;The feedback loop: corrections flow into a versioned annotation store, triggering dataset updates and model retraining on a configurable schedule. Each retrained model gets validated against a golden test set before deployment.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Human review isn&amp;apos;t just a safety net&amp;#x2014;it&amp;apos;s a data generation mechanism. Active learning extracts maximum value from every correction.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 4: Different Fields Need Different Confidence&lt;/h3&gt;&lt;p&gt;A name field and a medication dosage field have different error costs. Getting the name slightly wrong (Robert vs Roberto) is recoverable. Getting the dosage wrong (15 mg vs 150 mg) could cause patient harm.&lt;/p&gt;&lt;p&gt;Rather than a single confidence threshold, the system uses &lt;strong&gt;per-field calibration&lt;/strong&gt;. High-stakes fields like medication names and dosages have strict thresholds (98%). Lower-stakes fields like clinic names have relaxed thresholds (90%).&lt;/p&gt;&lt;p&gt;Field types also get different self-consistency settings. Checkboxes use k=5 because they&amp;apos;re error-prone. Printed text uses k=1 because it&amp;apos;s usually unambiguous. The prompt selector chooses templates based on region type, and the confidence scorer combines region difficulty with field-specific calibration.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;thresholds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;medication_name&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.98&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;dosage&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.98&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;patient_name&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.95&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;date&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.95&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;clinic_name&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.90&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;general_text&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.85&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; confidence &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; thresholds[field_type]:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    route_to_human_review(document, field)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The calibrator maintains separate temperature parameters per field type. Medication fields might need T=1.5 (model is overconfident), while dates might need T=0.8 (model is underconfident). The system learns these automatically from correction data.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Not all fields are equal. Calibrate and threshold based on error cost, not just raw accuracy.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 5: The Annotation Interface Is a Product&lt;/h3&gt;&lt;p&gt;I initially treated the human review UI as an afterthought&amp;#x2014;a simple form to view extractions and submit corrections. That was a mistake. Reviewer efficiency directly impacts system economics. A UI that takes 60 seconds per correction costs twice as much as one that takes 30 seconds.&lt;/p&gt;&lt;p&gt;The Streamlit-based review interface became a first-class product:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Priority queue&lt;/strong&gt;: Items sorted by confidence (lowest first), field type (high-stakes first), and document age (oldest first)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Context preservation&lt;/strong&gt;: Show the original image region alongside the extracted text, with bounding boxes highlighted  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Keyboard shortcuts&lt;/strong&gt;: Accept, reject, and edit without touching the mouse  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Audit trail&lt;/strong&gt;: Every correction tracked with reviewer ID, timestamp, and before/after values  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bulk actions&lt;/strong&gt;: Accept all high-confidence items with one click when the queue backs up  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The queue also shows aggregate metrics: current backlog size, average review time, acceptance rate by field type. Reviewers can see patterns&amp;#x2014;if checkbox extractions are consistently wrong, that&amp;apos;s feedback to the model team.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Human-in-the-loop is only sustainable if the loop is fast. Invest in annotation UX as if it were user-facing (it is).  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 6: Observability Is Non-Negotiable&lt;/h3&gt;&lt;p&gt;In a system with this many moving parts&amp;#x2014;preprocessing, inference, calibration, routing, review, retraining&amp;#x2014;things break in subtle ways. A calibrator that stops updating. A queue that grows faster than reviewers can drain it. A retrained model that&amp;apos;s worse than its predecessor.&lt;/p&gt;&lt;p&gt;The observability stack tracks everything:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;MLflow&lt;/strong&gt;: Every inference logged with input hash, prompt version, output, confidence, latency. Every model version registered with metrics and lineage.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Prometheus&lt;/strong&gt;: Gauges for queue depth, p50/p95 inference latency, autopilot vs review ratio, calibration error.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Grafana dashboards&lt;/strong&gt;: Real-time visualization of throughput, accuracy by field type, reviewer productivity, model comparison.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Every request carries a correlation ID that propagates through all services. When something goes wrong, you can trace from API request &amp;#x2192; preprocessing &amp;#x2192; inference &amp;#x2192; calibration &amp;#x2192; routing &amp;#x2192; review &amp;#x2192; export in a single query.&lt;/p&gt;&lt;p&gt;Alerts fire on SLO breaches: queue depth &amp;gt; 1000, p95 latency &amp;gt; 30s, ECE &amp;gt; 0.05. The on-call has dashboards and runbooks to diagnose and recover.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: ML systems fail silently. Instrument everything, alert on meaningful thresholds, and make debugging possible before you need it.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;What I&amp;apos;d Do Differently&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. Start with calibration, not model architecture&lt;/strong&gt;: I spent too long optimizing the VLM before realizing that calibration was the bottleneck. A well-calibrated weaker model beats an uncalibrated stronger one for production routing decisions.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Build synthetic data generation earlier&lt;/strong&gt;: The system includes a synthetic form generator for testing, but it came late. Having diverse synthetic data from day one would have caught more edge cases before production.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. Add fallback OCR sooner&lt;/strong&gt;: For text-heavy regions, traditional OCR (Tesseract, PaddleOCR) is often faster and just as accurate. The system now routes by region type, but initially it VLM&amp;apos;d everything.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. Implement canary deployments for prompts&lt;/strong&gt;: Prompts are code. Changes can break extraction in subtle ways. The system now versions prompts and supports canary rollouts, but we learned this after a prompt change caused a 15% accuracy regression.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Key Takeaways&lt;/h3&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Self-consistency reveals uncertainty&lt;/strong&gt;: Run inference multiple times and use agreement ratio as a confidence signal&amp;#x2014;it&amp;apos;s more reliable than logit-based probabilities.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Calibrate before routing&lt;/strong&gt;: Raw model confidence is miscalibrated. Temperature scaling makes thresholds meaningful.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Corrections are training data&lt;/strong&gt;: Every human review is an active learning opportunity. Select samples strategically to maximize model improvement.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Threshold by error cost&lt;/strong&gt;: High-stakes fields need higher confidence thresholds. Calibrate and route per field type.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Instrument everything&lt;/strong&gt;: ML systems fail silently. Correlation IDs, MLflow tracking, and Prometheus metrics make debugging possible.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;This project is a medical document processing platform built with olmOCR-2-7B-MLX, FastAPI, MLflow, and Streamlit. Designed for HIPAA-compliant healthcare workflows with human-in-the-loop quality assurance. &lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Tiny Reasoner: Engineering a 135M Model for Medical Expertis</title>
      <link>https://nayanachandrika99.github.io/posts/tiny-reasoner-engineering-a-135m-model-for-medical-expertis/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/tiny-reasoner-engineering-a-135m-model-for-medical-expertis/</guid>
      <description>How I built a specialized medical coding agent using RLVR, Chain-of-Thought, and Self-Amplification.</description>
      <pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 08:05:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/tiny-reasoner-engineering-a-135m-model-for-medical-expertis/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;How I built a specialized medical coding agent using RLVR, Chain-of-Thought, and Self-Amplification.&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; November 14, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; January 12, 2026 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;1. Introduction&lt;/h3&gt;&lt;p&gt;Large Language Models (LLMs) like GPT-4 are generalists. But for specific, high-stakes domains like medical coding (assigning ICD-10/CPT codes to clinical notes), we don&amp;apos;t always need a 100B+ parameter giant. We need a &lt;strong&gt;specialist&lt;/strong&gt;.&lt;/p&gt;&lt;h4&gt;The Problem: Medical Coding is Complex&lt;/h4&gt;&lt;p&gt;Medical coding is the process of translating clinical narratives into standardized billing codes. Consider this example:&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; &amp;quot;Patient presents with uncontrolled Type 2 diabetes mellitus with diabetic retinopathy.&amp;quot;  &lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;A human coder must:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Identify the primary condition (Type 2 diabetes).  &lt;/li&gt;&lt;li&gt; Note the complication (retinopathy).  &lt;/li&gt;&lt;li&gt; Determine specificity (uncontrolled).  &lt;/li&gt;&lt;li&gt; Assign the correct ICD-10 code: &lt;strong&gt;E11.319&lt;/strong&gt; (Type 2 diabetes with unspecified diabetic retinopathy, uncontrolled).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This requires domain expertise, reasoning through clinical guidelines, and understanding code hierarchies. A wrong code can lead to claim denials, compliance issues, or lost revenue.&lt;/p&gt;&lt;h4&gt;Why Not Just Use GPT-4?&lt;/h4&gt;&lt;p&gt;While large models like GPT-4 can perform this task, they have critical limitations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt;: Running inference on 70B+ models at scale (millions of claims/day) is prohibitively expensive.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: Large models are slow, creating bottlenecks in real-time workflows.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hallucination&lt;/strong&gt;: Without grounding, they can invent plausible-sounding but invalid codes.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;My Hypothesis&lt;/h4&gt;&lt;p&gt;A tiny, specialized model (135M parameters) can &lt;strong&gt;outperform generalist giants&lt;/strong&gt; on this specific task if we:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Teach it &lt;strong&gt;how to reason&lt;/strong&gt; (Chain-of-Thought).  &lt;/li&gt;&lt;li&gt; Ground it with &lt;strong&gt;verifiable rewards&lt;/strong&gt; (rule-based validation).  &lt;/li&gt;&lt;li&gt; Scale it with &lt;strong&gt;self-amplification&lt;/strong&gt; (iterative self-training).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This post details my engineering journey building &lt;strong&gt;Tiny Reasoner&lt;/strong&gt;, a project that proves this hypothesis.&lt;/p&gt;&lt;h4&gt;The Result (Sneak Peek)&lt;/h4&gt;&lt;p&gt;Before diving into the architecture, here is what the final model actually does:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Input: &amp;quot;Patient presents with uncontrolled Type 2 diabetes mellitus&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        with mild nonproliferative diabetic retinopathy.&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Tiny Reasoner (135M):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;+---------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| &amp;lt;think&amp;gt;                                                       |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| 1. Identify primary condition: Type 2 Diabetes Mellitus.      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| 2. Identify complications: Retinopathy (Diabetic).            |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| 3. Specify severity: Mild, Nonproliferative.                  |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| 4. Check control status: Uncontrolled (Hyperglycemia).        |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| 5. Construct Code: E11 (Type 2) + .3 (Retinopathy)            |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;|    + .2 (Mild Nonproliferative) + .9 (Unspecified eye).       |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| &amp;lt;/think&amp;gt;                                                      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;|                                                               |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| Code: E11.329                                                 |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;+---------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;It doesn&amp;apos;t just guess; it &lt;strong&gt;derives&lt;/strong&gt; the answer.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;2. System Architecture&lt;/h3&gt;&lt;p&gt;My pipeline is designed to bootstrap reasoning capabilities from zero, verify them rigorously, and then scale them up autonomously.&lt;/p&gt;&lt;h4&gt;Design Philosophy&lt;/h4&gt;&lt;p&gt;I moved away from the standard &amp;quot;SFT &amp;#x2192; DPO&amp;quot; pipeline used in most LLM alignment work. Instead, I built a &lt;strong&gt;verification-centric&lt;/strong&gt; architecture where every training decision is grounded in deterministic correctness checks.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;: In medical coding, &amp;quot;correctness&amp;quot; isn&amp;apos;t subjective. A code is either valid (exists in the ICD-10 database) or it isn&amp;apos;t. This allows me to replace expensive human preference labeling with a Python script.&lt;/p&gt;&lt;h4&gt;High-Level Data Flow&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[ Raw Data Sources ]       [ Phase 1: Foundation ]       [ Phase 2: Alignment ]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;+------------------+       +---------------------+       +--------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;|  CMS Guidelines  |------&amp;gt;|  Extraction Script  |------&amp;gt;|    SFT Trainer     |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;|  (Text/PDF)      |       |  (Regex/Parsing)    |       |   (SmolLM2-135M)   |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;+------------------+       +---------------------+       +--------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                    |                              |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;+------------------+                v                              v&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;|   OpenAI API     |------&amp;gt;| Synthetic Generator |       +--------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;| (Teacher Model)  |       | (CoT + Validation)  |       |    RLVR Trainer    |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;+------------------+       +---------------------+       |    (PPO + Rule)    |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         +--------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   v&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         [ Phase 4: Scaling ]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         +--------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         |  Self-Amplification|&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         |  (Iterative Loop)  |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         +--------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;3. Phase 1: The Foundation (Bootstrapping)&lt;/h3&gt;&lt;p&gt;Before a model can &amp;quot;reason,&amp;quot; it needs to see examples of reasoning. I couldn&amp;apos;t just use raw text; I needed &lt;strong&gt;Chain-of-Thought (CoT)&lt;/strong&gt; data.&lt;/p&gt;&lt;h4&gt;3.1. Data Extraction Strategy&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;: Most medical coding datasets are either proprietary or lack reasoning traces. I needed examples that showed &lt;em&gt;how&lt;/em&gt; to arrive at a code, not just the final answer.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;: I built &lt;code&gt;scripts/extract_cms_guidelines.py&lt;/code&gt; to mine &amp;quot;gold standard&amp;quot; examples from official CMS (Centers for Medicare &amp;amp; Medicaid Services) documentation.&lt;/p&gt;&lt;p&gt;CMS guidelines often contain structured examples like:&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; &amp;quot;Example: Patient admitted for acute bronchitis... &lt;p&gt;&lt;em&gt;Rationale: The condition is acute (not chronic), so we select from the J20 family...&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Code Assignment: J20.9&amp;quot;&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;I used regex patterns to extract these structured examples:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Raw Text:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;quot;Example: Patient with Type 2 diabetes... Rationale: Code E11.9 is selected because...&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      v  (Regex Parsing)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Structured JSONL:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  &amp;quot;narrative&amp;quot;: &amp;quot;Patient with Type 2 diabetes...&amp;quot;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  &amp;quot;reasoning&amp;quot;: &amp;quot;Code E11.9 is selected because...&amp;quot;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  &amp;quot;code&amp;quot;: &amp;quot;E11.9&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;3.2. Synthetic Teacher Generation&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;: CMS guidelines only yielded ~5,000 examples. I needed 15,000+ to train a robust model.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;: I used GPT-4o as a &amp;quot;teacher&amp;quot; to generate synthetic examples. But not just any examples&amp;#x2014;I enforced a strict &lt;strong&gt;Chain-of-Thought (CoT)&lt;/strong&gt; schema.&lt;/p&gt;&lt;h4&gt;The Prompt Strategy&lt;/h4&gt;&lt;p&gt;I designed a prompt that forces the teacher model to:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Generate a realistic clinical narrative (50-200 words).  &lt;/li&gt;&lt;li&gt; Show its reasoning step-by-step in &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; tags.  &lt;/li&gt;&lt;li&gt; Assign a valid ICD-10/CPT/HCPCS code.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;prompt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;You are a medical coding expert. Generate a realistic clinical narrative,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;step-by-step reasoning process, and correct medical billing code.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Narrative should be 50-200 words&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Include 4-6 reasoning steps in &amp;lt;think&amp;gt; tags&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Assign ONE code (ICD-10, CPT, or HCPCS)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Code must be valid and commonly used&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Focus on: &lt;/span&gt;&lt;span&gt;{specialty}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Code family: &lt;/span&gt;&lt;span&gt;{code_family}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Complexity level: &lt;/span&gt;&lt;span&gt;{complexity}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;The Validation Gate&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Critical Problem&lt;/strong&gt;: LLMs hallucinate. GPT-4 might generate &amp;quot;ICD-10: Z99.999 (Patient abducted by aliens).&amp;quot;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;My Solution&lt;/strong&gt;: Every generated example passes through &lt;code&gt;CodeValidator&lt;/code&gt; before being added to the training set.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# scripts/generate_synthetic.py&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; attempt &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt;(max_attempts):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; gpt4.generate(prompt)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    parsed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; parse_response(response)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # CRITICAL: Validate the code&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; validator.validate(parsed[&lt;/span&gt;&lt;span&gt;&amp;apos;code&amp;apos;&lt;/span&gt;&lt;span&gt;], parsed[&lt;/span&gt;&lt;span&gt;&amp;apos;code_type&amp;apos;&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        continue&lt;/span&gt;&lt;span&gt;  # Discard hallucinated codes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    training_data.append(parsed)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This filtering step is crucial. In my experiments, ~8-10% of GPT-4&amp;apos;s generated codes were invalid or non-existent.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How to Run Phase 1:&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; scripts/extract_cms_guidelines.py&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; scripts/generate_synthetic.py&lt;/span&gt;&lt;span&gt; --count&lt;/span&gt;&lt;span&gt; 5000&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; data/prepare_foundation.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;4. Phase 2: RLVR (The &amp;quot;Secret Sauce&amp;quot;)&lt;/h3&gt;&lt;p&gt;This is where the project diverges from standard LLM training.&lt;/p&gt;&lt;h4&gt;Why Not DPO?&lt;/h4&gt;&lt;p&gt;Direct Preference Optimization (DPO) is the current state-of-the-art for alignment. But it has a fatal flaw for medical coding:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;DPO requires preference pairs&lt;/strong&gt;: &lt;em&gt;&amp;quot;Response A is better than Response B.&amp;quot;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Creating these pairs requires:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Generating multiple responses per prompt.  &lt;/li&gt;&lt;li&gt; Having human experts rank them.  &lt;/li&gt;&lt;li&gt; Training a reward model to predict preferences.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Expensive&lt;/strong&gt;: ~&amp;#x24;500-700 for 40k preference pairs.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Noisy&lt;/strong&gt;: Human annotators might disagree.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Gameable&lt;/strong&gt;: The reward model can be fooled by plausible-sounding but incorrect codes.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;The RLVR Alternative&lt;/h4&gt;&lt;p&gt;For medical coding, I had a ground truth: &lt;strong&gt;The Code Book&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;I implemented &lt;strong&gt;RLVR (Reinforcement Learning with Verifiable Rewards)&lt;/strong&gt; in &lt;code&gt;post_training/rlvr.py&lt;/code&gt;. Instead of a neural reward model, I used a deterministic Python function.&lt;/p&gt;&lt;h4&gt;4.1. The Reward Function&lt;/h4&gt;&lt;p&gt;Instead of a neural network, my reward function is a Python script:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;      Model Output                Ground Truth&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;quot;Code: E11.9&amp;quot;               &amp;quot;Code: E11.9&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;           |                           |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;           v                           v&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    +-------------+             +-------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    |  Parser     |             |  Parser     |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    +-------------+             +-------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;           |                           |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;           v                           v&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      [E11.9] ==?== [E11.9] ----&amp;gt;  +1.0 Reward&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;           |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      [E11]   ==?== [E11]   ----&amp;gt;  +0.5 Reward (Partial)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; compute_reward&lt;/span&gt;&lt;span&gt;(prediction, ground_truth):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # 1. Extract code from model output&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    pred_code &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; extract_code(prediction)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # 2. Check Validity (Is it a real ICD-10 code?)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; validator.validate(pred_code):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # 3. Check Correctness&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; pred_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; ground_truth:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    elif&lt;/span&gt;&lt;span&gt; pred_code.family &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; ground_truth.family:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; 0.3&lt;/span&gt;&lt;span&gt;  # Partial credit!&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt; # Valid but wrong&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;4.2. PPO Training Loop&lt;/h4&gt;&lt;p&gt;I used Proximal Policy Optimization (PPO) to optimize the model against this rule-based reward.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How PPO Works (Simplified)&lt;/strong&gt;:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; The model generates a response.  &lt;/li&gt;&lt;li&gt; The verifier computes a reward (0.0 to 1.0).  &lt;/li&gt;&lt;li&gt; PPO adjusts the model&amp;apos;s weights to increase the probability of high-reward responses.  &lt;/li&gt;&lt;li&gt; A KL-divergence penalty prevents the model from deviating too far from the original SFT model (preventing &amp;quot;reward hacking&amp;quot;).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;The Training Loop&lt;/strong&gt;:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; batch &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; dataloader:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # 1. Generate responses&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    prompts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; batch[&lt;/span&gt;&lt;span&gt;&amp;apos;prompts&amp;apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    responses &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; model.generate(prompts, &lt;/span&gt;&lt;span&gt;temperature&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0.8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # 2. Compute rewards&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    rewards &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; response, ground_truth &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; zip&lt;/span&gt;&lt;span&gt;(responses, batch[&lt;/span&gt;&lt;span&gt;&amp;apos;ground_truths&amp;apos;&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        reward &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; verifier(response, ground_truth)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        rewards.append(reward)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # 3. PPO step (update model weights)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    ppo_trainer.step(prompts, responses, rewards)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why This Works&lt;/strong&gt;: The model learns: &lt;em&gt;&amp;quot;I don&amp;apos;t just need to sound confident; I need to output codes that pass the validator.&amp;quot;&lt;/em&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;   +-------------+        Action (Text)       +-------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   |   Policy    |---------------------------&amp;gt;| Environment |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   | (The Model) |                            | (Verifier)  |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   +-------------+                            +-------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          ^                                          |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          |             Reward (Float)               |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          +------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This teaches the model: &lt;em&gt;&amp;quot;I don&amp;apos;t know exactly what to write, but I know that if I output a valid code that matches the family, I get a reward.&amp;quot;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How to Run Phase 2:&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# First, SFT&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; post_training/sft.py&lt;/span&gt;&lt;span&gt; --config-path&lt;/span&gt;&lt;span&gt; post_training/config/foundation_sft.yaml&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Then, RLVR&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; post_training/rlvr.py&lt;/span&gt;&lt;span&gt; --config&lt;/span&gt;&lt;span&gt; post_training/config/rlvr.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;5. Phase 4: Self-Amplification (Scaling Up)&lt;/h3&gt;&lt;p&gt;Once the model was &amp;quot;good enough&amp;quot; (Phase 2), I used it to teach itself. This is the &lt;strong&gt;Self-Amplification&lt;/strong&gt; loop implemented in &lt;code&gt;scripts/iterative_training.py&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;5.1. The Loop&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;      +------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |  Current Model   |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      +------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;               |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      (1) Generate (Temp=0.8)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;               v&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      +------------------+      +------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |  Pseudo-Labels   |-----&amp;gt;|  CodeValidator   |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      +------------------+      +------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                         |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                    (2) Filter&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                         v&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      +------------------+      +------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |   Next Model     |&amp;lt;-----|  Clean Dataset   |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      +------------------+      +------------------+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             (3) Train&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Generate&lt;/strong&gt;: The model looks at a clinical narrative and generates a &lt;em&gt;new&lt;/em&gt; reasoning path and code.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt;: I run the &lt;code&gt;CodeValidator&lt;/code&gt;. &lt;ul&gt;&lt;li&gt; Invalid code? -&amp;gt; &lt;strong&gt;Trash&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt; Valid code? -&amp;gt; &lt;strong&gt;Keep&lt;/strong&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Train&lt;/strong&gt;: I add the &amp;quot;Keep&amp;quot; examples to the training set and re-train.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;5.2. Why This Works&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;The Intuition&lt;/strong&gt;: The model has learned &lt;em&gt;how&lt;/em&gt; to reason about medical codes (from Phase 1 &amp;amp; 2). Now, when it sees a new narrative, it can generate a plausible reasoning path. Sometimes, it discovers a correct path that wasn&amp;apos;t in the original training data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Mechanism&lt;/strong&gt;:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Exploration&lt;/strong&gt;: By using temperature=0.8 (sampling), the model generates diverse responses.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Validation&lt;/strong&gt;: The &lt;code&gt;CodeValidator&lt;/code&gt; acts as a filter, keeping only correct examples.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Crystallization&lt;/strong&gt;: By retraining on these validated examples, I &amp;quot;lock in&amp;quot; the correct reasoning patterns.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;The Flywheel Effect&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Better model &amp;#x2192; Better pseudo-labels &amp;#x2192; Better model &amp;#x2192; ...  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Scaling Results&lt;/strong&gt;:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Iteration 1: 20k labeled examples &amp;#x2192; 85% accuracy&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Iteration 2: 20k labeled + 30k pseudo &amp;#x2192; 89% accuracy&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Iteration 3: 20k labeled + 80k pseudo &amp;#x2192; 92% accuracy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;: I didn&amp;apos;t need to label 100k examples manually. I labeled 20k, and the model generated the rest (with validation).&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Iteration 1:  20k Examples  -&amp;gt;  85% Accuracy&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      v (Generate &amp;amp; Filter)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Iteration 2:  50k Examples  -&amp;gt;  89% Accuracy&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      v (Generate &amp;amp; Filter)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Iteration 3:  100k Examples -&amp;gt;  92% Accuracy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;How to Run Phase 4:&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; scripts/iterative_training.py&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    --foundation-model&lt;/span&gt;&lt;span&gt; outputs/rlvr/final&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    --iterations&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h3&gt;6. Evaluation&lt;/h3&gt;&lt;p&gt;I don&amp;apos;t just check accuracy; I check &lt;strong&gt;reasoning quality&lt;/strong&gt;.&lt;/p&gt;&lt;h4&gt;Evaluation Metrics&lt;/h4&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Exact Match&lt;/strong&gt;: Did the model predict the exact code? (e.g., &lt;code&gt;E11.319&lt;/code&gt;)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Code Validity&lt;/strong&gt;: Is the predicted code even real? (Checks against ICD-10/CPT databases)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Family Match&lt;/strong&gt;: Did it get the right code family? (e.g., &lt;code&gt;E11.*&lt;/code&gt; for Type 2 diabetes)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reasoning Presence&lt;/strong&gt;: Did it use the &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; tags?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reasoning Quality&lt;/strong&gt;: Are the reasoning steps coherent and relevant?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Inference Time&lt;/strong&gt;: How fast is the model? (Target: &amp;lt;200ms per prediction)  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;Evaluation Script&lt;/h4&gt;&lt;p&gt;My &lt;code&gt;evaluation/final_evaluation.py&lt;/code&gt; script:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Loads the model.  &lt;/li&gt;&lt;li&gt; Runs inference on multiple test sets (validation, CMS holdout, synthetic hard cases).  &lt;/li&gt;&lt;li&gt; Computes all metrics.  &lt;/li&gt;&lt;li&gt; Generates a JSON report with detailed breakdowns.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; evaluation/final_evaluation.py&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    --model-path&lt;/span&gt;&lt;span&gt; outputs/iterative/iter_3/final&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    --test-sets&lt;/span&gt;&lt;span&gt; data/processed/foundation_val.jsonl&lt;/span&gt;&lt;span&gt; data/test/cms_holdout.jsonl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: The model is not only accurate but also &lt;strong&gt;explainable&lt;/strong&gt; (shows reasoning) and &lt;strong&gt;efficient&lt;/strong&gt; (runs on CPU).&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;7. Engineering Challenges &amp;amp; Solutions&lt;/h3&gt;&lt;h4&gt;Challenge 1: Reward Hacking&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Early in RLVR training, the model learned to output &lt;em&gt;any&lt;/em&gt; valid code to get partial credit, ignoring the clinical narrative.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Prompt: &lt;em&gt;&amp;quot;Patient has a broken arm.&amp;quot;&lt;/em&gt;&lt;/li&gt;&lt;li&gt; Model Output: &lt;code&gt;E11.9&lt;/code&gt; (Type 2 diabetes) &amp;#x2014; Valid code, but completely wrong!  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: I tuned the KL-divergence penalty in PPO. This forces the model to stay close to the original language distribution (the SFT model) while still optimizing for the reward. I also reduced the partial credit reward from 0.5 to 0.3.&lt;/p&gt;&lt;h4&gt;Challenge 2: Slow Pseudo-Label Generation&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Generating 80,000 examples for self-training was taking 3+ days on a single GPU.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: I rewrote &lt;code&gt;scripts/generate_pseudo_labels.py&lt;/code&gt; to use &lt;strong&gt;batch generation&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Instead of generating one example at a time, I batch 8 prompts together.  &lt;/li&gt;&lt;li&gt; Used masked attention to handle variable-length sequences.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Result&lt;/strong&gt;: 8x speedup (3 days &amp;#x2192; 9 hours).  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Challenge 3: &amp;quot;Empty Thinking&amp;quot;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: The model sometimes output &lt;code&gt;&amp;lt;think&amp;gt;&amp;lt;/think&amp;gt;&lt;/code&gt; (empty tags) and then the code, gaming the &amp;quot;reasoning presence&amp;quot; metric.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: I added a heuristic to the reward function:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(reasoning_text) &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;# Too short&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;span&gt;  # Zero reward&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This forced the model to generate substantive reasoning.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;8. What Didn&amp;apos;t Work (Lessons from Failed Experiments)&lt;/h3&gt;&lt;p&gt;Not everything worked on the first try. Here are some approaches I tried that failed:&lt;/p&gt;&lt;h4&gt;Failed Approach 1: Pure Synthetic Data&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;What I Tried&lt;/strong&gt;: Initially, I attempted to train the model entirely on GPT-4 generated synthetic data (no CMS extraction).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why It Failed&lt;/strong&gt;: The model learned GPT-4&amp;apos;s &amp;quot;style&amp;quot; but lacked grounding in official guidelines. When evaluated on real CMS examples, accuracy dropped by ~12%. The model was mimicking a teacher that itself didn&amp;apos;t have access to authoritative sources.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: &lt;strong&gt;Domain expertise matters&lt;/strong&gt;. Even with a powerful teacher model, you need gold-standard examples from authoritative sources.&lt;/p&gt;&lt;h4&gt;Failed Approach 2: Standard DPO with Human Preferences&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;What I Tried&lt;/strong&gt;: Before implementing RLVR, I experimented with collecting preference pairs (having annotators rank &amp;quot;Response A vs Response B&amp;quot;).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why It Failed&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Annotators frequently disagreed on which response was &amp;quot;better&amp;quot; when both codes were valid but different levels of specificity.  &lt;/li&gt;&lt;li&gt; The reward model learned to prefer longer, more verbose reasoning, not necessarily correct codes.  &lt;/li&gt;&lt;li&gt; Cost was prohibitive (&amp;#x24;700 for just 5k pairs).  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: &lt;strong&gt;When you have ground truth, use it&lt;/strong&gt;. Don&amp;apos;t introduce subjectivity where objectivity exists.&lt;/p&gt;&lt;h4&gt;Failed Approach 3: Single-Shot Self-Training&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;What I Tried&lt;/strong&gt;: I attempted to generate all 80k pseudo-labels in one go (no iterations).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why It Failed&lt;/strong&gt;: The model at 85% accuracy generated too many incorrect examples. Even with filtering, ~30% of pseudo-labels were subtly wrong (e.g., correct family, wrong specificity). Training on this degraded performance.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: &lt;strong&gt;Iterate gradually&lt;/strong&gt;. Self-amplification works best when you start with a strong foundation and scale incrementally.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;9. Lessons Learned&lt;/h3&gt;&lt;p&gt;After 8 weeks and countless experiments, here are the key insights I gained:&lt;/p&gt;&lt;h4&gt;1. Small Models Need Quality, Not Quantity&lt;/h4&gt;&lt;p&gt;A 135M model has limited capacity. Feeding it 100k mediocre examples is worse than 20k high-quality examples with clear reasoning traces. &lt;strong&gt;Quality &amp;gt; Quantity&lt;/strong&gt; for specialized domains.&lt;/p&gt;&lt;h4&gt;2. Deterministic Verification Beats Learned Rewards&lt;/h4&gt;&lt;p&gt;For tasks with objective correctness (math, coding, medical codes), a simple Python function outperforms a neural reward model. It&amp;apos;s:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Cheaper (no training needed)  &lt;/li&gt;&lt;li&gt; More reliable (can&amp;apos;t be fooled)  &lt;/li&gt;&lt;li&gt; Easier to debug (just read the code)  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3. Self-Amplification Only Works with Strict Filtering&lt;/h4&gt;&lt;p&gt;The model will happily learn from its own mistakes if you let it. The validator must be unforgiving&amp;#x2014;99% valid isn&amp;apos;t good enough. I set the threshold at 100% code validity.&lt;/p&gt;&lt;h4&gt;4. Reasoning Traces Are Worth the Effort&lt;/h4&gt;&lt;p&gt;Adding &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; tags increased training complexity, but the payoff was huge:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Easier debugging (I could see &lt;em&gt;why&lt;/em&gt; the model chose a code)  &lt;/li&gt;&lt;li&gt; Better generalization (the model learned patterns, not memorization)  &lt;/li&gt;&lt;li&gt; Trust from end-users (medical coders want to see the reasoning)  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;5. Engineering Matters More Than Algorithms&lt;/h4&gt;&lt;p&gt;The biggest performance gains came from:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Better data extraction (CMS guidelines)  &lt;/li&gt;&lt;li&gt; Smarter filtering (validation gates)  &lt;/li&gt;&lt;li&gt; Batch optimization (8x speedup)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Not from tweaking hyperparameters or trying fancier RL algorithms.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;10. Future Directions&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Thought-Level Verification&lt;/strong&gt;: Grading the reasoning steps themselves, not just the final code (Phase 3 implementation ready).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Model Distillation&lt;/strong&gt;: Compressing the reasoning patterns into an even smaller model (&amp;lt;50M parameters) for edge deployment.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-Code Support&lt;/strong&gt;: Extending the system to handle cases requiring multiple codes (e.g., diabetes + hypertension).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Real-Time Feedback Loop&lt;/strong&gt;: Integrating with hospital EHR systems for continuous learning from coder corrections.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h4&gt;Conclusion&lt;/h4&gt;&lt;p&gt;By combining &lt;strong&gt;symbolic verification&lt;/strong&gt; (the code validator) with &lt;strong&gt;neural generation&lt;/strong&gt; (the LLM), I built a system that is:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Accurate&lt;/strong&gt;: 89% exact match, 99.2% code validity. It can&amp;apos;t hallucinate invalid codes.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Explainable&lt;/strong&gt;: Provides step-by-step reasoning traces in &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; tags, crucial for medical compliance.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Efficient&lt;/strong&gt;: 500MB model, runs on CPU, &amp;lt;150ms inference time. Deployable in local hospital environments.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Cost-Effective&lt;/strong&gt;: 64% cheaper than standard approaches, with superior results.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;The Key Insight&lt;/h4&gt;&lt;p&gt;This project proves that for specialized domains, &lt;strong&gt;smart engineering beats raw parameter count&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;You don&amp;apos;t need a 70B model to be an expert. You need:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;High-quality data&lt;/strong&gt; (CMS guidelines + validated synthetic).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Verifiable rewards&lt;/strong&gt; (deterministic correctness checks).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Self-amplification&lt;/strong&gt; (the model teaching itself).  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The future of AI isn&amp;apos;t just bigger models&amp;#x2014;it&amp;apos;s &lt;strong&gt;smarter training pipelines&lt;/strong&gt;.&lt;/p&gt;&lt;h4&gt;Personal Reflection&lt;/h4&gt;&lt;p&gt;When I started this project, I thought the hard part would be the RL training. I was wrong.&lt;/p&gt;&lt;p&gt;The hard part was &lt;strong&gt;data engineering&lt;/strong&gt;&amp;#x2014;extracting structured examples from messy PDFs, designing prompts that forced reasoning, and building filters that caught subtle errors. The RL part was almost trivial once I had high-quality data and a reliable validator.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The biggest surprise?&lt;/strong&gt; How well self-amplification worked. I expected diminishing returns after iteration 1, but the model kept improving. By iteration 3, it was generating reasoning paths I hadn&amp;apos;t seen in the training data&amp;#x2014;it had genuinely learned &lt;em&gt;how to think&lt;/em&gt; about medical codes, not just memorize patterns.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What would I do differently?&lt;/strong&gt; I&amp;apos;d start with the validator earlier. I initially built it as an afterthought for evaluation, but it became the cornerstone of the entire pipeline. If I were to do this again, I&amp;apos;d design the validator first, then build the training pipeline around it.&lt;/p&gt;&lt;p&gt;This project taught me that &lt;strong&gt;constraints breed creativity&lt;/strong&gt;. Working with a tiny 135M model forced me to be smarter about data, training, and evaluation. And in the end, that smartness beat brute force.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;Check out the full code on &lt;/em&gt;&lt;a href=&quot;https://github.com/NayanaChandrika99/tlr-tnnr.git&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/&quot;&gt;home&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Medical Reasoning-Service</title>
      <link>https://nayanachandrika99.github.io/posts/medical-reasoning-service/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/medical-reasoning-service/</guid>
      <description>Building a Reasoning Engine for Healthcare: Ditching Vector Search for Tree Traversal</description>
      <pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Wed Jan 07 2026 07:29:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/medical-reasoning-service/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;Building a Reasoning Engine for Healthcare: Ditching Vector Search for Tree Traversal&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; November 14, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; January 7, 2026 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;In the world of automated decision-making, &amp;quot;Retrieval-Augmented Generation&amp;quot; (RAG) has become the standard answer for connecting LLMs to private data. The recipe is usually simple: chunk your text, embed it into vectors, and retrieve the top-k chunks based on cosine similarity.&lt;/p&gt;&lt;p&gt;But when I set out to build a &lt;strong&gt;Prior Authorization Reasoning Service&lt;/strong&gt;: a system that determines if a patient meets the strict clinical criteria for a medical procedure: I found that standard vector RAG wasn&amp;apos;t enough. Medical policies aren&amp;apos;t just collections of semantic similarity; they are &lt;strong&gt;hierarchical rule sets&lt;/strong&gt; where context, parent-child relationships, and specific exclusionary clauses matter more than general topic overlap.&lt;/p&gt;&lt;p&gt;This is the technical story of how Ibuilt a reasoning engine that moves beyond flat vectors to &lt;strong&gt;hierarchical tree search&lt;/strong&gt;, auditable &lt;strong&gt;ReAct agents&lt;/strong&gt;, and a custom &lt;strong&gt;Go database&lt;/strong&gt; built from scratch.&lt;/p&gt;&lt;h3&gt;The Problem: &amp;quot;Is this MRI covered?&amp;quot;&lt;/h3&gt;&lt;p&gt;The core question our service answers is: &lt;em&gt;&amp;quot;Is this patient referral ready to file according to the current policy?&amp;quot;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;To answer this, a human (or machine) must:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Read a 50+ page PDF policy (e.g., &amp;quot;Lumbar MRI Coverage&amp;quot;).  &lt;/li&gt;&lt;li&gt; Understand the structure: &amp;quot;Section 1.2 applies, but ONLY if Section 1.1.4 is NOT met.&amp;quot;  &lt;/li&gt;&lt;li&gt; Cross-reference patient facts (age, diagnosis codes, treatment history).  &lt;/li&gt;&lt;li&gt; Make a binary decision with a citation: &amp;quot;Approved because of Section 1.2(a), page 14.&amp;quot;  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Patient Packet &amp;#x2500;&amp;#x2500;&amp;#x25ba; Policy PDF &amp;#x2500;&amp;#x2500;&amp;#x25ba; Structured Notes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x2502;                &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x251c;&amp;#x2500; demographics  &amp;#x251c;&amp;#x2500; tables         &amp;#x251c;&amp;#x2500; coverage clauses&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x2502;                &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x25bc;                &amp;#x25bc;                 &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   Facts DB     Section map        Rule references&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x2502;                &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25ba; Decision Rationale &amp;#x25c4;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Standard vector search fails here because it flattens the document. It might retrieve a paragraph about &amp;quot;exclusion criteria&amp;quot; without retrieving the parent header that says &amp;quot;For pediatric patients only,&amp;quot; leading to hallucinations.&lt;/p&gt;&lt;h3&gt;The Solution Architecture&lt;/h3&gt;&lt;p&gt;Our system is composed of three specialized components, each handling a distinct part of the reasoning pipeline:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;PageIndex (Ingestion &amp;amp; Retrieval):&lt;/strong&gt; Turns PDFs into semantic trees, not flat chunks.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;TreeStore (Storage Engine):&lt;/strong&gt; A custom high-performance Go database optimized for hierarchical documents.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reasoning Service (The Brain):&lt;/strong&gt; A Python/FastAPI application running an LLM-driven ReAct controller.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; Policy PDFs &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  PageIndex   &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  TreeStore   &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; ReAct Reasoning Loop &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2502;  (structure) &amp;#x2502;     &amp;#x2502; (hierarchy)  &amp;#x2502;     &amp;#x2502;  (decide &amp;amp; cite)    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;     &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                            &amp;#x2502;                    &amp;#x2502;                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                            &amp;#x25bc;                    &amp;#x25bc;                       &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                      JSON policy tree     Node-oriented reads     Approval decision&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;1. PageIndex: Structure First, Text Second&lt;/h4&gt;&lt;p&gt;Instead of &amp;quot;chunking&amp;quot; text, Iuse &lt;strong&gt;PageIndex&lt;/strong&gt; to convert visual PDFs into a &lt;strong&gt;hierarchical JSON tree&lt;/strong&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Input:&lt;/strong&gt; A raw PDF.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Process:&lt;/strong&gt; OCR + Layout Analysis + LLM-based Tree Construction.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Output:&lt;/strong&gt; A structured tree where every node knows its parent, children, page range, and summary.  &lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;PDF Page&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x251c;&amp;#x2500;&amp;#x25ba; OCR Blocks&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x251c;&amp;#x2500;&amp;#x25ba; Layout Detector (coordinates, fonts)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x2502;        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x2502;        &amp;#x2514;&amp;#x2500;&amp;#x25ba; Section Classifier&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x2502;                 &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25ba; Node Builder &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x2502;                                            &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;   &amp;#x25bc;                                            &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Tree Root &amp;#x2500;&amp;#x2500;&amp;#x25ba; Policy 1.0 &amp;#x2500;&amp;#x2500;&amp;#x25ba; 1.1 Indications &amp;#x2500;&amp;#x2500;&amp;#x25ba; 1.1.4 Exceptions&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                &amp;#x2502;                &amp;#x2502;                     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                &amp;#x2502;                &amp;#x2514;&amp;#x2500;&amp;#x25ba; Child summaries   &amp;#x2514;&amp;#x2500;&amp;#x25ba; Tokens + cite span&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                &amp;#x2514;&amp;#x2500;&amp;#x25ba; Page anchors &amp;amp; parent pointers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This allows us to perform &lt;strong&gt;LLM Tree Search&lt;/strong&gt;. Instead of searching for keywords, the model traverses the document tree:&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; &amp;quot;I need coverage criteria. I&amp;apos;ll open node 1.0 (Policy). Now I see 1.1 (Indications) and 1.2 (Limitations). I&amp;apos;ll expand 1.1...&amp;quot;  &lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;This mimics how a human reads a table of contents, preserving the vital context that vector databases destroy.&lt;/p&gt;&lt;h4&gt;2. TreeStore: A Database Built for Trees&lt;/h4&gt;&lt;p&gt;Irealized Ineeded a data store that treats &amp;quot;parent-child&amp;quot; relationships as first-class citizens. Relational DBs (SQL) are clunky for deep trees; Graph DBs are overkill.&lt;/p&gt;&lt;p&gt;So, Ibuilt &lt;strong&gt;TreeStore&lt;/strong&gt; in &lt;strong&gt;Go&lt;/strong&gt;. (Also because I wanted to understand the foundations of building a database)&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Core Tech:&lt;/strong&gt; A custom B+Tree implementation with ACID transactions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Design:&lt;/strong&gt; Optimized for &lt;code&gt;GetNode&lt;/code&gt;, &lt;code&gt;GetChildren&lt;/code&gt;, and &lt;code&gt;GetSubtree&lt;/code&gt; operations.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Capable of 100k+ reads/sec, ensuring that our reasoning agent can traverse complex policies with near-zero latency.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Features:&lt;/strong&gt; Native support for document versioning (vital for medical policies that change yearly) and metadata indexing.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It&amp;#x2019;s a single-binary, embeddable database that bridges the gap between a file system and a document store.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;          [Root]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          /   \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     [Index] [Leaf]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             / | \\&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        Node refs  &amp;#x25ba; On-disk pages&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;3. The ReAct Controller: Auditable Reasoning&lt;/h4&gt;&lt;p&gt;The heart of the Python service is the &lt;strong&gt;ReAct (Reason + Act) Controller&lt;/strong&gt;. Imoved away from &amp;quot;one-shot&amp;quot; prompts to an agentic loop.&lt;/p&gt;&lt;p&gt;The Controller doesn&amp;apos;t just guess; it &lt;strong&gt;investigates&lt;/strong&gt;:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;THINK:&lt;/strong&gt; &amp;quot;I need to check the patient&amp;apos;s age and diagnosis code.&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;ACT:&lt;/strong&gt; Call tool &lt;code&gt;facts.get(&amp;quot;age&amp;quot;)&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;OBSERVE:&lt;/strong&gt; &amp;quot;Patient is 45.&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;THINK:&lt;/strong&gt; &amp;quot;Now I need to find the age limit in the policy.&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;ACT:&lt;/strong&gt; Call tool &lt;code&gt;pi.search(&amp;quot;age requirement&amp;quot;)&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;OBSERVE:&lt;/strong&gt; &amp;quot;Policy Section 3.1 requires age &amp;gt; 18.&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;DECIDE:&lt;/strong&gt; &amp;quot;Criteria MET.&amp;quot;  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This generates a &lt;strong&gt;reasoning trace&lt;/strong&gt;:a step-by-step log of &lt;em&gt;how&lt;/em&gt; the decision was reached. In healthcare, this auditability is non-negotiable.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    THINK    &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;    ACT     &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; Context &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; Policy Tree&amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6;&amp;#x2502; Tool Calls   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;             &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;            &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x25b2;                       &amp;#x2502;                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x2502;                 OBSERVE&amp;#x2502;                         &amp;#x2502;RESULTS&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x2502;                       &amp;#x25bc;                         &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; Patient &amp;#x2502;   UPDATE     &amp;#x2502; Evidence   &amp;#x2502;&amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2502; Trace + Confidence &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  Facts  &amp;#x2502;              &amp;#x2502;  Buffer    &amp;#x2502;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;              &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                       DECIDE&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                 Approve / Uncertain / Deny&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;Key Technical Decisions&lt;/h3&gt;&lt;h4&gt;Safety &amp;amp; &amp;quot;Uncertainty&amp;quot;&lt;/h4&gt;&lt;p&gt;Iimplemented a strict safety gate. If the LLM&amp;apos;s confidence score (a composite of retrieval relevance and evidence alignment) drops below &lt;strong&gt;0.65&lt;/strong&gt;, the system refuses to guess. It returns a status of &lt;code&gt;UNCERTAIN&lt;/code&gt;, flagging the case for human review. Iprefer a &amp;quot;I don&amp;apos;t know&amp;quot; over a hallucinated &amp;quot;Yes.&amp;quot;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Confidence Meter&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502; 0.0 &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25ba; &amp;#x2502; 0.65 threshold&amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25ba; 1.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2534;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;#x2502;                 &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;#x25bc;                 &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  Human review        Automatic decision&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Hybrid Retrieval&lt;/h4&gt;&lt;p&gt;While Ifavor the Tree Search, Iacknowledge its limits. If a specific node is massive (e.g., a 20-page list of CPT codes), the agent can trigger a &lt;strong&gt;local SQLite FTS5&lt;/strong&gt; fallback. This performs a classic BM25 keyword search &lt;em&gt;inside&lt;/em&gt; the selected node to pinpoint the exact line, combining the best of structural navigation and keyword precision.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Tree Traversal&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Large Node? &amp;#x2500;&amp;#x2500;No&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25ba; Continue expanding children&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    Yes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; [SQLite FTS5]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;     &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Precise line hit &amp;#x2500;&amp;#x2500;&amp;#x25ba; return span to controller&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;The &amp;quot;Shadow Mode&amp;quot; Rollout&lt;/h4&gt;&lt;p&gt;Deploying LLM agents is scary. To mitigate risk, Ibuilt a &lt;strong&gt;Shadow Mode&lt;/strong&gt; into our controller. The production traffic is handled by a heuristic (rule-based) controller, while the LLM agent runs in the background. Ilog and compare their decisions. Only when the LLM matches or beats the heuristic on our evaluation dataset do Iflip the switch.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;        &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;#x2502; Incoming Prior Auth Request &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25bc;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x2502; Heuristic Control &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x25ba; Production decision&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                       &amp;#x2502; mirroring inputs&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25bc;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x2502;  LLM Shadow Loop  &amp;#x2502;&amp;#x2500;&amp;#x2500;&amp;#x25ba; Logged reasoning&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;             &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                Offline comparison&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                 Trigger cutover&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;reasoning-service&lt;/code&gt; project demonstrates that for complex, high-stakes domains, &lt;strong&gt;structure is all you need&lt;/strong&gt;. By respecting the document&amp;apos;s hierarchy with &lt;strong&gt;PageIndex&lt;/strong&gt; and &lt;strong&gt;TreeStore&lt;/strong&gt;, and forcing the LLM to show its work via &lt;strong&gt;ReAct&lt;/strong&gt;, we&amp;apos;ve built a system that doesn&amp;apos;t just retrieve information: it actually &lt;em&gt;understands&lt;/em&gt; it.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;Check out the full code on &lt;/em&gt;&lt;a href=&quot;https://github.com/NayanaChandrika99/DocReasoner.git&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Aegis-Building a Fraud Detection System</title>
      <link>https://nayanachandrika99.github.io/posts/aegis-building-a-fraud-detection-system/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/aegis-building-a-fraud-detection-system/</guid>
      <description>Combining NLP and traditional ML to achieve 12x better fraud detection than manual review</description>
      <pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 08:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/aegis-building-a-fraud-detection-system/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;Combining NLP and traditional ML to achieve 12x better fraud detection than manual review&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; November 6, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; January 12, 2026 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;p&gt;Insurance fraud costs the industry over &amp;#x24;80 billion annually. Yet most fraud detection systems still rely on simple rule-based approaches: flag claims over &amp;#x24;X, or those from first-time claimants. The problem? Fraudsters adapt, and rules don&amp;apos;t.&lt;/p&gt;&lt;p&gt;I built &lt;strong&gt;Aegis&lt;/strong&gt;, a fraud detection system that combines the semantic understanding of language models with the interpretability of gradient boosting. The result: &lt;strong&gt;75% precision at 50% recall&lt;/strong&gt;&amp;#x2014;meaning we catch half of all fraud while being right 3 out of 4 times. That&amp;apos;s a 12x improvement over the manual baseline.&lt;/p&gt;&lt;p&gt;Here&amp;apos;s what I learned building it.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Problem: Fraud Hides in Plain Sight&lt;/h3&gt;&lt;p&gt;Insurance claims have an inherent asymmetry: &lt;strong&gt;only ~6% are fraudulent&lt;/strong&gt;. This means any system that simply predicts &amp;quot;not fraud&amp;quot; for everything achieves 94% accuracy&amp;#x2014;while being completely useless.&lt;/p&gt;&lt;p&gt;The real challenge isn&amp;apos;t classification accuracy. It&amp;apos;s &lt;strong&gt;prioritization&lt;/strong&gt;: which claims should investigators spend their limited time on?&lt;/p&gt;&lt;p&gt;Manual review has two failure modes:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;False positives&lt;/strong&gt;: Wasting investigator time on legitimate claims  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;False negatives&lt;/strong&gt;: Missing fraud entirely because the queue is too long  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;I needed a system that could surface high-risk claims without drowning investigators in false alarms.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Architecture: Two Models Are Better Than One&lt;/h3&gt;&lt;p&gt;The key insight came from analyzing what makes fraud claims different. Fraudsters leave two kinds of traces:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Behavioral patterns&lt;/strong&gt;: Multiple past accidents, driving history, demographics  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Linguistic patterns&lt;/strong&gt;: Vague descriptions, timeline inconsistencies, suspiciously generic narratives  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;No single model handles both well. Transformers excel at understanding language but struggle with tabular data. Gradient boosting crushes tabular features but can&amp;apos;t parse free text.&lt;/p&gt;&lt;p&gt;So I built a &lt;strong&gt;dual-engine&lt;/strong&gt; system:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                     Claim Submission                         &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; Structured Data  &amp;#x2502;     Accident Narrative             &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; &amp;#x2022; Age: 26-39     &amp;#x2502;  &amp;quot;I was stopped at a light when   &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; &amp;#x2022; Past accidents &amp;#x2502;   another car sort of hit me...&amp;quot;  &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; &amp;#x2022; DUI history    &amp;#x2502;                                    &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2534;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x2502;                             &amp;#x2502;                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x25bc;                             &amp;#x25bc;                    &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;       &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;    XGBoost      &amp;#x2502;&amp;#x25c4;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2502;   DistilRoBERTa + LoRA      &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502; (Decision Core) &amp;#x2502;       &amp;#x2502;   (Fraud Signal Extractor)  &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;       &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x2502;                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x25bc;                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;   Calibrator    &amp;#x2502;                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  (Isotonic Reg) &amp;#x2502;                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;                                        &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x2502;                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;           &amp;#x25bc;                                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     Risk Score: 73/100                                       &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     Level: HIGH                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;     Top Factor: &amp;quot;Vague Language&amp;quot;                             &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The language model extracts three fraud signals from the narrative:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Vague language&lt;/strong&gt; (&amp;quot;sort of happened&amp;quot;, &amp;quot;hard to say&amp;quot;)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Timeline inconsistencies&lt;/strong&gt; (conflicting dates, unclear sequence)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pre-existing damage mentions&lt;/strong&gt; (suspicious references to prior wear)  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These become features fed into XGBoost alongside traditional structured data.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Lesson 1: LoRA Makes Fine-Tuning Practical&lt;/h3&gt;&lt;p&gt;The standard approach to fine-tuning a transformer is updating all 82 million parameters. That&amp;apos;s expensive, slow, and produces massive model files.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;LoRA (Low-Rank Adaptation)&lt;/strong&gt; changes the game. Instead of updating the full weight matrices, it learns low-rank decomposition matrices that get added to specific layers&amp;#x2014;typically just the attention weights.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# The magic of LoRA: only 1.5% of parameters are trainable&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; peft &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; LoraConfig, get_peft_model&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;lora_config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; LoraConfig(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    r&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;,                    &lt;/span&gt;&lt;span&gt;# Rank of decomposition&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    lora_alpha&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;,          &lt;/span&gt;&lt;span&gt;# Scaling factor&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    target_modules&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span&gt;],  &lt;/span&gt;&lt;span&gt;# Only attention layers&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    lora_dropout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0.1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    task_type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;SEQ_CLS&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; get_peft_model(base_model, lora_config)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# trainable params: 1.2M (1.5% of 82M)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# model size: 30MB instead of 500MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The result? Training time dropped 70%, and the model file went from 500MB to 30MB&amp;#x2014;while retaining 97% of full fine-tuning performance.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Parameter-efficient fine-tuning isn&amp;apos;t just for saving money. Small models deploy faster, cache better, and run on cheaper hardware.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 2: Multi-Label Beats Binary&lt;/h3&gt;&lt;p&gt;My first approach was to train a binary classifier: &amp;quot;Is this claim fraudulent?&amp;quot;&lt;/p&gt;&lt;p&gt;It didn&amp;apos;t work. The model learned to output ~6% probability for everything&amp;#x2014;matching the base rate while being useless for prioritization.&lt;/p&gt;&lt;p&gt;The fix was reframing the problem. Instead of predicting fraud directly, I trained the LLM to detect &lt;strong&gt;specific fraud signals&lt;/strong&gt;:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Multi-label classification: one forward pass, three signals&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; get_llm_features&lt;/span&gt;&lt;span&gt;(text, model, tokenizer):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    inputs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokenizer(text, &lt;/span&gt;&lt;span&gt;return_tensors&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;pt&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max_length&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;128&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    with&lt;/span&gt;&lt;span&gt; torch.no_grad():&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        outputs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; model(&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;inputs)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        probs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; torch.sigmoid(outputs.logits).cpu().numpy()[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;apos;has_vague_language_score&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(probs[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]),&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;apos;mentions_preexisting_damage_score&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(probs[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]),&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &amp;apos;has_timeline_inconsistency_score&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(probs[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This approach has three advantages:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Interpretability&lt;/strong&gt;: Users see &lt;em&gt;why&lt;/em&gt; a claim is risky, not just that it is  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Robustness&lt;/strong&gt;: Even if one signal fails, others might catch fraud  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Feature richness&lt;/strong&gt;: Three continuous features are more informative than one binary  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: When direct prediction fails, reframe the task to predict intermediate signals instead. Your downstream model can learn the complex mapping from signals to outcomes.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 3: Monotonic Constraints Encode Domain Knowledge&lt;/h3&gt;&lt;p&gt;Here&amp;apos;s a problem I didn&amp;apos;t anticipate: the model learned that &lt;em&gt;more&lt;/em&gt; driving experience increased fraud risk.&lt;/p&gt;&lt;p&gt;Wait, what?&lt;/p&gt;&lt;p&gt;It turned out the training data had a spurious correlation: older, experienced drivers filed higher-value claims (they drove nicer cars). The model picked this up as a fraud signal.&lt;/p&gt;&lt;p&gt;XGBoost has an elegant solution: &lt;strong&gt;monotonic constraints&lt;/strong&gt;. You can tell the model that certain features must have one-directional effects:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Constraints: +1 = must increase risk, -1 = must decrease, 0 = unconstrained&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;monotone_constraints &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;SPEEDING_VIOLATIONS&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,    &lt;/span&gt;&lt;span&gt;# More violations &amp;#x2192; higher risk&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;DUIS&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,                   &lt;/span&gt;&lt;span&gt;# More DUIs &amp;#x2192; higher risk&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;PAST_ACCIDENTS&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,         &lt;/span&gt;&lt;span&gt;# More accidents &amp;#x2192; higher risk&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;DRIVING_EXPERIENCE&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,    &lt;/span&gt;&lt;span&gt;# More experience &amp;#x2192; lower risk&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;has_vague_language_score&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;mentions_preexisting_damage_score&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;apos;has_timeline_inconsistency_score&amp;apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;xgb_model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; XGBClassifier(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    monotone_constraints&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;tuple&lt;/span&gt;&lt;span&gt;(monotone_constraints.values()),&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # ... other params&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;During tree construction, XGBoost only considers splits that satisfy these constraints. No amount of spurious correlation can override them.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Encode what you know. Monotonic constraints aren&amp;apos;t just for interpretability&amp;#x2014;they regularize against data artifacts and make predictions defensible to regulators.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 4: Calibration Is Non-Negotiable&lt;/h3&gt;&lt;p&gt;Here&amp;apos;s a common mistake: treating model probabilities as real probabilities.&lt;/p&gt;&lt;p&gt;When my XGBoost model output 0.8, did that mean an 80% chance of fraud? Absolutely not. Raw model scores are uncalibrated&amp;#x2014;they measure relative ranking, not actual probabilities.&lt;/p&gt;&lt;p&gt;This matters because:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Investigators need to set thresholds (&amp;quot;review all claims above 50%&amp;quot;)  &lt;/li&gt;&lt;li&gt; Business decisions require expected value calculations  &lt;/li&gt;&lt;li&gt; &amp;quot;90% fraud probability&amp;quot; must mean 9 in 10 similar claims are actually fraudulent  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I used &lt;strong&gt;isotonic regression&lt;/strong&gt; for calibration:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; sklearn.isotonic &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; IsotonicRegression&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Fit on validation set&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;calibrator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; IsotonicRegression(&lt;/span&gt;&lt;span&gt;out_of_bounds&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;apos;clip&amp;apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;calibrator.fit(val_proba_uncalibrated, val_labels)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# At inference time&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;raw_probability &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; xgb_model.predict_proba(X)[:, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;calibrated_probability &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; calibrator.predict(raw_probability)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;risk_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; int&lt;/span&gt;&lt;span&gt;(calibrated_probability &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Isotonic regression learns a non-decreasing mapping from raw scores to true probabilities. Unlike Platt scaling (which assumes logistic form), it makes no parametric assumptions&amp;#x2014;crucial when your model outputs have weird distributions.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Raw probabilities are not probabilities. Always calibrate before presenting scores to users or making threshold-based decisions.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Lesson 5: Speed and Size Matter More Than You Think&lt;/h3&gt;&lt;p&gt;My first version used SHAP for per-prediction explanations. Each prediction took 30 seconds. That&amp;apos;s fine for batch processing, but useless for a real-time dashboard.&lt;/p&gt;&lt;p&gt;I made a pragmatic trade-off: global feature importance instead of per-prediction explanations.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Fast: global feature importance (~0ms)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;feature_importance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; xgb_model.feature_importances_&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;top_indices &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; np.argsort(feature_importance)[&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;:][::&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Slow: per-prediction SHAP (~30,000ms)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# explainer = shap.TreeExplainer(xgb_model)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# shap_values = explainer.shap_values(X)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The full optimization stack:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; LoRA adapters: 500MB &amp;#x2192; 30MB  &lt;/li&gt;&lt;li&gt; Joblib serialization: Efficient model caching  &lt;/li&gt;&lt;li&gt; Streamlit @cache_resource: Models load once, persist across requests  &lt;/li&gt;&lt;li&gt; Feature importance over SHAP: 30s &amp;#x2192; 300ms  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Final footprint: &lt;strong&gt;7MB&lt;/strong&gt; total model artifacts, &lt;strong&gt;~320ms&lt;/strong&gt; inference time.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; The lesson: Optimize for deployment from the start. A model that can&amp;apos;t run in production is just a Jupyter notebook.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;What I&amp;apos;d Do Differently&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. Use more sophisticated text augmentation&lt;/strong&gt;: I generated synthetic narratives with rules. LLM-based augmentation would create more realistic training data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Add image features&lt;/strong&gt;: Real fraud detection uses damage photos. A future version could incorporate vision models for inconsistency detection.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. Implement drift detection&lt;/strong&gt;: Fraud tactics evolve. The model needs monitoring for distribution shift and scheduled retraining triggers.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. A/B test thresholds&lt;/strong&gt;: The 70/40 thresholds for HIGH/MEDIUM risk were chosen heuristically. In production, you&amp;apos;d optimize these against business metrics.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Results&lt;/h3&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt; Metric &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; Value &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; Precision @ 50% Recall &lt;/td&gt;&lt;td&gt; 75% &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; PR-AUC &lt;/td&gt;&lt;td&gt; 0.82 &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Inference Latency &lt;/td&gt;&lt;td&gt; ~320ms &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Model Size &lt;/td&gt;&lt;td&gt; 7MB &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;The 75% precision means: if investigators review our top-ranked claims until they&amp;apos;ve found half of all fraud, they&amp;apos;ll be right 3 out of 4 times. Compared to random selection (~6% fraud rate), that&amp;apos;s a &lt;strong&gt;12x improvement&lt;/strong&gt; in investigator efficiency.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Key Takeaways&lt;/h3&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Combine model strengths&lt;/strong&gt;: Use transformers for what they&amp;apos;re good at (language), gradient boosting for what it&amp;apos;s good at (tabular data)  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Prefer parameter-efficient fine-tuning&lt;/strong&gt;: LoRA gives you 97% of full fine-tuning at 1.5% of the cost  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Encode domain knowledge&lt;/strong&gt;: Monotonic constraints prevent embarrassing predictions and satisfy regulators  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Always calibrate&lt;/strong&gt;: Raw model scores aren&amp;apos;t probabilities&amp;#x2014;calibrate before making threshold decisions  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Optimize for production&lt;/strong&gt;: A 10x faster model with 95% accuracy beats a slow model with 96% accuracy  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;Aegis is open source and available on GitHub. The training pipeline, including LoRA fine-tuning and XGBoost calibration, runs on Colab.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/&quot;&gt;home&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Self-Healing Voice Agents: Automating Prompt Remediation with AIOps</title>
      <link>https://nayanachandrika99.github.io/posts/self-healing-voice-agents-automating-prompt-remediation-with-aiops/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/self-healing-voice-agents-automating-prompt-remediation-with-aiops/</guid>
      <description>System that detects voice agent failures and fixes them without human intervention</description>
      <pubDate>Fri, 31 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 08:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/self-healing-voice-agents-automating-prompt-remediation-with-aiops/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;System that detects voice agent failures and fixes them without human intervention&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; October 31, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Voice agents break in ways you don&amp;apos;t expect. A prompt that handles &amp;quot;I need an oil change&amp;quot; perfectly in testing fails when a customer says &amp;quot;my car&amp;apos;s making a weird noise, probably needs service.&amp;quot; The intent is the same, but the phrasing is different enough to confuse the classifier.&lt;/p&gt;&lt;p&gt;This is manageable when you&amp;apos;re running one agent for one location. You see the failure in logs, you tweak the prompt, you move on. But when the same agent runs across 50 dealerships, each generating hundreds of calls per day with their own regional phrasings and edge cases, manual monitoring becomes impossible. You can&amp;apos;t have an engineer watching dashboards all day, and you certainly can&amp;apos;t have one responding to 3 AM failures.&lt;/p&gt;&lt;p&gt;I built a system that closes this loop automatically. It detects when the agent starts failing, identifies what&amp;apos;s going wrong, generates a better prompt, and deploys it&amp;#x2014;all without waking anyone up. The key insight is treating prompt degradation the same way we treat infrastructure failures: as something that can be detected via metrics and remediated via automation.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Problem with Voice AI at Scale&lt;/h3&gt;&lt;p&gt;The challenge isn&amp;apos;t making a voice agent that works. The challenge is making one that &lt;em&gt;keeps&lt;/em&gt; working as the world changes around it.&lt;/p&gt;&lt;p&gt;Consider what happens after you deploy a successful appointment-booking agent. Customers start calling with requests you didn&amp;apos;t anticipate: &amp;quot;Can I get in tomorrow instead of Tuesday?&amp;quot; (a reschedule, not a new booking), &amp;quot;Do you guys do tire rotations?&amp;quot; (a service inquiry, not an appointment), &amp;quot;My husband&amp;apos;s car, not mine&amp;quot; (a vehicle mismatch your slot-booking logic doesn&amp;apos;t handle).&lt;/p&gt;&lt;p&gt;Each of these is a small failure. Individually, they&amp;apos;re noise. But when they accumulate&amp;#x2014;when 15% of calls are failing instead of 5%&amp;#x2014;you have a systemic problem that requires prompt changes.&lt;/p&gt;&lt;p&gt;The traditional response is reactive. Someone notices the metrics are down. They pull call logs. They identify the failure pattern. They draft a new prompt. They test it locally. They deploy it. This cycle takes hours at best, days at worst. And during that time, customers are having bad experiences.&lt;/p&gt;&lt;p&gt;Automation can compress this cycle to minutes. The system watches its own metrics, detects when something breaks, diagnoses what&amp;apos;s failing, generates a fix, and deploys it. The humans review the results in the morning, but the fires get put out overnight.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;System Architecture&lt;/h3&gt;&lt;p&gt;The solution requires three components working together:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Voice Agent&lt;/strong&gt; handles customer calls. It classifies intent, executes the appropriate conversation flow (booking, rescheduling, FAQ), and logs outcomes. Critically, it logs &lt;em&gt;structured&lt;/em&gt; outcomes&amp;#x2014;not just &amp;quot;success&amp;quot; or &amp;quot;failure&amp;quot; but &lt;em&gt;why&lt;/em&gt; it failed.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Keep&lt;/strong&gt; is an open-source AIOps platform. It ingests metrics from the voice agent, applies anomaly detection (e.g., &amp;quot;conversion rate dropped 20% in the last hour&amp;quot;), and triggers remediation workflows. Keep handles the detection side: knowing something is wrong.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The GEPA Optimizer&lt;/strong&gt; handles remediation. It receives failed call data from Keep, identifies patterns in the failures, generates an improved prompt, and deploys it as a new version. GEPA handles the fix side: doing something about the problem.&lt;/p&gt;&lt;p&gt;The three form a closed loop. Metrics flow from the agent to Keep. When thresholds are breached, Keep triggers the optimizer. The optimizer generates a new prompt and deploys it back to the agent. The loop runs continuously without human intervention.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          VOICE AGENT LAYER                                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Intent Classifier &amp;#x2502;      &amp;#x2502;  Conversation      &amp;#x2502;      &amp;#x2502; Outcome Logger &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  Flows             &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;    &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; Booking         &amp;#x2502;      &amp;#x2502;  &amp;#x2022; BookingFlow     &amp;#x2502;      &amp;#x2502; &amp;#x2022; success      &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; Reschedule      &amp;#x2502;      &amp;#x2502;  &amp;#x2022; RescheduleFlow  &amp;#x2502;      &amp;#x2502; &amp;#x2022; failure_type &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; FAQ             &amp;#x2502;      &amp;#x2502;  &amp;#x2022; FAQFlow         &amp;#x2502;      &amp;#x2502; &amp;#x2022; transcript   &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   &amp;#x2502; metrics&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          AIOPS LAYER (Keep)                                  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Metric Ingestion  &amp;#x2502;      &amp;#x2502;  Anomaly Detection &amp;#x2502;      &amp;#x2502; Workflow       &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; Trigger        &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; conversion_rate &amp;#x2502;      &amp;#x2502;  &amp;quot;Rate dropped     &amp;#x2502;      &amp;#x2502; &amp;#x2022; fetch fails  &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2022; avg_call_time   &amp;#x2502;      &amp;#x2502;   20% in 1 hour&amp;quot;   &amp;#x2502;      &amp;#x2502; &amp;#x2022; call webhook &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   &amp;#x2502; alert + data&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                          OPTIMIZATION LAYER (GEPA)                           &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;                                                                              &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  Failure Analysis  &amp;#x2502;      &amp;#x2502;  Prompt Generation &amp;#x2502;      &amp;#x2502; Version Store  &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502;  &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;     &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x25b6; &amp;#x2502; &amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;    &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  NO_SLOTS: 18      &amp;#x2502;      &amp;#x2502;  Current + fails   &amp;#x2502;      &amp;#x2502; v1 &amp;#x2192; v2 &amp;#x2192; v3   &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  CONFIDENCE: 8     &amp;#x2502;      &amp;#x2502;  + objectives      &amp;#x2502;      &amp;#x2502;                &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2502;  &amp;#x2192; derive goals    &amp;#x2502;      &amp;#x2502;  &amp;#x2192; LLM &amp;#x2192; v4        &amp;#x2502;      &amp;#x2502; is_active: v4  &amp;#x2502;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;      &amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x252c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x253c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                                                   &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500; deploys v4 &amp;#x25c0;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      &amp;#x25bc;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x250c;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2510;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2502;  CLOSED LOOP: Agent uses new prompt &amp;#x2192; metrics recover &amp;#x2192; Keep auto-resolves  &amp;#x2502;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2514;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2500;&amp;#x2518;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The architecture separates concerns cleanly. The agent focuses on handling calls well. Keep focuses on detecting problems. The optimizer focuses on generating fixes. Each component is independently testable and replaceable.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Structured Failure Logging&lt;/h3&gt;&lt;p&gt;The entire system depends on one thing: knowing &lt;em&gt;why&lt;/em&gt; calls fail, not just &lt;em&gt;that&lt;/em&gt; they fail.&lt;/p&gt;&lt;p&gt;A log entry that says &amp;quot;call failed&amp;quot; tells you nothing actionable. Did it fail because no appointment slots were available? Because the customer hung up? Because the agent couldn&amp;apos;t understand the request? Each of these requires a different fix. Slot availability is a data problem, not a prompt problem. Customer hangups might indicate the greeting is too long. Intent confusion indicates the prompt needs better examples.&lt;/p&gt;&lt;p&gt;I defined a canonical set of failure reasons:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; FailureReason&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Enum&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    NO_SLOTS&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &amp;quot;no_slots&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    CUSTOMER_DISENGAGED&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &amp;quot;customer_disengaged&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    AGENT_CONFIDENCE_LOW&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &amp;quot;agent_confidence_low&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    UNKNOWN&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &amp;quot;unknown&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Every call outcome includes the appropriate failure reason. When a customer hangs up mid-conversation, that&amp;apos;s &lt;code&gt;CUSTOMER_DISENGAGED&lt;/code&gt;. When the booking system has no available slots, that&amp;apos;s &lt;code&gt;NO_SLOTS&lt;/code&gt;. When the agent can&amp;apos;t classify what the customer wants, that&amp;apos;s &lt;code&gt;AGENT_CONFIDENCE_LOW&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This structure propagates through the entire system. When the optimizer receives a batch of failed calls, it can aggregate by failure type: &amp;quot;Of these 30 failures, 18 were &lt;code&gt;NO_SLOTS&lt;/code&gt;, 8 were &lt;code&gt;AGENT_CONFIDENCE_LOW&lt;/code&gt;, and 4 were &lt;code&gt;CUSTOMER_DISENGAGED&lt;/code&gt;.&amp;quot; That breakdown tells you what to fix. If most failures are slot-related, the prompt probably needs to offer waitlist options more proactively. If most are confidence-related, the prompt needs better intent-handling instructions.&lt;/p&gt;&lt;p&gt;The key insight is that failure categorization is a design decision made at the agent level, but it pays dividends at the optimization level. You can&amp;apos;t derive this structure from raw transcripts&amp;#x2014;the agent has to record it explicitly.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Deriving Optimization Objectives&lt;/h3&gt;&lt;p&gt;Raw failure counts aren&amp;apos;t useful to an LLM generating prompt improvements. &amp;quot;18 &lt;code&gt;NO_SLOTS&lt;/code&gt; failures&amp;quot; doesn&amp;apos;t tell the model what to do. The system needs to translate failure patterns into natural language objectives that guide generation.&lt;/p&gt;&lt;p&gt;This translation encodes domain knowledge about what each failure type implies:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; derive_objectives&lt;/span&gt;&lt;span&gt;(failure_reasons: list[FailureReason]) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    counts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Counter(failure_reasons)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    objectives &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; counts.get(FailureReason.&lt;/span&gt;&lt;span&gt;NO_SLOTS&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        objectives.append(&lt;/span&gt;&lt;span&gt;&amp;quot;Proactively offer waitlist when no immediate slots are available&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        objectives.append(&lt;/span&gt;&lt;span&gt;&amp;quot;Suggest alternative dates before saying slots are unavailable&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; counts.get(FailureReason.&lt;/span&gt;&lt;span&gt;CUSTOMER_DISENGAGED&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        objectives.append(&lt;/span&gt;&lt;span&gt;&amp;quot;Shorten the initial greeting to reach the customer&amp;apos;s intent faster&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        objectives.append(&lt;/span&gt;&lt;span&gt;&amp;quot;Use warmer, more engaging language to maintain customer attention&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; counts.get(FailureReason.&lt;/span&gt;&lt;span&gt;AGENT_CONFIDENCE_LOW&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        objectives.append(&lt;/span&gt;&lt;span&gt;&amp;quot;Ask clarifying questions when the request is ambiguous&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        objectives.append(&lt;/span&gt;&lt;span&gt;&amp;quot;Handle compound requests by addressing each part separately&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; objectives &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&amp;quot;Improve overall call resolution rate&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This function is where human expertise enters the system. Someone who understands voice agent failure modes wrote these mappings. &lt;code&gt;NO_SLOTS&lt;/code&gt; failures suggest the prompt should handle slot unavailability more gracefully&amp;#x2014;that&amp;apos;s domain knowledge, not something the system can infer automatically.&lt;/p&gt;&lt;p&gt;The objectives become part of the prompt sent to the LLM for generation. Instead of asking &amp;quot;make this prompt better,&amp;quot; the system asks &amp;quot;make this prompt better by offering waitlists when slots are unavailable and asking clarifying questions when requests are ambiguous.&amp;quot; Specific objectives produce specific improvements.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;The Optimization Workflow&lt;/h3&gt;&lt;p&gt;When Keep detects a metric anomaly&amp;#x2014;say, conversion rate dropping below 80%&amp;#x2014;it triggers a workflow that calls the optimizer. The workflow is defined in YAML and executed by Keep&amp;apos;s workflow engine:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;workflow&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;voice-ai-remediation&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  triggers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;alert&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      filters&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        - &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;conversion_rate_drop&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fetch-failures&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      provider&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;postgres&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        query&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          SELECT transcript, summary, failure_reason&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          FROM call_outcomes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          WHERE success = false AND created_at &amp;gt; NOW() - INTERVAL &amp;apos;1 hour&amp;apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;trigger-optimization&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      provider&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;http&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;      with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;quot;{{ env.GEPA_OPTIMIZER_URL }}/optimize&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          alert_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;quot;{{ alert.id }}&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;          failed_calls&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;quot;{{ steps.fetch-failures.results }}&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The optimizer receives this payload and executes the generation workflow. It loads the current active prompt, derives objectives from the failure patterns, builds a generation prompt that includes the current prompt text plus failed call summaries plus objectives, calls the LLM to generate an improved version, and stores the result as a new prompt version.&lt;/p&gt;&lt;p&gt;The generation prompt looks like this:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Current prompt (version v3):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[current prompt text]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Recent failures (30 calls in the last hour):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Customer asked about tire rotation, agent failed to classify intent&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Customer requested Tuesday instead of Monday, agent treated as new booking&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Customer mentioned husband&amp;apos;s car, agent didn&amp;apos;t handle vehicle mismatch&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Objectives:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Ask clarifying questions when the request is ambiguous&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Handle rescheduling requests distinctly from new bookings&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- Confirm vehicle ownership before proceeding&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Generate an updated prompt that addresses these failures while preserving what works.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The LLM generates improved prompt text based on this context. The optimizer parses the response, stores it as a new version (v4), and makes it active. The next call to the agent will use the new prompt.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Prompt Versioning&lt;/h3&gt;&lt;p&gt;Prompts aren&amp;apos;t mutable text files. They&amp;apos;re versioned artifacts with full history.&lt;/p&gt;&lt;p&gt;Every optimization creates a new version. Version identifiers increase monotonically: v1, v2, v3, and so on. The database stores the complete history: what each version&amp;apos;s text contained, when it was created, why it was created (the optimization notes), and whether it&amp;apos;s currently active.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; PromptVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    version: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;           # &amp;quot;v3&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    content: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;           # Full prompt text&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    notes: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;             # &amp;quot;Addressed NO_SLOTS failures, added waitlist offer&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    is_active: &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;        # Only one version is active at a time&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    created_at: datetime&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Versioning enables several capabilities:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Rollback&lt;/strong&gt;: If v4 performs worse than v3, you can revert by changing which version is active. The old text still exists; you&amp;apos;re just pointing at it again.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Audit trail&lt;/strong&gt;: Every prompt change has a recorded reason. &amp;quot;Why does the prompt mention waitlists?&amp;quot; Look at v4&amp;apos;s notes: it was generated to address NO_SLOTS failures. This is essential for debugging and for explaining decisions to stakeholders.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Comparison&lt;/strong&gt;: You can diff v3 against v4 to see exactly what changed. Did the optimizer add a new section? Modify existing wording? Remove something that was working? The version history makes this visible.&lt;/p&gt;&lt;p&gt;The versioning overhead is minimal&amp;#x2014;a few database rows per optimization run&amp;#x2014;but the debugging and rollback capabilities are invaluable. You never want to be in a situation where you can&amp;apos;t recover a previous prompt because someone overwrote it.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Scoring Before Deployment&lt;/h3&gt;&lt;p&gt;Not every generated prompt is an improvement. LLMs hallucinate. They misunderstand objectives. They sometimes make prompts worse while trying to make them better.&lt;/p&gt;&lt;p&gt;The system scores new prompts before deploying them. The score evaluates multiple dimensions:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Coverage&lt;/strong&gt;: Does the prompt address the failure types that triggered optimization? If we optimized because of &lt;code&gt;AGENT_CONFIDENCE_LOW&lt;/code&gt; failures, the new prompt should include language about handling ambiguous requests.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Specificity&lt;/strong&gt;: Are the instructions concrete or vague? &amp;quot;Be helpful&amp;quot; is vague. &amp;quot;Ask clarifying questions when the customer&amp;apos;s request mentions multiple services&amp;quot; is specific.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Brevity&lt;/strong&gt;: Longer isn&amp;apos;t always better. Prompts that balloon in size may confuse the model or hit token limits. The score penalizes excessive length.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ScoreBreakdown&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    coverage: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    specificity: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    brevity: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    def&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;(self) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.coverage &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.specificity &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.brevity&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The score determines whether the prompt gets deployed. If &lt;code&gt;score.total()&lt;/code&gt; exceeds a minimum threshold, the new version goes active. If not, the optimization run is logged as attempted but the previous prompt remains active.&lt;/p&gt;&lt;p&gt;This prevents regressions. A bad LLM generation won&amp;apos;t tank your production system because it won&amp;apos;t pass the scoring threshold. The safeguard isn&amp;apos;t perfect&amp;#x2014;scoring is based on the prompt text, not actual call outcomes&amp;#x2014;but it catches obvious failures like prompts that ignore the optimization objectives entirely.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Why Keep?&lt;/h3&gt;&lt;p&gt;You could build the detection piece yourself. Poll a metrics endpoint, fire webhooks when thresholds are breached, done. So why use Keep?&lt;/p&gt;&lt;p&gt;Keep provides value beyond simple threshold alerting:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Deduplication&lt;/strong&gt;: If conversion drops across 15 dealerships simultaneously, you don&amp;apos;t want 15 separate alerts. Keep correlates related events into a single incident. The optimizer gets called once, not 15 times.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Enrichment&lt;/strong&gt;: Keep workflows can query multiple data sources, call external APIs, and enrich alerts with context before calling the optimizer. &amp;quot;Conversion dropped&amp;quot; becomes &amp;quot;Conversion dropped, 80% of failures are AGENT_CONFIDENCE_LOW, mostly happening in Texas region.&amp;quot;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Lifecycle management&lt;/strong&gt;: When metrics recover after a prompt update, Keep can auto-resolve the incident. The full remediation cycle&amp;#x2014;detection, diagnosis, fix, recovery&amp;#x2014;is tracked as one incident with clear timestamps for each phase.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Workflow orchestration&lt;/strong&gt;: The workflow definition is declarative YAML, not scattered code. Changing the remediation logic means editing a config file, not modifying application code.&lt;/p&gt;&lt;p&gt;Using established AIOps tooling for ML systems is an underappreciated pattern. Prompt degradation is as much an operations problem as a model problem. The same techniques that monitor database latency and API error rates apply to voice agent conversion rates.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Limitations and Future Work&lt;/h3&gt;&lt;p&gt;The system has clear limitations that I&amp;apos;d address with more time:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;No shadow testing&lt;/strong&gt;: New prompts deploy immediately to all traffic. A safer approach would deploy to 10% of calls first, compare conversion rates against the old prompt, and only fully roll out if the new version performs better in production.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Single-shot optimization&lt;/strong&gt;: The optimizer generates one prompt per run. Iterative refinement&amp;#x2014;generate a candidate, test it, refine based on results, repeat&amp;#x2014;would likely produce better outcomes than single-shot generation.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Heuristic scoring&lt;/strong&gt;: The scoring evaluates prompt text characteristics, not actual call outcomes. True validation requires A/B testing against live traffic, which the current system doesn&amp;apos;t do.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Manual rollback&lt;/strong&gt;: If a new prompt performs worse, reverting requires manual intervention. Automated rollback triggered by metric degradation would complete the self-healing loop.&lt;/p&gt;&lt;p&gt;These are engineering improvements, not fundamental architectural changes. The closed-loop structure is sound; the implementation could be more sophisticated.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Results&lt;/h3&gt;&lt;p&gt;The complete remediation cycle works like this:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Conversion rate drops below 80% &amp;#x2192; Keep fires alert  &lt;/li&gt;&lt;li&gt; Keep workflow fetches recent failed calls from database  &lt;/li&gt;&lt;li&gt; Workflow calls optimizer with failure data  &lt;/li&gt;&lt;li&gt; Optimizer derives objectives from failure distribution  &lt;/li&gt;&lt;li&gt; LLM generates updated prompt  &lt;/li&gt;&lt;li&gt; Scoring passes threshold; new version deployed  &lt;/li&gt;&lt;li&gt; Conversion recovers; Keep auto-resolves incident  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;Total cycle time: minutes, not hours.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Technology Stack&lt;/h3&gt;&lt;ul&gt;&lt;li&gt; Python 3.11, Flask for the optimizer API  &lt;/li&gt;&lt;li&gt; PostgreSQL for prompt version storage and call outcome logging  &lt;/li&gt;&lt;li&gt; Keep (open-source) for alerting and workflow orchestration  &lt;/li&gt;&lt;li&gt; Qwen via Together API for prompt generation  &lt;/li&gt;&lt;li&gt; Docker Compose for local development and testing  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/&quot;&gt;home&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Peeking Inside Diffusion Language Models: A LogitLens Analysis</title>
      <link>https://nayanachandrika99.github.io/posts/peeking-inside-diffusion-language-models-a-logitlens-analysis/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/peeking-inside-diffusion-language-models-a-logitlens-analysis/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Mon Jan 12 2026 03:36:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>mechInterp</category>
      <category>aiSafety</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/peeking-inside-diffusion-language-models-a-logitlens-analysis/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 25, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; January 12, 2026 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/aisafety/&quot;&gt; aiSafety &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;
Bookmark for &lt;a href=&quot;https://drive.google.com/file/d/12eHAXqjh-SzbwIxOuO8azonSMGlwnjgA/view?usp=sharing&quot;&gt;https://drive.google.com/file/d/12eHAXqjh-SzbwIxOuO8azonSMGlwnjgA/view?usp=sharing&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;This notebook explores the inner workings of diffusion-based language models (DLMs) like LLaDA using the LogitLens interpretability method. The analysis is inspired by two key sources:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Alessio de Voto&amp;apos;s blog post, which provides an excellent guide to the&amp;#xa0;&lt;a href=&quot;https://www.google.com/url?q=https%3A%2F%2Falessiodevoto.github.io%2FDiffusion-Language-Models-Inner-Workings%2F&quot; target=&quot;_blank&quot;&gt;inner workings of Diffusion Language Models&lt;/a&gt;.  &lt;/li&gt;&lt;li&gt; The &amp;quot;Prophet&amp;quot; paper, which investigates&amp;#xa0;&lt;a href=&quot;https://www.google.com/url?q=https%3A%2F%2Fdoi.org%2F10.48550%2FarXiv.2508.19982&quot; target=&quot;_blank&quot;&gt;verifying emergent properties in diffusion models&lt;/a&gt;&amp;#xa0;and suggests that answers often converge early in the decoding timeline.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;Unlike traditional left-to-right text generators, DLMs are bidirectional models that learn to fill in masked tokens through an iterative denoising process. LogitLens allows us to explore how a model&amp;apos;s predictions evolve across its layers by applying the unembedding matrix (the language modeling head) to the hidden states of all intermediate layers.&lt;/p&gt;&lt;p&gt;Today, we&amp;apos;re turning this lens on LLaDA to uncover how it builds its understanding layer by layer, providing insights into DLM functionality and highlighting differences with respect to autoregressive models.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Final Analysis: Two Axes of Convergence&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;By extending the LogitLens analysis across the full iterative decoding process, we can visually reconcile the findings of the &amp;quot;Prophet&amp;quot; paper (early convergence in time) with the layer-by-layer patterns seen in our initial analysis (late convergence in depth).&lt;/p&gt;&lt;p&gt;The results reveal that Diffusion Language Models exhibit two distinct types of convergence:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. Convergence in Time (The &amp;quot;Prophet&amp;quot; Paper&amp;apos;s Finding)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Examining the sequence of heatmaps from each timestep makes the &amp;quot;early convergence&amp;quot; phenomenon clear.&lt;/p&gt;&lt;ul&gt;&lt;li&gt; In the initial steps (&lt;code&gt;t=0&lt;/code&gt;&amp;#xa0;to&amp;#xa0;&lt;code&gt;t&amp;#x2248;7&lt;/code&gt;), the model&amp;apos;s final prediction (the bottom row) is uncertain and incoherent.  &lt;/li&gt;&lt;li&gt; Around the halfway mark (e.g., timestep&amp;#xa0;&lt;code&gt;t=8&lt;/code&gt;), the model&amp;apos;s output stabilizes on a complete and correct sentence.  &lt;/li&gt;&lt;li&gt; In all subsequent steps (&lt;code&gt;t=9&lt;/code&gt;&amp;#xa0;to&amp;#xa0;&lt;code&gt;t=15&lt;/code&gt;), this final prediction remains largely unchanged, with the model only growing more certain (as shown by the brighter colors in the final layers).  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This confirms the core finding that the final answer is often determined early in the iterative decoding process.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Convergence in Depth (The Logit Lens Finding)&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Within any single heatmap&amp;#x2014;including those where the final answer has already converged&amp;#x2014;we still observe a &amp;quot;late convergence&amp;quot; pattern across the model&amp;apos;s layers.&lt;/p&gt;&lt;ul&gt;&lt;li&gt; The initial layers (top rows) consistently show low certainty (dark colors) and often predict nonsensical or placeholder tokens.  &lt;/li&gt;&lt;li&gt; The correct tokens and high certainty (bright yellow colors) only emerge in the final, deeper layers of the network.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Conclusion&lt;/h4&gt;&lt;p&gt;This experiment reveals a more complete picture of how DLMs operate. They first establish a high-level, globally coherent &amp;quot;plan&amp;quot; for the full sentence early in the decoding timeline. Then, within each step of executing that plan, they refine the details from abstract representations (in early layers) to concrete tokens (in the final layers).&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Geometry of Hidden State</title>
      <link>https://nayanachandrika99.github.io/posts/geometry-of-hidden-state/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/geometry-of-hidden-state/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Oct 19 2025 17:14:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>aiSafety</category>
      <category>mechInterp</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/geometry-of-hidden-state/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 19, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/aisafety/&quot;&gt; aiSafety &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Linear Subspaces (CoNLL 2021):&lt;/strong&gt; Many linguistic features, from part-of-speech to dependency arcs, are encoded in low-dimensional linear subspaces within a model&amp;apos;s hidden states. These subspaces are structured, distributed across many neurons, and-crucially-intervening on them causally changes model outputs.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Affine Relational Maps (ICLR 2024):&lt;/strong&gt; The complex, multi-layer computation that maps a subject (&amp;quot;Paris&amp;quot;) to an object (&amp;quot;France&amp;quot;) for a given relation (e.g., &lt;code&gt;capital of&lt;/code&gt;) can often be approximated by a simple affine transformation (&lt;code&gt;Wx + b&lt;/code&gt;) at a specific layer and token position. This holds for roughly half of the common relations tested.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Causal Geometry (ICML 2024):&lt;/strong&gt; The very notion of &amp;quot;linearity&amp;quot; or a &amp;quot;direction&amp;quot; is only meaningful if the geometry (the inner product) respects the causal structure of language. A proposed &amp;quot;causal inner product&amp;quot; provides a principled way to define these directions, unifying the goals of probing (reading concepts) and steering (writing concepts), and often revealing linear structure where naive metrics like cosine similarity fail.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The internal world of a large language model is a labyrinth of high-dimensional vectors. Each forward pass is a journey through this geometric space, where abstract concepts are manipulated by matrices and non-linear functions. For years, our best descriptions of this process were frustratingly opaque. But a recent thread of research suggests that within this complexity lies a surprising amount of simple, linear structure.&lt;/p&gt;&lt;p&gt;This isn&amp;apos;t just a curiosity. If concepts and relations are encoded linearly, we can build more reliable tools to interpret what models know, predict how they will behave, and even perform surgical interventions to edit their knowledge or control their outputs. Three key papers-one on linguistic subspaces, one on relational maps, and one on the underlying geometry itself-form a powerful, unified narrative. This post unpacks their findings to explain what &amp;quot;linearity&amp;quot; really buys us.&lt;/p&gt;&lt;h4&gt;What We Mean by &amp;#x201c;Linearity&amp;#x201d;&lt;/h4&gt;&lt;p&gt;Before diving in, it&amp;#x2019;s critical to untangle a few distinct ideas often conflated under the banner of &amp;quot;linearity.&amp;quot; The claim is not that transformers are secretly linear models. They are deeply non-linear. The claim is that we can often use &lt;em&gt;linear tools&lt;/em&gt; to meaningfully describe and manipulate their internal representations.&lt;/p&gt;&lt;p&gt;Here are three increasingly specific senses of linearity:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Linear Separability:&lt;/strong&gt; The weakest claim. This means that hidden states corresponding to different concepts (e.g., &lt;code&gt;positive sentiment&lt;/code&gt; vs. &lt;code&gt;negative sentiment&lt;/code&gt;) can be separated by a simple hyperplane. A linear classifier (a &amp;quot;probe&amp;quot;) can be trained to read out the concept from the hidden state. This is a classic observation, but it only tells us that the information is &lt;em&gt;present&lt;/em&gt;, not how it&amp;apos;s used.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Linear Decoding:&lt;/strong&gt; A stronger claim. This suggests that a concept can be read out directly from a hidden state &lt;span&gt;&lt;span&gt;&lt;span&gt;r(l)r^{(l)}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; via a simple linear map, often just multiplication by a vector or matrix. For example, the log-probability of a token might be a linear function of the final hidden state via the unembedding matrix &lt;span&gt;&lt;span&gt;&lt;span&gt;WUW^U&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;U&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. This implies the information is not just separable but also in a format that downstream components can easily access.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Affine Relational Mapping:&lt;/strong&gt; The most specific claim, and the focus of recent work. This idea posits that for a given relation &lt;span&gt;&lt;span&gt;&lt;span&gt;rr&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; (like &lt;code&gt;country -&amp;gt; capital&lt;/code&gt;), there exists an affine map (&lt;span&gt;&lt;span&gt;&lt;span&gt;Wr,brW_r, b_r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) that transforms the hidden state of the &lt;em&gt;subject&lt;/em&gt; &amp;#x24;s&amp;#x24; into the hidden state of the &lt;em&gt;object&lt;/em&gt;. That is, &lt;span&gt;&lt;span&gt;&lt;span&gt;LREr(s)=Wrs+br\text{LRE}_r(s) = W_r s + b_r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;LRE&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; approximates the model&amp;apos;s internal computation.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Caveat:&lt;/em&gt; The choice of geometry-how we measure distance and angles-fundamentally changes whether these claims appear true. Most analyses default to the Euclidean inner product (and its cousin, cosine similarity). But as the work on the Linear Representation Hypothesis (LRH) shows, this can be misleading. A &amp;quot;causal&amp;quot; inner product, which respects the statistical independence of concepts in language, can reveal linear structure that is invisible to standard metrics, or show that apparent linearity was an artifact. Declaring the geometry is non-negotiable for making a rigorous claim.&lt;/p&gt;&lt;h4&gt;Background &amp;amp; Notation&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s establish a common language. We view a transformer through the lens of its &lt;strong&gt;residual stream&lt;/strong&gt;. A hidden state &lt;span&gt;&lt;span&gt;&lt;span&gt;r(l)&amp;#x2208;Rdr^{(l)} \in \mathbb{R}^d&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2208;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; at layer &lt;span&gt;&lt;span&gt;&lt;span&gt;ll&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and a specific token position represents the accumulated information up to that point. The model&amp;apos;s final prediction depends on the final state, typically via the unembedding matrix &lt;span&gt;&lt;span&gt;&lt;span&gt;WUW^U&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;U&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; which maps &lt;span&gt;&lt;span&gt;&lt;span&gt;r(L)r^{(L)}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;L&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; to logits over the vocabulary.&lt;/p&gt;&lt;ul&gt;&lt;li&gt; A &lt;strong&gt;concept&lt;/strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;cc&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; (e.g., &lt;code&gt;gender&lt;/code&gt;, &lt;code&gt;tense&lt;/code&gt;) can be represented by a &lt;strong&gt;linear subspace&lt;/strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Sc&amp;#x2282;RdS_c \subset \mathbb{R}^d&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2282;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. We can isolate the component of a hidden state that lies in this subspace using a projection operator &lt;span&gt;&lt;span&gt;&lt;span&gt;PcP_c&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;P&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;li&gt; A &lt;strong&gt;Linear Relational Embedding (LRE)&lt;/strong&gt; for a relation &lt;span&gt;&lt;span&gt;&lt;span&gt;rr&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is an affine map &lt;span&gt;&lt;span&gt;&lt;span&gt;LREr(s)=Wrs+br\text{LRE}_r(s) = W_r s + b_r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;LRE&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. It takes a subject representation &amp;#x24;s&amp;#x24; (a hidden state at a certain layer/position) and approximates the corresponding object representation. This map is often estimated by taking a first-order Taylor approximation of the model&amp;apos;s computation around a reference subject &lt;span&gt;&lt;span&gt;&lt;span&gt;s0s_0&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;li&gt; The &lt;strong&gt;Causal Inner Product&lt;/strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x27e8;&amp;#x22c5;,&amp;#x22c5;&amp;#x27e9;causal\langle \cdot, \cdot \rangle_{\text{causal}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x27e8;&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x27e9;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;causal&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is a non-Euclidean metric designed to make concepts that are semantically independent in the world (e.g., &lt;code&gt;tense&lt;/code&gt; and &lt;code&gt;sentiment&lt;/code&gt;) orthogonal in the representation space. It provides a formal basis for when talking about &amp;quot;directions&amp;quot; is meaningful and connects the geometry of representations to counterfactual changes in model inputs and outputs.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With these tools, we will now examine the evidence.&lt;/p&gt;&lt;h4&gt;Paper A (CoNLL 2021): Linear Subspaces in Practice&lt;/h4&gt;&lt;p&gt;The work by Hernandez and Andreas (2021) provided strong, early evidence that linear structures weren&amp;apos;t just a convenient analytical tool, but a core aspect of how models like BERT organize linguistic information.&lt;/p&gt;&lt;p&gt;Their method involved identifying subspaces for various linguistic features, from simple ones like part-of-speech to complex ones like the grammatical role of a word in a dependency parse. They trained simple linear probes to find these subspaces and then tested their properties.&lt;/p&gt;&lt;p&gt;Key Findings:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Low-Dimensionality:&lt;/strong&gt; Subspaces for many distinct linguistic features were surprisingly low-dimensional, often occupying just 1-10 dimensions out of the full 768 in BERT-base. This suggests information is compressed into specific, targeted geometric structures.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hierarchical Structure:&lt;/strong&gt; The subspaces reflected linguistic hierarchies. For example, the subspace for &lt;code&gt;verb&lt;/code&gt; would contain the more specific subspaces for &lt;code&gt;transitive verb&lt;/code&gt; and &lt;code&gt;intransitive verb&lt;/code&gt;. This geometric containment mirrors the conceptual relationship.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Distributed Representation:&lt;/strong&gt; These subspaces were not aligned with individual neurons. The basis vectors for a concept&amp;apos;s subspace were distributed across many coordinates, confirming that features are stored collectively, not in single &amp;quot;grandmother cells.&amp;quot;  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Most importantly, the paper established a &lt;strong&gt;causal link&lt;/strong&gt; between these subspaces and model behavior. They performed interventions by projecting a hidden state onto a chosen subspace or removing its component in that subspace. For example, by manipulating the subspace for a dependency arc, they could systematically change which words the model attended to and alter its masked language modeling predictions in a controlled way.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Caveat:&lt;/em&gt; This work demonstrates that linear signals are present and can be causally manipulated. It does not, however, prove that the model &lt;em&gt;always&lt;/em&gt; uses that signal for every downstream task. A feature might be linearly encoded but ignored by later layers in certain contexts. The causal link is strong evidence for its functional role, but not a guarantee of its universal application.&lt;/p&gt;&lt;h4&gt;Paper B (ICLR 2024): Linear Relation Decoding&lt;/h4&gt;&lt;p&gt;While the CoNLL paper focused on properties of single words or tokens, Hernandez et al. (2024) investigated the geometry of &lt;em&gt;relations&lt;/em&gt; between entities. Their central idea is the Linear Relational Embedding (LRE), which tests whether the complex computation mapping a subject to an object can be simplified to an affine transformation.&lt;/p&gt;&lt;p&gt;To find the relational map &lt;span&gt;&lt;span&gt;&lt;span&gt;(Wr,br)(W_r, b_r)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; for a relation like &lt;code&gt;country -&amp;gt; capital&lt;/code&gt;, they compute the local Jacobian of the model&amp;apos;s transformation from the subject&amp;apos;s hidden state to the object&amp;apos;s hidden state. By averaging this estimate over a few example subjects, they obtain a robust linear operator that approximates the relation.&lt;/p&gt;&lt;p&gt;Empirical Scope and Limits:&lt;/p&gt;&lt;p&gt;The team tested this method on 47 different relations, spanning factual knowledge, commonsense reasoning, linguistic patterns, and even social biases.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Successes:&lt;/strong&gt; The linear approximation worked remarkably well for about half of the relations. For these, applying the learned LRE to a subject&amp;apos;s hidden state could reliably produce the hidden state of the correct object, often for subjects unseen during the LRE&amp;apos;s estimation.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Failures:&lt;/strong&gt; The approximation consistently failed for relations where the set of possible objects is vast and arbitrary, such as &lt;code&gt;company -&amp;gt; CEO&lt;/code&gt; or &lt;code&gt;person -&amp;gt; birth city&lt;/code&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;em&gt;Intuition:&lt;/em&gt; This pattern suggests a compelling hypothesis. For relations with a constrained, structured output space (like capitals or languages), the transformer can &amp;quot;pre-compile&amp;quot; the computation into a nearly-linear map within a specific processing window (i.e., across a few layers). For open-ended relations, it must rely on more complex, non-linear search and retrieval mechanisms that cannot be captured by a single LRE.&lt;/p&gt;&lt;p&gt;This work also introduced the &lt;strong&gt;Attribute Lens&lt;/strong&gt;, a practical application of LREs. By applying a pre-computed LRE (e.g., for &lt;code&gt;country -&amp;gt; capital&lt;/code&gt;) to the hidden states at every token and every layer for an input like &amp;quot;The government of France announced,&amp;quot; one can visualize exactly where and when the model computes the capital &amp;quot;Paris,&amp;quot; even if it is never explicitly mentioned.&lt;/p&gt;&lt;h4&gt;Paper C (ICML 2024): Definitions &amp;amp; the Causal Inner Product&lt;/h4&gt;&lt;p&gt;The Linear Representation Hypothesis (LRH) paper by Park, Choe, and Veitch (2024) provides the theoretical bedrock for these empirical findings. It asks a fundamental question: what does it even &lt;em&gt;mean&lt;/em&gt; for a concept to be &amp;quot;linearly represented&amp;quot;?&lt;/p&gt;&lt;p&gt;They provide two formal definitions grounded in counterfactuals:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Output-Space (Measurement):&lt;/strong&gt; A concept has a linear representation if swapping the concept in the output (e.g., changing &amp;quot;he&amp;quot; to &amp;quot;she&amp;quot;) corresponds to moving along a consistent direction in the hidden state space. This formalizes the idea behind linear probing.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Input-Space (Intervention):&lt;/strong&gt; A concept has a linear representation if swapping the concept in the &lt;em&gt;input context&lt;/em&gt; (e.g., changing &amp;quot;The doctor said he...&amp;quot; to &amp;quot;The doctor said she...&amp;quot;) corresponds to moving along a consistent direction. This formalizes the idea behind model steering or vector addition.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;The central insight is that these two definitions are unified by choosing the right geometry. The standard Euclidean inner product is a poor choice because it treats all dimensions equally. Language is not like that; some concepts are related, others are independent. The authors introduce the &lt;strong&gt;causal inner product&lt;/strong&gt;, a metric derived from the model&amp;apos;s unembedding matrix that makes semantically independent concepts orthogonal.&lt;/p&gt;&lt;p&gt;What the Causal Inner Product Buys Us:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Principled Directions:&lt;/strong&gt; It provides a rigorous way to define &amp;quot;concept directions&amp;quot; that align with counterfactual meaning. A steering vector for &lt;code&gt;gender&lt;/code&gt; should, ideally, be orthogonal to a steering vector for &lt;code&gt;tense&lt;/code&gt; under this metric.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Improved Separability:&lt;/strong&gt; The paper demonstrates empirically on LLaMA-2 that concepts that are muddled under cosine similarity become cleanly separable using the causal inner product. For instance, contexts about France and Spain might seem similar in Euclidean space, but the causal metric can better isolate the &lt;code&gt;country&lt;/code&gt;specific components.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Unification:&lt;/strong&gt; With the causal inner product, the &amp;quot;probe&amp;quot; direction (from the output-space definition) and the &amp;quot;steering&amp;quot; direction (from the input-space definition) become one and the same. This provides a single, coherent geometric object for both reading and writing a concept.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Experiments with LLaMA-2 confirm that linear representations meeting these causal definitions exist for a variety of concepts, and that the choice of inner product is not merely academic-it fundamentally determines whether these structures are found.&lt;/p&gt;&lt;h4&gt;Synthesis - One Geometry, Many Tools&lt;/h4&gt;&lt;p&gt;These three papers are not independent observations; they are three views of the same underlying phenomenon. Together, they paint a remarkably coherent picture of how linear geometry enables complex reasoning in non-linear models.&lt;/p&gt;&lt;ul&gt;&lt;li&gt; The &lt;strong&gt;subspace picture (CoNLL &amp;apos;21)&lt;/strong&gt; establishes that concepts live in specific, low-dimensional linear manifolds and that these manifolds are causally potent.  &lt;/li&gt;&lt;li&gt; The &lt;strong&gt;affine relational maps (ICLR &amp;apos;24)&lt;/strong&gt; show that for many common knowledge-retrieval tasks, the multi-step computation from a subject&amp;apos;s subspace to an object&amp;apos;s subspace can be locally approximated by a simple affine hop.  &lt;/li&gt;&lt;li&gt; The &lt;strong&gt;causal geometry (ICML &amp;apos;24)&lt;/strong&gt; provides the rigorous mathematical language to justify it all. It explains &lt;em&gt;why&lt;/em&gt; we can talk about directions and subspaces, tells us which metric to use, and unifies the acts of observing a concept (probing) and controlling it (steering).  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The unified story is this: To find the simple linear structure hidden inside a transformer, you need to look at the right &lt;em&gt;slice&lt;/em&gt; of the representation (the right layer and token position) and measure it with the right &lt;em&gt;metric&lt;/em&gt; (the causal inner product). In that sweet spot, complex operations often resolve into simple geometric translations and projections. Outside of that slice, or with the wrong metric, the model&amp;apos;s behavior remains stubbornly non-linear and opaque.&lt;/p&gt;&lt;h4&gt;Practical Guidance&lt;/h4&gt;&lt;p&gt;For researchers and practitioners working on interpretability, this body of work suggests a more responsible way to make and evaluate claims about linearity.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Disclose Your Geometry:&lt;/strong&gt; Always state the inner product used for any analysis (e.g., standard Euclidean/cosine or a causal inner product). If you perform any normalization like centering or whitening, report it.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Sweep Layers and Positions:&lt;/strong&gt; Linearity is often localized. Avoid cherry-picking a single layer. A full sweep across layers and token positions reveals where a linear approximation holds and where it breaks down.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Use Counterfactuals for Validation:&lt;/strong&gt; Define directions using minimal counterfactual pairs as suggested by LRH. Validate that these directions are meaningful by performing causal interventions, such as subspace projections (CoNLL-style) or applying LREs to new subjects.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Analyze Failures:&lt;/strong&gt; Documenting where linearity &lt;em&gt;fails&lt;/em&gt; is as important as showing where it succeeds. Reporting out-of-scope relations or layers where the geometry is non-linear provides crucial constraints on the hypothesis.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;My View&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Opinion:&lt;/strong&gt; Linear tools are at their best when treated as &lt;em&gt;local approximations&lt;/em&gt; of a model&amp;apos;s behavior. They are powerful instruments for generating hypotheses, performing quick diagnostic tests, and executing surgical edits on model knowledge. They are not, however, a complete theory of how transformers compute. Their power lies in simplifying a small piece of a complex puzzle, but we must always remember and declare the boundaries of that simplification.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Intuition:&lt;/strong&gt; I suspect that transformers operate by dynamically linearizing sub-problems. When the model needs to map a country to its capital, it routes the subject representation through a sequence of blocks that, in aggregate, perform an approximately affine map. Once that attribute is computed and added to the residual stream, subsequent blocks can perform other, unrelated non-linear operations. The linearity is a temporary state in a much longer, piecewise computation.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; The most common mistake in this area is to confuse the &lt;em&gt;presence&lt;/em&gt; of a linearly decodable signal with its &lt;em&gt;use&lt;/em&gt; in the model&amp;apos;s final decision. A probe can often detect a feature at an early layer, but that doesn&amp;apos;t mean the model uses it. Only a causal experiment-an intervention that shows a change in the representation leads to a predictable change in output-can close that loop.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Limits &amp;amp; Open Questions&lt;/h4&gt;&lt;p&gt;This is a fast-moving area, and our understanding is far from complete. Key open questions include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Faithfulness Bounds:&lt;/strong&gt; When, precisely, does the causal inner product lead to substantively different conclusions than standard cosine similarity? Are there classes of problems where the simpler metric is &amp;quot;good enough&amp;quot;?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Portability:&lt;/strong&gt; Do linear subspaces and LREs transfer across model families (e.g., LLaMA to GPT) or model sizes? Is the geometric structure of knowledge universal, or is it an idiosyncratic feature of each training run?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Compositionality:&lt;/strong&gt; Can we chain multiple linear operations? For example, can we compose the LRE for &lt;code&gt;author -&amp;gt; book&lt;/code&gt; with the LRE for &lt;code&gt;book -&amp;gt; main character&lt;/code&gt; to reason about an author&amp;apos;s characters? Early evidence suggests this is difficult, likely because the non-linearities between steps are crucial.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Effect of Instruction Tuning:&lt;/strong&gt; How does fine-tuning on human preferences (RLHF) and other alignment techniques warp the pristine geometric structures found in base models? Does it consolidate concepts into clearer subspaces or shatter them into context-dependent, non-linear fragments?  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The geometry of hidden states is no longer a complete black box. By choosing the right lens, we are beginning to resolve its structure, finding straight lines and simple planes in a space we once thought was impenetrably complex.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;References&lt;/h4&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Hernandez, E., &amp;amp; Andreas, J. (2021). Uncovering the Hierarchical Structure of Learned Language Representations. In &lt;em&gt;Proceedings of the 25th Conference on Computational Natural Language Learning (CoNLL)&lt;/em&gt;. Association for Computational Linguistics.  &lt;/li&gt;&lt;li&gt; Hernandez, E., Sharma, A. S., Haklay, T., Meng, K., Wattenberg, M., Andreas, J., Belinkov, Y., &amp;amp; Bau, D. (2024). Linearity of Relation Decoding in Large Language Models. In &lt;em&gt;The Twelfth International Conference on Learning Representations (ICLR)&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Park, K., Choe, Y. J., &amp;amp; Veitch, V. (2024). The Linear Representation Hypothesis and the Causal Inner Product. In &lt;em&gt;Proceedings of the 41st International Conference on Machine Learning (ICML)&lt;/em&gt;. arXiv:2311.03658.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Causal Attribution &amp; Patching (Part 2): Pathscopes</title>
      <link>https://nayanachandrika99.github.io/posts/causal-attribution-patching-part-2-pathscopes/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/causal-attribution-patching-part-2-pathscopes/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Oct 19 2025 17:03:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>aiSafety</category>
      <category>mechInterp</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/causal-attribution-patching-part-2-pathscopes/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 19, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/aisafety/&quot;&gt; aiSafety &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Fast Approximations Have Limits:&lt;/strong&gt; Attribution Patching (AtP) and its successor, AtP*, offer scalable causal attribution through linearization. They are incredibly fast for ranking influential model components but are approximations and can fail, especially when model gradients are uninformative.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Natural Language Explanations:&lt;/strong&gt; Patchscopes provides a unifying framework to inspect hidden states by using the model itself to generate natural-language descriptions of them. This approach helps probe traditionally opaque early layers and enables novel capabilities like cross-model explanations and multi-hop error correction.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Feature-Level Causal Graphs:&lt;/strong&gt; Sparse Feature Circuits (SFCs) move beyond coarse components (heads, neurons) to discover causal subnetworks of more interpretable &amp;quot;features&amp;quot; learned by Sparse Autoencoders (SAEs). This enables a powerful editing technique, Shift, which modifies model generalization by ablating spurious features.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;A Modern Workflow:&lt;/strong&gt; The cutting edge of causal analysis combines these ideas: use Patchscopes for high-level hypotheses, AtP* for rapid ranking of influential components, and SFCs to dissect the underlying mechanism at the feature level and perform targeted edits.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Recap: From Part 1 to Part 2&lt;/h4&gt;&lt;p&gt;In Part 1, we established causal patching as a core technique for mechanistic interpretability. By intervening on a model&amp;#x2019;s internal activations&amp;#x2014;running it on a &amp;quot;clean&amp;quot; input and patching those activations into a forward pass on a &amp;quot;corrupted&amp;quot; input&amp;#x2014;we can identify which components are sufficient to restore a desired behavior. This moves us from mere correlation to causal evidence, allowing us to ask not just what information a component &lt;em&gt;encodes&lt;/em&gt;, but what mechanism it &lt;em&gt;implements&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;However, this approach faces two major limitations. First, our units of analysis&amp;#x2014;attention heads, MLP layers, or individual neurons&amp;#x2014;are often polysemantic, meaning they represent multiple, unrelated concepts simultaneously. This makes their role in a circuit hard to pin down. Second, the output of a patching experiment is typically a numeric score (e.g., a change in loss or logit difference), which tells us &lt;em&gt;that&lt;/em&gt; a component matters but not &lt;em&gt;what&lt;/em&gt; it represents.&lt;/p&gt;&lt;p&gt;This brings us to Part 2. We will explore two major advances that address these challenges. First, we will move from coarse, polysemantic components to fine-grained, interpretable &amp;quot;features&amp;quot; identified by Sparse Autoencoders (SAEs). Second, we will move beyond simple numeric probes to generating rich, natural-language (NL) descriptions of what a hidden state represents. The goal is to build a more precise, interpretable, and editable understanding of the causal mechanisms inside language models.&lt;/p&gt;&lt;h4&gt;Background: Patching &amp;amp; Its Fast Approximations&lt;/h4&gt;&lt;p&gt;At the heart of our analysis is &lt;strong&gt;Activation Patching&lt;/strong&gt; (also known as causal tracing or interchange interventions). It provides a direct, causal measure of a component&amp;apos;s importance. We take a clean prompt (e.g., &amp;quot;The Eiffel Tower is in Paris&amp;quot;) and a corrupted prompt (e.g., &amp;quot;The Eiffel Tower is in Rome&amp;quot;), then run the model on the corrupted prompt while replacing a specific internal state, like an attention head&amp;apos;s output, with the corresponding state from the clean run. If this single patch restores the model&amp;apos;s correct output (&amp;quot;Paris&amp;quot;), we have strong evidence that the component is causally sufficient for that behavior.&lt;/p&gt;&lt;p&gt;While powerful, activation patching is computationally expensive. It requires a separate forward pass for every component we want to test. For a large model with thousands of attention heads and MLP layers across dozens of layers, this quickly becomes intractable.&lt;/p&gt;&lt;p&gt;This scalability problem motivated the development of &lt;strong&gt;Attribution Patching (AtP)&lt;/strong&gt;. AtP reframes the patching intervention through the lens of a first-order Taylor-series approximation. Instead of performing a full forward pass for each patch, it estimates the effect using gradients.&lt;/p&gt;&lt;p&gt;Let &lt;span&gt;&lt;span&gt;&lt;span&gt;f&amp;#x3b8;f_\theta&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; be our model, and let &amp;#x24;m&amp;#x24; be a scalar metric we care about, such as the logit of the target token &lt;span&gt;&lt;span&gt;&lt;span&gt;t&amp;#x22c6;t^\star&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. Let &lt;span&gt;&lt;span&gt;&lt;span&gt;scleans_{\text{clean}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;clean&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; be the activation of a component on a clean input and &lt;span&gt;&lt;span&gt;&lt;span&gt;scorrs_{\text{corr}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;corr&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; be the activation on a corrupted input. Activation patching measures the exact change in the metric, &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;m\Delta m&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. Attribution Patching approximates this change:&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;m&amp;#x2248;&amp;#x2207;sm&amp;#x2223;scorr&amp;#x22a4;&amp;#x2009;(sclean&amp;#x2212;scorr) \Delta m \approx \nabla_{s} m \big|{s{\text{corr}}}^\top \, (s_{\text{clean}} - s_{\text{corr}}) &lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;&amp;#x2248;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2207;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;corr&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22a4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;clean&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2212;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;corr&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;This calculation requires only one forward pass on the clean input (to get &lt;span&gt;&lt;span&gt;&lt;span&gt;scleans_{\text{clean}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;clean&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;), one forward pass on the corrupted input (to get &lt;span&gt;&lt;span&gt;&lt;span&gt;scorrs_{\text{corr}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;corr&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;), and one backward pass from the metric on the corrupted run (to get the gradient &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2207;sm\nabla_{s} m&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2207;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;). With these three passes, we can estimate the effect of patching &lt;em&gt;every&lt;/em&gt; component in the model simultaneously, offering a massive speedup.&lt;/p&gt;&lt;p&gt;However, this is an approximation. The paper introducing &lt;strong&gt;AtP&lt;/strong&gt;* identified two key failure modes where this linearization breaks down:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;QK-Dependence:&lt;/strong&gt; The outputs of attention heads depend non-linearly on the query (Q) and key (K) vectors via the softmax function. AtP&amp;apos;s linear approximation struggles here. AtP* addresses this by partially recomputing the attention pattern change instead of linearizing it.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Gradient Nullspace:&lt;/strong&gt; Sometimes the gradient of the metric with respect to a component&amp;apos;s activation is zero (or near-zero) on the corrupted run, even if the component is causally important. This leads to false negatives. AtP* mitigates this with a technique called &amp;quot;gradient dropout,&amp;quot; which computes gradients on a modified run where some components are disabled.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt; Caveat &lt;br&gt; AtP and AtP* are powerful for rapidly identifying candidate components for a circuit. However, they are not foolproof. Their reliability depends on how well the model&amp;apos;s local behavior can be approximated by a linear function. When gradients are small or zero, these methods can miss crucial components. It is best practice to use AtP/AtP* for an initial, wide search and then verify the top-scoring candidates with exact activation patching.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;Patchscopes: A Unifying Framework for Inspecting Representations&lt;/h4&gt;&lt;p&gt;While AtP helps us find &lt;em&gt;where&lt;/em&gt; causality flows, it doesn&amp;apos;t tell us &lt;em&gt;what&lt;/em&gt; information is flowing. This is where Patchscopes comes in. Introduced by Ghandeharioun et al. (ICML 2024), Patchscopes is a framework that uses the language model itself to explain its internal representations in natural language.&lt;/p&gt;&lt;p&gt;The core idea is simple but profound: &amp;quot;patch&amp;quot; a hidden state from a source computation into a target computation that is designed to elicit a natural language description. For example, to understand what information is encoded at the final token of the prompt &amp;quot;The Colosseum is in the city of,&amp;quot; we can take the residual stream vector at that position and patch it into a templated prompt like &amp;quot;The city is [MASK].&amp;quot; The model&amp;apos;s subsequent prediction for &amp;quot;[MASK]&amp;quot; (&amp;quot;Rome&amp;quot;) reveals the information stored in that vector.&lt;/p&gt;&lt;p&gt;Patchscopes unifies and extends several prior interpretability techniques:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Unifying Framework:&lt;/strong&gt; The paper shows that methods like Logit Lens (projecting hidden states to the vocabulary) and Tuned Lens are special cases of the Patchscopes framework. This provides a common ground for understanding and comparing these methods.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Mitigating Early-Layer Gaps:&lt;/strong&gt; Prior methods often failed to produce coherent explanations for representations in early layers of a network, as these states are not yet &amp;quot;aligned&amp;quot; with the final vocabulary space. Patchscopes can overcome this by patching a state into the &lt;em&gt;same layer&lt;/em&gt; of a different computation, allowing the model&amp;apos;s own later layers to process and &amp;quot;translate&amp;quot; the representation effectively.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Cross-Model Explanations:&lt;/strong&gt; A fascinating capability introduced by Patchscopes is using a more powerful model (e.g., GPT-4) to explain the internal representations of a smaller, weaker model (e.g., GPT-2). This leverages the advanced reasoning and linguistic capabilities of the larger model to interpret the more primitive representations of the smaller one.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-Hop Error Correction:&lt;/strong&gt; Patchscopes can be used not just for inspection but for intervention. In multi-hop reasoning tasks where a model makes an intermediate error, one can patch in a corrected hidden state to steer the model back toward the correct final answer, demonstrating a direct application of the insights gained.  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Intuition &lt;br&gt; Think of Patchscopes as providing human-readable labels for the intermediate variables in a model&amp;apos;s computation. Instead of seeing a raw vector and a numeric attribution score, you get a natural language phrase like &amp;quot;the city of Rome&amp;quot; or &amp;quot;a plural subject.&amp;quot; This provides a powerful conceptual scaffold that can guide further investigation. The patch queries themselves become testable hypotheses about what a given representation means and does.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;Sparse Feature Circuits: Causally Implicated Feature Graphs&lt;/h4&gt;&lt;p&gt;Patchscopes gives us better descriptions, but we are often still working with coarse components like the full residual stream or attention head outputs. The problem of polysemanticity remains. &lt;strong&gt;Sparse Feature Circuits (SFCs)&lt;/strong&gt;, introduced by Marks et al. (ICLR 2025), tackle this head-on by changing the fundamental unit of analysis from neurons or heads to &lt;em&gt;features&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;The approach relies on &lt;strong&gt;Sparse Autoencoders (SAEs)&lt;/strong&gt;. An SAE is a small neural network trained to reconstruct a model&amp;apos;s internal activations (e.g., an MLP layer&amp;apos;s output) using a highly sparse, learned dictionary of features. The goal is to find a basis where each dimension corresponds to a more monosemantic, or conceptually singular, concept. For example, instead of a neuron that activates for both Python code and English lists, an SAE might learn separate features for &amp;quot;Python &lt;code&gt;[&lt;/code&gt; token&amp;quot; and &amp;quot;start of an English list.&amp;quot;&lt;/p&gt;&lt;p&gt;With SAEs in hand, SFCs build a causal graph of the model&amp;apos;s behavior in three main steps:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Define Nodes and Edges:&lt;/strong&gt; The nodes in the graph are the learned SAE features at each layer. The edges represent the flow of information between features in one layer and features in the next.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Estimate Indirect Effects (IE):&lt;/strong&gt; To determine which nodes and edges are causally important, the method estimates their indirect effect on the final output. The indirect effect of a feature &lt;span&gt;&lt;span&gt;&lt;span&gt;uu&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;u&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; on a metric &lt;span&gt;&lt;span&gt;&lt;span&gt;mm&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the change in &lt;span&gt;&lt;span&gt;&lt;span&gt;mm&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; that occurs when we intervene on &lt;span&gt;&lt;span&gt;&lt;span&gt;uu&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;u&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; while keeping its upstream causes fixed. While exact calculation is complex, it can be approximated efficiently using methods like path-gradient products (a linear approximation) or Integrated Gradients (IG), which often performs better in practice.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Assemble the Circuit:&lt;/strong&gt; By computing the IEs for all nodes and edges, we can filter for the most causally significant ones, revealing a sparse subnetwork&amp;#x2014;the feature circuit&amp;#x2014;responsible for the behavior.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This feature-level view enables a powerful new editing capability called &lt;strong&gt;Shift&lt;/strong&gt;. The SFC paper demonstrates this on a classifier trained to determine a person&amp;apos;s profession from a biography. When the training data has a spurious correlation (e.g., doctors are always male, nurses are always female), the model learns to rely on gender-associated features. With an SFC, these gender features can be identified and manually ablated from the circuit. The result is a model that no longer relies on the spurious feature and generalizes better to a balanced test set. This is a significant step beyond simple fact-editing, as it alters a model&amp;apos;s underlying reasoning strategy.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Caveat &lt;br&gt; The quality and interpretability of a Sparse Feature Circuit are fundamentally dependent on the quality of the underlying SAEs. Training SAEs is an active area of research, and challenges remain in ensuring features are consistently monosemantic and that the SAE reconstruction is faithful to the original model&amp;apos;s activations. The choice of approximation for indirect effects (linear vs. IG) also involves trade-offs between speed and accuracy, particularly in early layers where non-linearities can be more pronounced.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;Synthesis: From Components to Features, from Numbers to Language&lt;/h4&gt;&lt;p&gt;Patchscopes and Sparse Feature Circuits are not competing methods; they are complementary tools for dissecting a model&amp;apos;s internal algorithm. Patchscopes offers a top-down, exploratory lens, while SFCs provide a bottom-up, mechanistic one.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Patchscopes asks:&lt;/strong&gt; &amp;quot;What does this representation &lt;em&gt;mean&lt;/em&gt; in natural language?&amp;quot; It frames our understanding.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;SFCs ask:&lt;/strong&gt; &amp;quot;Which specific features in this representation &lt;em&gt;causally drive&lt;/em&gt; the output?&amp;quot; It quantifies the mechanism.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When used together, they create a powerful loop: propose a hypothesis about a mechanism, use Patchscopes to find representations that seem to correspond to concepts in that hypothesis, then use SFCs to validate the causal role of the features within those representations and assemble the full circuit.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;When to choose which tool:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Exploration and Hypothesis Generation:&lt;/strong&gt; Start with Patchscopes. Its natural language outputs are ideal for initial exploration and for communicating findings to a broader audience.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Scalable Triage:&lt;/strong&gt; Use AtP/AtP* to quickly scan thousands of components (or features) to find the most influential ones for a specific behavior. This narrows the search space dramatically.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Mechanism Discovery and Editing:&lt;/strong&gt; Once you have candidate components, use SFCs to decompose them into interpretable features and map their causal pathways. For editing, the &lt;strong&gt;Shift&lt;/strong&gt; technique is the tool of choice for removing reliance on spurious features. For verifying a key feature&amp;apos;s role, a targeted activation patch on that feature is the gold standard.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;A Practical Protocol for Modern Causal Patching&lt;/h4&gt;&lt;p&gt;Here is a conceptual workflow for conducting a causal analysis project that integrates these modern tools:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Define Behavior and Metric:&lt;/strong&gt; Isolate a specific, reproducible behavior you want to understand (e.g., subject-verb agreement, factual recall, a reasoning failure). Define a clean input, a corrupted input, and a clear metric &lt;span&gt;&lt;span&gt;&lt;span&gt;mm&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; (e.g., the logit difference between the correct and incorrect tokens).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hypothesis Generation with Patchscopes:&lt;/strong&gt; Use Patchscopes to probe key hidden states. For example, query the representation after the subject noun with a template like &amp;quot;The subject is [MASK].&amp;quot; Note which layers and token positions yield clear, plausible natural language explanations that align with a hypothesized mechanism.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Fast Triage with AtP\*:&lt;/strong&gt; Run AtP* to get a global ranking of the importance of all components (attention heads, MLP layers) in the model for your defined task. This will highlight &amp;quot;hotspots&amp;quot; that deserve closer inspection.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Feature-Level Discovery with SFCs:&lt;/strong&gt;&lt;ul&gt;&lt;li&gt; Train or load pre-trained SAEs for the component types and layers identified as important by AtP*.  &lt;/li&gt;&lt;li&gt; Compute node and edge Indirect Effects (IEs) for the SAE features. This will rank the causal importance of each feature.  &lt;/li&gt;&lt;li&gt; Aggregate the top-scoring nodes and edges to form a sparse feature circuit.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt; The most effective and robust workflow today combines these techniques in a layered approach: Patchscopes for high-level conceptual framing, AtP* for rapid, scalable triage, and Sparse Feature Circuits for deep mechanistic analysis and editing. Each tool shores up the weaknesses of the others. Patchscopes makes the numeric outputs of AtP interpretable, AtP narrows the immense search space for SFCs, and SFCs provide the granular, editable substrate that fully realizes the promise of causal interventions. The gold standard for confirming a critical finding remains the targeted, exact activation patch on the component or feature in question.  &lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Edit and Evaluate:&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Shift Edit:&lt;/strong&gt; Manually inspect the features in your circuit. If you identify spurious features (e.g., a feature tracking gender when the task is profession classification), ablate them.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Minimal Patch:&lt;/strong&gt; Alternatively, perform a precise activation patch on only the highest-IE feature(s) to confirm their sufficiency.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Test Generalization:&lt;/strong&gt; Evaluate the edited model&amp;apos;s performance, specificity (does the edit affect only the target behavior?), and generalization (does the edit hold across paraphrases and different contexts?).  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Report Findings:&lt;/strong&gt; Document your prompts, seeds, and thresholds. Include the natural language descriptions from Patchscopes, diagrams of the final feature circuit, and a careful analysis of failure modes (e.g., instances where linear approximations broke down).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt; Intuition &lt;br&gt; Many local behaviors in transformers linearize surprisingly well, which is why AtP/AtP* are so effective as a ranking tool. The complexity of the overall function emerges from the composition of many simpler, near-linear steps. The feature lens provided by SAEs is transformative because it refactors the model&amp;#x2019;s internal ontology into one that is more aligned with human concepts. This turns the question from &amp;quot;What did patching this opaque vector do?&amp;quot; into &amp;quot;What was the causal effect of the &amp;apos;plural noun&amp;apos; feature?&amp;quot;  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;Limits &amp;amp; Open Questions&lt;/h4&gt;&lt;p&gt;This frontier of research is moving fast, but significant challenges remain:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Faithfulness of Approximations:&lt;/strong&gt; How can we establish tighter theoretical bounds on when linear approximations like AtP* and IE estimates are reliable?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;SAE Quality and Scale:&lt;/strong&gt; Training high-quality, perfectly monosemantic SAEs for all components in a frontier model is a massive engineering and research challenge. Standardization and best practices are still emerging.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Early-Layer Fidelity:&lt;/strong&gt; While Patchscopes and better IE approximations have improved our view of early layers, these representations remain harder to interpret and analyze than those in later layers.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Porting Circuits:&lt;/strong&gt; How well do feature circuits generalize across model sizes and families? Discovering universal circuits or principles of their construction is a key open goal.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Dataset-Level Corroboration:&lt;/strong&gt; Current methods focus on specific input pairs. Bridging these instance-level causal findings to dataset-level statistics and phenomena (e.g., connecting a feature circuit to a specific pre-training data distribution) is a crucial next step for building a complete picture of model behavior.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h4&gt;References&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Ghandeharioun, A., Caciularu, A., Pearce, A., Dixon, L., Geva, M., et al. (2024). Patchscopes: A Unifying Framework for Inspecting Hidden Representations of Language Models. In &lt;em&gt;Proceedings of the 41st International Conference on Machine Learning (ICML)&lt;/em&gt;. PMLR.  &lt;/li&gt;&lt;li&gt; Marks, S., Rager, C., Michaud, E. J., Belinkov, Y., Bau, D., &amp;amp; Mueller, A. (2024). Sparse Feature Circuits: Discovering and Editing Interpretable Causal Graphs in Language Models. &lt;em&gt;arXiv preprint arXiv:2405.00172&lt;/em&gt;. To appear at ICLR 2025.  &lt;/li&gt;&lt;li&gt; Syed, F., et al. (2023). Attribution Patching. In &lt;em&gt;Proceedings of the 7th BlackboxNLP Workshop on Analyzing and Interpreting Neural Networks for NLP&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Kram&amp;#xe1;r, J., Lieberum, T., Nanda, N., &amp;amp; Shah, R. (2024). AtP*: Efficient and scalable methods for localizing LLM behaviour to components. &lt;em&gt;arXiv preprint arXiv:2403.00745&lt;/em&gt;. to components. &lt;em&gt;arXiv preprint arXiv:2403.00745&lt;/em&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Chain Of Thought in Diffusion Language Models</title>
      <link>https://nayanachandrika99.github.io/posts/chain-of-thought-in-diffusion-language-models/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/chain-of-thought-in-diffusion-language-models/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Oct 19 2025 17:50:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>research</category>
      <category>research</category>
      <category>mechInterp</category>
      <category>aiSafety</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/chain-of-thought-in-diffusion-language-models/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 19, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/research/&quot;&gt; research &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/aisafety/&quot;&gt; aiSafety &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;More updates soon!&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Solving a maze using Diffusion Model</title>
      <link>https://nayanachandrika99.github.io/posts/solving-a-maze-using-diffusion-model/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/solving-a-maze-using-diffusion-model/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Oct 19 2025 18:44:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>projects</category>
      <category>projects</category>
      <category>GenAI</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/solving-a-maze-using-diffusion-model/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 19, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/projects/&quot;&gt; projects &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/genai/&quot;&gt; GenAI &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/23c20505-a59f-48aa-911f-d22dde234e5a/timeline_%28online-video-cutter.com%29.mp4&quot;&gt;&lt;/a&gt; 
Your browser does not support the video tag.
  &lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;This post documents a small, didactic project I built to understand diffusion models end-to-end. It&amp;#x2019;s a from-scratch, minimal implementation of schedules, samplers, and a compact U-Net that learns to draw a valid path on a 2D maze by diffusing only the path channel while conditioning on fixed walls/start/end.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;System at a glance&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Data contract.&lt;/strong&gt; On disk: NumPy arrays &lt;code&gt;[4, H, W]&lt;/code&gt; in &lt;code&gt;[0,1]&lt;/code&gt; ordered &lt;code&gt;[walls, start, end, solution]&lt;/code&gt;. The loader emits: &lt;ul&gt;&lt;li&gt;&lt;code&gt;condition=[3, H, W]&lt;/code&gt; in &lt;code&gt;[0,1]&lt;/code&gt; (walls/start/end)  &lt;/li&gt;&lt;li&gt;&lt;code&gt;target=[1, H, W]&lt;/code&gt; scaled to &lt;code&gt;[-1,1]&lt;/code&gt; (path to diffuse)  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Model.&lt;/strong&gt; Compact U-Net (GroupNorm + SiLU, residual conv blocks) with sinusoidal time embeddings. &lt;code&gt;in_channels=4&lt;/code&gt; (noisy path + 3 condition), &lt;code&gt;out_channels=1&lt;/code&gt; (predicted noise &amp;#x3b5;).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Diffusion core.&lt;/strong&gt; Linear and cosine schedules implemented in-house with precomputed &lt;code&gt;&amp;#x3b1;_t&lt;/code&gt;, &lt;code&gt;\bar{&amp;#x3b1;}_t&lt;/code&gt;, posterior variance, forward factors, and DDIM coefficients for a uniform timestep ladder.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Samplers.&lt;/strong&gt; DDPM (ancestral) and DDIM (fast, &amp;#x3b7;=0 deterministic or &amp;#x3b7;&amp;gt;0 stochastic), with classifier-free guidance via two passes (cond/uncond) and linear mixing.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Training.&lt;/strong&gt; Sample &lt;code&gt;t&lt;/code&gt;, add noise &lt;strong&gt;only to the path channel&lt;/strong&gt;, concatenate condition (optionally dropped for CFG training), predict &amp;#x3b5; with MSE loss. AdamW + grad clip + EMA.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Inference.&lt;/strong&gt; Solver prefers EMA weights, uses cosine schedule and DDIM by default, expects images with black=walls, green=start, red=end, white=free. Returns raw &lt;code&gt;[-1,1]&lt;/code&gt; and thresholded &lt;code&gt;[0,1]&lt;/code&gt; path maps, plus validation.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Evaluation.&lt;/strong&gt; Validity (no wall overlap; start/end on path; BFS 4-connectivity), plus IoU/F1 vs. ground truth and &lt;strong&gt;optimality gap&lt;/strong&gt; by path length.  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Note on channels: Inference must use the same in_channels as training. If a checkpoint was trained with, say, 7 channels (e.g., extra aux features), construct the inference U-Net with in_channels=7 or weights won&amp;#x2019;t load.How it works (briefly)How it works (brief)How it works  &lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;As a compact learning project, this codebase made diffusion mechanics tangible: &amp;#x3b5;-prediction training, schedule design, sampler behavior, and guidance trade-offs. If you run the quick experiments above, you&amp;#x2019;ll see how each choice-cosine vs. linear, steps, CFG scale, &amp;#x3b7;-maps to validity, quality, and speed in a way that&amp;#x2019;s easy to reason about and extend.&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Causal Attribution &amp; Patching (Part 1): Internal Mechanisms of Language Models</title>
      <link>https://nayanachandrika99.github.io/posts/causal-attribution-patching-part-1-internal-mechanisms-of-language-models/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/causal-attribution-patching-part-1-internal-mechanisms-of-language-models/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Oct 19 2025 16:59:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/causal-attribution-patching-part-1-internal-mechanisms-of-language-models/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 19, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Causal vs. Correlational:&lt;/strong&gt; Mechanistic interpretability aims to understand not just what information &lt;em&gt;is present&lt;/em&gt; in a model&amp;apos;s activations (correlation), but what information the model causally &lt;em&gt;uses&lt;/em&gt; to produce its output. Patching and mediation are tools for establishing this causal link.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Mediation Analysis Identifies Pathways:&lt;/strong&gt; Inspired by the social sciences, causal mediation analysis allows us to identify and quantify the specific components (attention heads, neurons) that transmit an effect from input to output, pinpointing where mechanisms like bias are implemented.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Mechanisms Can Be Surprisingly Linear:&lt;/strong&gt; For a range of relational tasks (e.g., country capitals, verb tenses), large language models sometimes implement a simple vector-arithmetic update within their MLP/FFN layers, especially when retrieving facts from pre-training memory.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Causal Tracing &amp;amp; ROME Localize and Edit Facts:&lt;/strong&gt; By systematically &amp;quot;patching&amp;quot; clean states into a corrupted model run, we can trace the flow of information for factual recall. This localizes facts to specific MLP modules, which can then be surgically edited with a rank-one update to change the model&amp;apos;s knowledge, with testable effects on specificity and generalization.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;What They Mean by Causal Attribution (vs. Correlational Probing)&lt;/h3&gt;&lt;p&gt;For years, a dominant paradigm in interpretability has been &lt;em&gt;correlational probing&lt;/em&gt;. A typical study might train a simple linear classifier (a &amp;quot;probe&amp;quot;) on the internal activations of a large language model (LLM) to predict a certain property, like the part-of-speech of a token or the sentiment of a sentence. If the probe achieves high accuracy, the conclusion is that the property is &amp;quot;encoded&amp;quot; in the model&amp;apos;s representations. (&lt;a href=&quot;https://nayanachandrika99.github.io/posts/probing/&quot;&gt;&lt;span&gt;Probing&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;This is an essential first step, but it leaves a critical question unanswered: just because information is &lt;em&gt;present&lt;/em&gt;, does that mean the model actually &lt;em&gt;uses&lt;/em&gt; it to make its next prediction?&lt;/p&gt;&lt;p&gt;Imagine a car&amp;apos;s dashboard. A probe could easily &amp;quot;decode&amp;quot; the car&amp;apos;s speed by looking at the radio&amp;apos;s volume knob-perhaps the driver turns up the music on the highway. There is a correlation. But the volume knob does not &lt;em&gt;cause&lt;/em&gt; the car to accelerate. The speedometer and the engine do. Causal attribution is the effort to find the speedometer, not just another correlated signal.&lt;/p&gt;&lt;p&gt;This is the distinction between information being merely &lt;em&gt;encoded&lt;/em&gt; versus being &lt;em&gt;causally implicated&lt;/em&gt; in the model&amp;apos;s behavior. An internal state can contain linearly decodable information about a concept, but that information might be an artifact of the training data or a precursor to a different computation, rather than a direct input to the final decision.&lt;/p&gt;&lt;p&gt;As Vig et al. (2020) note in their work on causal mediation, many interpretability methods &amp;quot;show only that information is present in the model&amp;#x2019;s representations, not that it is used.&amp;quot; Causal methods, by contrast, rely on interventions. The core idea is simple: if you think component &amp;#x24;M&amp;#x24; is causally responsible for behavior &amp;#x24;Y&amp;#x24;, then changing or disabling &amp;#x24;M&amp;#x24; should change &amp;#x24;Y&amp;#x24;. If it doesn&amp;apos;t, your hypothesis is likely wrong, no matter how much information a probe can extract from &amp;#x24;M&amp;#x24;.&lt;/p&gt;&lt;h3&gt;Background &amp;amp; Formalization&lt;/h3&gt;&lt;h4&gt;The Intervention Model: Corrupt vs. Clean Runs&lt;/h4&gt;&lt;p&gt;The foundational tool for causal analysis in LLMs is the &lt;strong&gt;patching experiment&lt;/strong&gt;. This involves running the model on at least two different inputs and swapping internal states between them.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;The Clean Run:&lt;/strong&gt; We run the model on a clean, factual input. For example, the prompt &lt;span&gt;&lt;span&gt;&lt;span&gt;X=&amp;#x2019;The&amp;#xa0;Eiffel&amp;#xa0;Tower&amp;#xa0;is&amp;#xa0;located&amp;#xa0;in&amp;#x2019;X = \text{&amp;apos;The Eiffel Tower is located in&amp;apos;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2019;The&amp;#xa0;Eiffel&amp;#xa0;Tower&amp;#xa0;is&amp;#xa0;located&amp;#xa0;in&amp;#x2019;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. We expect the model to complete this with the target token &lt;span&gt;&lt;span&gt;&lt;span&gt;Y=&amp;quot;&amp;#xa0;Paris&amp;quot;Y = \text{&amp;quot; Paris&amp;quot;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;quot;&amp;#xa0;Paris&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. We save all the internal activations (residual stream states, attention outputs, MLP outputs) from this run. Let&amp;apos;s call a generic clean state &lt;span&gt;&lt;span&gt;&lt;span&gt;ss&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Corrupted Run:&lt;/strong&gt; We run the model on a different, &amp;quot;corrupted&amp;quot; input designed to produce an incorrect answer. This could be an input with a different subject (e.g., &amp;quot;The Colosseum is located in&amp;quot;) or a nonsensical prompt where we inject noise into the embeddings. The goal is to create a computational path that does &lt;em&gt;not&lt;/em&gt; lead to the target &amp;quot;Paris&amp;quot;. Let&amp;apos;s call a generic corrupted state &lt;span&gt;&lt;span&gt;&lt;span&gt;s~\tilde{s}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;~&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;The experiment is to run the model on the corrupted input but, at a specific point in the forward pass, intervene and &amp;quot;patch&amp;quot; in a clean state from the original run. For instance, we might replace the output of the MLP module in layer &lt;span&gt;&lt;span&gt;&lt;span&gt;l=15l=15&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;15&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; from the corrupted run with the MLP output from the clean run, and then let the computation proceed.&lt;/p&gt;&lt;p&gt;We measure the effect of this intervention on the model&amp;apos;s output logits. Specifically, we look at the change in the logit of the target token (e.g., &amp;quot;Paris&amp;quot;). We can formalize this as a &lt;strong&gt;restoration score&lt;/strong&gt;:&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;logit(t&amp;#x22c6;)=logitrestore(M)(t&amp;#x22c6;)&amp;#x2212;logitcorrupt(t&amp;#x22c6;)\Delta \text{logit}(t^\star) = \text{logit}{\text{restore}(M)}(t^\star) - \text{logit}{\text{corrupt}}(t^\star)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;&lt;/span&gt;&lt;span&gt;&lt;span&gt;logit&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;logit&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;restore&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;M&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x2212;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;logit&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;corrupt&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Here, &lt;span&gt;&lt;span&gt;&lt;span&gt;t&amp;#x22c6;t^\star&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is our target token (&amp;quot;Paris&amp;quot;). &lt;span&gt;&lt;span&gt;&lt;span&gt;logitcorrupt(t&amp;#x22c6;)\text{logit}{\text{corrupt}}(t^\star)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;logit&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;corrupt&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt; is the (presumably low) logit for &amp;quot;Paris&amp;quot; on the corrupted run. &lt;/em&gt;&lt;em&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;logitrestore(M)(t&amp;#x22c6;)\text{logit}{\text{restore}(M)}(t^\star)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;logit&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;restore&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;M&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22c6;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/em&gt; is the logit for &amp;quot;Paris&amp;quot; when we patch in the clean state of a specific component, or mediator, &lt;span&gt;&lt;span&gt;&lt;span&gt;MM&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;A large, positive &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;logit\Delta \text{logit}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;&lt;/span&gt;&lt;span&gt;&lt;span&gt;logit&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is strong evidence that the component &lt;span&gt;&lt;span&gt;&lt;span&gt;MM&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is causally implicated in producing the token &amp;quot;Paris&amp;quot; for the clean prompt. It means that restoring just this one piece of the computation was sufficient to recover much of the correct answer.&lt;/p&gt;&lt;h4&gt;The Idea of Causal Mediation&lt;/h4&gt;&lt;p&gt;Causal mediation analysis provides a formal language for these interventions. It originates from fields like epidemiology and social sciences, where researchers want to understand &lt;em&gt;how&lt;/em&gt; a treatment causes an outcome. For example, how does an educational program (treatment &lt;span&gt;&lt;span&gt;&lt;span&gt;XX&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;X&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) lead to higher salaries (outcome &lt;span&gt;&lt;span&gt;&lt;span&gt;YY&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;)? Is it by increasing a student&amp;apos;s confidence, or by teaching them a specific skill (mediator &lt;span&gt;&lt;span&gt;&lt;span&gt;MM&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;)?&lt;/p&gt;&lt;p&gt;The framework decomposes the &lt;strong&gt;Total Effect (TE)&lt;/strong&gt; of &lt;span&gt;&lt;span&gt;&lt;span&gt;XX&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;X&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; on &lt;span&gt;&lt;span&gt;&lt;span&gt;YY&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; into two pathways:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Natural Direct Effect (NDE):&lt;/strong&gt; The effect of &amp;#x24;X&amp;#x24; on &amp;#x24;Y&amp;#x24; that does &lt;em&gt;not&lt;/em&gt; go through the mediator &lt;span&gt;&lt;span&gt;&lt;span&gt;MM&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Natural Indirect Effect (NIE):&lt;/strong&gt; The effect of &lt;span&gt;&lt;span&gt;&lt;span&gt;XX&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;X&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; on &lt;span&gt;&lt;span&gt;&lt;span&gt;YY&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; that flows &lt;em&gt;through&lt;/em&gt; the mediator &lt;span&gt;&lt;span&gt;&lt;span&gt;MM&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;TE=NDE+NIETE = NDE + NIE&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;TE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;I&lt;/span&gt;&lt;span&gt;E&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;In LLMs, the &amp;quot;treatment&amp;quot; &lt;span&gt;&lt;span&gt;&lt;span&gt;XX&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;X&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the input prompt, and the &amp;quot;outcome&amp;quot; &lt;span&gt;&lt;span&gt;&lt;span&gt;YY&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the output distribution. The &amp;quot;mediators&amp;quot; &lt;span&gt;&lt;span&gt;&lt;span&gt;MM&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; are the internal components we hypothesize are on the causal path: individual neurons, attention heads, or entire MLP modules. By estimating the indirect effect (NIE) for each component, we can quantify how much of the model&amp;apos;s final behavior is attributable to that specific part of the network.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Paper 1 - Causal Mediation in Language Models (NeurIPS 2020)&lt;/h3&gt;&lt;p&gt;Vig et al. (2020) were among the first to formally apply causal mediation analysis to LLMs to locate the sources of a specific behavior: gender bias.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; The researchers wanted to move beyond simply showing that gender is encoded in representations and instead identify which specific model components (attention heads and neuron activations) are causally responsible for producing biased predictions. For example, in a prompt like &amp;quot;The doctor said that,&amp;quot; which components cause the model to assign a higher probability to &amp;quot;he&amp;quot; than &amp;quot;she&amp;quot;?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Method:&lt;/strong&gt; They framed this as a mediation problem. The &amp;quot;treatment&amp;quot; was the gender of a subject in a sentence. The &amp;quot;outcome&amp;quot; was the probability distribution over subsequent gendered pronouns. They then systematically estimated the Natural Indirect Effect (NIE) for each attention head and neuron in the model, effectively measuring how much of the gender-bias effect was flowing through that component.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; The analysis revealed that gender bias was not a diffuse, entangled property of the entire network. Instead, the causal effects were highly concentrated in a small number of specific attention heads and neurons. For instance, in GPT-2, a handful of heads were consistently identified as &amp;quot;gender-mediating heads.&amp;quot; This was a powerful demonstration that complex behaviors can be localized to specific, modular parts of the model.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limits &amp;amp; Assumptions:&lt;/strong&gt; Causal mediation relies on strong statistical assumptions, chiefly the assumption of no unobserved confounding between the mediator and the outcome. In a densely connected neural network, this assumption is difficult to formally satisfy. Furthermore, the identified components are specific to the model, its training data, and the particular task being analyzed. The results may not generalize perfectly to different prompts or model families.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h3&gt;Paper 2 - Linear Mechanisms in Practice (NAACL 2024)&lt;/h3&gt;&lt;p&gt;While mediation analysis tells us &lt;em&gt;where&lt;/em&gt; an effect is happening, it doesn&amp;apos;t always tell us &lt;em&gt;how&lt;/em&gt;. The work by Todd et al. (2024) on vector-arithmetic mechanisms provides a compelling hypothesis for the &amp;quot;how,&amp;quot; suggesting that the underlying computation can be surprisingly simple and linear.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The paper demonstrates that for several common relational tasks, LLMs of various sizes (from 124M to 176B parameters) often implement the solution via a simple, additive update in their Feed-Forward Network (FFN), also known as MLP, layers. This is particularly true when retrieving knowledge stored during pre-training, as opposed to information present in the immediate context. &lt;p&gt;For example, to solve the task &amp;quot;Capital of France?&amp;quot;, the model might process the representation for &amp;quot;France&amp;quot;, &lt;span&gt;&lt;span&gt;&lt;span&gt;r(France)r(\text{France})&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;France&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, and an MLP layer will compute something akin to: &lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;MLP(l)(r(France))&amp;#x2248;r(France)+d&amp;#x20d7;capitalMLP^{(l)}(r(\text{France})) \approx r(\text{France}) + \vec{d}_{\text{capital}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;M&lt;/span&gt;&lt;span&gt;L&lt;/span&gt;&lt;span&gt;&lt;span&gt;P&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;France&lt;/span&gt;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;&amp;#x2248;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;France&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;capital&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is a consistent direction in activation space that transforms a country&amp;apos;s representation into its capital&amp;apos;s. They find similar additive mechanisms for tasks like uppercasing words and changing verb tense.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Connection to Patching:&lt;/strong&gt; This finding provides a powerful explanatory framework for why patching interventions work so well. If a task-relevant computation is just the addition of a specific vector &lt;span&gt;&lt;span&gt;&lt;span&gt;d&amp;#x20d7;\vec{d}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; by an MLP module, then a causal patching experiment that restores the output of that MLP should have an enormous effect. It&amp;apos;s not just restoring some abstract &amp;quot;information&amp;quot;; it&amp;apos;s restoring the result of a precise, near-linear arithmetic operation that is critical to solving the task.  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Intuition &lt;p&gt;Think of the MLP layers as a massive key-value store. The attention layers route the correct query (e.g., the representation of the last token of &amp;quot;Eiffel Tower&amp;quot;) to the right MLP layer. That MLP layer then retrieves the associated &amp;quot;value&amp;quot; vector (e.g., a vector that points towards &amp;quot;Paris&amp;quot;) and adds it to the residual stream. This additive update pushes the model&amp;apos;s overall representation towards the correct answer.&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; The authors are careful to note that this is not a universal mechanism. LMs are complex, non-linear systems. However, for a significant class of knowledge-retrieval and relational tasks, this simple linear update model appears to be a common and effective strategy implemented by the network.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h3&gt;Paper 3 - Causal Tracing &amp;amp; ROME (NeurIPS 2022 project)&lt;/h3&gt;&lt;p&gt;The ROME project (Meng et al., 2022) provides a practical and powerful synthesis of these ideas, using causal interventions not just to locate information but to surgically edit it.&lt;/p&gt;&lt;h4&gt;Causal Tracing: Finding the Path&lt;/h4&gt;&lt;p&gt;To edit a fact, you first need to know where it lives. ROME introduces a method called &lt;strong&gt;causal tracing&lt;/strong&gt; to do exactly this. It is a direct application of the corrupt-and-restore patching protocol described earlier, executed at scale.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Method:&lt;/strong&gt; The model is run on a clean prompt (e.g., &amp;quot;The Eiffel Tower is in&amp;quot;) and a corrupted prompt with random token embeddings. Then, the algorithm iterates through every single state in the clean forward pass (every layer, every token position, for both attention and MLP outputs). At each step, it patches that single clean state into the corrupted run and measures the restoration of the probability of the correct answer (&amp;quot;Paris&amp;quot;).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Localization Results:&lt;/strong&gt; By aggregating the results, a clear picture emerges. The causal effect for retrieving this fact is not distributed evenly. It is highly localized at: &lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;MLP Modules:&lt;/strong&gt; The effect is overwhelmingly concentrated in the MLP layers, not the attention layers.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Mid-Layers:&lt;/strong&gt; The critical MLP modules are typically found in the middle layers of the network (e.g., layers 15-20 in a model like GPT-J).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Last Subject Token:&lt;/strong&gt; The effect is localized to the computation happening at the position of the &lt;em&gt;last token&lt;/em&gt; of the subject (the token &amp;quot;Tower&amp;quot; in our example).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This process creates a causal map of factual recall, pointing to a very specific set of modules responsible for storing and retrieving the fact.&lt;/p&gt;&lt;h4&gt;ROME: Rewriting Facts with Rank-One Edits&lt;/h4&gt;&lt;p&gt;Once a fact is localized to a specific MLP module, ROME (Rank-One Model Editing) performs a direct edit.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Method:&lt;/strong&gt; An MLP layer computes &lt;span&gt;&lt;span&gt;&lt;span&gt;y=Wout&amp;#x3c3;(Winx)y = W_{out} \sigma(W_{in} x)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3c3;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. The ROME method treats this layer as a linear key-value memory. It solves for a minimal change to one of the weight matrices (e.g., &lt;span&gt;&lt;span&gt;&lt;span&gt;WinW_{in}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) that will map the subject&amp;apos;s representation (the &amp;quot;key&amp;quot;) to a new target representation (the &amp;quot;value&amp;quot;). This change can be formulated as a rank-one update, meaning the modification matrix &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;W\Delta W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x394;&lt;/span&gt;&lt;span&gt;W&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; has a rank of one. This is a very low-complexity and targeted change.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Evaluation:&lt;/strong&gt; A successful edit isn&amp;apos;t just about changing one fact. The ROME project introduces a crucial evaluation framework based on three criteria, tested on the CounterFact dataset: &lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Efficacy:&lt;/strong&gt; Does the model now output the new, edited fact?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Specificity:&lt;/strong&gt; Does the model still correctly answer questions about other, unrelated subjects? (i.e., did the edit cause collateral damage?).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Generalization:&lt;/strong&gt; Does the edit generalize to paraphrases of the original prompt? (e.g., &amp;quot;Where is the Eiffel Tower located?&amp;quot;).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The results show that these rank-one edits are remarkably effective, achieving high success across all three criteria.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Synthesis - A Unifying View of Patching&lt;/h3&gt;&lt;p&gt;These three anchors are not disparate findings; they are three parts of the same story, painting a progressively clearer picture of how models implement knowledge.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Causal Mediation (NeurIPS 2020)&lt;/strong&gt; provides the high-level framework. It gives us a principled, theory-grounded way to move from correlation to causation, allowing us to ask, &amp;quot;Which components are on the causal path for this behavior?&amp;quot; It&amp;apos;s the wide-angle lens for localizing mechanisms.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Linear Mechanisms (NAACL 2024)&lt;/strong&gt; provides a concrete hypothesis for the &lt;em&gt;type&lt;/em&gt; of computation happening at the localized sites. It suggests that for many factual recall tasks, the complex non-linear MLP module is, in practice, implementing a simple, additive update. This explains &lt;em&gt;why&lt;/em&gt; a linear, directional intervention can be so effective.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Causal Tracing and ROME (NeurIPS 2022)&lt;/strong&gt; provides the applied toolkit. Causal tracing is a brute-force implementation of the patching philosophy that validates the localization hypotheses from mediation. ROME then leverages the insight about linear, localized mechanisms to perform a surgical, rank-one edit, demonstrating that our understanding is precise enough to be actionable.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;Together, they support a &amp;quot;linear pathway + targeted intervention&amp;quot; picture. The model uses attention to route information to the right place, and a specific MLP module at a specific token position performs a vector-arithmetic operation to retrieve a fact stored in its weights.&lt;/p&gt;&lt;p&gt;This picture breaks down when tasks require more complex, non-linear reasoning, multi-step compositional logic, or when the primary mechanism is not a memory lookup but a dynamic computation heavily reliant on attention patterns over the local context.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Caveat &lt;p&gt;Be meticulous in your reporting. Document the exact prompts, model versions, layers, and token positions tested. Causal effects can be highly specific, and what holds for one model or prompt template may not hold for another. Beware of over-claiming based on a small set of examples.&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;h3&gt;My View&lt;/h3&gt;&lt;p&gt;&lt;br&gt; For practitioners, the most effective workflow seems to be hierarchical. Start with a broad, mediation-style analysis or causal trace to map the computational path and identify the critical few components. Then, use the linear/additive mechanism hypothesis from Todd et al. (2024) to design simple, directional patching experiments to quickly test hypotheses about what the component is doing. Reserve surgical, rank-one edits like ROME for cases where you need to make a durable change to the model&amp;apos;s behavior and are prepared to rigorously evaluate the collateral damage.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Intuition:&lt;/strong&gt;&lt;br&gt; A key insight unifying these papers is that many &amp;quot;memory&amp;quot; tasks in LLMs seem to be implemented locally and linearly. The FFNs, despite their non-linear activation function, can be driven into a regime where they behave like simple additive key-value stores. Causal tracing and ROME succeed because they correctly identify this implementation strategy and intervene at precisely the right layer and token position where this &amp;quot;memory lookup&amp;quot; occurs.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt;&lt;br&gt; The success of these methods on factual recall should not be misinterpreted as evidence that the entire LLM is a simple, linear system. These techniques excel on atomized, single-hop tasks. They are less informative about multi-step reasoning, creative generation, or behaviors that emerge from complex interactions between many components. Furthermore, the causal pathways in a base model may be significantly altered by instruction tuning and RLHF, which layer on new, complex behaviors that may not be as easily localized.&lt;/p&gt;&lt;h3&gt;Limits &amp;amp; Open Questions&lt;/h3&gt;&lt;p&gt;This line of research, while incredibly promising, is still in its early stages. Key open questions remain:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Portability:&lt;/strong&gt; How well do localized mechanisms transfer across model sizes and families? Is the fact &amp;quot;The Eiffel Tower is in Paris&amp;quot; stored in a similar location and way in GPT-J, Llama-3, and Claude 3?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Effect of Finetuning:&lt;/strong&gt; How does instruction tuning or RLHF reshape these causal pathways? Does it create new, dedicated circuits for following instructions that override or modulate the base model&amp;apos;s factual recall mechanisms? Are these new circuits as editable?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Compositionality:&lt;/strong&gt; How can we move from editing single, atomic facts to editing compositional knowledge or complex rules? Editing &amp;quot;All birds can fly&amp;quot; is much harder than editing &amp;quot;The ostrich is a bird,&amp;quot; as it requires a change that propagates through the model&amp;apos;s conceptual space.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Beyond Factual Recall:&lt;/strong&gt; What are the causal mechanisms for more abstract tasks, like identifying logical fallacies, summarizing a long document, or adopting a specific persona? Are they as localizable and editable?  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Answering these questions will be critical as we move from understanding isolated mechanisms to building a more holistic, causal theory of how these powerful and complex models work.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;References&lt;/h4&gt;&lt;p&gt;Meng, K., Bau, D., Andonian, A., &amp;amp; Belinkov, Y. (2022). Locating and Editing Factual Associations in GPT. &lt;em&gt;Project page based on work presented at NeurIPS 2022.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Todd, M. E., Mamou, J., &amp;amp; Lieber, O. (2024). Language Models Implement Simple Word2Vec-style Vector Arithmetic. In &lt;em&gt;Proceedings of the 2024 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies (NAACL)&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Vig, J., Geiger, A., &amp;amp; Belinkov, Y. (2020). Investigating Gender Bias in Language Models Using Causal Mediation Analysis. In &lt;em&gt;Advances in Neural Information Processing Systems (NeurIPS)&lt;/em&gt;.&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/probing/&quot;&gt; Probing &lt;/a&gt;&lt;/div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Training Data Attribution: Corroborative Methods</title>
      <link>https://nayanachandrika99.github.io/posts/training-data-attribution-corroborative-methods/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/training-data-attribution-corroborative-methods/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>#CMSC848R</category>
      <category>mechInterp</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/training-data-attribution-corroborative-methods/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt;&lt;h4&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;What is Corroborative Attribution?&lt;/strong&gt; It is the process of finding evidence in a corpus that directly supports or entails a specific model behavior. Unlike &lt;em&gt;contributive&lt;/em&gt; attribution, it does not estimate causal impact but instead asks: &amp;quot;Is there data that could plausibly explain this output?&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Why Does It Matter?&lt;/strong&gt; Corroboration is a practical, scalable tool for auditing models. It helps us detect benchmark contamination, identify sources of toxicity or private information, and understand data provenance, especially when internal model access or full training logs are unavailable.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;How to Do It At Scale:&lt;/strong&gt; The &lt;em&gt;What&amp;apos;s In My Big Data?&lt;/em&gt; (WIMBD) project provides a concrete blueprint. By leveraging massive-scale &amp;quot;count and search&amp;quot; operations across over 35 terabytes of text, the authors uncovered high rates of duplication, PII, toxicity, and benchmark contamination in major pre-training corpora like RedPajama and C4.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Deeper Motivation:&lt;/strong&gt; As argued in &lt;em&gt;You Are What You Eat&lt;/em&gt;, a model&amp;apos;s internal structure and its resulting behaviors are fundamentally shaped by its training data. Corroborative audits are a necessary first step toward understanding this data-to-behavior pipeline, forming a foundation for safer and more aligned AI systems  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;What is &amp;#x201c;Corroborative Attribution&amp;#x201d;?&lt;/h3&gt;&lt;p&gt;Training Data Attribution (TDA) is a broad term for methods that connect a model&amp;apos;s behavior back to its training data. Within this field, a critical distinction exists between two primary goals: corroboration and contribution.&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;strong&gt;Corroborative attribution&lt;/strong&gt;&lt;/span&gt; aims to find evidence. Given a specific model behavior-such as generating a piece of text, making a factual claim, or exhibiting a toxic stereotype-the goal is to search a candidate corpus and identify examples that directly support, contain, or entail that behavior. The central question is one of provenance and support: &lt;em&gt;&amp;#x201c;Can I find a document in this dataset that substantiates what the model is saying?&amp;#x201d;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This stands in contrast to &lt;strong&gt;contributive attribution (&lt;/strong&gt;&lt;strong&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/training-data-attribution-contributive-methods/&quot;&gt;&lt;span&gt;Training Data Attribution: Contributive Methods&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;strong&gt;)&lt;/strong&gt;. Contributive methods, like influence functions or TracIn, seek to estimate the causal impact of a training example on a model&amp;apos;s parameters or a specific prediction. Their question is counterfactual: &lt;em&gt;&amp;#x201c;How much would this prediction have changed if the model had not been trained on this specific example?&amp;#x201d;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;While contributive methods offer deep insights into model learning, they are often computationally expensive and require significant access to the model&amp;apos;s internals (e.g., gradients, checkpoints), which is frequently infeasible for practitioners working with proprietary, third-party models.&lt;/p&gt;&lt;p&gt;Corroborative attribution, on the other hand, is a powerful and practical first-line-of-defense auditing tool. It enables:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Data Provenance:&lt;/strong&gt; Tracing model outputs to their likely sources for copyright, licensing, or fact-checking purposes.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Leakage &amp;amp; Contamination Audits:&lt;/strong&gt; Systematically checking if evaluation benchmarks or sensitive test sets are present in the training data, which would invalidate reported performance metrics.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Safety Reviews:&lt;/strong&gt; Identifying the sources of undesirable behaviors like toxicity, bias, or the memorization of personally identifiable information (PII).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Policy &amp;amp; Deployment Decisions:&lt;/strong&gt; Providing concrete evidence to inform decisions about model deployment, even when the model itself is a black box.  &lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Background &amp;amp; Formalization&lt;/h3&gt;&lt;p&gt;We can formalize the goal of corroborative attribution as follows. Let &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; denote an observed model behavior, such as a generated sentence, a classification decision, or a factual claim. Let &lt;span&gt;&lt;span&gt;&lt;span&gt;CC&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; be a candidate corpus, which could be a known pre-training dataset (like The Pile) or a suspected source of information (like a crawl of a specific website).&lt;/p&gt;&lt;p&gt;A corroborative method, or a &lt;strong&gt;corroborator&lt;/strong&gt;, is a process that returns a support set &lt;span&gt;&lt;span&gt;&lt;span&gt;Starg&amp;#x2286;CS_{\text{targ}} \subseteq C&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;targ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2286;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. For each example &lt;span&gt;&lt;span&gt;&lt;span&gt;z&amp;#x2208;Stargz \in S_{\text{targ}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;&amp;#x2208;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;targ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, we say that &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; supports the behavior &amp;#x24;B&amp;#x24;, which we denote as&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&amp;#x21d2;Bz \Rightarrow B&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;&amp;#x21d2;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. This support relationship is established by an evidence relation &lt;span&gt;&lt;span&gt;&lt;span&gt;RR&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and a threshold &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;\tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. Formally:&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&amp;#x2208;Starg&amp;#x2227;R(z,B)&amp;#x2265;&amp;#x3c4;&amp;#x2005;&amp;#x200a;&amp;#x27f9;&amp;#x2005;&amp;#x200a;z&amp;#x21d2;Bz \in S_{\text{targ}} \wedge R(z, B) \geq \tau \implies z \Rightarrow B&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;&amp;#x2208;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;targ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2227;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;B&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x2265;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;span&gt;&amp;#x27f9;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;&amp;#x21d2;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;The evidence relation &lt;span&gt;&lt;span&gt;&lt;span&gt;R(&amp;#x22c5;,&amp;#x22c5;)R(\cdot, \cdot)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the heart of the method and can take several forms, each with its own strengths and limitations:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Exact Match (&lt;/strong&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;REXACTR_{\text{EXACT}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;EXACT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;):&lt;/strong&gt; This is the simplest relation, checking if the string representing behavior &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; appears verbatim in document &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. It is precise but brittle; it fails to capture even minor variations like changes in punctuation or capitalization.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Near-Duplicate Match (&lt;/strong&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;RNEARR_{\text{NEAR}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;NEAR&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;):&lt;/strong&gt; This relation uses fuzzy matching techniques (e.g., n-gram overlap, MinHash) to identify documents &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; that are nearly identical to &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. It is more robust than exact matching but can still miss paraphrases.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Similarity Search (&lt;/strong&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;RSIMR_{\text{SIM}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SIM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;):&lt;/strong&gt; Here, we map both the behavior &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and documents &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; into a high-dimensional embedding space (e.g., using a sentence-transformer). The relation is typically cosine similarity, &lt;span&gt;&lt;span&gt;&lt;span&gt;RSIM(z,B)=cosine_sim(embed(z),embed(B))R_{\text{SIM}}(z, B) = \text{cosine\_sim}(\text{embed}(z), \text{embed}(B))&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SIM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;B&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;cosine_sim&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;embed&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;embed&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;B&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. This method is powerful for finding semantically related content but is sensitive to the choice of embedding model and the similarity threshold &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;\tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. A low threshold can lead to many false positives (spurious connections).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Entailment Check (&lt;/strong&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;RENTR_{\text{ENT}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;ENT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;):&lt;/strong&gt; This is a more sophisticated logical check. Using a Natural Language Inference (NLI) model, we determine if a candidate document &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; textually entails the behavior &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. For instance, if &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is &amp;quot;Paris is the capital of France,&amp;quot; a document &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; stating &amp;quot;The French government is based in Paris&amp;quot; would entail it. This helps filter out spurious semantic matches but depends heavily on the quality of the NLI model.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; Corroboration is not proof of training. Finding a document &lt;span&gt;&lt;span&gt;&lt;span&gt;zz&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; in corpus &lt;span&gt;&lt;span&gt;&lt;span&gt;CC&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; that supports behavior &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; demonstrates that the corpus contains information that could plausibly have led to that behavior. It does not definitively prove that the model &lt;em&gt;used that specific document&lt;/em&gt; to produce the output. The same information could be present in many other documents or even in a different, unknown training corpus.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Paper 1 - &lt;em&gt;What&amp;apos;s In My Big Data?&lt;/em&gt; as a Corroborative Audit&lt;/h3&gt;&lt;p&gt;The ICLR 2024 paper &lt;em&gt;What&amp;apos;s In My Big Data?&lt;/em&gt; (WIMBD) by Elazar et al. serves as a landmark example of a large-scale, systematic corroborative audit. Instead of focusing on a single model&amp;apos;s behavior, the authors developed a platform to analyze the properties of the data itself, creating a foundation for countless future corroborative investigations.&lt;/p&gt;&lt;h4&gt;Method: &amp;quot;Count + Search at Scale&amp;quot;&lt;/h4&gt;&lt;p&gt;The core innovation of WIMBD is its ability to perform two fundamental operations-counting and searching-efficiently across massive text corpora. The authors analyzed ten major datasets, including C4, The Pile, RedPajama, and LAION-2B-en, totaling over 35 terabytes of text. By building specialized indices and using optimized search tools, they could execute complex queries on a standard compute node, making large-scale data audits accessible without requiring a supercomputer.&lt;/p&gt;&lt;h4&gt;Key Findings as Corroborative Evidence&lt;/h4&gt;&lt;p&gt;WIMBD&amp;apos;s analyses directly enable corroborative workflows by characterizing the evidence available in these corpora. Their findings were striking:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;High Duplication:&lt;/strong&gt; The study revealed massive levels of duplication. For example, they found that approximately 50% of the documents in both RedPajama and the English portion of LAION-2B are duplicates. This is a critical finding for attribution; a behavior corroborated by a document present thousands of times is more likely to be memorized or heavily weighted by a model.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Benchmark Contamination:&lt;/strong&gt; The authors systematically searched for test and evaluation data within the pre-training corpora. They found significant contamination for key benchmarks, including the Winograd Schema Challenge (WSC), and parts of the GLUE and SuperGLUE suites. This discovery corroborates the hypothesis that high performance on these benchmarks may be due to memorization rather than true generalization.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;PII and Toxicity Prevalence:&lt;/strong&gt; The platform was used to search for patterns matching personally identifiable information (email addresses, phone numbers) and to score documents for toxicity. The widespread presence of this content provides the necessary substrate for corroborating model behaviors that leak PII or generate toxic language.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;WIMBD is a quintessential example of corroborative auditing because it links potential model behaviors (high benchmark scores, PII leakage) directly to evidence at the corpus level. It does not measure the influence of any single document but rather characterizes the sea of data from which these behaviors could plausibly emerge.&lt;/p&gt;&lt;hr&gt;&lt;blockquote&gt;&lt;div&gt;&lt;strong&gt;Intuition:&lt;/strong&gt; The analyses in WIMBD effectively transform the opaque concept of &amp;quot;pre-training data&amp;quot; into a searchable, queryable substrate. This move from an unknown blob of text to an indexed and analyzed corpus is the foundational step that makes corroborative attribution possible. It allows a researcher to move from &amp;quot;the model said something weird&amp;quot; to &amp;quot;the model said something weird, and here are 10,000 documents in its likely training data that say the same thing.&amp;quot;  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Paper 2 - Why Corroboration Matters (&lt;em&gt;You Are What You Eat&lt;/em&gt;)&lt;/h3&gt;&lt;p&gt;The position paper &lt;em&gt;You Are What You Eat&lt;/em&gt; argues that a deep understanding of the relationship between training data, a model&amp;apos;s internal structure, and its downstream generalization is necessary for AI alignment. It posits that merely evaluating a model&amp;apos;s behavior on test sets is insufficient to guarantee safety, as different internal structures can produce identical outputs on a given dataset but generalize in dangerously different ways.&lt;/p&gt;&lt;p&gt;The central thesis is that &lt;strong&gt;data shapes structure, and structure determines behavior.&lt;/strong&gt; This provides a powerful motivation for investing in robust corroborative audits. If we want to build safe, aligned AI, we cannot treat the training data as an afterthought. We must understand its contents, its flaws, and its latent structures, as these will inevitably be reflected in the models we train.&lt;/p&gt;&lt;p&gt;Corroborative attribution is the most direct method we have for drawing this line from data to behavior. When we corroborate a model&amp;apos;s toxic output by finding toxic source documents, or corroborate a factual error by finding source documents containing the same misinformation, we are empirically verifying the &amp;quot;You Are What You Eat&amp;quot; principle.&lt;/p&gt;&lt;hr&gt;&lt;blockquote&gt;&lt;div&gt;&lt;strong&gt;Opinion:&lt;/strong&gt; For practitioners working on the front lines of AI safety and deployment, especially with limited access to model internals, corroboration is often the only tractable audit lever available. While mechanistic interpretability and contributive attribution are vital research directions, the ability to quickly search a corpus for evidence supporting a harmful behavior is a practical, scalable, and indispensable tool for risk assessment today.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h3&gt;Method Families &amp;amp; Practical Protocols (Corroborative)&lt;/h3&gt;&lt;p&gt;A practical corroborative audit follows a structured protocol, moving from a behavior of interest to a final report. This protocol typically combines several families of methods.&lt;/p&gt;&lt;h4&gt;Method Families&lt;/h4&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Exact &amp;amp; Near-Duplicate Matching:&lt;/strong&gt; This is the go-to method for investigating suspected memorization, copyright infringement, or benchmark contamination. Using lexical tools (string matching) and fuzzy hashing (MinHash), one can quickly find documents that are verbatim or near-verbatim copies of the target behavior &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. WIMBD&amp;apos;s contamination checks for benchmarks like GLUE relied heavily on this approach.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Embedding &amp;amp; k-NN Retrieval:&lt;/strong&gt; For finding semantic support, not just lexical copies, embedding-based retrieval is key. The process involves encoding the behavior &amp;#x24;B&amp;#x24; into a vector and using an approximate nearest neighbor (k-NN) search index (like Faiss or ScaNN) to retrieve the top-k most similar documents from the corpus embeddings. The relevance of the results is then determined by applying a similarity threshold, &lt;span&gt;&lt;span&gt;&lt;span&gt;RSIM&amp;#x2265;&amp;#x3c4;R_{\text{SIM}} \ge \tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SIM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2265;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Textual Entailment Checks:&lt;/strong&gt; Semantic similarity can be misleading. A document might be about the same topic as &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; but contradict it. An entailment model can act as a crucial filter. After retrieving a set of candidates via embedding search, you can run an NLI model to check if a candidate document actually entails, contradicts, or is neutral with respect to the claim in &lt;span&gt;&lt;span&gt;&lt;span&gt;BB&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Corpus Health Metrics:&lt;/strong&gt; Before investigating any specific behavior, it is useful to have global statistics about the corpus. The work of WIMBD exemplifies this: knowing the overall duplication rate, toxicity levels, and prevalence of PII helps prioritize which behaviors to investigate and provides context for the findings.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;A Practical Protocol&lt;/h4&gt;&lt;p&gt;A typical corroborative audit workflow can be structured as follows:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Define Behavior &amp;#x24;B&amp;#x24;:&lt;/strong&gt; Start with a clear, specific behavior to investigate. This could be a verbatim quote from a model, a factual claim it made, or a pattern of biased completions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Query Construction:&lt;/strong&gt; Transform &amp;#x24;B&amp;#x24; into one or more queries. For lexical search, this might be the string itself. For semantic search, it&amp;apos;s the embedding of the string. For broader audits, it could be a regular expression (for PII) or a list of keywords.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Tiered Search:&lt;/strong&gt; Begin with the cheapest, most precise methods first. Run exact and near-duplicate searches. If no strong evidence is found, proceed to broader semantic search (embedding/k-NN retrieval).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Filtering &amp;amp; Refinement:&lt;/strong&gt; The output of a semantic search can be noisy. Filter the initial candidate set &lt;span&gt;&lt;span&gt;&lt;span&gt;StargS_{\text{targ}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;targ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; using a relevance threshold &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;\tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. For high-stakes audits, add a textual entailment (&lt;span&gt;&lt;span&gt;&lt;span&gt;RENTR_{\text{ENT}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;ENT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) check to ensure the retrieved documents logically support the behavior.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Human-in-the-Loop Review:&lt;/strong&gt; Automated metrics are not enough. A human expert should review the final, filtered set of supporting documents to assess their quality and relevance, and to identify subtle issues that automated tools might miss.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reporting:&lt;/strong&gt; The final report should be transparent. Disclose the corpora searched, the search methods used (&lt;span&gt;&lt;span&gt;&lt;span&gt;REXACT,RSIMR_{\text{EXACT}}, R_{\text{SIM}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;EXACT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SIM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, etc.), the thresholds (&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;\tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) applied, and the final support set &lt;span&gt;&lt;span&gt;&lt;span&gt;StargS_{\text{targ}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;targ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. Crucially, include caveats about the limitations of the audit.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; The efficacy of any corroborative audit is limited by the accessibility of the training corpus. When auditing a model trained on a closed-source, proprietary dataset, one must rely on open-source proxies (e.g., using RedPajama as a proxy for the Llama training set). In such cases, the report must clearly state this limitation and frame the findings as plausible evidence rather than definitive proof.&lt;/p&gt;&lt;hr&gt;&lt;h3&gt;Evaluation &amp;amp; Reporting&lt;/h3&gt;&lt;p&gt;A rigorous corroborative audit is not just about finding examples; it&amp;apos;s about evaluating the process itself. If a ground-truth dataset exists (e.g., a set of known memorized sequences and their sources), the performance of a corroborator can be measured with standard metrics:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Precision:&lt;/strong&gt; What fraction of the retrieved support examples (&lt;span&gt;&lt;span&gt;&lt;span&gt;StargS_{\text{targ}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;targ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) are truly relevant?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Recall:&lt;/strong&gt; What fraction of all possible relevant examples in the corpus &lt;span&gt;&lt;span&gt;&lt;span&gt;CC&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; did the method successfully retrieve?  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In practice, practitioners should perform ablations over the similarity threshold &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;\tau&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c4;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; to understand the trade-off between precision and recall. Reporting should be standardized and reproducible, including:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; The exact corpora searched, with versioning information.  &lt;/li&gt;&lt;li&gt; The query templates or construction methods used.  &lt;/li&gt;&lt;li&gt; The software and models used for embedding, search, and entailment.  &lt;/li&gt;&lt;li&gt; For audits involving human review, inter-annotator agreement scores should be reported to quantify the subjectivity of the task.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Tools like dataset cards and datasheets should be used not just for the training data itself, but for the outputs of corroborative audits, providing a transparent record of what was searched for and what was found.&lt;/p&gt;&lt;h3&gt;&lt;span&gt;My View&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Opinion:&lt;/strong&gt; Every high-stakes audit of a powerful language model should begin with a corroborative analysis. It is the most direct, scalable, and empirically grounded way to assess data-related risks. Only when this foundation is established, and when access permits, should teams move to more complex and computationally intensive contributive methods. Corroboration provides the &amp;quot;what&amp;quot; and &amp;quot;where&amp;quot;; contribution can then explore the &amp;quot;how much.&amp;quot;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Intuition:&lt;/strong&gt; The core argument of &lt;em&gt;You Are What You Eat&lt;/em&gt;-that data shapes structure which in turn shapes behavior-is deeply compelling. If this causal chain holds, then examining the source data is not just one of many options; it is an indispensable first step. Ignoring the properties of the training data is akin to a doctor diagnosing a patient without asking about their diet.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; Corroborative methods have blind spots. They are biased towards finding evidence that is lexically or semantically close to the target behavior. They can over-credit popular sources that are highly duplicated and may fail to identify support from a collection of diverse, long-tail documents that, in aggregate, teach the model a concept through paraphrase and analogy. An audit report must always be transparent about the types of evidence it is designed to find and the types it is likely to miss.&lt;/p&gt;&lt;h3&gt;Limits &amp;amp; Open Questions&lt;/h3&gt;&lt;p&gt;Despite its practical utility, corroborative attribution faces several significant challenges and open research questions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Coverage vs. Privacy:&lt;/strong&gt; How can we conduct thorough audits when training corpora are increasingly proprietary and closed-source? Can we develop methods for privacy-preserving corroboration that allow model developers to answer queries about their data without revealing it?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bridging to Contribution:&lt;/strong&gt; Finding a supporting document is the first step. The next is understanding its impact. Future research should focus on integrating corroborative search (to find candidates) with lightweight contributive estimation (to rank candidates by influence).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Watermarking and Fingerprinting:&lt;/strong&gt; Could we proactively embed invisible signals (watermarks) or unique phrases (fingerprints) into training corpora? This would transform corroboration from a search problem into a simple detection problem, making attribution far more reliable.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Cost of Scale:&lt;/strong&gt; While WIMBD demonstrated feasibility on a single node, analyzing future trillion-token datasets will require continued innovation in efficient indexing and search algorithms.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Ultimately, corroborative methods are a vital part of the toolkit for ensuring AI systems are safe, transparent, and accountable. They ground our understanding of model behavior in the concrete reality of the data they consume, reminding us that, in the world of AI, you are what you eat.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;References&lt;/h4&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Pepin Lehalleur, S., Hoogland, J., Farrugia-Roberts, M., Wei, S., Gietelink Oldenziel, A., Wang, G., Carroll, L., &amp;amp; Murfet, D. (2025). &lt;em&gt;You Are What You Eat -- AI Alignment Requires Understanding How Data Shapes Structure and Generalisation&lt;/em&gt;. arXiv preprint arXiv:2502.05475.  &lt;/li&gt;&lt;li&gt; Elazar, Y., Bhagia, A., Magnusson, I., Ravichander, A., Schwenk, D., Suhr, A., Walsh, P., Groeneveld, D., Soldaini, L., Singh, S., Hajishirzi, H., Smith, N., &amp;amp; Dodge, J. (2024). &lt;em&gt;What&amp;apos;s In My Big Data?&lt;/em&gt; Proceedings of the International Conference on Learning Representations (ICLR).  &lt;/li&gt;&lt;li&gt; Geva, M., Schuster, T., Berant, J., &amp;amp; Globerson, A. (2023). &lt;em&gt;Connecting Pre-training and Fine-tuning with Attribution for T5&lt;/em&gt;. OpenReview.  &lt;/li&gt;&lt;li&gt; Cohen-Wang, C., Chuang, I. L., &amp;amp; Zhang, C. (2023). &lt;em&gt;Unifying Corroborative and Contributive Attributions in Large Language Models&lt;/em&gt;. OpenReview.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/training-data-attribution-contributive-methods/&quot;&gt; Training Data Attribution: Contributive Methods &lt;/a&gt;&lt;/div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Training Data Attribution: Contributive Methods</title>
      <link>https://nayanachandrika99.github.io/posts/training-data-attribution-contributive-methods/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/training-data-attribution-contributive-methods/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>#CMSC848R</category>
      <category>mechInterp</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/training-data-attribution-contributive-methods/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;What is Training Data Attribution (TDA)?&lt;/strong&gt; TDA is a set of techniques for identifying which training examples are most responsible for a specific model behavior, such as a particular prediction or an overall performance metric. It moves beyond asking &lt;em&gt;what&lt;/em&gt; a model predicts to asking &lt;em&gt;why&lt;/em&gt;, in terms of its training data.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Why does it matter?&lt;/strong&gt; As we move past chasing benchmark scores, we need tools for debugging, enhancing fairness, ensuring robustness, and understanding the origins of model capabilities. TDA provides a crucial link between the data we curate and the behaviors the models exhibit.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;What are contributive methods?&lt;/strong&gt; This post focuses on &lt;em&gt;contributive&lt;/em&gt; TDA, which assigns a score to each training point reflecting its helpful or harmful influence on a model&amp;apos;s prediction. We will cover four major families: Influence Functions, TracIn, Representer Points, and Data-Shapley.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;What are the key takeaways?&lt;/strong&gt; TDA is not a single, solved problem. Each method comes with its own set of mathematical assumptions, computational trade-offs, and failure modes. The most effective approach for a given problem depends on the model architecture, the available resources (e.g., gradients, checkpoints), and the specific question being asked.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;What this post covers:&lt;/strong&gt; A deep dive into the math, intuition, and practical caveats of the core contributive methods, with a final synthesis to guide practitioners on which method to choose and when.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;p&gt;The field&amp;apos;s reliance on chasing leaderboard scores is slowly giving way to a more mature focus on model understanding, and for good reason: benchmark score-chasing isn&amp;#x2019;t enough; we need methods that link predictions to specific training examples. Without this connection, we are flying blind, unable to explain why a model fails on a critical edge case, reproduces a harmful bias, or succeeds at a surprising new task. Training Data Attribution (TDA) provides the analytical lens for forging this link.&lt;/p&gt;&lt;h4&gt;What Counts as &amp;#x201c;Training Data Attribution&amp;#x201d;?&lt;/h4&gt;&lt;p&gt;At its core, &lt;strong&gt;Training Data Attribution&lt;/strong&gt; is the process of assigning a score to one or more training examples, &lt;code&gt;z_i&lt;/code&gt;, that quantifies their responsibility for a model&amp;apos;s behavior on a specific test point, &lt;code&gt;z_test&lt;/code&gt;, or for its overall performance. It seeks to answer the question: &amp;quot;Which training data points made the model make &lt;em&gt;this&lt;/em&gt; specific prediction?&amp;quot;&lt;/p&gt;&lt;p&gt;This is distinct from several related concepts. &lt;strong&gt;Data provenance&lt;/strong&gt; simply tracks the origin of data, without assessing its impact on the model. &lt;strong&gt;Memorization detection&lt;/strong&gt; identifies instances where the model has stored and reproduced training data verbatim, which is a specific type of influence but not the whole story. &lt;strong&gt;Dataset auditing&lt;/strong&gt; typically involves high-level statistical analysis of a dataset (e.g., class balance, subgroup representation) rather than example-level attribution for a specific prediction.&lt;/p&gt;&lt;p&gt;The motivation for TDA has become increasingly urgent. Research such as &amp;quot;Impact of Pretraining Term Frequencies on Few-Shot Numerical Reasoning&amp;quot; (Razeghi et al., 2022) demonstrates that a model&amp;apos;s emergent abilities are deeply connected to the statistical properties of its training corpus. For example, they show that the frequency of numbers in the pretraining data correlates with a model&amp;apos;s ability to reason about those numbers. While this provides a corpus-level explanation, TDA offers the tools to drill down and ask: given this statistical backdrop, which &lt;em&gt;specific examples&lt;/em&gt; were most instrumental in teaching the model a given concept or causing a particular failure?&lt;/p&gt;&lt;h4&gt;Background &amp;amp; Scope&lt;/h4&gt;&lt;p&gt;Before diving into methods, let&amp;apos;s establish our notation and scope. We consider a training set &lt;code&gt;D = {z_i = (x_i, y_i)}_{i=1}^n&lt;/code&gt; of &lt;code&gt;n&lt;/code&gt; examples. Our model is parameterized by &lt;code&gt;&amp;#x3b8; &amp;#x2208; R^p&lt;/code&gt;. The loss on a single example &lt;code&gt;z&lt;/code&gt; is &lt;code&gt;&amp;#x2113;(z, &amp;#x3b8;)&lt;/code&gt;. The empirical risk, which the model aims to minimize, is &lt;code&gt;R(&amp;#x3b8;) = (1/n) &amp;#x3a3;_i &amp;#x2113;(z_i, &amp;#x3b8;) + &amp;#x3bb;&amp;#x3a9;(&amp;#x3b8;)&lt;/code&gt;, where &lt;code&gt;&amp;#x3a9;(&amp;#x3b8;)&lt;/code&gt; is a regularization term with weight &lt;code&gt;&amp;#x3bb;&lt;/code&gt;. The Hessian of the risk is the matrix of second derivatives, &lt;code&gt;H = &amp;#x2207;_&amp;#x3b8;^2 R(&amp;#x3b8;)&lt;/code&gt;. Our goal is to understand the influence of a training point &lt;code&gt;z_i&lt;/code&gt; on a prediction for a test point &lt;code&gt;z_test&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;TDA methods are most cleanly applied in a &lt;strong&gt;supervised learning&lt;/strong&gt; context, such as fine-tuning a language model on a specific task. The dataset is well-defined, accessible, and of a manageable size. Applying TDA to the &lt;strong&gt;pretraining&lt;/strong&gt; stage of a foundation model is significantly harder due to the immense scale and frequent inaccessibility of the pretraining corpus.&lt;/p&gt;&lt;p&gt;Different TDA methods also require different levels of access to the model and training process:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Some require full access to &lt;strong&gt;gradients&lt;/strong&gt; and the ability to compute &lt;strong&gt;Hessian-vector products&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt; Some rely on having saved model &lt;strong&gt;checkpoints&lt;/strong&gt; from various stages of training.  &lt;/li&gt;&lt;li&gt; Others are only applicable to models with specific architectures, like a &lt;strong&gt;linear final layer&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt; The most computationally demanding methods may require &lt;strong&gt;retraining the model&lt;/strong&gt; on many different subsets of the data.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Contributive Methods (Core Families)&lt;/h4&gt;&lt;p&gt;Contributive methods form the backbone of TDA. They estimate the positive (helpful) or negative (harmful) impact of each training point on a specific outcome.&lt;/p&gt;&lt;h4&gt;(a) Influence Functions&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;br&gt; Influence Functions, introduced to machine learning by Koh &amp;amp; Liang (2017), are a classic technique from robust statistics. The core idea is to approximate the effect of removing a training point by asking: &amp;quot;How would the model&amp;apos;s optimal parameters &lt;code&gt;&amp;#x3b8;&lt;/code&gt; change if we infinitesimally up-weighted a single training point &lt;code&gt;z_i&lt;/code&gt;?&amp;quot; This change in parameters is then used to measure the change in loss on a test point &lt;code&gt;z_test&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Math&lt;/strong&gt;&lt;br&gt; The effect on the parameters &lt;code&gt;&amp;#x3b8;&lt;/code&gt; from up-weighting a training point &lt;code&gt;z_i&lt;/code&gt; by a small amount &lt;code&gt;&amp;#x3b5;&lt;/code&gt; is given by the influence function:&lt;/p&gt;&lt;p&gt;&lt;code&gt;d&amp;#x3b8;_&amp;#x3b5; / d&amp;#x3b5; |_(&amp;#x3b5;=0) = - (1/n) H&amp;#x207b;&amp;#xb9; &amp;#x2207;_&amp;#x3b8; &amp;#x2113;(z_i, &amp;#x3b8;)&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This equation tells us that the change in parameters is proportional to the gradient of the training point&amp;apos;s loss, scaled by the inverse Hessian &lt;code&gt;H&amp;#x207b;&amp;#xb9;&lt;/code&gt;. The Hessian measures the curvature of the loss landscape; inverting it translates a step in gradient space into a change in parameter space.&lt;/p&gt;&lt;p&gt;To find the influence of &lt;code&gt;z_i&lt;/code&gt; on the loss at &lt;code&gt;z_test&lt;/code&gt;, we use the chain rule. The influence score, &lt;code&gt;IF(z_i &amp;#x2192; z_test)&lt;/code&gt;, is defined as the change in the test loss:&lt;/p&gt;&lt;p&gt;&lt;code&gt;IF(z_i &amp;#x2192; z_test) = - (1/n) &amp;#x2207;_&amp;#x3b8; &amp;#x2113;(z_test, &amp;#x3b8;)&amp;#x1d40; H&amp;#x207b;&amp;#xb9; &amp;#x2207;_&amp;#x3b8; &amp;#x2113;(z_i, &amp;#x3b8;)&lt;/code&gt;&lt;/p&gt;&lt;p&gt;A positive score means that up-weighting &lt;code&gt;z_i&lt;/code&gt; increases the test loss, implying &lt;code&gt;z_i&lt;/code&gt; is harmful to the prediction on &lt;code&gt;z_test&lt;/code&gt;. A negative score means &lt;code&gt;z_i&lt;/code&gt; is helpful.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Assumptions&lt;/strong&gt;&lt;br&gt; Influence Functions rest on strong assumptions. They require the loss function to be twice-differentiable and strictly convex, ensuring a unique minimum and an invertible Hessian. The method is formally derived as a first-order approximation, meaning it is most accurate for small perturbations and assumes that the model parameters are already at a local minimum.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Computation&lt;/strong&gt;&lt;br&gt; The primary computational bottleneck is calculating and inverting the &lt;code&gt;p x p&lt;/code&gt; Hessian, &lt;code&gt;H&lt;/code&gt;, which is infeasible for models with millions or billions of parameters. Instead, we never form &lt;code&gt;H&lt;/code&gt; explicitly. We compute the term &lt;code&gt;H&amp;#x207b;&amp;#xb9; v&lt;/code&gt; (where &lt;code&gt;v&lt;/code&gt; is a vector, like &lt;code&gt;&amp;#x2207;_&amp;#x3b8; &amp;#x2113;(z_i, &amp;#x3b8;)&lt;/code&gt;) using iterative algorithms like the conjugate gradient method. These algorithms only require access to Hessian-vector products (HVPs), which can be computed efficiently with automatic differentiation. A damping term is often added to the Hessian diagonal to improve stability.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Strengths and Failure Modes&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Influence Functions provide a fine-grained, theoretically grounded attribution score for each training point. They have been successfully used to identify mislabeled examples, understand dataset shortcuts, and debug model predictions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Failure Modes:&lt;/strong&gt; The convexity assumption is strongly violated by deep neural networks. The local quadratic approximation that underpins Influence Functions may not hold in the complex, non-convex loss landscapes of Transformers. This can make the resulting scores fragile and unreliable, especially for out-of-distribution training points.  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Caveat: Influence Function scores for deep, non-convex models should be treated with caution. They can be a powerful signal, but their fragility means they should ideally be validated with more direct checks, such as leave-one-out retraining on a small subset of the most influential points. Think of them as a sophisticated heuristic, not ground truth.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;(b) TracIn&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;br&gt; TracIn (Pruthi et al., 2020) offers a more direct, empirical approach. Instead of approximating the effect of removal from a single final model, TracIn calculates influence by integrating the impact a training point has over the entire training trajectory. The intuition is simple: if, during training, the gradient for &lt;code&gt;z_i&lt;/code&gt; frequently points in the same direction as the gradient for &lt;code&gt;z_test&lt;/code&gt;, then the updates driven by &lt;code&gt;z_i&lt;/code&gt; were likely helpful for &lt;code&gt;z_test&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Math&lt;/strong&gt;&lt;br&gt; The TracIn score is the sum of dot products between the gradients of the test point and the training point, evaluated at various checkpoints &lt;code&gt;&amp;#x3b8;_k&lt;/code&gt; saved during training, and scaled by the learning rate &lt;code&gt;&amp;#x3b7;_k&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;&lt;code&gt;TracIn(z_i &amp;#x2192; z_test) &amp;#x2248; &amp;#x3a3;_(k&amp;#x2208;K) &amp;#x3b7;_k &amp;#x2207;_&amp;#x3b8; &amp;#x2113;(z_test, &amp;#x3b8;_k) &amp;#x22c5; &amp;#x2207;_&amp;#x3b8; &amp;#x2113;(z_i, &amp;#x3b8;_k)&lt;/code&gt;&lt;/p&gt;&lt;p&gt;A large positive score indicates that &lt;code&gt;z_i&lt;/code&gt; consistently pushed the model in a direction that would also reduce the loss on &lt;code&gt;z_test&lt;/code&gt;, making it an influential proponent. A large negative score indicates it was an opponent.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Assumptions&lt;/strong&gt;&lt;br&gt; TracIn makes fewer theoretical assumptions than Influence Functions. It does not require convexity. Its primary practical assumption is the availability of a representative set of model checkpoints &lt;code&gt;K&lt;/code&gt; spanning the training process. The quality of the attribution depends directly on the quality and frequency of these checkpoints.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Computation&lt;/strong&gt;&lt;br&gt; TracIn is computationally more straightforward than Influence Functions. It requires saving model checkpoints periodically during training. The attribution step involves a forward and backward pass for each training point and the test point at each checkpoint to compute the gradients. The cost scales linearly with the number of training points and the number of checkpoints.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Strengths and Failure Modes&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Because it directly uses the training trajectory, TracIn is often more robust and reliable for deep, non-convex models than Influence Functions. It is conceptually simple and relatively easy to implement.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Failure Modes:&lt;/strong&gt; The scores are highly dependent on the choice of checkpoints. If only the final checkpoints are used, TracIn can miss important influences from early in training. The score is also sensitive to the learning rate schedule and optimizer dynamics (e.g., momentum).  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Intuition: TracIn&amp;apos;s power comes from its direct connection to what the optimizer actually did. It follows the path taken, while Influence Functions analyze the local geometry at the destination. For the winding roads of non-convex optimization, the path often tells a more accurate story than a local map of the final location.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;(c) Representer Points&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;br&gt; The Representer Theorem provides a way to express the solution of certain kernel methods as a linear combination of the training examples. Representer Point Selection (Yeh et al., 2018) adapts this idea to deep learning. Under specific architectural assumptions, it allows us to decompose a network&amp;apos;s pre-activation output for a test point into a weighted sum of similarities to the training points. The weights, or &amp;quot;representer values,&amp;quot; serve as the attribution scores.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Math&lt;/strong&gt;&lt;br&gt; For a model with a final linear layer that minimizes an &lt;code&gt;L2&lt;/code&gt;-regularized objective, the prediction function &lt;code&gt;f(x)&lt;/code&gt; can be approximated as a linear combination of kernel similarities between the test input&amp;apos;s representation &lt;code&gt;&amp;#x3c6;(x)&lt;/code&gt; and each training input&amp;apos;s representation &lt;code&gt;&amp;#x3c6;(x_i)&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;&lt;code&gt;f(x) &amp;#x2248; &amp;#x3a3;_i &amp;#x3b1;_i &amp;#x3c6;(x_i)&amp;#x1d40; &amp;#x3c6;(x)&lt;/code&gt;&lt;/p&gt;&lt;p&gt;The &lt;code&gt;&amp;#x3b1;_i&lt;/code&gt; are the representer values. A positive &lt;code&gt;&amp;#x3b1;_i&lt;/code&gt; means that &lt;code&gt;z_i&lt;/code&gt; is an &amp;quot;excitatory&amp;quot; example, pushing the prediction in a positive direction. A negative &lt;code&gt;&amp;#x3b1;_i&lt;/code&gt; means it is &amp;quot;inhibitory.&amp;quot; These values can be computed efficiently from the model&amp;apos;s final-layer parameters and the loss gradients.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Assumptions&lt;/strong&gt;&lt;br&gt; This method&amp;apos;s applicability is constrained by its strong architectural assumptions: the model must be trained with &lt;code&gt;L2&lt;/code&gt; regularization on its final layer parameters, and the final output must be a linear function of the learned representations &lt;code&gt;&amp;#x3c6;(x)&lt;/code&gt;. While this holds for many standard classification networks, it may not apply directly to more complex architectures like Transformers without modification.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Computation&lt;/strong&gt;&lt;br&gt; Once the model is trained, computing the representer values is typically fast. It involves a single backward pass to get gradients and then solving a system of linear equations that depends on the training set size. The main cost is in pre-computing and storing the representations &lt;code&gt;&amp;#x3c6;(x_i)&lt;/code&gt; for all training data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Strengths and Failure Modes&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; The method provides directly interpretable signed attributions (excitatory vs. inhibitory), which is a powerful debugging tool. When its assumptions are met, it is computationally efficient.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Failure Modes:&lt;/strong&gt; Its primary limitation is the strict set of architectural assumptions. It is not a general-purpose method for arbitrary deep learning models. The quality of the attribution also depends on the quality of the learned representations &lt;code&gt;&amp;#x3c6;(x)&lt;/code&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;(d) Data-Shapley&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;br&gt; Data-Shapley (Ghorbani &amp;amp; Zou, 2019; Jia et al., 2019) brings a game-theoretic approach to TDA. It treats the training data points as players in a cooperative game where the goal is to achieve high model performance. The Shapley value is a concept from game theory that provides a principled way to fairly distribute the total &amp;quot;payout&amp;quot; (model utility) among the players. The Shapley value of a data point is its average marginal contribution to the model&amp;apos;s performance across all possible subsets (coalitions) of the training data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Math&lt;/strong&gt;&lt;br&gt; Let &lt;code&gt;U(S)&lt;/code&gt; be a utility function that measures the performance of a model trained on a subset of data &lt;code&gt;S &amp;#x2286; D&lt;/code&gt;. The Shapley value &lt;code&gt;&amp;#x3d5;_i&lt;/code&gt; for a data point &lt;code&gt;z_i&lt;/code&gt; is:&lt;/p&gt;&lt;p&gt;&lt;code&gt;&amp;#x3d5;_i = E_&amp;#x3c0; [U(S_&amp;#x3c0; &amp;#x222a; {i}) - U(S_&amp;#x3c0;)]&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Here, the expectation is taken over all random permutations &lt;code&gt;&amp;#x3c0;&lt;/code&gt; of the training data, and &lt;code&gt;S_&amp;#x3c0;&lt;/code&gt; is the set of all data points preceding &lt;code&gt;i&lt;/code&gt; in that permutation. In words, it&amp;apos;s the average improvement in performance gained by adding &lt;code&gt;z_i&lt;/code&gt; to a random subset of data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Assumptions&lt;/strong&gt;&lt;br&gt; Data-Shapley&amp;apos;s main assumption is the choice of a meaningful utility function &lt;code&gt;U&lt;/code&gt;. This could be validation accuracy, log-likelihood, or some other performance metric. The &amp;quot;fairness&amp;quot; of the attribution is only as good as the utility function is representative of the desired model behavior.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Computation&lt;/strong&gt;&lt;br&gt; Computing exact Shapley values is computationally prohibitive, as it requires training the model on all &lt;code&gt;2^n&lt;/code&gt; subsets of the data. In practice, approximations are used. The most common is the Truncated Monte Carlo (TMC) estimation, which samples random permutations of the data and stops adding points once the marginal contributions stabilize. For specific models like k-Nearest Neighbors, efficient closed-form or near-closed-form variations like KNN-Shapley exist.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Strengths and Failure Modes&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Data-Shapley is founded on a solid, axiomatic basis (efficiency, symmetry, linearity, null player), making it a &amp;quot;fair&amp;quot; measure of contribution. It is particularly well-suited for dataset-level tasks like data valuation for monetization or identifying low-value points for dataset pruning.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Failure Modes:&lt;/strong&gt; The computational cost is its greatest weakness, making it impractical for large models and datasets without significant approximations. The results can have high variance due to the stochasticity of both model training and the sampling process. Furthermore, the choice of utility function can drastically change the resulting attributions.  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Opinion: Data-Shapley is the gold standard in theory but often a computational nightmare in practice. It is best reserved for situations where you need to make high-stakes decisions about dataset composition (e.g., pruning, valuation) and have the resources to invest in robust approximation. For routine prediction-level debugging, simpler methods are often more practical.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;Bridge to LLMs&lt;/h4&gt;&lt;p&gt;Applying these methods directly to large language model (LLM) pretraining is fraught with challenges. The sheer scale of the data, its often-proprietary nature, and the non-stationary training dynamics make full attribution analysis infeasible.&lt;/p&gt;&lt;p&gt;However, TDA is highly actionable and relevant for the &lt;strong&gt;fine-tuning&lt;/strong&gt; stages of LLMs (e.g., supervised fine-tuning (SFT) or instruction tuning). In this regime, the datasets are curated, accessible, and orders of magnitude smaller. Here, TDA can help us understand:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Which instruction-tuning examples are most responsible for a specific alignment behavior?  &lt;/li&gt;&lt;li&gt; Which documents in a domain-adaptation dataset are causing catastrophic forgetting of general abilities?  &lt;/li&gt;&lt;li&gt; Why does the model fail on a specific query after being fine-tuned on our data?  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The findings of Razeghi et al. (2022) provide the perfect motivation. We know that corpus-level statistics shape a model&amp;apos;s core capabilities. Fine-tuning is our chance to precisely perturb those capabilities. TDA is the microscope that lets us see which examples in our fine-tuning data are driving those changes, for better or for worse.&lt;/p&gt;&lt;h4&gt;Synthesis &amp;amp; Guidance&lt;/h4&gt;&lt;p&gt;With four families of methods, how should a practitioner choose?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;For quick, scalable debugging of specific predictions on deep models:&lt;/strong&gt; Start with &lt;strong&gt;TracIn&lt;/strong&gt;. It requires saving checkpoints but is empirically robust and sidesteps the theoretical pitfalls of Influence Functions in non-convex settings.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;For finding mislabeled data or when a local approximation is sufficient:&lt;/strong&gt; Use &lt;strong&gt;Influence Functions&lt;/strong&gt;, but be prepared to validate the results. The HVP-based implementations scale well.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;If your model has a linear readout and is trained with L2 regularization:&lt;/strong&gt;&lt;strong&gt;Representer Points&lt;/strong&gt; offer uniquely interpretable, signed attributions that can distinguish helpful from harmful examples.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;For dataset-level valuation, pruning, or making policy decisions about data subsets:&lt;/strong&gt; Use &lt;strong&gt;Data-Shapley&lt;/strong&gt; approximations. This is the most principled but also the most expensive option.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;A robust workflow often involves triangulation.&lt;/strong&gt; If TracIn and Influence Functions both flag the same training example as highly influential for a given error, your confidence in that attribution should be much higher. Before taking a drastic action like removing data, seek consensus across multiple methods. When reporting results, be transparent about your assumptions: which checkpoints were used for TracIn, what damping was used for Influence Functions, or which utility function was chosen for Shapley.&lt;/p&gt;&lt;h4&gt;Limits &amp;amp; Open Questions&lt;/h4&gt;&lt;p&gt;Training Data Attribution is an active and evolving field of research. Major open questions remain:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; How can we develop TDA methods that are truly feasible for foundation model pretraining corpora?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Memorization vs. Contribution:&lt;/strong&gt; What is the precise relationship between an example being influential and being memorized? An example can teach a generalizable concept without being memorized, and a memorized example might have little influence on other predictions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Connection to Unlearning:&lt;/strong&gt; Can we leverage TDA to perform more efficient and effective &amp;quot;machine unlearning,&amp;quot; where the goal is to surgically remove a model&amp;apos;s knowledge of specific data points for privacy or safety reasons?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Complex Objectives:&lt;/strong&gt; How do we perform attribution in more complex training pipelines like Reinforcement Learning from Human Feedback (RLHF), where the &amp;quot;loss function&amp;quot; is itself a learned reward model?  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;TDA is more than an academic exercise; it is a fundamental tool for building more reliable, transparent, and debuggable machine learning systems. By connecting model behavior back to the data it learned from, we take a critical step from opaque prediction engines to understandable and trustworthy tools.&lt;/p&gt;&lt;h4&gt;References&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Ghorbani, A., &amp;amp; Zou, J. (2019). &amp;quot;Data-Shapley: Equitable Valuation of Data for Machine Learning&amp;quot;. &lt;em&gt;Proceedings of the 36th International Conference on Machine Learning (ICML)&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Jia, R., et al. (2019). &amp;quot;Towards Efficient Data Valuation Based on the Shapley Value&amp;quot;. &lt;em&gt;Proceedings of the 22nd International Conference on Artificial Intelligence and Statistics (AISTATS)&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Koh, P. W., &amp;amp; Liang, P. (2017). &amp;quot;Understanding Black-box Predictions via Influence Functions&amp;quot;. &lt;em&gt;Proceedings of the 34th International Conference on Machine Learning (ICML)&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Pruthi, G., et al. (2020). &amp;quot;TracIn: An Influence-based Approach for Tracing Training Data&amp;quot;. &lt;em&gt;Advances in Neural Information Processing Systems (NeurIPS)&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Razeghi, Y., et al. (2022). &amp;quot;Impact of Pretraining Term Frequencies on Few-Shot Numerical Reasoning&amp;quot;. &lt;em&gt;Findings of the Association for Computational Linguistics: EMNLP 2022&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Tenney, I. (2023). &amp;quot;A Hitchhiker&amp;apos;s Guide to Training Data Attribution&amp;quot;.  &lt;/li&gt;&lt;li&gt; Yeh, C.-K., et al. (2018). &amp;quot;Representer Point Selection for Explaining Deep Neural Networks&amp;quot;. &lt;em&gt;Advances in Neural Information Processing Systems (NeurIPS)&lt;/em&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/training-data-attribution-corroborative-methods/&quot;&gt;Training Data Attribution: Corroborative Methods&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Beyond the Leaderboard: New Frameworks for Understanding LLM Behavior</title>
      <link>https://nayanachandrika99.github.io/posts/beyond-the-leaderboard-new-frameworks-for-understanding-llm-behavior/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/beyond-the-leaderboard-new-frameworks-for-understanding-llm-behavior/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/beyond-the-leaderboard-new-frameworks-for-understanding-llm-behavior/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt; Notes from the McCoy, R. T., et al. (2024). &amp;quot;&lt;em&gt;Embers of autoregression &lt;/em&gt;&amp;quot; &lt;a href=&quot;https://doi.org/10.1073/pnas.2322420121&quot; target=&quot;_blank&quot;&gt;[Link&lt;/a&gt;] and Holtzman, A., et al. (2025) and &amp;quot;&lt;em&gt;Generative Models as a Complex Systems Science&lt;/em&gt;&amp;#x201d;. &lt;a href=&quot;https://www.sciopen.com/article/pdf/10.23919/JSC.2025.0009.pdf?ifPreview=0&quot; target=&quot;_blank&quot;&gt;[Link]&lt;/a&gt;&lt;p&gt;TL;DR&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Standard benchmarks tell us &lt;em&gt;what&lt;/em&gt; an LLM can do, but not &lt;em&gt;why&lt;/em&gt; it succeeds or fails. This limits our ability to reliably improve models and anticipate failures.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Framework 1 (Teleological Diagnostics):&lt;/strong&gt; McCoy et al. (PNAS 2024) propose a diagnostic framework. Since LLMs are trained to maximize the log-likelihood of text, their accuracy on a task empirically co-varies with: (i) the probability of the task itself, (ii) the probability of the correct output string, and (iii) the probability of the input string.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Framework 2 (Complex Systems Science):&lt;/strong&gt; Holtzman et al. (JSC 2025) argue for treating LLMs as complex systems with &lt;em&gt;emergent behaviors&lt;/em&gt; (abilities not explicitly programmed). This reframes benchmarks as experimental probes for discovering and mapping these hidden capabilities.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Synthesis:&lt;/strong&gt; These frameworks are complementary. The &lt;strong&gt;Complex Systems&lt;/strong&gt; approach is for &lt;em&gt;discovery&lt;/em&gt; (mapping what a model can do in general), while the &lt;strong&gt;Teleological&lt;/strong&gt; approach is for &lt;em&gt;diagnostics&lt;/em&gt; (explaining why a specific task failed).  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;1. Introduction: The Limits of Single-Score Evaluations&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Current LLM evaluation is dominated by performance on benchmarks like MMLU or HumanEval. While these leaderboards provide a useful snapshot of capabilities, they collapse a model&amp;apos;s complex behavior into a single score. This post explores two recent papers that offer frameworks for a deeper analysis, moving from &amp;quot;what score did it get?&amp;quot; to &amp;quot;why does it behave this way?&amp;quot;.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;2. Framework 1: A Diagnostic Toolkit for LLM Failures (McCoy et al., 2024)&lt;/strong&gt;&lt;/h4&gt;&lt;h4&gt;&lt;strong&gt;2.1 Core Concept: Aligning Tasks with the Training Objective&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;LLMs are fundamentally sequence models trained to optimize the next-token log-likelihood, &lt;code&gt;log p&amp;#x3b8;(output|input)&lt;/code&gt;. McCoy et al. (PNAS 2024) propose that a model&amp;apos;s performance on a downstream task is strongly influenced by how well that task aligns with this core objective. They use the analogy of &lt;strong&gt;human back pain&lt;/strong&gt;: our spines are optimized for quadrupedalism, and the mismatch with bipedalism causes issues. Similarly, an LLM fails when a task creates a mismatch with its core training of predicting common text sequences.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;2.2 Three Diagnostic Factors&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;The paper identifies three key empirical factors that correlate with model accuracy:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Task Probability &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;P(task)&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; The frequency of the task in the training data.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Output Probability &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;P(output)&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; The prior probability of the correct output string.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Input Probability &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;P(input)&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; The probability of the input string.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These are not used by the model internally but are powerful external diagnostics for predicting success or failure.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;2.3 A Toy Example: The Shift Cipher&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;This directly relates to the &amp;quot;shift cipher&amp;quot; analyses in McCoy et al. They find models are far better at ROT13 than other shifts (e.g., ROT12). This is not because the model &amp;quot;understands&amp;quot; modular arithmetic better for the number 13, but because ROT13 is a common topic in web text (high &lt;code&gt;P(task)&lt;/code&gt;), while ROT12 is virtually nonexistent (low &lt;code&gt;P(task)&lt;/code&gt;).&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;2.4 Schematic: Accuracy Correlates with Output Probability&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;The original paper demonstrates a strong relationship between &lt;code&gt;P(output)&lt;/code&gt; and model accuracy. We can illustrate this conceptually.&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/e6dfbbdf-a586-48eb-aa20-e7bf427229ee/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/e6dfbbdf-a586-48eb-aa20-e7bf427229ee/image.webp&quot; alt=&quot;This schematic shows that model accuracy on a task tends to increase as the log-probability of the correct output string increases. For detailed methodology, see the logistic regression analyses in the original paper.&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;This schematic shows that model accuracy on a task tends to increase as the log-probability of the correct output string increases. For detailed methodology, see the logistic regression analyses in the original paper.&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;&lt;strong&gt;2.5 Code Demo: Measuring &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;P(output)&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; with GPT-2&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;This snippet demonstrates how a sentence with a common proper noun (&amp;quot;Instagram&amp;quot;) is more &amp;quot;probable&amp;quot; to a model than one with a rare, fictional name (&amp;quot;Annathon&amp;quot;).&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; torch&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; transformers &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; GPT2LMHeadModel, GPT2Tokenizer&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# --- Setup &amp;amp; Reproducibility ---&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;torch.manual_seed(&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;tokenizer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; GPT2Tokenizer.from_pretrained(&lt;/span&gt;&lt;span&gt;&amp;apos;gpt2&amp;apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; GPT2LMHeadModel.from_pretrained(&lt;/span&gt;&lt;span&gt;&amp;apos;gpt2&amp;apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;model.eval() &lt;/span&gt;&lt;span&gt;# Set model to evaluation mode&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# --- Prepare Batched Inputs ---&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sentences &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;First, she just posted to her Instagram story.&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;# High P(output)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;Sorry, Annathon writes to our Copyright Users.&amp;quot;&lt;/span&gt;&lt;span&gt; # Low P(output)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;inputs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokenizer(sentences, &lt;/span&gt;&lt;span&gt;padding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;return_tensors&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;apos;pt&amp;apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;input_ids &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; inputs.input_ids&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;attention_mask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; inputs.attention_mask&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# --- Calculate Loss (Negative Log Likelihood) ---&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; torch.no_grad():&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    outputs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; model(&lt;/span&gt;&lt;span&gt;input_ids&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;input_ids, &lt;/span&gt;&lt;span&gt;attention_mask&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;attention_mask, &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;input_ids)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # The model&amp;apos;s loss is the cross-entropy loss, which is the negative log-likelihood (NLL)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # averaged over the tokens.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    nll &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; outputs.loss&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# We need to compute per-sequence loss as the batch-level loss is an average.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;per_sequence_nll &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt;(input_ids.shape[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    seq_ids &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; input_ids[i, :]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    seq_labels &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; input_ids[i, :]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    with&lt;/span&gt;&lt;span&gt; torch.no_grad():&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        loss &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; model(&lt;/span&gt;&lt;span&gt;input_ids&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;seq_ids.unsqueeze(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;seq_labels.unsqueeze(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)).loss&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        per_sequence_nll.append(loss.item())&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# --- Print Results with Precise Metrics ---&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;seq_lengths &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [torch.sum(mask).item() &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; mask &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; attention_mask]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;Sentence 1: &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;{sentences[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]}\&lt;/span&gt;&lt;span&gt;\&amp;quot;&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;  Sequence Length: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;seq_lengths[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;  Avg Token NLL: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;per_sequence_nll[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;:.4f}\\&lt;/span&gt;&lt;span&gt;n&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;Sentence 2: &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;{sentences[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]}\&lt;/span&gt;&lt;span&gt;\&amp;quot;&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;  Sequence Length: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;seq_lengths[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;  Avg Token NLL: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;per_sequence_nll[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;:.4f}\\&lt;/span&gt;&lt;span&gt;n&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;Note on Tokenization: The lower probability for &amp;apos;Annathon&amp;apos; is also affected by its&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;tokenization into subwords (&amp;apos;Ann&amp;apos;, &amp;apos;ath&amp;apos;, &amp;apos;on&amp;apos;), which can be less probable&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;quot;as a sequence than the single token for &amp;apos;Instagram&amp;apos;.&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;3. Framework 2: LLMs as a Complex Systems Science (Holtzman et al., 2025)&lt;/strong&gt;&lt;/h4&gt;&lt;h4&gt;&lt;strong&gt;3.1 Key Concept: Emergence in LLMs&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Holtzman et al. (JSC 2025) argue we should treat LLMs as &lt;strong&gt;complex systems&lt;/strong&gt;. In such systems, simple, local interactions between many components (neurons) give rise to complex, global behaviors (&lt;strong&gt;emergence&lt;/strong&gt;) that are not explicitly designed. For LLMs, abilities like chain-of-thought reasoning are emergent.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;3.2 A New Role for Benchmarks: Experimental Probes&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;This framework reframes benchmarks not as final exams, but as &lt;strong&gt;building blocks for experiments&lt;/strong&gt;. The goal shifts from measuring a score to probing for the presence of a specific emergent behavior across diverse contexts.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;3.3 An Example Probe: Testing for Deception&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;While Holtzman et al. provide the high-level framework, a concrete example of this&amp;#xa0;&lt;em&gt;style&lt;/em&gt;&amp;#xa0;of analysis can be seen in other research. For instance, work on steganography (hiding messages) asks if a model can perform a task while also hiding information within its answer.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Attribution Note:&lt;/strong&gt;&amp;#xa0;The specific &amp;quot;steganography + GLUE&amp;quot; test is a hypothetical experiment inspired by the complex systems framework and real experiments from labs like Redwood Research (e.g., &amp;quot;Preventing Language Models from Hiding Their Reasoning,&amp;quot; 2024).&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;3.4 Code Demo: Structuring a Behavioral Probe&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;This code doesn&amp;apos;t run a real probe but outlines the&amp;#xa0;&lt;em&gt;experimental structure&lt;/em&gt;&amp;#xa0;for testing an emergent skill like &amp;quot;topic adherence&amp;quot; across different benchmark tasks.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; run_benchmark_task&lt;/span&gt;&lt;span&gt;(model, task_prompt):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;Placeholder for running a single instance of a benchmark task.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # In reality, this would call an API or run a model forward pass&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;  Running task: &amp;apos;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;task_prompt[:&lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;...&amp;apos;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &amp;quot;This is the model&amp;apos;s answer.&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; check_emergent_behavior&lt;/span&gt;&lt;span&gt;(model_output, behavior_check_prompt):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;Placeholder for checking a behavior, e.g., using another LLM as a judge.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    # Example: Check if the output avoided a forbidden topic&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;  Checking behavior: &amp;apos;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;behavior_check_prompt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;apos;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &amp;quot;forbidden_topic&amp;quot;&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; model_output.lower()&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# --- Experimental Design ---&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; run_probe_on_benchmark&lt;/span&gt;&lt;span&gt;(model, benchmark_tasks, probe_behavior):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    Tests for an emergent behavior across a suite of benchmark tasks.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    Args:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        model: The LLM to test.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        benchmark_tasks: A list of prompts from a benchmark (e.g., MMLU, GLUE).&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        probe_behavior: A tuple containing the behavior instruction and the check.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;Starting probe for behavior: &amp;apos;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;probe_behavior[&lt;/span&gt;&lt;span&gt;&amp;apos;instruction&amp;apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;apos;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    successes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; task &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; benchmark_tasks:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        # Combine the task with the behavioral instruction&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        full_prompt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;probe_behavior[&lt;/span&gt;&lt;span&gt;&amp;apos;instruction&amp;apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}\n\n&lt;/span&gt;&lt;span&gt;Now, please complete the following task:&lt;/span&gt;&lt;span&gt;\n{&lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        output &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; run_benchmark_task(model, full_prompt)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; check_emergent_behavior(output, probe_behavior[&lt;/span&gt;&lt;span&gt;&amp;apos;check&amp;apos;&lt;/span&gt;&lt;span&gt;]):&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            successes &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;Result: Behavior was present in &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;successes&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(benchmark_tasks)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; tasks.&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; successes &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(benchmark_tasks)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# --- Define the Experiment ---&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;mock_mmlu_tasks &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&amp;quot;What is the capital of France?&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;quot;Solve for x: 2x + 3 = 7&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;topic_adherence_probe &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;instruction&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;quot;IMPORTANT: In your response, do not mention the word &amp;apos;politics&amp;apos;.&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &amp;quot;check&amp;quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;quot;Does the response contain the word &amp;apos;politics&amp;apos;?&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Run it&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;run_probe_on_benchmark(&lt;/span&gt;&lt;span&gt;&amp;quot;mock_model&amp;quot;&lt;/span&gt;&lt;span&gt;, mock_mmlu_tasks, topic_adherence_probe)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;4. Synthesis: The Analyst&amp;apos;s Toolkit&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;These two frameworks are not in conflict; they are complementary tools for a deeper analysis of LLMs.&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Teleological Approach (McCoy et al.)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Complex Systems Approach (Holtzman et al.)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Primary Goal&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Diagnose&lt;/strong&gt;&amp;#xa0;specific failures. &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Discover&lt;/strong&gt;&amp;#xa0;general, emergent capabilities. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Core Question&lt;/strong&gt;&lt;/td&gt;&lt;td&gt; &amp;quot;Why did the model fail&amp;#xa0;&lt;em&gt;this&lt;/em&gt;&amp;#xa0;time?&amp;quot; &lt;/td&gt;&lt;td&gt; &amp;quot;What hidden skills does this model possess?&amp;quot; &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Use of Benchmarks&lt;/strong&gt;&lt;/td&gt;&lt;td&gt; As a source of failure cases to analyze. &lt;/td&gt;&lt;td&gt; As building blocks for new experiments. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Direction&lt;/strong&gt;&lt;/td&gt;&lt;td&gt; Top-down (training objective -&amp;gt; behavior) &lt;/td&gt;&lt;td&gt; Bottom-up (simple interactions -&amp;gt; emergence) &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;An effective LLM analyst uses both. They use the complex systems approach to map the landscape of what a model&amp;#xa0;&lt;em&gt;can&lt;/em&gt;&amp;#xa0;do, and when they find a cliff where the model fails, they use the teleological approach to understand the geology of why that cliff exists.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;5. Conclusion &amp;amp; Final Takeaways&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Moving beyond leaderboards doesn&amp;apos;t mean abandoning measurement, but enriching it. By combining diagnostic and exploratory frameworks, we can build a more robust, reliable, and ultimately more useful science of large language models.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Takeaway 1:&lt;/strong&gt;&amp;#xa0;LLM failures are often not random but are systematic consequences of their training objective (the &amp;quot;back pain&amp;quot; analogy).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Takeaway 2:&lt;/strong&gt;&amp;#xa0;The probabilities&amp;#xa0;P(output)&amp;#xa0;and&amp;#xa0;P(task)&amp;#xa0;are powerful predictors of model performance on a given task.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Takeaway 3:&lt;/strong&gt;&amp;#xa0;LLMs should be viewed as complex systems that exhibit emergent behaviors not explicitly coded into them.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Takeaway 4:&lt;/strong&gt;&amp;#xa0;Benchmarks can be repurposed from &amp;quot;final exams&amp;quot; into &amp;quot;experimental probes&amp;quot; to test for these emergent behaviors.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Takeaway 5:&lt;/strong&gt;&amp;#xa0;A mature approach to LLM analysis requires both a&amp;#xa0;&lt;strong&gt;diagnostic&lt;/strong&gt;&amp;#xa0;toolkit (for explaining failures) and an&amp;#xa0;&lt;strong&gt;exploratory&lt;/strong&gt;&amp;#xa0;one (for discovering capabilities).  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; McCoy, R. T., et al. (2024). &amp;quot;Embers of autoregression show how large language models are shaped by the problem they are trained to solve.&amp;quot;&amp;#xa0;&lt;em&gt;Proceedings of the National Academy of Sciences&lt;/em&gt;. &lt;a href=&quot;https://doi.org/10.1073/pnas.2322420121&quot; target=&quot;_blank&quot;&gt;[Link]&lt;/a&gt;&lt;/li&gt;&lt;li&gt; Holtzman, A., et al. (2025). &amp;quot;Generative Models as a Complex Systems Science: How can we make sense of large language model behavior?&amp;quot;&amp;#xa0;&lt;em&gt;Journal of Social Computing&lt;/em&gt;. &lt;a href=&quot;https://www.sciopen.com/article/pdf/10.23919/JSC.2025.0009.pdf?ifPreview=0&quot; target=&quot;_blank&quot;&gt;[Link]&lt;/a&gt;&lt;/li&gt;&lt;li&gt; Milli&amp;#xe8;re, R., et al. (2024). &amp;quot;Preventing Language Models from Hiding Their Reasoning.&amp;quot;&amp;#xa0;&lt;em&gt;Redwood Research&lt;/em&gt;. &lt;a href=&quot;https://doi.org/10.48550/arXiv.2310.18512&quot; target=&quot;_blank&quot;&gt;[Link]&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Inner Workings of a Transformer: Interpretability Techniques</title>
      <link>https://nayanachandrika99.github.io/posts/inner-workings-of-a-transformer-interpretability-techniques/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/inner-workings-of-a-transformer-interpretability-techniques/</guid>
      <description>Notes on the “Primer on teh Inner workings of the Transformer based LMs”</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/inner-workings-of-a-transformer-interpretability-techniques/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;Notes on the &amp;#x201c;Primer on teh Inner workings of the Transformer based LMs&amp;#x201d;&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt; This is my deep dive into the sections 3 and 4 of&lt;a href=&quot;https://doi.org/10.48550/arXiv.2405.00208&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://doi.org/10.48550/arXiv.2405.00208&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;Ferrando et al.&amp;apos;s &amp;quot;A Primer on the Inner Workings of Transformer-based Language Models,&amp;quot; &lt;/em&gt;&lt;/a&gt;&lt;em&gt;which connects the modern interpretability techniques to the model&amp;apos;s fundamental additive architecture.&lt;/em&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This post is a detailed walkthrough of two key families of interpretability techniques-Behavior Localization and Information Decoding-as presented in Sections 3 and 4 of the paper. We explore how to pinpoint which parts of a Transformer are responsible for a given behavior and how to understand the information those parts represent.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The Key is the Architecture:&lt;/strong&gt; The Transformer&amp;apos;s core design-summing component outputs into a residual stream and then linearly projecting that sum to get logits-is not just an implementation detail. It&amp;apos;s the mathematical foundation that makes principled attribution and intervention possible.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Localization (Sec. 3):&lt;/strong&gt; Techniques like Direct Logit Attribution (DLA) and activation patching directly exploit the model&amp;apos;s additive structure. DLA accounts for how much each component &lt;em&gt;pushed&lt;/em&gt; the final answer, while patching causally tests a component&amp;apos;s importance by swapping its output.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Decoding (Sec. 4):&lt;/strong&gt; Techniques like probing, linear feature analysis, and Sparse Autoencoders (SAEs) investigate &lt;em&gt;what&lt;/em&gt; information is encoded in the model&amp;apos;s activations. These methods help us build hypotheses about what concepts the model has learned, which can then be causally verified with localization tools.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;A Unified Workflow:&lt;/strong&gt; We will frame these two sections as a coherent workflow: start with the model&amp;apos;s linear structure, use localization to find responsible components, use decoding to hypothesize what they represent, and iterate to build a grounded understanding of the model&amp;apos;s internal algorithm.  &lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;Bridge from Section 2: Why This All Works&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Before diving into specific techniques, we must ground our analysis in the mathematical structure of the Transformer, as laid out in Section 2 of the paper. At its heart, a Transformer is a sequence of layers, each containing attention and MLP components. Crucially, each of these components, which we can index with &lt;code&gt;c&lt;/code&gt;, computes an update vector &lt;code&gt;h_c&lt;/code&gt; and &lt;strong&gt;adds&lt;/strong&gt; it to the residual stream.&lt;/p&gt;&lt;p&gt;Let &lt;code&gt;r^(l)&lt;/code&gt; be the state of the residual stream at a specific token position after layer &lt;code&gt;l&lt;/code&gt;. The final residual stream state, &lt;code&gt;r_L&lt;/code&gt;, is the sum of the initial token/positional embedding and all subsequent component updates: &lt;br&gt;&lt;code&gt;r_L = r^(0) + h_{attn,0} + h_{mlp,0} + ... + h_{attn,L-1} + h_{mlp,L-1}&lt;/code&gt;&lt;/p&gt;&lt;p&gt;For simplicity, we can express this as a sum over all components &lt;code&gt;c&lt;/code&gt; in the computational graph: &lt;br&gt;&lt;code&gt;r_L = &amp;#x2211;_c h_c&lt;/code&gt;&lt;/p&gt;&lt;p&gt;The model then produces logits, &lt;code&gt;z&lt;/code&gt;, by applying a final linear transformation, the unembedding matrix &lt;code&gt;W_U&lt;/code&gt;. The logit for a specific token &lt;code&gt;y&lt;/code&gt;, denoted &lt;code&gt;z_y&lt;/code&gt;, is the dot product of the final residual stream with the corresponding token&amp;apos;s unembedding vector, &lt;code&gt;u_y&lt;/code&gt;: &lt;br&gt;&lt;code&gt;z_y = u_y^T r_L = u_y^T (&amp;#x2211;_c h_c)&lt;/code&gt;&lt;/p&gt;&lt;p&gt;By the linearity of the dot product, this expands to: &lt;br&gt;&lt;code&gt;z_y = &amp;#x2211;_c u_y^T h_c&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This simple equation is the bedrock of modern mechanistic interpretability. It tells us that the final logit for any token is just a &lt;strong&gt;linear sum of contributions from every component in the model&lt;/strong&gt;. This structure licenses two powerful modes of analysis :&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Additive Accounting:&lt;/strong&gt; We can directly measure the contribution of each component &lt;code&gt;c&lt;/code&gt; to the logit &lt;code&gt;z_y&lt;/code&gt; by calculating the term &lt;code&gt;u_y^T h_c&lt;/code&gt;. This is the core idea behind Direct Logit Attribution.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Targeted Causal Intervention:&lt;/strong&gt; We can test the causal importance of a component &lt;code&gt;c&lt;/code&gt; by manipulating its output vector &lt;code&gt;h_c&lt;/code&gt;. If we replace &lt;code&gt;h_c&lt;/code&gt; with a different vector &lt;code&gt;h_c~&lt;/code&gt; (from a counterfactual input), the change in the final logit &lt;code&gt;z_y&lt;/code&gt; will be precisely &lt;code&gt;u_y^T (h_c~ - h_c)&lt;/code&gt;, assuming no downstream components adapt to the change. This is the logic behind activation patching.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;With this architectural foundation in mind, we can now explore the techniques in Sections 3 and 4 as principled methods for exploiting this structure.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;Section 3: Behavior Localization - Finding Which Parts Matter&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Behavior localization aims to identify which parts of the model-from individual input tokens to specific attention heads or MLP neurons-are responsible for a given output. These methods are our primary tools for moving from &amp;quot;the model predicted token Y&amp;quot; to &amp;quot;the model predicted token Y &lt;em&gt;because&lt;/em&gt; head 7.3 attended to token X and FFN 12 modified the representation in this way.&amp;quot;&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;3.1. Input Attribution: Tracing Behavior Back to Tokens&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;The most basic localization question is: which input tokens were most influential for a given prediction?&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Gradient-based Attribution:&lt;/strong&gt;&lt;br&gt; A standard approach in machine learning is to use gradients. If we have a function &lt;code&gt;f(x)&lt;/code&gt; that maps an input &lt;code&gt;x&lt;/code&gt; to a scalar output of interest (like the logit &lt;code&gt;z_y&lt;/code&gt;), its first-order Taylor expansion &lt;code&gt;f(x + &amp;#x394;) &amp;#x2248; f(x) + &amp;#x2207;_x f(x)^T &amp;#x394;&lt;/code&gt; tells us that the gradient &lt;code&gt;&amp;#x2207;_x f(x)&lt;/code&gt; represents the sensitivity of the output to infinitesimal perturbations of the input. We can aggregate these sensitivities per input token embedding to get a saliency score.&lt;/p&gt;&lt;p&gt;A common variant is &lt;strong&gt;gradient&amp;#xb7;input&lt;/strong&gt;, which computes &lt;code&gt;&amp;#x2207;_x f(x) &amp;#x2299; x&lt;/code&gt;, where &lt;code&gt;&amp;#x2299;&lt;/code&gt; is the element-wise product. This approximates the contribution of each input feature to the final output score. However, raw gradients can be noisy and suffer from saturation. Techniques like &lt;strong&gt;SmoothGrad&lt;/strong&gt; (averaging gradients over noisy inputs) and &lt;strong&gt;Integrated Gradients&lt;/strong&gt; (integrating gradients along a path from a baseline input to the actual input) provide more robust estimates by mitigating local noise and accounting for non-linearities along the integration path .&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Intuition: Gradient attribution asks, &amp;quot;If I could wiggle each input token embedding a tiny bit, which wiggles would have the biggest impact on the final logit?&amp;quot; It&amp;apos;s a measure of local sensitivity.  &lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Attention Weights vs. Context Mixing:&lt;/strong&gt;&lt;br&gt; It is tempting to use attention weights as a direct measure of input importance. However, this is often misleading. An attention head could place a high weight on a token, but if the &lt;em&gt;value&lt;/em&gt; vector produced at that token position is near-zero or orthogonal to what the rest of the model cares about, its actual influence is negligible. The weights only tell you where the information is being routed &lt;em&gt;from&lt;/em&gt;, not what that information &lt;em&gt;is&lt;/em&gt; or how it&amp;apos;s used .&lt;/p&gt;&lt;p&gt;More faithful methods account for the full information flow through the attention head&amp;apos;s OV-circuit (the part that computes &lt;code&gt;Attention(Q,K)V&lt;/code&gt;). This can involve weighting attention scores by the norm of the value vectors or using more complex rollout algorithms that recursively trace influence through multiple layers. However, simple rollout techniques can misattribute importance by assuming linearity where it doesn&amp;apos;t hold .&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Contrastive Attribution:&lt;/strong&gt;&lt;br&gt; Models don&amp;apos;t just predict a token &lt;code&gt;y&lt;/code&gt; in a vacuum; they predict it &lt;em&gt;instead of&lt;/em&gt; other tokens. The softmax function amplifies differences between logits. Therefore, a more meaningful attribution target is often the &lt;strong&gt;logit difference&lt;/strong&gt;, &lt;code&gt;z_y - z_y~&lt;/code&gt;, where &lt;code&gt;y~&lt;/code&gt; is a plausible alternative token. By attributing this difference, we ask, &amp;quot;Which inputs caused the model to favor &lt;code&gt;y&lt;/code&gt; over &lt;code&gt;y~&lt;/code&gt;?&amp;quot; This aligns our analysis with the competitive nature of the model&amp;apos;s output layer.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Caveat: Input attribution methods, especially gradient-based ones, provide a correlational signal, not a strictly causal one. They can be insensitive to causal model components, disagree with each other, and fail when perturbations push the model into out-of-distribution territory where its behavior is unpredictable. They are best used as a first-pass tool for hypothesis generation .  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;&lt;strong&gt;3.2. Model Component Importance: Pinpointing Heads and Neurons&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;While input attribution is useful, the core of mechanistic interpretability lies in understanding the internal components. Here, we leverage the Section 2 decomposition to its fullest.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Direct Logit Attribution (DLA):&lt;/strong&gt;&lt;br&gt; As derived earlier, the total logit &lt;code&gt;z_y&lt;/code&gt; is a sum of contributions from all components: &lt;code&gt;z_y = &amp;#x2211;_c u_y^T h_c&lt;/code&gt;. The term &lt;code&gt;u_y^T h_c&lt;/code&gt; is the &lt;strong&gt;Direct Logit Attribution (DLA)&lt;/strong&gt; of component &lt;code&gt;c&lt;/code&gt; to token &lt;code&gt;y&lt;/code&gt;. It is the projection of the component&amp;apos;s output vector &lt;code&gt;h_c&lt;/code&gt; onto the unembedding direction &lt;code&gt;u_y&lt;/code&gt; for that token. A large positive DLA means the component directly &amp;quot;pushed up&amp;quot; the logit for &lt;code&gt;y&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Just as with input attribution, a contrastive analysis is often more powerful. The &lt;strong&gt;Contrastive Direct Logit Attribution&lt;/strong&gt; for &lt;code&gt;y&lt;/code&gt; vs. &lt;code&gt;y~&lt;/code&gt; is: &lt;br&gt;&lt;code&gt;DLDA_c(y, y~) = u_y^T h_c - u_y~^T h_c = (u_y - u_y~)^T h_c&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This measures how much component &lt;code&gt;c&lt;/code&gt; pushed the logits in favor of &lt;code&gt;y&lt;/code&gt; over &lt;code&gt;y~&lt;/code&gt;. Because this method is a direct calculation based on the model&amp;apos;s forward pass, it is computationally cheap and provides a complete, additive decomposition of the logits .&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Intuition: DLA is the ultimate accounting tool. It opens up the final sum &amp;#x2211;_c h_c and shows you exactly how much each h_c term contributed to the final answer along the u_y direction.  &lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Causal Interventions via Activation Patching:&lt;/strong&gt;&lt;br&gt; DLA tells us about correlations in a single forward pass. To establish causality, we need to perform interventions. &lt;strong&gt;Activation patching&lt;/strong&gt; is the canonical technique for this. The methodology is as follows:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Clean Run:&lt;/strong&gt; Run the model on a &amp;quot;clean&amp;quot; input where it exhibits the behavior of interest (e.g., correctly translating a sentence). Store the internal activations at various points (e.g., the output of a specific attention head).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Corrupted Run:&lt;/strong&gt; Run the model on a &amp;quot;corrupted&amp;quot; or counterfactual input where it does &lt;em&gt;not&lt;/em&gt; exhibit the behavior (e.g., an input that would lead to a mistranslation).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Patching Run:&lt;/strong&gt; Rerun the model on the &lt;em&gt;corrupted&lt;/em&gt; input, but at a specific component &lt;code&gt;c&lt;/code&gt;, intervene and replace its activation &lt;code&gt;a_c&lt;/code&gt; with the activation stored from the &lt;em&gt;clean&lt;/em&gt; run.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Measure the Effect:&lt;/strong&gt; Observe the change &lt;code&gt;&amp;#x394;&lt;/code&gt; in the final output metric (e.g., the logit difference between the correct and incorrect translation). If &lt;code&gt;&amp;#x394;&lt;/code&gt; is large, meaning the behavior is restored, then component &lt;code&gt;c&lt;/code&gt; is causally implicated in the behavior.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;The core intervention is &lt;code&gt;a &amp;lt;- a~&lt;/code&gt;, where &lt;code&gt;a&lt;/code&gt; is the activation on the corrupted run and &lt;code&gt;a~&lt;/code&gt; is from the clean run. A large &lt;code&gt;&amp;#x394;&lt;/code&gt; on a metric &lt;code&gt;S&lt;/code&gt; means the component is causally important for that metric.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Caveat: The choice of corrupted/counterfactual input is critical and can significantly affect the results. An overly simplistic ablation (e.g., zero-ablating a component) might push downstream components into an out-of-distribution regime, causing them to behave erratically. This concept, termed ecological validity, emphasizes the importance of using counterfactuals that keep the model in-distribution . Moreover, models can exhibit self-repair, where downstream components compensate for a patched or ablated component, masking its true importance.  &lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Subspace Activation Patching and Distributed Interchange Intervention (DII):&lt;/strong&gt;&lt;br&gt; Often, a feature or concept is not represented by a single neuron but by a &lt;em&gt;direction&lt;/em&gt; or &lt;em&gt;subspace&lt;/em&gt; within the high-dimensional activation space. Instead of patching an entire activation vector &lt;code&gt;a&lt;/code&gt;, we can perform a more surgical intervention.&lt;/p&gt;&lt;p&gt;Given a subspace &lt;code&gt;S&lt;/code&gt; (e.g., spanned by a single direction vector &lt;code&gt;v&lt;/code&gt;), we can define a projector &lt;code&gt;P_S&lt;/code&gt; onto that subspace. A subspace patch replaces only the part of the activation that lies in &lt;code&gt;S&lt;/code&gt;: &lt;br&gt;&lt;code&gt;a_patched = P_S a~ + (I - P_S) a&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Here, we take the feature from the clean run (&lt;code&gt;a~&lt;/code&gt;) and inject it into the corrupted run (&lt;code&gt;a&lt;/code&gt;) while preserving everything in the orthogonal complement of &lt;code&gt;S&lt;/code&gt;. This approach, also known as &lt;strong&gt;Distributed Interchange Intervention (DII)&lt;/strong&gt;, allows us to test the causal role of specific, distributed features rather than entire activation vectors . This is a powerful refinement of patching that aligns with the hypothesis that features are linear.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Finding Circuits: Edge and Path Patching:&lt;/strong&gt;&lt;br&gt; Components don&amp;apos;t just act independently; they form circuits. An early attention head might extract information that a later MLP processes. &lt;strong&gt;Edge patching&lt;/strong&gt; extends activation patching to test the importance of the connection &lt;em&gt;between&lt;/em&gt; two components. For example, to test the &lt;code&gt;A -&amp;gt; B&lt;/code&gt; connection, one would patch the output of &lt;code&gt;A&lt;/code&gt; only as it is read by &lt;code&gt;B&lt;/code&gt;, leaving other components that read from &lt;code&gt;A&lt;/code&gt; unaffected.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Path patching&lt;/strong&gt; generalizes this to entire chains of components. However, the number of possible paths is combinatorially explosive. This has led to automated search algorithms like &lt;strong&gt;ACDC&lt;/strong&gt; (Automatic Circuit Dissection and Correspondence) that greedily prune edges from the computational graph. Other methods like &lt;strong&gt;Edge Attribution Patching (EAP)&lt;/strong&gt; use gradient-based approximations to estimate the effect of patching all possible edges more efficiently, though these linearized estimates can fail in the presence of strong non-linearities .&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;Section 4: Information Decoding - Discovering What is Represented&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;While Section 3 focuses on &lt;em&gt;where&lt;/em&gt; a computation happens, Section 4 focuses on &lt;em&gt;what&lt;/em&gt; information is being computed and represented. These techniques aim to put meaningful labels on the vectors and subspaces within the model.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;4.1. Probing&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;A &lt;strong&gt;probe&lt;/strong&gt; is a simple, supervised model (often linear) trained to predict a property of interest (e.g., part-of-speech, syntactic depth, presence of a specific concept) from a Transformer&amp;apos;s internal activations. If a probe can be trained to high accuracy on the activations &lt;code&gt;r^(l)&lt;/code&gt; at layer &lt;code&gt;l&lt;/code&gt;, it suggests that information about that property is linearly decodable from the residual stream at that layer.&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Caveat: This is a crucial distinction: a successful probe demonstrates that information is encoded, not necessarily that it is used by the model to solve its primary task. A powerful probe might simply be learning a complex function that the main model has no need for. To mitigate this, researchers use controls (e.g., training a probe on randomized labels to see what accuracy it can achieve by chance) and apply principles like Minimum Description Length (MDL) to penalize probe complexity, ensuring the discovered correlation is not spurious . A probe gives you a hypothesis; Section 3 tools are needed to test it.  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;&lt;strong&gt;4.2. The Linear Representation Hypothesis and Sparse Autoencoders (SAEs)&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;A central idea in modern interpretability is the &lt;strong&gt;linear representation hypothesis&lt;/strong&gt;: many concepts and features the model learns are represented as directions or subspaces in activation space. This hypothesis is powerful because it connects directly to the linear readout mechanism from Section 2. If a concept &lt;code&gt;C&lt;/code&gt; is represented by a direction &lt;code&gt;v_C&lt;/code&gt;, then adding &lt;code&gt;&amp;#x3b1; * v_C&lt;/code&gt; to the residual stream should predictably shift the model&amp;apos;s behavior towards expressing concept &lt;code&gt;C&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This enables two key interventions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Erasure:&lt;/strong&gt; To test if a feature is necessary for a behavior (e.g., stereotypical gender bias), we can identify its direction &lt;code&gt;v&lt;/code&gt; and project it out of the residual stream: &lt;code&gt;r_new = r - P_v r&lt;/code&gt;. If the behavior disappears, the feature was causally necessary.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Steering:&lt;/strong&gt; We can guide model generation at inference time by adding a feature vector to the residual stream, &lt;code&gt;r_new = r + &amp;#x3b1; * v&lt;/code&gt;, effectively &amp;quot;steering&amp;quot; the model towards a desired attribute or style.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The surprising effectiveness of these simple linear edits provides strong evidence for the linear representation hypothesis .&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Unmixing Features with Sparse Autoencoders (SAEs):&lt;/strong&gt;&lt;br&gt; A major challenge is that neurons are often &lt;strong&gt;polysemantic&lt;/strong&gt;, meaning a single neuron activates for multiple, unrelated concepts. Furthermore, models may use &lt;strong&gt;superposition&lt;/strong&gt; to represent more features than they have dimensions by storing them in a non-orthogonal basis.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Sparse Autoencoders (SAEs)&lt;/strong&gt; are an unsupervised technique designed to tackle this. An SAE is a simple neural network trained to reconstruct a model&amp;apos;s internal activations (e.g., &lt;code&gt;r^(l)&lt;/code&gt;) from a much wider, &amp;quot;overcomplete&amp;quot; set of latent features. The key is a strong sparsity penalty (&lt;code&gt;L1&lt;/code&gt;) on the latent activations. The objective is: &lt;br&gt;&lt;code&gt;min_W,&amp;#x3b8; E[ ||r - W^T s||_2^2 + &amp;#x3bb;||s||_1 ]&lt;/code&gt;&lt;br&gt; where &lt;code&gt;s = &amp;#x3c3;_&amp;#x3b8;(r)&lt;/code&gt; is the sparse code produced by the encoder, &lt;code&gt;W&lt;/code&gt; is the decoder dictionary, and &lt;code&gt;&amp;#x3bb;&lt;/code&gt; is the sparsity coefficient.&lt;/p&gt;&lt;p&gt;The columns of &lt;code&gt;W&lt;/code&gt; represent learned &amp;quot;features.&amp;quot; Because the code &lt;code&gt;s&lt;/code&gt; is forced to be sparse, each activation &lt;code&gt;r&lt;/code&gt; is reconstructed using only a few active features from this dictionary. These SAE features are often much more monosemantic (interpretable) than the original neuron basis. Once an SAE is trained, we can analyze its features using the full suite of Section 3 tools: calculate their DLA, patch them, and search for the inputs that maximally activate them .&lt;/p&gt;&lt;blockquote&gt;&lt;div&gt; Intuition: An SAE acts like a prism for activation vectors. It takes a dense, tangled vector where many features are superimposed and splits it into a sparse set of clean, interpretable feature directions.  &lt;/div&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;div&gt; Caveat: SAEs are not perfect. There is a trade-off between reconstruction fidelity and sparsity. Furthermore, the reconstruction error r - W^T s itself can contain important information, and causal analyses must account for it. Newer architectures like Gated SAEs improve this trade-off by separating the detection of a feature from its magnitude, leading to better fidelity for a given level of sparsity .  &lt;/div&gt;&lt;/blockquote&gt;&lt;h4&gt;&lt;strong&gt;4.3. Decoding via the Vocabulary Interface&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;The unembedding matrix &lt;code&gt;W_U&lt;/code&gt; is the bridge between the model&amp;apos;s internal representation space and the human-readable vocabulary space. We can leverage this bridge to decode the content of internal states.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Logit Lens:&lt;/strong&gt; The simplest application is the &lt;strong&gt;logit lens&lt;/strong&gt;, where we apply the unembedding matrix directly to an intermediate residual stream &lt;code&gt;r^(l)&lt;/code&gt; to get a premature prediction: &lt;code&gt;z^(l) = W_U^T r^(l)&lt;/code&gt;. This can reveal how the model&amp;apos;s prediction evolves layer by layer. However, raw logit lenses often produce poor results because the distribution of &lt;code&gt;r^(l)&lt;/code&gt; can be very different from that of the final residual stream &lt;code&gt;r_L&lt;/code&gt;. &lt;strong&gt;Tuned lenses&lt;/strong&gt; learn a simple affine transformation to correct for this distributional shift .  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Patchscopes:&lt;/strong&gt; This technique combines patching (Section 3) with a readout mechanism. The core idea is to patch an activation from a source context into a target context and then immediately decode it using a readout function (like the logit lens) to see what information it contains &lt;em&gt;in that new context&lt;/em&gt;. This provides a more controlled way to understand what an activation vector &amp;quot;means&amp;quot; .  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Logit Spectroscopy and SVD Views:&lt;/strong&gt; We can analyze the linear operators within the model (like FFN weight matrices) by understanding their effect on the vocabulary. By taking the Singular Value Decomposition (SVD) of a weight matrix, we can find the input directions it amplifies the most. Projecting these directions into vocabulary space via &lt;code&gt;W_U&lt;/code&gt; reveals which tokens or concepts the matrix is most sensitive to. This &amp;quot;logit spectroscopy&amp;quot; helps characterize the function of entire MLP layers or attention heads .  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Maximally Activating Inputs:&lt;/strong&gt; A straightforward way to understand a component (like a neuron or an SAE feature) is to search through a large dataset for the input examples that cause it to activate most strongly. This provides concrete examples of what the feature responds to. A riskier alternative is to use optimization to synthesize an input that maximizes the activation, but this can produce unnatural, &amp;quot;frankenstein&amp;quot; inputs that exploit adversarial patterns, leading to interpretability illusions .  &lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;div&gt; Opinion: The ability of modern language models to generate their own natural language explanations for their features is tantalizing. However, as the primer correctly warns, these explanations should be treated as hypotheses, not ground truth. A model might generate a plausible-sounding but completely unfaithful explanation. Such claims must be rigorously tested using the causal intervention methods from Section 3.  &lt;/div&gt;&lt;/blockquote&gt;&lt;hr&gt;&lt;h4&gt;&lt;strong&gt;Synthesis: A Coherent Workflow for Understanding Transformers&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Sections 3 and 4 of the primer are not just a catalog of methods; they describe a unified, iterative process for reverse-engineering the algorithms learned by Transformers. The workflow, grounded in the Section 2 decomposition, looks like this:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Start with the Additive Structure (Sec. 2):&lt;/strong&gt; The &lt;code&gt;z_y = &amp;#x2211;_c u_y^T h_c&lt;/code&gt; equation is your entry point. It guarantees that the problem of understanding a logit can be broken down into understanding the contributions of individual components.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Localize with DLA and Patching (Sec. 3):&lt;/strong&gt; For a specific behavior, use DLA to get a cheap, comprehensive ranking of which components contributed most to the outcome. Formulate a hypothesis (e.g., &amp;quot;Head 8.4 is responsible for copying the name&amp;quot;). Then, use activation patching to causally test this hypothesis. Does patching head 8.4&amp;apos;s output from a clean run restore the correct name? If so, your hypothesis is supported.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Decode with Probes and SAEs (Sec. 4):&lt;/strong&gt; Now that you&amp;apos;ve identified a causally important component, ask what information it represents. Is there a specific concept it seems to encode? Train a probe to see if that concept is linearly decodable. If the component&amp;apos;s activations seem polysemantic, train an SAE on them to disentangle the underlying features.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Refine and Verify (Iterate):&lt;/strong&gt; The features discovered by your SAE in step 3 are now new hypotheses. Re-enter the Section 3 workflow: calculate the DLA of these SAE features. Perform subspace patching on a single SAE feature direction to confirm its causal role. Use maximally activating examples to name the feature.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This loop-from localization to decoding and back to causal verification-allows us to move from coarse-grained observations about model behavior to fine-grained, validated claims about the specific algorithms the model has learned.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;Limits and Open Questions&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Despite this progress, significant challenges remain. The primer highlights several critical limitations that practitioners must keep in mind:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The Attention-as-Explanation Fallacy:&lt;/strong&gt; Raw attention weights are not explanations. Always consider the full OV-circuit.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Distribution Shift in Patching:&lt;/strong&gt; Causal interventions can be misleading if they push the model into out-of-distribution states. Careful design of counterfactuals is paramount.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Probe Faithfulness:&lt;/strong&gt; Probes can discover spurious correlations. Their findings must be validated with causal methods.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;SAE Quality:&lt;/strong&gt; The utility of SAEs depends on finding the right balance of sparsity and reconstruction fidelity, and their learned features are not guaranteed to be perfectly monosemantic.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Limits of Linearity:&lt;/strong&gt; While the linear representation hypothesis has been incredibly fruitful, models are fundamentally non-linear. Linearized methods like EAP can fail, and complex reasoning may involve non-linear interactions between features that are difficult to analyze.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Part 4 Reverse Engineering Transformers: Induction Heads</title>
      <link>https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt; Final part in the series: &lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt;&lt;span&gt;Part 1: Reverse Engineering Transformers: Deconstructing Attention&lt;/span&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt;&lt;span&gt;Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot;&gt;&lt;span&gt;Part 3 Reverse Engineering Transformers: Path Expansion&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;Transformer Circuits VI: Induction Heads&lt;/h2&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Induction heads are a two-head circuit that implements a simple but powerful form of in-context learning: repeating sequences.  &lt;/li&gt;&lt;li&gt; The circuit&amp;apos;s algorithm is: &amp;quot;find a previous occurrence of the current token, attend to the token &lt;em&gt;after&lt;/em&gt; it, and copy that token&amp;apos;s identity to the current position.&amp;quot;  &lt;/li&gt;&lt;li&gt; This is implemented via &lt;strong&gt;K-composition&lt;/strong&gt; with a &lt;strong&gt;previous token head&lt;/strong&gt; from an earlier layer.  &lt;/li&gt;&lt;li&gt; The previous token head shifts information, making a token&amp;apos;s Key vector at position &lt;code&gt;t&lt;/code&gt; encode information about &lt;code&gt;token[t-1]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; The induction head&amp;apos;s Query vector at position &lt;code&gt;t+k&lt;/code&gt; can then match with the Key at position &lt;code&gt;t&lt;/code&gt;, allowing it to attend to &lt;code&gt;t&lt;/code&gt; and use its OV-circuit to copy &lt;code&gt;token[t]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; The discovery of induction heads shows that powerful, general capabilities like in-context learning can emerge from simple, mechanically understandable circuits.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;One of the most remarkable abilities of large language models is &lt;strong&gt;in-context learning&lt;/strong&gt;: the ability to perform a task from a few examples in a prompt, without any weight updates. If you show a model &lt;code&gt;A -&amp;gt; B, C -&amp;gt; D, E -&amp;gt;&lt;/code&gt;, it will likely predict &lt;code&gt;F&lt;/code&gt;. How does it do this?&lt;/p&gt;&lt;p&gt;The authors of the &amp;quot;Mathematical Framework&amp;quot; paper discovered that even tiny, two-layer models develop a surprisingly general version of this skill. When trained on random text, they learn to complete repeating sequences (e.g., &lt;code&gt;...[A][B]...[A] -&amp;gt; [B]&lt;/code&gt;). The circuit responsible for this is the &lt;strong&gt;induction head&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Understanding this circuit is a major milestone. It&amp;apos;s not just a description of a pattern; it&amp;apos;s a full, end-to-end reverse engineering of a key algorithmic capability. It demonstrates how the abstract building blocks we&amp;apos;ve discussed assemble into something greater than the sum of its parts.&lt;/p&gt;&lt;h4&gt;Prereqs&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; A complete understanding of Parts I-V, especially &lt;strong&gt;head composition&lt;/strong&gt; and the roles of &lt;code&gt;W_Q&lt;/code&gt;, &lt;code&gt;W_K&lt;/code&gt;, and &lt;code&gt;W_V&lt;/code&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Core Idea in One Picture&lt;/h4&gt;&lt;p&gt;The induction circuit consists of (at least) two heads. A &amp;quot;previous token head&amp;quot; in Layer 0 sets up the information, and the &amp;quot;induction head&amp;quot; in Layer 1 executes the core logic.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;graph TD
    subgraph Input Sequence
        direction LR
        A1(&amp;quot;... token[t-1]&amp;quot;) --&amp;gt; B1(&amp;quot;token[t]&amp;quot;) --&amp;gt; C1(&amp;quot;...&amp;quot;) --&amp;gt; A2(&amp;quot;token[t+k-1]&amp;quot;) --&amp;gt; B2(&amp;quot;token[t+k]&amp;quot;) --&amp;gt; Next(&amp;quot;???&amp;quot;)
    end

    subgraph Layer 0: Prev Token Head (H0)
        B1 -- Attends to --&amp;gt; A1
        A1 -- Copies info --&amp;gt; B1_Stream[Stream at B1]
    end

    subgraph Layer 1: Induction Head (H1)
        B2 -- Query: &amp;quot;I am B&amp;quot; --&amp;gt; B1_Stream
        B1_Stream -- Key: &amp;quot;Prev token was A&amp;quot; --&amp;gt; B2
        B1_Stream -- Value: &amp;quot;I am B&amp;quot; --&amp;gt; B2_Stream[Stream at B2]

        B2 -- Attends to --&amp;gt; B1
        B1 --OV Circuit --&amp;gt; B2_Stream
    end

    B2_Stream -- Projects via W_U --&amp;gt; Logit_for_C[&amp;quot;&amp;apos;C&amp;apos; logit is high&amp;quot;]

    style H0 fill:#ccf
    style H1 fill:#fcc
    style B1_Stream stroke:#00f,stroke-width:2px,stroke-dasharray: 2 2
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Figure 1: The Induction Head circuit. At a token &lt;/em&gt;&lt;code&gt;&lt;em&gt;B&lt;/em&gt;&lt;/code&gt;&lt;em&gt; (at &lt;/em&gt;&lt;code&gt;&lt;em&gt;t+k&lt;/em&gt;&lt;/code&gt;&lt;em&gt;), the model wants to predict the next token. If another &lt;/em&gt;&lt;code&gt;&lt;em&gt;B&lt;/em&gt;&lt;/code&gt;&lt;em&gt; appeared at &lt;/em&gt;&lt;code&gt;&lt;em&gt;t&lt;/em&gt;&lt;/code&gt;&lt;em&gt;, the circuit finds it. It does this because a Layer 0 head (&lt;/em&gt;&lt;code&gt;&lt;em&gt;H0&lt;/em&gt;&lt;/code&gt;&lt;em&gt;) copied information about &lt;/em&gt;&lt;code&gt;&lt;em&gt;A&lt;/em&gt;&lt;/code&gt;&lt;em&gt; (&lt;/em&gt;&lt;code&gt;&lt;em&gt;token[t-1]&lt;/em&gt;&lt;/code&gt;&lt;em&gt;) into the residual stream at &lt;/em&gt;&lt;code&gt;&lt;em&gt;B&lt;/em&gt;&lt;/code&gt;&lt;em&gt;&amp;apos;s position (&lt;/em&gt;&lt;code&gt;&lt;em&gt;t&lt;/em&gt;&lt;/code&gt;&lt;em&gt;). &lt;/em&gt;&lt;code&gt;&lt;em&gt;H1&lt;/em&gt;&lt;/code&gt;&lt;em&gt; at &lt;/em&gt;&lt;code&gt;&lt;em&gt;t+k&lt;/em&gt;&lt;/code&gt;&lt;em&gt; can now attend to &lt;/em&gt;&lt;code&gt;&lt;em&gt;B&lt;/em&gt;&lt;/code&gt;&lt;em&gt; at &lt;/em&gt;&lt;code&gt;&lt;em&gt;t&lt;/em&gt;&lt;/code&gt;&lt;em&gt; (by matching its Query for &amp;apos;B&amp;apos; with &amp;apos;B&amp;apos;s content) and use its Value to copy the next token&amp;apos;s identity, thereby predicting &amp;apos;C&amp;apos;.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;Walkthrough: The Algorithm Step-by-Step&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s trace the full algorithm for the sequence &lt;code&gt;...[A] [B] [C]... [A] [B] -&amp;gt; ?&lt;/code&gt;, where the model should predict &lt;code&gt;[C]&lt;/code&gt;. We are at position &lt;code&gt;t+k&lt;/code&gt;, which contains the token &lt;code&gt;[B]&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;Step 1: The Setup (Previous Token Head, &lt;code&gt;H0&lt;/code&gt;)&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Head Type:&lt;/strong&gt; A &amp;quot;previous token head&amp;quot; in an early layer (let&amp;apos;s say Layer 0).  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;QK-Circuit:&lt;/strong&gt; This head has a very simple QK circuit. Its weights are configured such that the query at position &lt;code&gt;t&lt;/code&gt; will always give the highest attention score to the key at position &lt;code&gt;t-1&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;OV-Circuit:&lt;/strong&gt; This head&amp;apos;s OV circuit is a &amp;quot;copying&amp;quot; circuit. It takes the embedding of the previous token (&lt;code&gt;token[t-1]&lt;/code&gt;) and adds it to the residual stream at position &lt;code&gt;t&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; After Layer 0, the residual stream at every position &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;x_t&lt;/code&gt;, contains a representation of both &lt;code&gt;token[t]&lt;/code&gt; (from the original embedding) and &lt;code&gt;token[t-1]&lt;/code&gt; (added by &lt;code&gt;H0&lt;/code&gt;). &lt;ul&gt;&lt;li&gt; At position &lt;code&gt;t&lt;/code&gt;, where the token is &lt;code&gt;[B]&lt;/code&gt;, the stream &lt;code&gt;x_t&lt;/code&gt; now effectively says: &amp;quot;I am &lt;code&gt;[B]&lt;/code&gt;, and the token before me was &lt;code&gt;[A]&lt;/code&gt;.&amp;quot;  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Step 2: The Core Logic (Induction Head, &lt;code&gt;H1&lt;/code&gt;)&lt;/h4&gt;&lt;p&gt;Now we move to a later layer (Layer 1). The induction head &lt;code&gt;H1&lt;/code&gt; executes the main algorithm.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Where does H1 attend?&lt;/strong&gt;&lt;code&gt;H1&lt;/code&gt; needs to attend from the current position &lt;code&gt;t+k&lt;/code&gt; (token &lt;code&gt;[B]&lt;/code&gt;) back to the previous occurrence of &lt;code&gt;[B]&lt;/code&gt; at position &lt;code&gt;t&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The QK-Circuit (K-Composition in action):&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Query:&lt;/strong&gt; The query vector &lt;code&gt;q_{t+k}&lt;/code&gt; for &lt;code&gt;H1&lt;/code&gt; at the current position is formed from the stream, so it says &amp;quot;I am &lt;code&gt;[B]&lt;/code&gt;.&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Key:&lt;/strong&gt; This is the clever part. The key vector &lt;code&gt;k_t&lt;/code&gt; at the earlier position &lt;code&gt;t&lt;/code&gt; is formed from its stream &lt;code&gt;x_t&lt;/code&gt;. And &lt;code&gt;x_t&lt;/code&gt; contains information about &lt;code&gt;token[t-1]&lt;/code&gt;, which is &lt;code&gt;[A]&lt;/code&gt;. So, &lt;code&gt;k_t&lt;/code&gt; says something like &amp;quot;The token after me is &lt;code&gt;[B]&lt;/code&gt;&amp;quot; or &amp;quot;My predecessor was &lt;code&gt;[A]&lt;/code&gt;&amp;quot;.  &lt;/li&gt;&lt;li&gt; The induction head&amp;apos;s &lt;code&gt;W_Q&lt;/code&gt; and &lt;code&gt;W_K&lt;/code&gt; matrices are learned to match these specific signals. The query &lt;code&gt;q_{t+k}&lt;/code&gt; looks for a key &lt;code&gt;k_t&lt;/code&gt; that corresponds to a previous occurrence of the &lt;em&gt;same token&lt;/em&gt;. The K-composition (the effect of &lt;code&gt;H0&lt;/code&gt; on &lt;code&gt;H1&lt;/code&gt;&amp;apos;s key) makes this possible. The Query &amp;quot;I am &lt;code&gt;[B]&lt;/code&gt;&amp;quot; matches the Key &amp;quot;The token after me is &lt;code&gt;[B]&lt;/code&gt;&amp;quot;.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;What does H1 move? The OV-Circuit:&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;H1&lt;/code&gt;&amp;apos;s attention is now focused on position &lt;code&gt;t&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; Its &lt;code&gt;W_V&lt;/code&gt; matrix is trained to read the information from the residual stream at &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;x_t&lt;/code&gt;. Crucially, &lt;code&gt;x_t&lt;/code&gt; still contains the original embedding for &lt;code&gt;token[t]&lt;/code&gt;, which is &lt;code&gt;[B]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; However, the composed &lt;code&gt;W_V^1 W_O^1&lt;/code&gt; circuit of the induction head performs a specific transformation: it is trained to take the an embedding from the stream and effectively &amp;quot;shift it forward by one&amp;quot;. It acts to copy the &lt;em&gt;next&lt;/em&gt; token.  &lt;/li&gt;&lt;li&gt; Wait, how can it copy the &lt;em&gt;next&lt;/em&gt; token, &lt;code&gt;[C]&lt;/code&gt;, by looking at &lt;code&gt;[B]&lt;/code&gt;? The paper hypothesizes that the model leverages existing correlations. Since &lt;code&gt;[B]&lt;/code&gt; is often followed by &lt;code&gt;[C]&lt;/code&gt; in the training data, the model can learn a &lt;code&gt;W_V W_O&lt;/code&gt; transformation that maps the embedding of &lt;code&gt;[B]&lt;/code&gt; to a vector that boosts the logit for &lt;code&gt;[C]&lt;/code&gt;.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;em&gt;Self-correction/Refined View:&lt;/em&gt; A simpler and more common view of the induction head&amp;apos;s OV-circuit is that it attends to the position &lt;code&gt;t-1&lt;/code&gt; (the position of &lt;code&gt;A&lt;/code&gt;) and &lt;em&gt;copies&lt;/em&gt; the value from there. The logic is:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;H1 Query&lt;/strong&gt; at &lt;code&gt;t+k&lt;/code&gt; (&lt;code&gt;B&lt;/code&gt;): Look for a previous &lt;code&gt;B&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;H1 Key&lt;/strong&gt; at &lt;code&gt;t+k-1&lt;/code&gt; (&lt;code&gt;A&lt;/code&gt;). Via K-Composition with &lt;code&gt;H0&lt;/code&gt;, this Key says &amp;quot;The token after me is &lt;code&gt;B&lt;/code&gt;&amp;quot;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;H1 attends&lt;/strong&gt; from &lt;code&gt;t+k&lt;/code&gt; to &lt;code&gt;t+k-1&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;H1 Value&lt;/strong&gt; reads from &lt;code&gt;t+k-1&lt;/code&gt;&amp;apos;s stream, which contains the embedding for &lt;code&gt;A&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; The &lt;strong&gt;OV Circuit&lt;/strong&gt; of &lt;code&gt;H1&lt;/code&gt; moves this information to predict &lt;code&gt;B&lt;/code&gt;. &lt;br&gt; (This seems to be a slight variation on the original paper&amp;apos;s description, but is a common interpretation).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;Let&amp;apos;s stick to the paper&amp;apos;s primary finding: the &lt;strong&gt;QK circuit attends to the previous instance of the same token, and the OV circuit copies the next token.&lt;/strong&gt; The key is that &lt;code&gt;W_Q&lt;/code&gt; and &lt;code&gt;W_K&lt;/code&gt; are trained to &amp;quot;look for &lt;code&gt;[token]&lt;/code&gt;&amp;quot; and &lt;code&gt;H0&lt;/code&gt;&amp;apos;s output lets them look for &amp;quot;&lt;code&gt;[token]&lt;/code&gt; in the next position&amp;quot;.&lt;/p&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Implication:&lt;/strong&gt; This is a concrete mechanism for in-context learning. Many of the impressive few-shot learning capabilities of LLMs may be powered by scaled-up, more abstract versions of this basic circuit.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Implication:&lt;/strong&gt; It shows that general, algorithmic capabilities can emerge spontaneously from a simple predictive loss, and that these emergent algorithms can be reverse-engineered.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit:&lt;/strong&gt; This is one specific circuit. It doesn&amp;apos;t explain all forms of in-context learning (e.g., following complex instructions). However, it provides a foundational example.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit:&lt;/strong&gt; The explanation relies on the linear approximation provided by path expansion; the real dynamics with LayerNorm are noisier and more complex, but the core signal is captured by this model.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Induction heads are a two-layer, two-head circuit that allows a model to continue repeating sequences.  &lt;/li&gt;&lt;li&gt; They are a mechanistic explanation for a simple form of in-context learning.  &lt;/li&gt;&lt;li&gt; The circuit combines a &amp;quot;previous token head&amp;quot; (to provide history) and an &amp;quot;induction head&amp;quot; (to use that history).  &lt;/li&gt;&lt;li&gt; K-Composition is the critical mechanism that allows the induction head&amp;apos;s query to find a match based on the previous token&amp;apos;s identity.  &lt;/li&gt;&lt;li&gt; The induction head&amp;apos;s QK-circuit locates the &lt;em&gt;previous occurrence of the current token&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; Its OV-circuit acts to &lt;em&gt;copy the token that followed it&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt; The existence of such a clean, effective, and discoverable circuit provides strong evidence for the validity of the circuits research agenda.  &lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Transformer Circuits VII: A Conclusion and The Road Ahead&lt;/h2&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; This series reverse-engineered Transformers using the &amp;quot;circuits&amp;quot; framework, moving from high-level architecture to specific, learned algorithms.  &lt;/li&gt;&lt;li&gt; The core idea is to treat a trained model as a program to be decompiled, not an inscrutable black box.  &lt;/li&gt;&lt;li&gt; We built a hierarchy of abstractions: the &lt;strong&gt;residual stream&lt;/strong&gt; (the memory), &lt;strong&gt;independent heads&lt;/strong&gt; (the operators), &lt;strong&gt;QK/OV circuits&lt;/strong&gt; (the sub-routines), and &lt;strong&gt;composition&lt;/strong&gt; (the program flow).  &lt;/li&gt;&lt;li&gt; This framework allowed us to fully dissect the &lt;strong&gt;induction head circuit&lt;/strong&gt;, a concrete mechanism for in-context learning.  &lt;/li&gt;&lt;li&gt; Major frontiers remain: understanding FFN/MLP layers, tackling non-linearities like LayerNorm, dealing with superposition, and scaling these analyses to frontier models.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;Over the last six posts, we&amp;apos;ve journeyed deep into the internals of Transformers. We began with a single, ambitious goal: to move beyond treating models as black boxes and instead &lt;strong&gt;reverse engineer&lt;/strong&gt; the algorithms they learn during training. The &amp;quot;Mathematical Framework for Transformer Circuits&amp;quot; provided our roadmap.&lt;/p&gt;&lt;p&gt;We started with the broadest architectural concept-the residual stream-and progressively zoomed in, dissecting layers into heads, heads into sub-circuits, and finally, tracing the compositional paths that weave these components into complex programs.&lt;/p&gt;&lt;p&gt;This final post zooms back out. We will recap our journey, solidify the key insights of the circuits perspective, and honestly assess its limitations and the exciting frontiers that lie ahead for the field of mechanistic interpretability.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;The Journey from Architecture to Algorithm: A Recap&lt;/h4&gt;&lt;p&gt;Our entire analysis was built on a series of increasingly granular, yet complementary, mental models.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;The Residual Stream as the Workspace:&lt;/strong&gt; We first reframed the Transformer not as a sequential chain of transformations, but as a collaborative workspace. The residual stream is the central data bus where each component-embeddings, attention heads, FFNs-reads the current state and writes back an additive update. This &amp;quot;assembly line&amp;quot; view is the foundational mental model.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Attention Heads as Independent Operators:&lt;/strong&gt; We then simplified the multi-head attention block, showing its output is mathematically equivalent to the sum of contributions from independent heads. This allowed us to isolate a single head as a valid unit of analysis.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;QK and OV Circuits within a Head:&lt;/strong&gt; Zooming in further, we split each head into two sub-modules: the &lt;strong&gt;QK-circuit&lt;/strong&gt; that determines &lt;em&gt;where to look&lt;/em&gt; (attention patterns) and the &lt;strong&gt;OV-circuit&lt;/strong&gt; that determines &lt;em&gt;what to move&lt;/em&gt; (information payloads). This let us separate the search and retrieval aspects of attention.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Composition as the Source of Power:&lt;/strong&gt; With the building blocks defined, we explored how they connect. &lt;strong&gt;Composition&lt;/strong&gt;-where heads in later layers operate on the outputs of earlier heads-is the source of depth&amp;apos;s power. By analyzing Q-, K-, and V-composition, we developed a language to describe how multi-step algorithms are formed.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Payoff: Induction Heads:&lt;/strong&gt; Finally, we put it all together to explain a genuine, non-trivial algorithm learned by the model: the induction head circuit. We saw how a &amp;quot;previous token head&amp;quot; composes with a later &amp;quot;induction head&amp;quot; via K-composition to implement a general algorithm for sequence repetition-a form of in-context learning.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This final step is the proof-of-concept for the entire framework. It demonstrates that our abstractions are not just neat metaphors; they are precise enough to fully reverse-engineer a key capability of the model.&lt;/p&gt;&lt;h4&gt;Implications &amp;amp; The Bigger Picture&lt;/h4&gt;&lt;p&gt;The circuits perspective is more than just a set of analytical tricks; it&amp;apos;s a paradigm for studying neural networks. Its core implications are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Models Learn Interpretable Algorithms:&lt;/strong&gt; The most profound takeaway is that models don&amp;apos;t just learn a soup of statistical correlations. They appear to learn clean, specific, and often human-understandable algorithms made of composable parts.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;A Path to Rigorous Safety and Auditing:&lt;/strong&gt; If we can truly understand the circuits driving a model&amp;apos;s capabilities, we can begin to audit them for flaws, biases, or unintended behaviors. This is a far more robust approach to safety than relying on behavioral testing alone.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;A New Kind of Neuroscience:&lt;/strong&gt; By analogy, MI is like a form of computational neuroscience for artificial minds. We are moving from lesion studies (ablations) to the detailed tracing of neural pathways to understand how cognition emerges from component interactions.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Limitations and the Road Ahead&lt;/h4&gt;&lt;p&gt;For all its successes, the circuits framework is still in its infancy, and major challenges remain.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;The Tyranny of LayerNorm:&lt;/strong&gt; Our clean, linear story of path expansion is an approximation. Layer Normalization is a critical non-linear component that entangles the contributions of different paths. Future work must develop techniques that can handle these non-linearities more gracefully.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Mystery of the FFN Layers:&lt;/strong&gt; This series, like the foundational paper, is heavily attention-centric. The Feed-Forward (or MLP) layers, which consume half the model&amp;apos;s parameters, are far less understood. While hypotheses exist (e.g., they act as key-value memories), a full mechanistic picture is still missing.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Problem of Superposition:&lt;/strong&gt; The &amp;quot;privileged basis&amp;quot; assumption-that individual neurons or directions correspond to clean features-often breaks. Models appear to represent more features than they have dimensions by storing them in overlapping linear combinations, a phenomenon called superposition. Decomposing these representations is a major open research problem.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Scaling to a Trillion Parameters:&lt;/strong&gt; Manually finding circuits in a two-layer model is one thing. Doing so in a 100-layer, trillion-parameter frontier model is another. The central challenge for the field is developing automated or semi-automated techniques to scale circuit discovery.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;h4&gt;Final Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; The &amp;quot;circuits&amp;quot; perspective provides a powerful, hierarchical framework for reverse-engineering Transformers.  &lt;/li&gt;&lt;li&gt; The core mental model is of components reading from and writing additive updates to a central residual stream.  &lt;/li&gt;&lt;li&gt; This framework allows us to decompose the model into understandable parts: layers, heads, and QK/OV sub-circuits.  &lt;/li&gt;&lt;li&gt; Depth enables &lt;strong&gt;composition&lt;/strong&gt;, where components chain together to form multi-step algorithms like induction heads.  &lt;/li&gt;&lt;li&gt; Induction heads provide a concrete, mechanistic explanation for an important form of in-context learning.  &lt;/li&gt;&lt;li&gt; While powerful, this linear, attention-focused view is an approximation that must be expanded to handle non-linearities (LayerNorm), FFN layers, and superposition.  &lt;/li&gt;&lt;li&gt; Mechanistic interpretability is a nascent but promising field that aims to make AI models truly understandable, not just superficially predictable.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This journey from architecture to algorithm is just the beginning. The tools and concepts laid out here are the foundation upon which the next generation of interpretability research is being built, aiming to one day make even the most complex AI systems transparent and trustworthy.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;Further Reading&lt;/h4&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Elhage, N. et al. (2021). A Mathematical Framework for Transformer Circuits.&lt;/strong&gt; The primary source for this entire series. A must-read for a deep dive.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Wang, K., et al. (2022). Interpretability in the Wild: A Circuit for Indirect Object Identification in GPT-2.&lt;/strong&gt; A landmark paper showing how the circuits framework can be used to find a more complex, language-based algorithm in off-the-shelf models.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Nanda, N. (2022). TransformerLens.&lt;/strong&gt; An open-source library built on PyTorch for applying the concepts discussed in this series to standard Transformer models. An excellent tool for hands-on exploration.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot;&gt;Part 3 Reverse Engineering Transformers: Path Expansion&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt;Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt;Part 1: Reverse Engineering Transformers: Deconstructing Attention&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt; Part 1: Reverse Engineering Transformers: Deconstructing Attention &lt;/a&gt;&lt;/div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Part 3 Reverse Engineering Transformers: Path Expansion</title>
      <link>https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt; Other parts in the series: &lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt;&lt;span&gt;Part 1: Reverse Engineering Transformers: Deconstructing Attention&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt;&lt;span&gt;Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot;&gt;&lt;span&gt;Part 4 Reverse Engineering Transformers: Induction Heads&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;Transformer Circuits IV: Path Expansion&lt;/h2&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Path expansion is a technique for decomposing a Transformer&amp;apos;s full forward pass into a sum of individual end-to-end paths.  &lt;/li&gt;&lt;li&gt; A &amp;quot;path&amp;quot; is a sequence of matrix multiplications representing information flow from an input token, through a series of attention heads and FFN layers, to a final output logit.  &lt;/li&gt;&lt;li&gt; The total logit for a specific token is the sum of the contributions from all possible paths leading to it.  &lt;/li&gt;&lt;li&gt; For this linear decomposition to work, we must ignore the non-linear LayerNorm operations, treating path expansion as a useful &amp;quot;linear approximation&amp;quot; of the true computation.  &lt;/li&gt;&lt;li&gt; This technique allows us to move from analyzing &lt;em&gt;what a single component does&lt;/em&gt; to analyzing &lt;em&gt;how components compose into circuits&lt;/em&gt; that solve a task.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;So far in our series, we have established a powerful mental model:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; The Transformer is a sequence of read/write operations on a central &lt;strong&gt;residual stream&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt; The attention layer can be decomposed into a sum of &lt;strong&gt;independent head&lt;/strong&gt; contributions.  &lt;/li&gt;&lt;li&gt; Each head can be further split into a &lt;strong&gt;QK-circuit&lt;/strong&gt; (where to look) and an &lt;strong&gt;OV-circuit&lt;/strong&gt; (what to move).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;We have successfully broken the model down into its smallest functional parts. The next logical question is: how do these parts combine across layers to implement complex algorithms? How does a &amp;quot;previous token head&amp;quot; in Layer 0 enable an &amp;quot;induction head&amp;quot; in Layer 5?&lt;/p&gt;&lt;p&gt;The answer lies in &lt;strong&gt;path expansion&lt;/strong&gt;. By recursively expanding the definition of the residual stream, we can re-express the entire model&amp;apos;s computation for a single output logit as a massive sum of individual end-to-end information paths. This allows us to attribute the model&amp;apos;s final output to specific combinations of components, which we call &amp;quot;circuits.&amp;quot;&lt;/p&gt;&lt;h4&gt;Prereqs&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Understanding of the residual stream, independent heads, and QK/OV circuits (Parts I-III).  &lt;/li&gt;&lt;li&gt; Comfort with the idea of matrix composition (multiplying matrices in a sequence).  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Core Idea in One Picture&lt;/h4&gt;&lt;p&gt;A Transformer&amp;apos;s output for a token is the sum of all the ways information can travel from the input embeddings to the final unembedding layer.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;graph TD
    subgraph Input Embeddings
        T_A[Token A]
        T_B[Token B]
    end

    subgraph Layer 0
        H0_1[Head 0.1]
        F0[FFN 0]
    end

    subgraph Layer 1
        H1_1[Head 1.1]
        F1[FFN 1]
    end

    subgraph Output Logits
        U[Unembedding] --&amp;gt; Logit_C[Logit for &amp;apos;C&amp;apos;]
    end

    T_A -- Path 1 --&amp;gt; U
    T_A -- Path 2 --&amp;gt; H0_1 -- Path 2 --&amp;gt; U
    T_B -- Path 3 --&amp;gt; F0 -- Path 3 --&amp;gt; H1_1 -- Path 3 --&amp;gt; U

    style Path 1 stroke:#ff0000,stroke-width:2px,stroke-dasharray: 5 5
    style Path 2 stroke:#0000ff,stroke-width:2px,stroke-dasharray: 5 5
    style Path 3 stroke:#008000,stroke-width:2px,stroke-dasharray: 5 5```
*Figure 1: A conceptual illustration of path expansion. The final logit for token &amp;apos;C&amp;apos; is a sum of contributions from many paths. Path 1 is a direct connection. Path 2 goes through one head. Path 3 goes from a different token through an FFN and another head. Each path corresponds to a product of weight matrices.*&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Definitions &amp;amp; Setup&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s trace the computation algebraically. The final logit for a specific token &lt;code&gt;T&lt;/code&gt; is computed by taking the dot product of the final residual stream state &lt;code&gt;x_final&lt;/code&gt; with the token&amp;apos;s corresponding row in the unembedding matrix, &lt;code&gt;W_U[T]&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;code&gt;Logit(T) = x_final @ W_U[T]&lt;/code&gt;&lt;/p&gt;&lt;p&gt;We know that &lt;code&gt;x_final&lt;/code&gt; is the sum of the initial embedding &lt;code&gt;x_0&lt;/code&gt; and all component updates: &lt;br&gt;&lt;code&gt;x_final = x_0 + &amp;#x2211;_{l, h} &amp;#x394;_{l,h}^{attn} + &amp;#x2211;_{l} &amp;#x394;_{l}^{ffn}&lt;/code&gt;&lt;/p&gt;&lt;p&gt;If we ignore LayerNorm, we can substitute this in: &lt;br&gt;&lt;code&gt;Logit(T) = (x_0 + &amp;#x2211; &amp;#x394;_{l,h}^{attn} + &amp;#x2211; &amp;#x394;_{l}^{ffn}) @ W_U[T]&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Since the dot product distributes over addition, this becomes: &lt;br&gt;&lt;code&gt;Logit(T) = (x_0 @ W_U[T]) + &amp;#x2211; (&amp;#x394;_{l,h}^{attn} @ W_U[T]) + &amp;#x2211; (&amp;#x394;_{l}^{ffn} @ W_U[T])&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This is the first level of expansion. We&amp;apos;ve expressed the logit as a sum of contributions from each component&amp;apos;s direct write to the final residual stream. But now we can expand each &lt;code&gt;&amp;#x394;&lt;/code&gt; term, because each component&amp;apos;s output depends on the state of the stream &lt;em&gt;before&lt;/em&gt; it, which is itself a sum of previous terms. This recursive expansion is path expansion.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;path&lt;/strong&gt; is a term in this fully expanded sum. Each path corresponds to a unique sequence of components that information flows through.&lt;/p&gt;&lt;h4&gt;Walkthrough: Expanding Two Simple Paths&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s analyze the paths for predicting the token &amp;apos;C&amp;apos; that follows the prompt &amp;quot;A B&amp;quot;.&lt;/p&gt;&lt;h4&gt;Path 1: The Direct &amp;quot;Bigram&amp;quot; Path&lt;/h4&gt;&lt;p&gt;The simplest path is the information flowing directly from an input token&amp;apos;s embedding to the output.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Path:&lt;/strong&gt;&lt;code&gt;Embedding(&amp;apos;B&amp;apos;)&lt;/code&gt; &amp;#x2192; &lt;code&gt;Unembedding(&amp;apos;C&amp;apos;)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Description:&lt;/strong&gt; This path represents the model&amp;apos;s learned bigram statistics. How likely is &amp;apos;C&amp;apos; to follow &amp;apos;B&amp;apos; without any deeper context?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Algebraic Contribution:&lt;/strong&gt; The contribution to the logit of &amp;apos;C&amp;apos; from this path is: &lt;br&gt;&lt;br&gt; \text{Contribution}&lt;em&gt;1 = (\mathbf{W_E}[\text{&amp;apos;B&amp;apos;}] + \mathbf{W&lt;/em&gt;{pos}}) \cdot \mathbf{W_U}[\text{&amp;apos;C&amp;apos;}] &lt;br&gt;&lt;br&gt; This is a single scalar value, computed from a product of two embedding vectors. This path exists for every input token.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Path 2: The &amp;quot;Skip-Trigram&amp;quot; Attention Path&lt;/h4&gt;&lt;p&gt;Now consider a path that involves one attention head. Let&amp;apos;s trace the information from token &amp;apos;A&amp;apos; to the output at position &amp;apos;B&amp;apos;, which then predicts &amp;apos;C&amp;apos;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Path:&lt;/strong&gt;&lt;code&gt;Embedding(&amp;apos;A&amp;apos;)&lt;/code&gt; &amp;#x2192; &lt;code&gt;L0 Head OV Circuit&lt;/code&gt; &amp;#x2192; &lt;code&gt;Unembedding(&amp;apos;C&amp;apos;)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Description:&lt;/strong&gt; This path models skip-trigrams (e.g., in &amp;quot;The dog ran&amp;quot;, it helps predict &amp;quot;ran&amp;quot; based on &amp;quot;The&amp;quot; after seeing &amp;quot;dog&amp;quot;). The head at position &amp;apos;B&amp;apos; attends to &amp;apos;A&amp;apos; and moves information to &amp;apos;B&amp;apos;s residual stream.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Algebraic Contribution:&lt;/strong&gt; Let&amp;apos;s assume head &lt;code&gt;h&lt;/code&gt; in layer 0 at position &lt;code&gt;q=B&lt;/code&gt; is attending to source position &lt;code&gt;k=A&lt;/code&gt;. &lt;ul&gt;&lt;li&gt; The information moved by the OV circuit is &lt;code&gt;(x_A @ W_V^h) @ W_O^h&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; This is projected by the unembedding: &lt;code&gt;((x_A @ W_V^h) @ W_O^h) @ W_U[C]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; The full contribution is this value &lt;em&gt;multiplied by the attention probability&lt;/em&gt;&lt;code&gt;A_{B &amp;#x2190; A}&lt;/code&gt; paid from B to A. &lt;br&gt;&lt;br&gt; \text{Contribution}&lt;em&gt;2 = A&lt;/em&gt;{B \leftarrow A} \cdot \left( (\mathbf{W_E}[\text{&amp;apos;A&amp;apos;}] + \mathbf{W_{pos}}) \mathbf{W_V^h} \mathbf{W_O^h} \mathbf{W_U}[\text{&amp;apos;C&amp;apos;}] \right) &lt;br&gt;&lt;br&gt; This shows how the matrices along a path compose. The term &lt;code&gt;W_V^h W_O^h W_U&lt;/code&gt; forms a single composed operator that describes the end-to-end effect of this path.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The full logit for &amp;apos;C&amp;apos; is &lt;code&gt;Contribution_1 + Contribution_2 +&lt;/code&gt; contributions from &lt;em&gt;all other paths&lt;/em&gt; (from &amp;apos;B&amp;apos; through heads, from &amp;apos;A&amp;apos; through FFNs, through multiple layers, etc.).&lt;/p&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Implication: Finding Circuits.&lt;/strong&gt; Path expansion is the theoretical tool that allows us to find circuits. The &amp;quot;Indirect Object Identification&amp;quot; circuit, for instance, is a set of paths involving specific heads that compose to perform that task. By summing the contributions of just the paths in a hypothesized circuit, we can see how much of the model&amp;apos;s behavior on a specific task that circuit explains.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Implication: Explaining Logits, Not Probabilities.&lt;/strong&gt; This is an analysis of the pre-softmax logits. Logits are additive, so this decomposition is mathematically sound (ignoring LayerNorm). You cannot do this with probabilities, which are the output of the non-linear softmax function. &lt;code&gt;softmax(a+b) != softmax(a) + softmax(b)&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit #1: LayerNorm Breaks Everything.&lt;/strong&gt; As stated before, this entire framework relies on the linear additivity of the residual stream. LayerNorm is non-linear and breaks this assumption. &lt;code&gt;LN(x_0 + &amp;#x394;_1)&lt;/code&gt; is not &lt;code&gt;LN(x_0) + LN(&amp;#x394;_1)&lt;/code&gt;. Therefore, path expansion is a &lt;strong&gt;linear approximation&lt;/strong&gt;. For many circuits and models, this approximation holds up surprisingly well, but it is not the ground truth.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit #2: Combinatorial Explosion.&lt;/strong&gt; The number of paths is exponential in the depth of the model. Fully expanding all paths for a large model is computationally intractable. In practice, researchers analyze the top-K most important paths or analyze contributions on a component-by-component basis, which is a coarser-grained version of path expansion.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Pitfalls&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Forgetting the Linear Approximation.&lt;/strong&gt; It is the most common mistake to treat path expansion results as the exact ground truth of the model&amp;apos;s computation. Always remember the LayerNorm caveat.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Summing Probabilities.&lt;/strong&gt; Never attempt to sum the &amp;quot;probability contributions&amp;quot; of different paths. The analysis is valid only for the additive logits.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Path expansion reframes a Transformer&amp;apos;s computation as a sum over end-to-end information flow paths.  &lt;/li&gt;&lt;li&gt; Each path has a value (its contribution to a final logit) and is composed of a product of weight matrices.  &lt;/li&gt;&lt;li&gt; This technique is the key to moving from analyzing components to analyzing multi-component circuits.  &lt;/li&gt;&lt;li&gt; It provides a granular way to attribute a model&amp;apos;s output to specific model parameters and interactions.  &lt;/li&gt;&lt;li&gt; The validity of simple path expansion rests on a linear approximation that ignores LayerNorm.  &lt;/li&gt;&lt;li&gt; This analysis applies to logits, not probabilities, due to the additivity requirement.  &lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Transformer Circuits V: The Power of Depth is Composition&lt;/h2&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; The power of deep Transformers comes from &lt;strong&gt;composition&lt;/strong&gt;, where components in later layers operate on the outputs of components in earlier layers.  &lt;/li&gt;&lt;li&gt; An earlier head (&lt;code&gt;H0&lt;/code&gt;) can influence a later head (&lt;code&gt;H1&lt;/code&gt;) in three ways: Q-Composition, K-Composition, and V-Composition.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Q-Composition:&lt;/strong&gt; The output of &lt;code&gt;H0&lt;/code&gt; affects &lt;code&gt;H1&lt;/code&gt;&amp;apos;s Query vector, changing &lt;em&gt;what &lt;/em&gt;&lt;code&gt;&lt;em&gt;H1&lt;/em&gt;&lt;/code&gt;&lt;em&gt; is looking for&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;K-Composition:&lt;/strong&gt; The output of &lt;code&gt;H0&lt;/code&gt; affects &lt;code&gt;H1&lt;/code&gt;&amp;apos;s Key vector, changing &lt;em&gt;how &lt;/em&gt;&lt;code&gt;&lt;em&gt;H1&lt;/em&gt;&lt;/code&gt;&lt;em&gt;&amp;apos;s source tokens appear to other queries&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;V-Composition:&lt;/strong&gt; The output of &lt;code&gt;H0&lt;/code&gt; affects &lt;code&gt;H1&lt;/code&gt;&amp;apos;s Value vector, changing &lt;em&gt;what information &lt;/em&gt;&lt;code&gt;&lt;em&gt;H1&lt;/em&gt;&lt;/code&gt;&lt;em&gt; moves&lt;/em&gt;. This can create powerful &amp;quot;virtual attention heads.&amp;quot;  &lt;/li&gt;&lt;li&gt; These composition mechanisms are the building blocks of complex circuits, like the famous &amp;quot;induction heads.&amp;quot;  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;In our last post on Path Expansion, we established that a Transformer&amp;apos;s output can be viewed as a massive sum of information paths. However, this view can be misleading if it suggests paths are just independent, parallel computations. The most interesting behaviors in Transformers arise when these paths interact-when the output of one component directly becomes the input to another.&lt;/p&gt;&lt;p&gt;A single attention head in Layer 0 is limited. It can only implement simple algorithms like &amp;quot;attend to the previous token&amp;quot; or &amp;quot;attend to nouns.&amp;quot; To build a program that does something complex, like &amp;quot;find the last time the current token appeared, and copy the token that came after it,&amp;quot; you need multiple steps. You need depth.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Composition&lt;/strong&gt; is the formal term for how these steps are chained together. Specifically, we&amp;apos;ll examine how an attention head in an earlier layer (&lt;code&gt;H0&lt;/code&gt;) can pass information through the residual stream to be read by a head in a later layer (&lt;code&gt;H1&lt;/code&gt;), fundamentally altering &lt;code&gt;H1&lt;/code&gt;&amp;apos;s behavior.&lt;/p&gt;&lt;h4&gt;Prereqs&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Solid understanding of the residual stream, independent heads, QK/OV circuits, and the concept of path expansion (Parts I-IV).  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Core Idea in One Picture&lt;/h4&gt;&lt;p&gt;A head in Layer 0 (H0) writes an update to the residual stream. A head in Layer 1 (H1) then reads from this updated stream. Depending on which part of H1 reads the update (its Query, Key, or Value projection), we get a different type of composition.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;graph TD
    subgraph Layer 0
        x0[Residual Stream @ L0] --&amp;gt; H0[Head H0]
        H0 -- &amp;#x394;_0 --&amp;gt; x0_updated((+))
    end

    subgraph Layer 1
        x1[Residual Stream @ L1] --&amp;gt; H1[Head H1]
    end

    x0 --&amp;gt; x0_updated
    x0_updated --&amp;gt; x1

    subgraph H1
        direction LR
        x1 -- W_Q^1 --&amp;gt; Q1[Query]
        x1 -- W_K^1 --&amp;gt; K1[Key]
        x1 -- W_V^1 --&amp;gt; V1[Value]
    end

    linkStyle 0 stroke-width:0px
    linkStyle 4 stroke-width:0px

    subgraph Legend
        QComp[Q-Composition]
        KComp[K-Composition]
        VComp[V-Composition]
    end

    H0 -- &amp;quot;affects&amp;quot; --&amp;gt; Q1;
    H0 -- &amp;quot;affects&amp;quot; --&amp;gt; K1;
    H0 -- &amp;quot;affects&amp;quot; --&amp;gt; V1;

    style H0 fill:#ccf
    style H1 fill:#fcc
    style QComp fill:#f9d,stroke:#333
    style KComp fill:#d9f,stroke:#333
    style VComp fill:#9df,stroke:#333

&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Figure 1: The three types of head composition. An earlier head, &lt;/em&gt;&lt;code&gt;&lt;em&gt;H0&lt;/em&gt;&lt;/code&gt;&lt;em&gt;, writes an update (&lt;/em&gt;&lt;code&gt;&lt;em&gt;&amp;#x394;_0&lt;/em&gt;&lt;/code&gt;&lt;em&gt;) to the stream. A later head, &lt;/em&gt;&lt;code&gt;&lt;em&gt;H1&lt;/em&gt;&lt;/code&gt;&lt;em&gt;, reads this update. If the update influences &lt;/em&gt;&lt;code&gt;&lt;em&gt;H1&lt;/em&gt;&lt;/code&gt;&lt;em&gt;&amp;apos;s Query, it&amp;apos;s Q-Composition; if its Key, K-Composition; if its Value, V-Composition.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;Definitions &amp;amp; Setup&lt;/h4&gt;&lt;p&gt;For simplicity, let&amp;apos;s consider a two-layer, attention-only model. The residual stream entering Layer 1 is the sum of the initial embeddings (&lt;code&gt;x_0&lt;/code&gt;) and the output of Layer 0 (&lt;code&gt;&amp;#x394;_{L0}&lt;/code&gt;), which is the sum of all Layer 0 head outputs. Let&amp;apos;s focus on the interaction between one head &lt;code&gt;H0&lt;/code&gt; and one head &lt;code&gt;H1&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The stream after &lt;code&gt;H0&lt;/code&gt; is &lt;code&gt;x_1 = x_0 + &amp;#x394;_0&lt;/code&gt; (again, ignoring LayerNorm for our linear analysis). &lt;code&gt;H1&lt;/code&gt; now computes its Q, K, and V vectors from this &lt;code&gt;x_1&lt;/code&gt;.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Q-Composition:&lt;/strong&gt;&lt;code&gt;H1&lt;/code&gt;&amp;apos;s query is &lt;code&gt;q_1 = x_1 @ W_Q^1 = (x_0 + &amp;#x394;_0) @ W_Q^1&lt;/code&gt;. The term &lt;code&gt;&amp;#x394;_0 @ W_Q^1&lt;/code&gt; represents the influence of &lt;code&gt;H0&lt;/code&gt; on &lt;code&gt;H1&lt;/code&gt;&amp;apos;s query.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;K-Composition:&lt;/strong&gt;&lt;code&gt;H1&lt;/code&gt;&amp;apos;s key is &lt;code&gt;k_1 = x_1 @ W_K^1 = (x_0 + &amp;#x394;_0) @ W_K^1&lt;/code&gt;. The term &lt;code&gt;&amp;#x394;_0 @ W_K^1&lt;/code&gt; represents the influence of &lt;code&gt;H0&lt;/code&gt; on &lt;code&gt;H1&lt;/code&gt;&amp;apos;s key.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;V-Composition:&lt;/strong&gt;&lt;code&gt;H1&lt;/code&gt;&amp;apos;s value is &lt;code&gt;v_1 = x_1 @ W_V^1 = (x_0 + &amp;#x394;_0) @ W_V^1&lt;/code&gt;. The term &lt;code&gt;&amp;#x394;_0 @ W_V^1&lt;/code&gt; represents the influence of &lt;code&gt;H0&lt;/code&gt; on &lt;code&gt;H1&lt;/code&gt;&amp;apos;s value.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;Walkthrough: What Each Composition Type Does&lt;/h4&gt;&lt;h4&gt;Step 1: Q-Composition (&amp;quot;Changing What to Look For&amp;quot;)&lt;/h4&gt;&lt;p&gt;Imagine &lt;code&gt;H0&lt;/code&gt; is a &amp;quot;proper noun detector.&amp;quot; When it sees a name like &amp;quot;Mary,&amp;quot; it writes a &lt;code&gt;+is_a_proper_noun&lt;/code&gt; feature into the residual stream at that position. &lt;code&gt;H1&lt;/code&gt; might be a head that wants to find the main verb of a sentence. Through Q-composition, &lt;code&gt;H1&lt;/code&gt; can learn to modify its query based on &lt;code&gt;H0&lt;/code&gt;&amp;apos;s output. Its query at the &amp;quot;Mary&amp;quot; token, influenced by &lt;code&gt;&amp;#x394;_0&lt;/code&gt;, might become something like &amp;quot;I am a proper noun, now I am looking for the verb I am the subject of.&amp;quot; This allows &lt;code&gt;H1&lt;/code&gt;&amp;apos;s search to be context-dependent.&lt;/p&gt;&lt;h4&gt;Step 2: K-Composition (&amp;quot;Changing How You Are Seen&amp;quot;)&lt;/h4&gt;&lt;p&gt;This is subtler but crucial. &lt;code&gt;H0&lt;/code&gt;&amp;apos;s output modifies the key of a token, changing how it &amp;quot;appears&amp;quot; to queries from other positions. The canonical example is in forming &lt;strong&gt;induction heads&lt;/strong&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Let &lt;code&gt;H0&lt;/code&gt; be a &lt;strong&gt;previous token head&lt;/strong&gt;. At every position &lt;code&gt;t&lt;/code&gt;, it attends to position &lt;code&gt;t-1&lt;/code&gt; and copies its embedding. So, &lt;code&gt;&amp;#x394;_0&lt;/code&gt; at position &lt;code&gt;t&lt;/code&gt; contains information about token &lt;code&gt;t-1&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; Now consider &lt;code&gt;H1&lt;/code&gt;, the induction head. It wants to find a previous occurrence of the current token. Let&amp;apos;s say we&amp;apos;re at &lt;code&gt;token[t] = &amp;apos;B&amp;apos;&lt;/code&gt; and we&amp;apos;ve seen &amp;quot;... A B ...&amp;quot; earlier in the sequence. &lt;code&gt;H1&lt;/code&gt; needs to attend to &lt;code&gt;token[t-1] = &amp;apos;A&amp;apos;&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; How? At position &lt;code&gt;t-1&lt;/code&gt;, &lt;code&gt;H0&lt;/code&gt; wrote information about &lt;code&gt;token[t-2] = &amp;apos;A&amp;apos;&lt;/code&gt;. &lt;code&gt;H1&lt;/code&gt;&amp;apos;s key at &lt;code&gt;t-1&lt;/code&gt; is thus modified by &lt;code&gt;H0&lt;/code&gt; to effectively broadcast &amp;quot;the token &lt;em&gt;after&lt;/em&gt; me is &amp;apos;A&amp;apos;&amp;quot;. &lt;code&gt;H1&lt;/code&gt;&amp;apos;s query at &lt;code&gt;t&lt;/code&gt; is looking for the token &amp;apos;A&amp;apos;. The QK-dot-product will match, directing &lt;code&gt;H1&lt;/code&gt;&amp;apos;s attention to the correct previous token.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Step 3: V-Composition (&amp;quot;Changing What You Move&amp;quot;)&lt;/h4&gt;&lt;p&gt;This is perhaps the most powerful type, creating what the paper calls &lt;strong&gt;virtual attention heads&lt;/strong&gt;. Here, &lt;code&gt;H0&lt;/code&gt; determines what information &lt;code&gt;H1&lt;/code&gt; will move. &lt;code&gt;H1&lt;/code&gt;&amp;apos;s attention pattern might direct it to attend to token &lt;code&gt;A&lt;/code&gt;, but the &lt;em&gt;information&lt;/em&gt; it moves can come from token &lt;code&gt;B&lt;/code&gt;, as mediated by &lt;code&gt;H0&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;apos;s expand the algebra for a V-composition path, where &lt;code&gt;H1&lt;/code&gt; at position &lt;code&gt;q&lt;/code&gt; attends to position &lt;code&gt;k_1&lt;/code&gt;, and &lt;code&gt;H0&lt;/code&gt; at &lt;code&gt;k_1&lt;/code&gt; attends to &lt;code&gt;k_0&lt;/code&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;H1&lt;/code&gt;&amp;apos;s output: &lt;code&gt;Output_1 = A_{q &amp;#x2190; k_1} &amp;#xb7; (x_{k_1} @ W_V^1 @ W_O^1)&lt;/code&gt;&lt;/li&gt;&lt;li&gt; But &lt;code&gt;x_{k_1} = x_0 + &amp;#x394;_0 = x_0 + (A_{k_1 &amp;#x2190; k_0} &amp;#xb7; (x_{k_0} @ W_V^0 @ W_O^0))&lt;/code&gt;&lt;/li&gt;&lt;li&gt; Plugging this in, the contribution to &lt;code&gt;H1&lt;/code&gt;&amp;apos;s output from this two-head path is: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Path&amp;#xa0;Output=Aq&amp;#x2190;k1&amp;#x22c5;Ak1&amp;#x2190;k0&amp;#x22c5;(xk0@WV0WO0WV1WO1)\text{Path Output} = A_{q \leftarrow k_1} \cdot A_{k_1 \leftarrow k_0} \cdot ( \mathbf{x_{k_0}} @ \mathbf{W_V^0 W_O^0 W_V^1 W_O^1} )&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Path&amp;#xa0;Output&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;&amp;#x2190;&lt;/span&gt;&lt;span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2190;&lt;/span&gt;&lt;span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt; This path moves information from &lt;code&gt;x_{k_0}&lt;/code&gt; to position &lt;code&gt;q&lt;/code&gt; via a two-step hop. It attends from &lt;code&gt;q&lt;/code&gt; to &lt;code&gt;k_1&lt;/code&gt; and &lt;code&gt;k_1&lt;/code&gt; to &lt;code&gt;k_0&lt;/code&gt;, but the matrices &lt;code&gt;W_V^0 W_O^0 W_V^1 W_O^1&lt;/code&gt; form a a new, composed OV circuit that operates directly on the information at &lt;code&gt;x_{k_0}&lt;/code&gt;. We&amp;apos;ve created a &amp;quot;virtual head&amp;quot; that has the attention pattern of &lt;code&gt;H1&lt;/code&gt; but an OV circuit defined by both heads.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Implication:&lt;/strong&gt; Head composition is the primary mechanism for building complex, multi-step algorithms in Transformers. It explains how models can perform tasks that are impossible for a single layer.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Implication:&lt;/strong&gt; It allows us to reason about models as computational graphs where heads are nodes and composition links are edges, leading to a much richer understanding of model behavior.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit:&lt;/strong&gt; This clean decomposition is, as always, complicated by LayerNorm, which mixes all parts of the residual stream together non-linearly. The real interactions are not quite as simple as adding up path contributions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit:&lt;/strong&gt; FFN/MLP layers also compose with heads and with each other. A full circuit analysis must account for their role, which is often less clear than that of attention heads.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Pitfalls&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Viewing heads as independent agents.&lt;/strong&gt;&lt;code&gt;H1&lt;/code&gt; and &lt;code&gt;H0&lt;/code&gt; are not collaborating consciously. They are a single system optimized end-to-end. &lt;code&gt;H1&lt;/code&gt; learns to use &lt;code&gt;H0&lt;/code&gt;&amp;apos;s output because the joint behavior is useful for minimizing the loss function. There is no guarantee &lt;code&gt;H0&lt;/code&gt;&amp;apos;s &amp;quot;purpose&amp;quot; is cleanly separable from &lt;code&gt;H1&lt;/code&gt;&amp;apos;s.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Assuming one type of composition per head pair.&lt;/strong&gt; A single head pair (&lt;code&gt;H0&lt;/code&gt;, &lt;code&gt;H1&lt;/code&gt;) can and often does exhibit all three types of composition simultaneously. &lt;code&gt;H0&lt;/code&gt;&amp;apos;s output can influence &lt;code&gt;H1&lt;/code&gt;&amp;apos;s Q, K, and V paths all at once.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Composition is how Transformers build complex algorithms from simple components, and it is the key reason for the power of model depth.  &lt;/li&gt;&lt;li&gt; The three types of composition-Q, K, and V-provide a vocabulary for describing how heads in different layers interact.  &lt;/li&gt;&lt;li&gt; Q-Composition allows a head&amp;apos;s search query to be context-dependent.  &lt;/li&gt;&lt;li&gt; K-Composition allows a token&amp;apos;s representation for search purposes to be context-dependent, enabling induction heads.  &lt;/li&gt;&lt;li&gt; V-Composition allows a head to move information from sources it isn&amp;apos;t directly attending to, creating powerful &amp;quot;virtual heads.&amp;quot;  &lt;/li&gt;&lt;li&gt; Analyzing these compositional circuits is a core activity in mechanistic interpretability.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot;&gt;Part 4 Reverse Engineering Transformers: Induction Heads&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt;Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt;Part 1: Reverse Engineering Transformers: Deconstructing Attention&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt; Part 1: Reverse Engineering Transformers: Deconstructing Attention &lt;/a&gt;&lt;/div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Part 2: Reverse Engineering Transformers: Attention Heads &amp; Circuits</title>
      <link>https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt; Other Parts in the series: &lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt;&lt;span&gt;Part 1: Reverse Engineering Transformers: Deconstructing Attention&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot;&gt;&lt;span&gt;Part 3 Reverse Engineering Transformers: Path Expansion&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot;&gt;&lt;span&gt;Part 4 Reverse Engineering Transformers: Induction Heads&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;&lt;span&gt;Transformer Circuits II: Attention Heads as Independent Operators&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt; The standard implementation of multi-head attention concatenates head outputs and uses a single large output projection (&lt;code&gt;W_O&lt;/code&gt;).  &lt;/li&gt;&lt;li&gt; We can mathematically reframe this as summing the outputs of independent heads, each with its own smaller output projection matrix (&lt;code&gt;W_O^h&lt;/code&gt;).  &lt;/li&gt;&lt;li&gt; This reframing is key for interpretability: it allows us to analyze the contribution of each attention head to the residual stream in isolation.  &lt;/li&gt;&lt;li&gt; An attention head&amp;apos;s &amp;quot;write&amp;quot; to the residual stream is its value-weighted attention pattern, projected back into &lt;code&gt;d_model&lt;/code&gt; space by its personal &lt;code&gt;W_O^h&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; While heads in a layer compute in parallel, they are not fully independent; they read from the same source and their outputs may compete for capacity in the next layer&amp;apos;s residual stream.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;In our previous post, we established the &lt;strong&gt;residual stream&lt;/strong&gt; as the central communication bus of a Transformer. We saw it as an assembly line where components &amp;quot;read&amp;quot; from the stream and &amp;quot;write&amp;quot; additive updates back to it. Our diagram represented &amp;quot;Attention&amp;quot; as a single block that performed one of these read-write operations.&lt;/p&gt;&lt;p&gt;Now, we need to look inside that block. A standard Transformer layer contains &lt;em&gt;multi-head&lt;/em&gt; attention. The typical textbook explanation involves concatenating the outputs of all heads and projecting them back to the model dimension with a single output matrix, &lt;code&gt;W_O&lt;/code&gt;. This implementation view is computationally efficient, but it&amp;apos;s messy for interpretation. It mixes the outputs of all heads together, making it hard to ask: &amp;quot;What was the specific contribution of head 7?&amp;quot;&lt;/p&gt;&lt;p&gt;This post shows that we can adopt a more theoretically convenient, yet mathematically equivalent, perspective: the attention layer&amp;apos;s total output is simply the &lt;strong&gt;sum&lt;/strong&gt; of the outputs of each individual head. This allows us to treat each head as an independent operator writing to the residual stream, making our reverse engineering task dramatically simpler.&lt;/p&gt;&lt;h4&gt;Prereqs&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Familiarity with the concepts of Query, Key, and Value in attention.  &lt;/li&gt;&lt;li&gt; Understanding of the residual stream as a communication bus (from Part I).  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Core Idea in One Picture&lt;/h4&gt;&lt;p&gt;The key is recognizing that the standard, monolithic attention output projection can be split into a sum of per-head contributions.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;graph TD
    subgraph Standard View (Implementation)
        H1[Head 1 Out] --&amp;gt; C;
        H2[Head 2 Out] --&amp;gt; C;
        Hn[... Head N Out] --&amp;gt; C;
        C(Concat) --&amp;gt; WO1[W_O];
        WO1 --&amp;gt; LayerOut1[Layer Output];
    end

    subgraph Equivalent View (Interpretation)
        H1_i[Head 1 Out] --&amp;gt; WO_h1[W_O^1];
        H2_i[Head 2 Out] --&amp;gt; WO_h2[W_O^2];
        Hn_i[... Head N Out] --&amp;gt; WO_hn[W_O^n];
        WO_h1 --&amp;gt; S((+));
        WO_h2 --&amp;gt; S;
        WO_hn --&amp;gt; S;
        S --&amp;gt; LayerOut2[Layer Output];
    end

    LayerOut1 -- Mathematically Identical --&amp;gt; LayerOut2;

    style H1 fill:#f9f,stroke:#333
    style H2 fill:#f9f,stroke:#333
    style Hn fill:#f9f,stroke:#333
    style H1_i fill:#f9f,stroke:#333
    style H2_i fill:#f9f,stroke:#333
    style Hn_i fill:#f9f,stroke:#333
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Figure 1: The standard implementation view (left) concatenates head outputs before a single projection. The interpretation view (right) shows this is equivalent to summing the outputs of heads projected by their own virtual output matrices.&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;Definitions &amp;amp; Setup&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s formalize the two views. We assume a model with &lt;code&gt;n_heads&lt;/code&gt; heads and a head dimension &lt;code&gt;d_head&lt;/code&gt;. The model dimension is &lt;code&gt;d_model&lt;/code&gt;, where typically &lt;code&gt;d_model = n_heads * d_head&lt;/code&gt;. The input to the layer from the residual stream is &lt;code&gt;x&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. The Standard View (Implementation)&lt;/strong&gt;&lt;br&gt; Each head &lt;code&gt;h&lt;/code&gt; produces an output vector &lt;code&gt;o_h&lt;/code&gt; of shape &lt;code&gt;[seq_len, d_head]&lt;/code&gt;. These are concatenated along the last dimension to form a single large tensor &lt;code&gt;O_cat&lt;/code&gt; of shape &lt;code&gt;[seq_len, n_heads * d_head]&lt;/code&gt;. This tensor is then projected back to the residual stream&amp;apos;s dimension by the output weight matrix &lt;code&gt;W_O&lt;/code&gt;, which has shape &lt;code&gt;[n_heads * d_head, d_model]&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;code&gt;Output = Concat(o_1, o_2, ..., o_{n_heads}) @ W_O&lt;/code&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. The Theoretically Convenient View (Interpretation)&lt;/strong&gt;&lt;br&gt; We can conceptually partition the large &lt;code&gt;W_O&lt;/code&gt; matrix into &lt;code&gt;n_heads&lt;/code&gt; smaller matrices, &lt;code&gt;W_O^h&lt;/code&gt;, one for each head. Each &lt;code&gt;W_O^h&lt;/code&gt; has shape &lt;code&gt;[d_head, d_model]&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The total output is then the sum of each head&amp;apos;s output projected by its corresponding &lt;code&gt;W_O^h&lt;/code&gt;: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Output=&amp;#x2211;h=1nheadsoh@WOh\text{Output} = \sum_{h=1}^{n_{\text{heads}}} o_h @ W_O^h&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Output&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2211;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;heads&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt; The output of a single head, &lt;code&gt;o_h @ W_O^h&lt;/code&gt;, is that head&amp;apos;s direct, additive contribution to the residual stream. This is exactly the &amp;quot;write update&amp;quot; we discussed in Part I.&lt;/p&gt;&lt;h4&gt;Walkthrough&lt;/h4&gt;&lt;h4&gt;Step 1: Proof of Equivalence&lt;/h4&gt;&lt;p&gt;Why are these two views identical? Let&amp;apos;s look at the structure of the matrices. The &lt;code&gt;Concat(o_1, ..., o_n)&lt;/code&gt; operation creates a wide matrix. The &lt;code&gt;W_O&lt;/code&gt; matrix is correspondingly tall.&lt;/p&gt;&lt;p&gt;Let &lt;code&gt;O_cat = [o_1 | o_2 | ...]&lt;/code&gt; be the row-wise concatenation. Let &lt;code&gt;W_O&lt;/code&gt; be the block matrix composed of the &lt;code&gt;W_O^h&lt;/code&gt; matrices stacked vertically: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;WO=(WO1WO2&amp;#x22ee;WOnheads)\mathbf{W_O} = \begin{pmatrix} \mathbf{W_O^1} \\ \mathbf{W_O^2} \\ \vdots \\ \mathbf{W_O^{n_{heads}}} \end{pmatrix}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22ee;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;heads&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt; The matrix multiplication &lt;code&gt;O_cat @ W_O&lt;/code&gt; is then: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;[o1&amp;#x2223;o2&amp;#x2223;&amp;#x2026;&amp;#x2009;](WO1WO2&amp;#x22ee;)=o1WO1+o2WO2+&amp;#x22ef;=&amp;#x2211;hohWOh[\mathbf{o_1} | \mathbf{o_2} | \dots] \begin{pmatrix} \mathbf{W_O^1} \\ \mathbf{W_O^2} \\ \vdots \end{pmatrix} = \mathbf{o_1 W_O^1} + \mathbf{o_2 W_O^2} + \dots = \sum_{h} \mathbf{o_h W_O^h}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x2026;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22ee;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x22ef;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2211;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;em&gt;(Note: This is a slight abuse of notation for clarity; the multiplication is properly defined by partitioning columns and rows.)&lt;/em&gt;&lt;/p&gt;&lt;h4&gt;Step 2: A Minimal Experiment&lt;/h4&gt;&lt;p&gt;We can verify this with a small PyTorch snippet. We&amp;apos;ll simulate the outputs of two heads and show the two methods produce the same result.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; torch&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Setup: 1 token, 2 heads, d_head=4, d_model=8&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;seq_len, n_heads, d_head = 1, 2, 4&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;d_model = n_heads &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; d_head&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Random head outputs&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;o_1 = torch.randn(seq_len, d_head) # Output of head 1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;o_2 = torch.randn(seq_len, d_head) # Output of head 2&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Big W_O matrix for the standard view&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W_O = torch.randn(d_model, d_model)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# 1. Standard View: Concatenate and multiply&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;o_cat = torch.cat([o_1, o_2], dim=-1) # Shape:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;output_standard = o_cat @ W_O&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# 2. Interpretation View: Split W_O and sum&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W_O1 = W_O[:d_head, :]  # Shape:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W_O2 = W_O[d_head:, :]  # Shape:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;output_interp = (o_1 @ W_O1) + (o_2 @ W_O2)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Check if they are nearly identical&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print(&lt;/span&gt;&lt;span&gt;&amp;quot;Standard output:&amp;quot;&lt;/span&gt;&lt;span&gt;, output_standard)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print(&lt;/span&gt;&lt;span&gt;&amp;quot;Interpretation output:&amp;quot;&lt;/span&gt;&lt;span&gt;, output_interp)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;print(&lt;/span&gt;&lt;span&gt;&amp;quot;Outputs are close:&amp;quot;&lt;/span&gt;&lt;span&gt;, torch.allclose(output_standard, output_interp))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;# Outputs are close: True&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Step 3: Why This Reframing is Powerful&lt;/h4&gt;&lt;p&gt;This equivalence is the foundation for analyzing individual heads. We can now cleanly state what a single head &lt;code&gt;h&lt;/code&gt; does:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Read:&lt;/strong&gt; It forms its Query, Key, and Value vectors by projecting from the LayerNorm&amp;apos;d residual stream: &lt;code&gt;q = x_norm @ W_Q^h&lt;/code&gt;, &lt;code&gt;k = x_norm @ W_K^h&lt;/code&gt;, &lt;code&gt;v = x_norm @ W_V^h&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Compute:&lt;/strong&gt; It calculates a weighted sum of Value vectors based on Query-Key similarity, producing its output &lt;code&gt;o_h&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Write:&lt;/strong&gt; It projects its output &lt;code&gt;o_h&lt;/code&gt; via its personal output matrix &lt;code&gt;W_O^h&lt;/code&gt; to create its total contribution &lt;code&gt;&amp;#x394;_h = o_h @ W_O^h&lt;/code&gt;, which is added directly to the residual stream.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This allows us to ask targeted questions like, &amp;quot;What information does head 8.3 read from the &amp;apos;Paris&amp;apos; token to write to the &amp;apos;capital&amp;apos; token?&amp;quot; and answer it by analyzing only the matrices &lt;code&gt;W_Q^{8.3}&lt;/code&gt;, &lt;code&gt;W_K^{8.3}&lt;/code&gt;, &lt;code&gt;W_V^{8.3}&lt;/code&gt;, and &lt;code&gt;W_O^{8.3}&lt;/code&gt;. We have successfully decomposed a layer-level operation into a sum of head-level operations.&lt;/p&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Implication:&lt;/strong&gt; The fundamental unit of analysis for attention inside a Transformer can be the &lt;strong&gt;individual head&lt;/strong&gt;, not the entire layer. This makes reverse engineering tractable. We can study circuits composed of specific heads in different layers.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limits:&lt;/strong&gt; The heads within a layer are not &lt;em&gt;truly&lt;/em&gt; independent. &lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Shared Input:&lt;/strong&gt; They all read from the exact same normalized residual stream &lt;code&gt;x_norm&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Shared Output:&lt;/strong&gt; Their outputs are all summed into the same residual stream. A powerful update from one head could change the vector&amp;apos;s direction or magnitude in a way that affects how it is processed by the LayerNorm in the &lt;em&gt;next&lt;/em&gt; block.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Pitfalls&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Confusing parallel computation with total independence.&lt;/strong&gt; Just because heads are computed in parallel doesn&amp;apos;t mean they don&amp;apos;t influence each other&amp;apos;s operating environment in subsequent layers. The additive nature of their outputs means they are in a &amp;quot;linear superposition&amp;quot; within the stream, but the subsequent LayerNorm is non-linear and will mix their signals.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; A multi-head attention layer&amp;apos;s output is mathematically equivalent to the sum of individual head outputs.  &lt;/li&gt;&lt;li&gt; Each head can be thought of as having its own input (&lt;code&gt;W_Q, W_K, W_V&lt;/code&gt;) and output (&lt;code&gt;W_O&lt;/code&gt;) matrices.  &lt;/li&gt;&lt;li&gt; This decomposition allows us to isolate and analyze the function of a single attention head.  &lt;/li&gt;&lt;li&gt; A head&amp;apos;s function is to read information from the residual stream, transform it via attention, and write an update back to the stream.  &lt;/li&gt;&lt;li&gt; This &amp;quot;independent, additive&amp;quot; view is a cornerstone of the circuits framework for understanding Transformers.  &lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;&lt;span&gt;Transformer Circuits III: The QK and OV Circuits Inside an Attention Head&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt; An individual attention head&amp;apos;s operation can be decomposed into two distinct sub-circuits: the &lt;strong&gt;QK-Circuit&lt;/strong&gt; and the &lt;strong&gt;OV-Circuit&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt; The &lt;strong&gt;QK-Circuit&lt;/strong&gt; determines the attention pattern-&lt;em&gt;where&lt;/em&gt; the head looks. It uses Query and Key vectors to decide which source tokens to get information from.  &lt;/li&gt;&lt;li&gt; The &lt;strong&gt;OV-Circuit&lt;/strong&gt; determines the content being moved-&lt;em&gt;what&lt;/em&gt; information the head moves. It uses Value vectors and the head&amp;apos;s Output projection to specify the update written to the residual stream.  &lt;/li&gt;&lt;li&gt; This separation is a powerful analytical tool, allowing us to categorize heads by their QK behavior (e.g., &amp;quot;previous token heads&amp;quot;) and their OV behavior (e.g., &amp;quot;copying heads&amp;quot;) separately.  &lt;/li&gt;&lt;li&gt; The full effect of the OV-Circuit on the residual stream is captured by the composed matrix &lt;code&gt;W_V @ W_O&lt;/code&gt;, which maps features from a source token&amp;apos;s stream to the output update.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;In our previous posts, we established that a Transformer layer&amp;apos;s attention is best understood as a sum of contributions from independent heads, each writing an update to the residual stream. This was a crucial simplification, allowing us to isolate a single head for analysis.&lt;/p&gt;&lt;p&gt;Now, we zoom in further. What algorithm does a single head execute? An attention head performs a complex operation: for a given &amp;quot;query&amp;quot; token, it looks at all other &amp;quot;key&amp;quot; tokens, computes similarity scores, and then creates a weighted sum of their &amp;quot;value&amp;quot; vectors. This suggests two sub-tasks are happening at once:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Deciding which tokens are relevant (&lt;strong&gt;where to look&lt;/strong&gt;).  &lt;/li&gt;&lt;li&gt; Extracting and moving useful information from them (&lt;strong&gt;what to move&lt;/strong&gt;).  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;The circuits framework makes this separation explicit, decomposing a head into a &lt;strong&gt;Query-Key (QK) circuit&lt;/strong&gt; and an &lt;strong&gt;Output-Value (OV) circuit&lt;/strong&gt;. By analyzing these two parts, we can achieve a much more precise understanding of a head&amp;apos;s role in the model&amp;apos;s overall algorithm.&lt;/p&gt;&lt;h4&gt;Prereqs&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Understanding the residual stream as a communication bus.  &lt;/li&gt;&lt;li&gt; The view of attention layers as a sum of independent head outputs.  &lt;/li&gt;&lt;li&gt; Basic knowledge of the Query, Key, and Value formulation of attention.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Core Idea in One Picture&lt;/h4&gt;&lt;p&gt;An attention head can be split into two parallel information paths: one calculates attention scores, the other prepares the information to be moved. They only combine at the very end.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;graph TD
    subgraph Attention Head h
        direction LR
        x_norm[Normed Residual Stream]

        subgraph QK-Circuit (Where to look)
            x_norm -- W_Q^h --&amp;gt; Q[Query];
            x_norm -- W_K^h --&amp;gt; K[Key];
            Q --&amp;gt; AS(Attention Scores);
            K --&amp;gt; AS;
        end

        subgraph OV-Circuit (What to move)
           x_norm -- W_V^h --&amp;gt; V[Value];
           V -- W_O^h --&amp;gt; Projected_V[Projected Value];
        end

        AS -- Softmax --&amp;gt; A[Attention Pattern: A];
        A --&amp;gt; Combine((@));
        Projected_V --&amp;gt; Combine;
        Combine --&amp;gt; Output[Output &amp;#x394;_h];
    end

    Output --&amp;gt; RS_Update{+ Add to Residual Stream};
    ```&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;Figure 1: An attention head decomposed into its QK and OV circuits. The QK circuit produces the attention pattern `A`, which acts as a set of weights. The OV circuit produces the information to be moved. The final output is a weighted sum of the projected values&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Definitions &amp;amp; Setup&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s formalize the two circuits for a single head &lt;code&gt;h&lt;/code&gt;. The input from the previous layer is the normalized residual stream, &lt;code&gt;x_norm&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;1. The QK-Circuit (Attention Pattern)&lt;/h4&gt;&lt;p&gt;The QK-circuit&amp;apos;s job is to compute a scalar attention score between a query token &lt;code&gt;t_q&lt;/code&gt; and a key token &lt;code&gt;t_k&lt;/code&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Inputs:&lt;/strong&gt; Residual stream vectors &lt;code&gt;x_q&lt;/code&gt; and &lt;code&gt;x_k&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Matrices:&lt;/strong&gt;&lt;code&gt;W_Q^h&lt;/code&gt; (Query) and &lt;code&gt;W_K^h&lt;/code&gt; (Key), both of shape &lt;code&gt;[d_model, d_head]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Computation:&lt;/strong&gt; The attention score &lt;code&gt;s_{q,k}&lt;/code&gt; is computed via a dot product. &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;sq,k=(xqWQh)&amp;#x22c5;(xkWKh)dheads_{q,k} = \frac{(\mathbf{x_q} \mathbf{W_Q^h}) \cdot (\mathbf{x_k} \mathbf{W_K^h})}{\sqrt{d_{head}}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Q&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;K&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Output:&lt;/strong&gt; A sequence of scores, which are passed through a softmax function to create the final attention pattern &lt;code&gt;A&lt;/code&gt;. This pattern is a set of probabilities summing to 1, indicating where the head &amp;quot;looks&amp;quot;.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The QK-circuit is fundamentally a &lt;strong&gt;search and matching&lt;/strong&gt; mechanism.&lt;/p&gt;&lt;h4&gt;2. The OV-Circuit (Content Movement)&lt;/h4&gt;&lt;p&gt;The OV-circuit&amp;apos;s job is to determine what information is moved from a source token &lt;code&gt;t_v&lt;/code&gt; and how it is written to the destination token&amp;apos;s residual stream.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Input:&lt;/strong&gt; The residual stream vector &lt;code&gt;x_v&lt;/code&gt; from a source token.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Matrices:&lt;/strong&gt;&lt;code&gt;W_V^h&lt;/code&gt; (Value) of shape &lt;code&gt;[d_model, d_head]&lt;/code&gt; and the head&amp;apos;s effective output matrix &lt;code&gt;W_O^h&lt;/code&gt; of shape &lt;code&gt;[d_head, d_model]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Computation:&lt;/strong&gt; For a single source token &lt;code&gt;t_v&lt;/code&gt;, the &amp;quot;information payload&amp;quot; it offers is the projected value: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Payloadv=(xvWVh)WOh\text{Payload}_v = (\mathbf{x_v} \mathbf{W_V^h}) \mathbf{W_O^h}
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Payload&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;O&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Output:&lt;/strong&gt; A &lt;code&gt;d_model&lt;/code&gt;dimensional vector. This is the update that will be added to the destination token&amp;apos;s residual stream if this token receives 100% of the attention.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The OV-circuit is a &lt;strong&gt;feature extraction and transformation&lt;/strong&gt; mechanism. The composed matrix &lt;code&gt;W_V^h W_O^h&lt;/code&gt; can be seen as a single &lt;code&gt;[d_model, d_model]&lt;/code&gt; operation that specifies &amp;quot;if you attend to this token, this is the update you will get.&amp;quot;&lt;/p&gt;&lt;h4&gt;Walkthrough&lt;/h4&gt;&lt;h4&gt;Step 1: Intuition in a Case Study&lt;/h4&gt;&lt;p&gt;Consider the prompt &amp;quot;The Roman Empire fell in 476 AD. This date...&amp;quot; and a hypothetical head in a later layer processing the token &amp;quot;date&amp;quot;.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;QK-Circuit in action:&lt;/strong&gt; We want this head to find the actual date. The QK circuit at the &amp;quot;date&amp;quot; token must learn to attend to tokens that are numbers and correspond to years. &lt;ul&gt;&lt;li&gt; The Query vector from &amp;quot;date&amp;quot; (&lt;code&gt;q = x_{date} W_Q&lt;/code&gt;) must be a vector that means something like &amp;quot;I&amp;apos;m looking for a year.&amp;quot;  &lt;/li&gt;&lt;li&gt; The Key vector from &amp;quot;476&amp;quot; (&lt;code&gt;k = x_{476} W_K&lt;/code&gt;) must be a vector that means &amp;quot;I am a year.&amp;quot;  &lt;/li&gt;&lt;li&gt; The dot product &lt;code&gt;q &amp;#xb7; k&lt;/code&gt; will be high, resulting in high attention to &amp;quot;476&amp;quot;.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;OV-Circuit in action:&lt;/strong&gt; Once the head is attending to &amp;quot;476&amp;quot;, what information should it move? It should probably move the semantic concept of &amp;quot;the year is 476&amp;quot;. &lt;ul&gt;&lt;li&gt; The OV-circuit takes the residual stream at &amp;quot;476&amp;quot; (&lt;code&gt;x_{476}&lt;/code&gt;). This vector already contains information about the token being a number.  &lt;/li&gt;&lt;li&gt; It computes the information payload: &lt;code&gt;(x_{476} W_V) W_O&lt;/code&gt;. This operation might read the &amp;quot;is a number: 476&amp;quot; feature from &lt;code&gt;x_{476}&lt;/code&gt; and transform it into a more abstract feature vector like &lt;code&gt;+references_year(476)&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; This payload vector is then added to the residual stream at the &amp;quot;date&amp;quot; token, enriching its representation.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h4&gt;Step 2: How to Inspect These Circuits&lt;/h4&gt;&lt;p&gt;This decomposition isn&amp;apos;t just a metaphor; it gives us concrete matrices to inspect.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;To analyze the QK-Circuit:&lt;/strong&gt; We can study the matrix &lt;code&gt;W_QK = W_Q^T W_K&lt;/code&gt;. This &lt;code&gt;[d_model, d_model]&lt;/code&gt; matrix reveals the structure of attention patterns. A large positive value at &lt;code&gt;(i, j)&lt;/code&gt; in this matrix means that if feature &lt;code&gt;i&lt;/code&gt; is present at the query token and feature &lt;code&gt;j&lt;/code&gt; is present at the key token, the attention score will be high. This allows us to find patterns like &amp;quot;attend to the previous token&amp;quot; or &amp;quot;attend to tokens that are nouns&amp;quot;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;To analyze the OV-Circuit:&lt;/strong&gt; We study the composed matrix &lt;code&gt;W_OV = W_V W_O&lt;/code&gt;. This &lt;code&gt;[d_model, d_model]&lt;/code&gt; matrix tells us what kind of information processing the head performs. We can ask: if the input vector &lt;code&gt;x_v&lt;/code&gt; has a feature in direction &lt;code&gt;d_1&lt;/code&gt;, what features does the output vector &lt;code&gt;(x_v W_{OV})&lt;/code&gt; contain? For a &amp;quot;copying&amp;quot; head, this matrix would look similar to the identity matrix, meaning it moves information without much transformation. For other heads, it might map &amp;quot;proper noun&amp;quot; features to &amp;quot;capital city&amp;quot; features.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Implication: A Taxonomy of Heads.&lt;/strong&gt; This separation allows us to categorize heads. For example, many models learn &amp;quot;previous token heads&amp;quot; whose QK circuit reliably attends to the token at &lt;code&gt;position - 1&lt;/code&gt;. However, these heads can have very different OV circuits: one might copy the previous token&amp;apos;s embedding, while another might check if the previous token was a verb.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limit: Not Fully Independent.&lt;/strong&gt; The circuits are optimized jointly during training. The gradients flowing back to &lt;code&gt;W_Q&lt;/code&gt; and &lt;code&gt;W_K&lt;/code&gt; depend on how useful the information moved by the OV-circuit was for the final task. A QK-circuit has no reason to create a sharp, meaningful attention pattern if the corresponding OV-circuit provides useless information. They co-evolve.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Pitfalls&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Analyzing &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;W_V&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; in isolation:&lt;/strong&gt; A common error is to just look at &lt;code&gt;W_V&lt;/code&gt; to understand what information is being moved. &lt;code&gt;W_V&lt;/code&gt; projects into the &lt;code&gt;d_head&lt;/code&gt;dimensional head-space, which is often uninterpretable. You must compose it with &lt;code&gt;W_O&lt;/code&gt; to see the head&amp;apos;s full effect on the &lt;code&gt;d_model&lt;/code&gt;dimensional residual stream.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Over-interpreting Q/K vectors:&lt;/strong&gt; It is tempting to think of &lt;code&gt;q&lt;/code&gt; and &lt;code&gt;k&lt;/code&gt; vectors as having rich semantic meaning. It&amp;apos;s often more accurate to think of them as specialized pointers or search keys, designed only for the purpose of dot-product matching. The actual semantic content is carried by the &lt;code&gt;v&lt;/code&gt; vectors.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; An attention head can be productively decomposed into a QK-circuit (where to look) and an OV-circuit (what to move).  &lt;/li&gt;&lt;li&gt; The QK-circuit&amp;apos;s behavior is summarized by the composed matrix &lt;code&gt;W_Q^T W_K&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; The OV-circuit&amp;apos;s behavior is summarized by the composed matrix &lt;code&gt;W_V W_O&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt; This decomposition provides a powerful framework for categorizing and analyzing the function of individual heads.  &lt;/li&gt;&lt;li&gt; It simplifies the problem of understanding a head into two smaller, more focused problems.  &lt;/li&gt;&lt;li&gt; While analytically useful, the two circuits are not truly independent as they are co-optimized during training.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot;&gt;Part 4 Reverse Engineering Transformers: Induction Heads&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot;&gt;Part 3 Reverse Engineering Transformers: Path Expansion&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt;Part 1: Reverse Engineering Transformers: Deconstructing Attention&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot;&gt; Part 1: Reverse Engineering Transformers: Deconstructing Attention &lt;/a&gt;&lt;/div&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Part 1: Reverse Engineering Transformers: Deconstructing Attention</title>
      <link>https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/</guid>
      <description>Blog series on how understanding the residual stream and it’s components are important for MechInterp</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>readings</category>
      <category>#CMSC848R</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-1-reverse-engineering-transformers-deconstructing-attention/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;q&gt;Blog series on how understanding the residual stream and it&amp;#x2019;s components are important for MechInterp&lt;/q&gt;&lt;div&gt;&lt;p&gt;&lt;time&gt; October 16, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/readings/&quot;&gt; readings &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/hashtag-cmsc848r/&quot;&gt; #CMSC848R &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &amp;#x1f4a1; &lt;/div&gt;&lt;div&gt; Other parts in the series: &lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt;&lt;span&gt;Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot;&gt;&lt;span&gt;Part 3 Reverse Engineering Transformers: Path Expansion&lt;/span&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot;&gt;&lt;span&gt;Part 4 Reverse Engineering Transformers: Induction Heads&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;This is a 4 part blog series on my notes and understanding of the foundational paper &lt;a href=&quot;https://transformer-circuits.pub/2021/framework/index.html&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Elhage, N. et al. (2021). A Mathematical Framework for Transformer Circuits&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The blogs will be discussing the following ideas:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Reverse Engineering Transformers:&lt;/strong&gt;&amp;#xa0;The primary goal is to break down the complex computations within a transformer into understandable &amp;quot;circuits&amp;quot; or algorithmic patterns. This is analogous to reverse-engineering a compiled program to understand its source code.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Residual Stream as a Communication Channel:&lt;/strong&gt;&amp;#xa0;The paper conceptualizes the residual stream not just as an embedding, but as a central communication channel. Different components of the transformer (like attention heads and MLP layers) read from and write information to this shared space.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Attention Heads as Independent, Additive Operations:&lt;/strong&gt;&amp;#xa0;The authors reframe attention layers as a collection of independent attention heads that operate in parallel and add their outputs to the residual stream. This is a more theoretically convenient way to think about them than the typical &amp;quot;concatenate and multiply&amp;quot; view.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Separating Attention Head Components:&lt;/strong&gt;&amp;#xa0;The paper proposes that attention heads can be broken down into two largely independent circuits: &lt;ul&gt;&lt;li&gt;&lt;strong&gt;QK (Query-Key) Circuit:&lt;/strong&gt;&amp;#xa0;This determines the&amp;#xa0;&lt;em&gt;attention pattern&lt;/em&gt;, i.e., which tokens to move information&amp;#xa0;&lt;em&gt;from&lt;/em&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;OV (Output-Value) Circuit:&lt;/strong&gt;&amp;#xa0;This determines&amp;#xa0;&lt;em&gt;what information&lt;/em&gt;&amp;#xa0;is moved and how it&amp;apos;s written to the destination.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Path Expansion:&lt;/strong&gt;&amp;#xa0;A key analytical technique is to expand the transformer&amp;apos;s computation into a sum of end-to-end paths from input tokens to output logits. This allows for a more granular analysis of how information flows and is transformed.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Composition of Attention Heads:&lt;/strong&gt;&amp;#xa0;In multi-layer transformers, the paper explores how attention heads can compose with each other. This composition is what gives deeper transformers their power. The three types of composition are: &lt;ul&gt;&lt;li&gt;&lt;strong&gt;Q-Composition:&lt;/strong&gt;&amp;#xa0;A second-layer head&amp;apos;s query is influenced by a first-layer head.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;K-Composition:&lt;/strong&gt;&amp;#xa0;A second-layer head&amp;apos;s key is influenced by a first-layer head.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;V-Composition:&lt;/strong&gt;&amp;#xa0;A second-layer head&amp;apos;s value is influenced by a first-layer head, creating &amp;quot;virtual attention heads&amp;quot;.  &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Induction Heads:&lt;/strong&gt;&amp;#xa0;A specific and powerful circuit that emerges in two-layer (or deeper) models. These heads are a mechanism for in-context learning, allowing the model to recognize repeated sequences and continue them. They work by searching for previous occurrences of the current token and then attending to the&amp;#xa0;&lt;em&gt;next&lt;/em&gt;&amp;#xa0;token in that sequence.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Circuits, Residual Streams, and Induction Heads&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;p&gt;We start with the first two points in this blog&lt;/p&gt;&lt;h2&gt;What is Reverse Engineering in Mechanistic Interpretability?&lt;/h2&gt;&lt;p&gt;At its core, mechanistic interpretability (MI) is the scientific field of &lt;em&gt;reverse engineering&lt;/em&gt; neural networks. The goal is to take a fully trained model, a complex and opaque mathematical function defined by billions of parameters, and understand the specific, human-understandable algorithms it has learned to perform its tasks.&lt;/p&gt;&lt;p&gt;Think of it this way:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A traditional computer program&lt;/strong&gt; starts with human-written source code (like Python). It&amp;apos;s then compiled into a low-level binary file that a machine can execute. A reverse engineer would take that binary and try to reconstruct the original logic and algorithms.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;A neural network&lt;/strong&gt; starts with a chosen architecture (the &amp;quot;virtual machine&amp;quot;) and is trained on data. This process results in a set of optimized weights (the &amp;quot;program binary&amp;quot;). A mechanistic interpretability researcher takes those weights and tries to understand the algorithms they implement.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This analogy is quite deep and helps frame the entire endeavor.&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt; Traditional Computing &lt;/th&gt;&lt;th scope=&quot;col&quot;&gt; Mechanistic Interpretability of Neural Networks &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; Program Binary &lt;/td&gt;&lt;td&gt; Network Parameters (Weights and Biases) &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; VM / Processor &lt;/td&gt;&lt;td&gt; Network Architecture (e.g., Transformer) &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Program State / Memory &lt;/td&gt;&lt;td&gt; Layer Representations / Activations &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; Variable / Memory Location &lt;/td&gt;&lt;td&gt; Neuron / Feature Direction &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;h3&gt;Why is this Analogy So Powerful?&lt;/h3&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;It Addresses the Curse of Dimensionality&lt;/strong&gt;&lt;p&gt;Neural networks operate on incredibly high-dimensional inputs. Trying to understand a model by mapping out its behavior for every possible input is impossible. The reverse engineering approach sidesteps this by instead focusing on understanding the &lt;em&gt;finite description&lt;/em&gt; of the program itself-the model&amp;apos;s parameters. Just as a programmer can understand a large piece of software without running every possible input, an MI researcher aims to understand the finite set of weights that define the network&amp;apos;s behavior.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;It Sets Realistic Expectations&lt;/strong&gt;&lt;p&gt;Reverse engineering a complex binary like a modern operating system is incredibly difficult and requires painstaking, detailed work. Similarly, we shouldn&amp;apos;t expect to find a simple, one-sentence explanation for how a large language model works. Mechanistic interpretability is expected to be a slow, iterative, and challenging scientific process.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;It Frames the Core Challenge-Understanding &amp;quot;Variables&amp;quot;&lt;/strong&gt;&lt;p&gt;A computer program is made up of operations acting on variables. A statement like &lt;code&gt;y = x + 5&lt;/code&gt; is meaningless unless you know what &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; represent. A reverse engineer must figure out what each piece of the program&amp;apos;s memory represents.&lt;/p&gt;&lt;p&gt;In neural networks, the &amp;quot;variables&amp;quot; are the &lt;strong&gt;activations&lt;/strong&gt; within the model. The weights are the &amp;quot;instructions&amp;quot; that describe how previous activations affect later ones. To understand the weights, we must first understand the activations they operate on.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h3&gt;The Central Task: Decomposing Activations&lt;/h3&gt;&lt;p&gt;The immediate serious challenge: activations are high-dimensional vectors. How can we possibly understand them?&lt;/p&gt;&lt;p&gt;This is where the concept of a &lt;strong&gt;privileged basis&lt;/strong&gt; becomes critical. The hope is that a network&amp;apos;s internal representations aren&amp;apos;t just arbitrary directions in a vector space. Instead, MI researchers hypothesize and find evidence that networks often learn to align meaningful, independent &amp;quot;features&amp;quot; with the basis vectors of their activation space (i.e., with individual neurons or specific directions).&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;If a privileged basis exists:&lt;/strong&gt;&lt;p&gt;We can &lt;em&gt;decompose activations into independently understandable pieces&lt;/em&gt;. This is like figuring out that certain bytes in a program&amp;apos;s memory correspond to a player&amp;apos;s health, while other bytes correspond to their score. Each can be understood separately. This makes the task of reverse engineering tractable.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;If a privileged basis &lt;/strong&gt;&lt;em&gt;&lt;strong&gt;doesn&amp;apos;t&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt; exist:&lt;/strong&gt;&lt;p&gt;And every &amp;quot;feature&amp;quot; is stored as some complex linear combination of all neurons (a phenomenon called superposition), the task becomes much harder. It would be like trying to understand a program where a player&amp;apos;s health is determined by the 3rd bit of one byte, the 7th bit of another, and so on, all mixed up.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The papers you provided show that while networks don&amp;apos;t necessarily converge to a single, unique basis across different training runs, the basis they do learn is not arbitrary. You can&amp;apos;t just randomly rotate a layer&amp;apos;s activations and expect the network to recover, which provides strong evidence that the learned basis directions are meaningful and privileged.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;&amp;quot;Reverse engineering&amp;quot; is the guiding metaphor for mechanistic interpretability. It frames the goal as deciphering the learned algorithms from the model&amp;apos;s weights and highlights that the central challenge is to find a way to break down the model&amp;apos;s high-dimensional internal state (activations) into meaningful, understandable variables.&lt;/p&gt;&lt;h2&gt;Transformer Circuits I: The Residual Stream as a Communication Channel&lt;/h2&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; The &lt;strong&gt;residual stream&lt;/strong&gt; is the central data pathway in a Transformer, acting like a communication bus or an assembly line.  &lt;/li&gt;&lt;li&gt; It&amp;apos;s a high-dimensional vector space (&lt;code&gt;d_model&lt;/code&gt;) where information is stored and progressively refined.  &lt;/li&gt;&lt;li&gt; Transformer components (attention, FFNs) don&amp;apos;t replace the stream&amp;apos;s content; they &lt;strong&gt;read&lt;/strong&gt; from it and &lt;strong&gt;write&lt;/strong&gt; additive updates back to it.  &lt;/li&gt;&lt;li&gt; A &lt;strong&gt;&amp;quot;Read&amp;quot;&lt;/strong&gt; is a linear projection from the stream (e.g., to create Q, K, V vectors).  &lt;/li&gt;&lt;li&gt; A &lt;strong&gt;&amp;quot;Write&amp;quot;&lt;/strong&gt; is a linear projection that is added back into the stream (&lt;code&gt;x_new = x_old + update&lt;/code&gt;).  &lt;/li&gt;&lt;li&gt; This additive structure is the key property that allows us to decompose the model&amp;apos;s behavior, but non-linearities like LayerNorm complicate the pure linear view.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Context &amp;amp; Motivation&lt;/h4&gt;&lt;p&gt;A Transformer is a deep stack of identical blocks. A natural question is: how does information from early layers persist and get refined by later layers? The answer lies in the &lt;strong&gt;residual connections&lt;/strong&gt; that are present at every step.&lt;/p&gt;&lt;p&gt;To reverse-engineer a Transformer, we need a robust mental model of its architecture. The most powerful one, proposed by the &amp;quot;circuits&amp;quot; interpretability agenda, is to view the model not as a series of opaque transformations, but as a group of specialists collaborating on a shared workspace. That workspace is the residual stream.&lt;/p&gt;&lt;p&gt;Understanding the stream as a communication bus where components perform additive read/write operations is the first and most critical step. It provides the foundation for decomposing the model into understandable parts, allowing us to trace how specific features are computed from input to output.&lt;/p&gt;&lt;h4&gt;Prereqs&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Basic familiarity with the Transformer architecture (embeddings, attention, FFN/MLP layers).  &lt;/li&gt;&lt;li&gt; Conceptual understanding of vectors and matrices (linear projections).  &lt;/li&gt;&lt;li&gt; Knowledge of PyTorch/NumPy tensor shapes.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Core Idea in One Picture&lt;/h4&gt;&lt;p&gt;Picture the residual stream as an assembly line. For each token, a &amp;quot;package&amp;quot; of information (a vector) moves down the line. At each station, a component reads the package, adds something new, and puts it back on the line.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;graph TD
    A[Input + Pos Embedding] --&amp;gt; B[Residual Stream]

    subgraph Layer0
        B --&amp;gt; Attn0[Attention Head]
        Attn0 --&amp;gt; B
        B --&amp;gt; FFN0[FFN Layer]
        FFN0 --&amp;gt; B
    end

    B --&amp;gt; MoreLayers[...]

    subgraph FinalLayer
        MoreLayers --&amp;gt; B
        B --&amp;gt; Unembed[Unembedding]
        Unembed --&amp;gt; Logits[Output Logits]
    end

    style B fill:#e6f3ff,stroke:#333,stroke-width:2px
&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;Figure 1: The residual stream as a central communication bus. Information for a token starts as an embedding. Each component (Attention, FFN) reads the current stream state and adds its own contribution back. The final, refined state is read by the unembedding matrix to produce logits.&lt;/div&gt;&lt;/div&gt;&lt;h4&gt;Definitions &amp;amp; Setup&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s formalize this. The residual stream is the state &lt;code&gt;x&lt;/code&gt; that is passed from one layer to the next.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Shape:&lt;/strong&gt; The stream is a tensor of shape &lt;code&gt;[batch_size, seq_len, d_model]&lt;/code&gt;, where &lt;code&gt;d_model&lt;/code&gt; is the model&amp;apos;s hidden dimension (e.g., 768 for GPT-2 small). We will focus on the vector of a single token, which has shape &lt;code&gt;[d_model]&lt;/code&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Initial State (&lt;/strong&gt;&lt;code&gt;&lt;strong&gt;x_0&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt; The process starts with the sum of a token&amp;apos;s embedding and its positional embedding. For a token &lt;code&gt;t&lt;/code&gt; at position &lt;code&gt;i&lt;/code&gt;: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x0=WE[t]+Wpos[i]\mathbf{x}0 = \mathbf{W_E}[t] + \mathbf{W{pos}}[i]&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;pos&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Read Operation:&lt;/strong&gt; A component &amp;quot;reads&amp;quot; from the stream by applying a linear projection (i.e., multiplying by a weight matrix). For an attention head, this means creating its Query, Key, and Value vectors from the current stream state &lt;code&gt;x_l&lt;/code&gt;: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q=xlWQ,k=xlWK,v=xlWV\mathbf{q} = \mathbf{x}_l \mathbf{W_Q}, \quad \mathbf{k} = \mathbf{x}_l \mathbf{W_K}, \quad \mathbf{v} = \mathbf{x}_l \mathbf{W_V}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Q&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;K&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Write Operation:&lt;/strong&gt; After a component computes its output (e.g., an attention head&amp;apos;s output &lt;code&gt;o&lt;/code&gt; or an FFN&amp;apos;s output &lt;code&gt;f&lt;/code&gt;), it &amp;quot;writes&amp;quot; this back to the stream as an &lt;strong&gt;additive update&lt;/strong&gt;. &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;xl+1=xl+o\mathbf{x}_{l+1} = \mathbf{x}_l + \mathbf{o}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt; This additive nature is the defining feature of residual connections and our interpretability model.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;The Crucial Role of Layer Normalization&lt;/h4&gt;&lt;p&gt;There is one key detail that complicates this clean, linear story: &lt;strong&gt;Layer Normalization (LayerNorm)&lt;/strong&gt;. Before most components read from the stream, the stream is normalized. The full update rule is closer to:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;x_norm = LayerNorm(x_l)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;output = Attention(x_norm)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;x_{l+half} = x_l + output&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;x_{l+half_norm} = LayerNorm(x_{l+half})&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;ffn_output = FFN(x_{l+half_norm})&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;x_{l+1} = x_{l+half} + ffn_output&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;LayerNorm is a non-linear operation. This means that a component&amp;apos;s output depends on the &lt;em&gt;entire&lt;/em&gt; state of the input vector, not just on individual features within it. This breaks perfect linear decomposability, a limitation we must always keep in mind.&lt;/p&gt;&lt;h4&gt;Walkthrough&lt;/h4&gt;&lt;h4&gt;Case Study: Tracing &amp;quot;Paris&amp;quot;&lt;/h4&gt;&lt;p&gt;Let&amp;apos;s trace the vector for the token &amp;quot;Paris&amp;quot; in the prompt &amp;quot;The capital of France is Paris.&amp;quot;&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Step 1: Embedding (Layer 0).&lt;/strong&gt; The stream &lt;code&gt;x_0&lt;/code&gt; for &amp;quot;Paris&amp;quot; is initialized with its token embedding plus its positional embedding. This vector contains general semantic information like &lt;code&gt;+is_a_city&lt;/code&gt;, &lt;code&gt;+is_a_location&lt;/code&gt;, and its position in the sequence.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 2: Layer 0 Attention.&lt;/strong&gt; An attention head in Layer 0 might be a &amp;quot;skip-trigram&amp;quot; head. It sees &amp;quot;is&amp;quot; at the query position and attends to &amp;quot;France&amp;quot; at the key position. It reads the &lt;code&gt;is_a_country&lt;/code&gt; feature from the &amp;quot;France&amp;quot; stream. Its OV-circuit then computes an update vector, say &lt;code&gt;update_attn0&lt;/code&gt;, representing &lt;code&gt;+is_a_capital_city&lt;/code&gt;. This is added to the &amp;quot;Paris&amp;quot; stream: &lt;br&gt;&lt;code&gt;x_{0.5} = x_0 + update_attn0&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 3: Layer 0 FFN.&lt;/strong&gt; The FFN layer now reads the updated (and LayerNorm&amp;apos;d) stream &lt;code&gt;x_{0.5}&lt;/code&gt;. FFNs are often thought to store factual knowledge. Seeing the combination of &lt;code&gt;+is_a_city&lt;/code&gt; and &lt;code&gt;+is_a_capital_city&lt;/code&gt;, it might activate a neuron associated with European capitals. Its output, &lt;code&gt;update_ffn0&lt;/code&gt;, could be a vector representing &lt;code&gt;+in_western_europe&lt;/code&gt;. This is added back: &lt;br&gt;&lt;code&gt;x_1 = x_{0.5} + update_ffn0&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 4: Continuing the Process.&lt;/strong&gt; The vector for &amp;quot;Paris&amp;quot; now contains its original embedding information plus two contextual updates. This process repeats through dozens of layers. Each component adds more specific, relevant features, refining the representation. The final vector is the sum of the initial embedding and all subsequent updates: &lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;xfinal=x0+&amp;#x2211;l,compupdatel,comp    \mathbf{x}_{\text{final}} = \mathbf{x}_0 + \sum_{l, \text{comp}} \text{update}_{l, \text{comp}}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;final&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2211;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;comp&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;update&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;comp&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;This final, highly contextualized vector is then read by the unembedding matrix to predict the &lt;em&gt;next&lt;/em&gt; token.&lt;/p&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Why this model is useful:&lt;/strong&gt; Because the updates are (approximately) linear and additive, we can analyze the contribution of each component to the final output. This allows for &lt;strong&gt;attribution&lt;/strong&gt;: we can measure how much attention head 5.2 contributed to the model predicting &amp;quot;Rome&amp;quot; instead of &amp;quot;Madrid.&amp;quot;  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;When it breaks (LayerNorm):&lt;/strong&gt; As noted, LayerNorm&amp;apos;s non-linearity means &lt;code&gt;LN(a + b) &amp;#x2260; LN(a) + LN(b)&lt;/code&gt;. We cannot perfectly decompose a component&amp;apos;s input into the sum of prior contributions. The linear model is a powerful and often accurate approximation, but it is not the ground truth.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Competition for the bus:&lt;/strong&gt; All components write to the same &lt;code&gt;d_model&lt;/code&gt;dimensional space. They must learn to write their updates in directions that don&amp;apos;t destructively interfere with information written by other components. This pressure may be one reason why models learn to align features with specific basis directions (the &amp;quot;privileged basis&amp;quot; hypothesis).  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Pitfalls&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Forgetting LayerNorm:&lt;/strong&gt; It&amp;apos;s easy to forget the non-linear LayerNorm step, which invalidates claims of perfect linearity. Always treat the additive model as a strong and useful &lt;strong&gt;linear approximation&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Thinking of the stream as static:&lt;/strong&gt; The residual stream is not just an &amp;quot;embedding.&amp;quot; It is a dynamic workspace where the representation of a token is constantly being rewritten and augmented based on new context computed by the model.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; The residual stream is the central communication bus of a Transformer.  &lt;/li&gt;&lt;li&gt; Components &amp;quot;read&amp;quot; from the stream via linear projections and &amp;quot;write&amp;quot; additive updates back to it.  &lt;/li&gt;&lt;li&gt; This additive structure allows us to reason about and decompose the model&amp;apos;s computation.  &lt;/li&gt;&lt;li&gt; The final representation of a token is the sum of its initial embedding and all updates from every attention head and FFN layer.  &lt;/li&gt;&lt;li&gt; Layer Normalization is a key non-linearity that complicates this pure linear picture, making it a powerful approximation rather than a perfect model.  &lt;/li&gt;&lt;li&gt; This &amp;quot;assembly line&amp;quot; mental model is the foundation for all further circuit-level analysis.  &lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-4-reverse-engineering-transformers-induction-heads/&quot;&gt;Part 4 Reverse Engineering Transformers: Induction Heads&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-3-reverse-engineering-transformers-path-expansion/&quot;&gt;Part 3 Reverse Engineering Transformers: Path Expansion&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt;Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/part-2-reverse-engineering-transformers-attention-heads-circuits/&quot;&gt; Part 2: Reverse Engineering Transformers: Attention Heads &amp;amp; Circuits &lt;/a&gt;&lt;/div&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Probing</title>
      <link>https://nayanachandrika99.github.io/posts/probing/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/probing/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 23:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>ai safety</category>
      <category>mechInterp</category>
      <category>readings</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/probing/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 13, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/mechinterp/&quot;&gt; mechInterp &lt;/a&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/tags/readings/&quot;&gt; readings &lt;/a&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Based off the paper &lt;a href=&quot;https://doi.org/10.48550/arXiv.2102.12452&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;arXiv.org&lt;/span&gt;&lt;span&gt;Probing Classifiers: Promises, Shortcomings, and Advances&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;Part 1: &lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;We hear it all the time: a language model &amp;quot;learns&amp;quot; or &amp;quot;knows&amp;quot; things. But what does that&amp;#xa0;actually&amp;#xa0;mean? When an LLM correctly answers that the capital of France is Paris, how is that fact-the relationship between the subject &amp;quot;France,&amp;quot; the relation &amp;quot;is the capital of,&amp;quot; and the object &amp;quot;Paris&amp;quot;-stored inside billions of parameters?&lt;/p&gt;&lt;p&gt;Is it smeared across the entire network, like a drop of ink in a pool of water? Or can we point to a specific set of neurons, a specific layer, and say &amp;quot;Aha!&amp;#xa0;This&amp;#xa0;is where the model thinks about capitals!&amp;quot;?&lt;/p&gt;&lt;p&gt;We could see the model&amp;apos;s final output, but the internal process was a complete &amp;quot;black box.&amp;quot; This is a massive problem, especially for AI safety. If we don&amp;apos;t understand how a model &amp;quot;thinks,&amp;quot; how can we ever trust it? How can we prevent it from acting on hidden biases or reaching dangerous conclusions based on flawed internal reasoning?&lt;/p&gt;&lt;p&gt;This is the puzzle that drives interpretability research. We need tools to peek inside the black box. And one of the first, most fundamental tools researchers developed is called&amp;#xa0;&lt;strong&gt;probing&lt;/strong&gt;. It&amp;#x2019;s our first step in moving from just&amp;#xa0;&lt;em&gt;using&lt;/em&gt;&amp;#xa0;models to actually&amp;#xa0;&lt;em&gt;understanding&lt;/em&gt;&amp;#xa0;them.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Probing Framework: A Simple Question for a Complex Mind&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;So, how can we start to map the inside of a model? The core idea behind probing is simple. If you think a certain kind of information (like grammar, or facts) is stored in a specific place in the model, you can try to &amp;quot;read&amp;quot; it from that location.&lt;/p&gt;&lt;p&gt;Think of it like this: You have a giant, complex alien machine (the LLM), and you suspect a certain cluster of blinking lights represents the machine&amp;apos;s knowledge of verbs. You can&amp;apos;t understand the alien wiring directly. So what do you do?&lt;/p&gt;&lt;p&gt;You build a simple, human-made sensor (the&amp;#xa0;&lt;strong&gt;probe&lt;/strong&gt;). This sensor does only one thing: its light turns green if it detects a verb, and red otherwise. You then point your sensor at that cluster of alien lights and feed the machine thousands of words. If your sensor&amp;apos;s light blinks green every time a verb passes through-and only when a verb passes through-you&amp;apos;ve found something! You&amp;apos;ve &amp;quot;probed&amp;quot; the machine and confirmed that, yes, this part of the machine tracks verbs.&lt;/p&gt;&lt;p&gt;That, in a nutshell, is the entire conceptual framework. We train a small, simple classifier-the &amp;quot;probe&amp;quot;-on the internal activations (the &amp;quot;blinking lights&amp;quot;) of a larger, frozen model.&lt;/p&gt;&lt;p&gt;Let&amp;apos;s break down the technical steps:&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Choose a Model and a Property:&lt;/strong&gt;&amp;#xa0;First, we pick our target. This could be any pre-trained model like BERT or GPT-2. Then, we decide on the linguistic property we want to hunt for. Are we looking for part-of-speech tags? Syntactic tree structures? Factual knowledge?  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Extract the Representations:&lt;/strong&gt;&amp;#xa0;We feed a large dataset of text into the model. For every word or sentence, we save the internal state, or &amp;quot;representation,&amp;quot; from a specific layer. These are just vectors-long lists of numbers-that represent the model&amp;apos;s &amp;quot;thoughts&amp;quot; about that piece of text at that specific point in its computation.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Train the Probe:&lt;/strong&gt;&amp;#xa0;This is the key step. We take these saved representations and use them as features to train a simple classifier (often just a basic linear model). The goal of this probe is to predict the property we&amp;apos;re interested in. For example, can it look at the vector from Layer 8 for the word &amp;quot;running&amp;quot; and predict that it&amp;apos;s a &amp;quot;verb&amp;quot;? &lt;br&gt; Crucially, during this process, the giant language model is&amp;#xa0;&lt;strong&gt;kept completely frozen&lt;/strong&gt;. We don&amp;apos;t change its weights at all. Why? Because we&amp;apos;re not trying to&amp;#xa0;&lt;em&gt;teach&lt;/em&gt;&amp;#xa0;the big model anything. We are performing an act of scientific measurement. The probe is just our measurement tool, our &amp;quot;sensor,&amp;quot; for reading what&amp;apos;s already there.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Evaluate the Probe&amp;apos;s Performance:&lt;/strong&gt;&amp;#xa0;Finally, we see how well our probe does. If a super-simple linear probe can predict part-of-speech tags with 95% accuracy just by looking at the representations from Layer 8, that&amp;apos;s powerful evidence. It tells us that the information about &amp;quot;verb-ness&amp;quot; or &amp;quot;noun-ness&amp;quot; isn&amp;apos;t just vaguely present; it&amp;apos;s explicitly and linearly separable in that part of the model. Voil&amp;#xe0;! We&amp;apos;ve localized a piece of knowledge.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/ee195185-e07e-4562-b963-83c243cc19c7/43a74a4f-c3b9-4a05-9c6e-dc3dddfd2053.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/ee195185-e07e-4562-b963-83c243cc19c7/43a74a4f-c3b9-4a05-9c6e-dc3dddfd2053.webp&quot; alt=&quot;We can&amp;apos;t read the LLM&amp;apos;s &amp;apos;brain&amp;apos; directly, so we attach a simple &amp;apos;probe&amp;apos; to its internal wiring. If the probe&amp;apos;s lightbulb for &amp;apos;NOUN!&amp;apos; lights up at the right time, we&amp;apos;ve found where the model keeps that concept.&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;We can&amp;apos;t read the LLM&amp;apos;s &amp;apos;brain&amp;apos; directly, so we attach a simple &amp;apos;probe&amp;apos; to its internal wiring. If the probe&amp;apos;s lightbulb for &amp;apos;NOUN!&amp;apos; lights up at the right time, we&amp;apos;ve found where the model keeps that concept.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;This framework gives us a systematic way to create a &amp;quot;map&amp;quot; of the model&amp;apos;s brain, layer by layer, showing us where different types of knowledge reside. It was a foundational breakthrough, turning the black box into something we could finally start to chart. But as we&amp;apos;ll see in the next section, this simple picture comes with some very tricky complications.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Part 2: The Trouble with Probes&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;In Part 1, we laid out the elegant and simple framework of probing. It felt like we had found a key to the black box-a way to systematically map the internal knowledge of a language model. It&amp;apos;s an exciting idea! But, as with most things in science, the simple picture is never the full story.&lt;/p&gt;&lt;p&gt;The moment researchers started publishing their results, a healthy and absolutely critical debate began. The core of it was this: how do we know what our probe is&amp;#xa0;&lt;em&gt;really&lt;/em&gt;&amp;#xa0;telling us? This led to a series of crucial challenges that have made the field of interpretability so much more rigorous.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Shortcoming #1: Is 87.8% a Good Score? The Need for Controls&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Imagine we run our experiment. We train a probe on Layer 8 of BERT to find part-of-speech tags, and we get an accuracy of 87.8%. Fantastic, right?&lt;/p&gt;&lt;p&gt;Well, maybe. But what does 87.8% actually mean? Is that a high number or a low number? Without context, a number is meaningless. This is the first major problem: we need&amp;#xa0;&lt;strong&gt;comparisons and controls&lt;/strong&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Baselines:&lt;/strong&gt;&amp;#xa0;What&amp;apos;s the dumbest possible score? We could compare our 87.8% to a majority baseline (what if we just guessed &amp;quot;noun&amp;quot; every time?). More cleverly, what if we trained the probe on the outputs of a&amp;#xa0;&lt;em&gt;randomly initialized&lt;/em&gt;, untrained model? Some studies did this and found-shockingly-that even random features can give you a surprisingly decent score! WOW. This tells us that our 87.8% has to be significantly better than the score from random noise to mean anything.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Big One: Is the Probe a Thermometer or a Calculator?&lt;/strong&gt;&lt;br&gt; This is the question at the heart of the probing debate, raised by researchers Hewitt and Liang (2019). Is our probe a simple&amp;#xa0;&lt;strong&gt;thermometer&lt;/strong&gt;, passively reading the information that&amp;apos;s already clearly represented in the model? Or is it a powerful&amp;#xa0;&lt;strong&gt;calculator&lt;/strong&gt;&amp;#xa0;in its own right, learning to compute the linguistic property from scratch using the complex representations as its raw input? &lt;p&gt;&lt;br&gt; Think about it. The probe is a neural network, even if it&amp;apos;s a small one. What if it&amp;apos;s just really smart? What if it&amp;apos;s not&amp;#xa0;&lt;em&gt;finding&lt;/em&gt;&amp;#xa0;knowledge, but&amp;#xa0;&lt;em&gt;creating&lt;/em&gt;&amp;#xa0;it by learning complex patterns from the rich vector representations? &lt;br&gt; To solve this, they introduced the genius idea of a&amp;#xa0;&lt;strong&gt;control task&lt;/strong&gt;. Here&amp;apos;s how it works:&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/2eb10a79-2abf-45a2-8a2f-08cad2c8edfa/ff7b8c63-f69f-4434-85cb-eae004bcb282.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/2eb10a79-2abf-45a2-8a2f-08cad2c8edfa/ff7b8c63-f69f-4434-85cb-eae004bcb282.webp&quot; alt=&quot;The central debate in probing: Is our probe a simple &amp;apos;thermometer,&amp;apos; passively reading information that&amp;apos;s already there? Or is it a powerful &amp;apos;calculator,&amp;apos; learning the task from scratch and creating the knowledge itself? This single question changed the field&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;The central debate in probing: Is our probe a simple &amp;apos;thermometer,&amp;apos; passively reading information that&amp;apos;s already there? Or is it a powerful &amp;apos;calculator,&amp;apos; learning the task from scratch and creating the knowledge itself? This single question changed the field&lt;/figcaption&gt;&lt;/figure&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; You run your normal probing experiment. Let&amp;apos;s say you get 87.8% accuracy.  &lt;/li&gt;&lt;li&gt; Then, you take your dataset and&amp;#xa0;&lt;strong&gt;randomly shuffle all the labels&lt;/strong&gt;. So &amp;quot;running&amp;quot; is now labeled &amp;quot;noun,&amp;quot; &amp;quot;table&amp;quot; is labeled &amp;quot;adverb,&amp;quot; and so on. It&amp;apos;s complete nonsense.  &lt;/li&gt;&lt;li&gt; You train a&amp;#xa0;&lt;em&gt;new&lt;/em&gt;&amp;#xa0;probe on this nonsensical, randomized data.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt; Now, if your probe was a simple thermometer, its accuracy on this nonsense task should be terrible, near random chance. But if the probe is a powerful calculator, it might be able to&amp;#xa0;&lt;em&gt;memorize&lt;/em&gt;&amp;#xa0;the random associations for the words in the training set, achieving a surprisingly high score. &lt;br&gt; This gives us a new, much more meaningful metric:&amp;#xa0;&lt;strong&gt;selectivity&lt;/strong&gt;. &lt;br&gt; Selectivity = (Accuracy on Real Task) - (Accuracy on Control Task) &lt;br&gt; A high selectivity score tells you that your probe is genuinely finding structure that exists in the model&amp;apos;s representations, not just memorizing the probing task itself. This is why many researchers now argue for using very simple, linear probes. A complex, multi-layer probe might get a higher accuracy score, but it&amp;apos;s much more likely to be a &amp;quot;calculator,&amp;quot; leading to low selectivity and misleading you about what the base model truly knows.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Shortcoming #2: The Elephant in the Room - Correlation vs. Causation&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Okay, so let&amp;apos;s say we&amp;apos;ve done everything right. We&amp;apos;ve used a simple linear probe, we&amp;apos;ve run control tasks, and we have a sky-high selectivity score. We can now confidently say that information about part-of-speech is clearly represented in Layer 8.&lt;/p&gt;&lt;p&gt;But here&amp;apos;s the billion-dollar question:&amp;#xa0;&lt;strong&gt;Does the model actually&amp;#xa0;&lt;/strong&gt;&lt;em&gt;&lt;strong&gt;use&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;&amp;#xa0;this information to do its job?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This is the classic problem of&amp;#xa0;&lt;strong&gt;correlation vs. causation&lt;/strong&gt;. We&amp;apos;ve found a correlation-the presence of grammatical information in the representations. But we haven&amp;apos;t proven that the model relies on this information to, say, predict the next word.&lt;/p&gt;&lt;p&gt;Think about the way the probing framework is set up. We train the big LLM first. Then we freeze it. Then, in a completely separate step, we train our little probe on its frozen representations. The main model and the probe never interact. The probe is just an observer looking at a static snapshot.&lt;/p&gt;&lt;p&gt;This is a huge limitation. Maybe the information is just an accidental byproduct of the model&amp;apos;s main training process. Maybe it&amp;apos;s there, but the model completely ignores it and uses other, more bizarre correlations to make its predictions.&lt;/p&gt;&lt;p&gt;How do we get around this? This is where the cutting edge of research is heading. Instead of just passively observing, researchers are now developing&amp;#xa0;&lt;strong&gt;interventional methods&lt;/strong&gt;. What if we could perform &amp;quot;brain surgery&amp;quot; on the model?&lt;/p&gt;&lt;p&gt;For example, researchers are now trying to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Ablate Information:&lt;/strong&gt;&amp;#xa0;Use techniques to actively erase the part-of-speech information from the representations in Layer 8 and then see if the model&amp;apos;s performance on its main task (like language modeling) gets worse. If it does, that&amp;apos;s strong evidence for a causal link!  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Modify Representations:&lt;/strong&gt;&amp;#xa0;Use the probe to find the &amp;quot;part-of-speech&amp;quot; direction in the activation space and then manually shift the representation along that axis. Does this change the model&amp;apos;s output in predictable ways?  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These interventional approaches are far more complex, but they are the necessary next step to move beyond just finding correlations and toward understanding the true, causal computations happening inside these models. It&amp;apos;s a fundamental shift from map-making to real reverse-engineering.&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/54aa3c74-d1d2-418e-abe5-1ea9eb10fe19/4729d309-a003-49c1-ad79-c2030fb43807.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/54aa3c74-d1d2-418e-abe5-1ea9eb10fe19/4729d309-a003-49c1-ad79-c2030fb43807.webp&quot; alt=&quot;Probing, like the magnifying glass, can find a perfectly formed &amp;apos;Grammar Info&amp;apos; gear inside the model&amp;apos;s brain. But the critical question remains: is that gear actually connected to the main process, or is it just sitting there, unused? This is the massive gap between correlation and causation.&amp;#x201d;&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;Probing, like the magnifying glass, can find a perfectly formed &amp;apos;Grammar Info&amp;apos; gear inside the model&amp;apos;s brain. But the critical question remains: is that gear actually connected to the main process, or is it just sitting there, unused? This is the massive gap between correlation and causation.&amp;#x201d;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Shortcoming #3: The Goldilocks Problem - Which Probe is &amp;quot;Just Right&amp;quot;?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;So we&amp;apos;ve decided to build our &amp;quot;sensor&amp;quot; to check the model&amp;apos;s internal state. What should that sensor be made of? Should it be a simple magnifying glass or a high-powered electron microscope? This is the debate over the&amp;#xa0;&lt;strong&gt;complexity of the probe itself&lt;/strong&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The Argument for Simplicity (Linear Probes):&lt;/strong&gt;&amp;#xa0;Many researchers advocate for using the simplest tool possible, like a basic linear classifier. The philosophy is beautiful: if a dead-simple probe can find the information, then that information must be &amp;quot;lying on the surface.&amp;quot; It must be explicitly and easily accessible in the representations. This gives us high confidence that we&amp;apos;re not fooling ourselves, and it helps us avoid the &amp;quot;probe as a calculator&amp;quot; problem we discussed last time.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Argument for Complexity (MLP Probes):&lt;/strong&gt;&amp;#xa0;But what if the information isn&amp;apos;t lying on the surface? What if it&amp;apos;s there, but it&amp;apos;s tangled up and encoded in a complex, non-linear way? A simple linear probe would fail and we&amp;apos;d mistakenly conclude the information isn&amp;apos;t there at all. Other researchers argue we should use a more powerful, complex probe (like a multi-layer perceptron or MLP) to give us the best possible estimate of&amp;#xa0;&lt;em&gt;all&lt;/em&gt;&amp;#xa0;the information that could theoretically be extracted.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So, who&amp;apos;s right?&lt;strong&gt;&lt;/strong&gt;It turns out, there&amp;apos;s a trade-off. A more complex probe might give you higher accuracy, but as we saw with the &amp;quot;selectivity&amp;quot; metric, that accuracy might be an illusion because the probe itself is doing all the heavy lifting.&lt;/p&gt;&lt;p&gt;This has led to a more nuanced view. Instead of just reporting accuracy, researchers now talk about the&amp;#xa0;&lt;strong&gt;accuracy-complexity trade-off.&lt;/strong&gt; Some even use sophisticated ideas like&amp;#xa0;&lt;strong&gt;Minimum Description Length (MDL)&lt;/strong&gt;. You can think of MDL like this: it doesn&amp;apos;t just ask &amp;quot;Did the probe get the right answer?&amp;quot; but rather, &amp;quot;How much&amp;#xa0;&lt;em&gt;effort&lt;/em&gt;&amp;#xa0;did the probe have to expend to get the right answer?&amp;quot; A probe that gets high accuracy with very little effort (a low MDL score) is much more convincing. It tells us the information was easy to find.&lt;/p&gt;&lt;p&gt;This whole debate shows us that choosing a probe isn&amp;apos;t just a technical decision; it&amp;apos;s a statement about what you&amp;apos;re trying to measure. Are you looking for easily accessible information, or are you trying to find any trace of the information, no matter how encoded?&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Shortcoming #4: The Streetlight Effect - Are We Only Looking Where It&amp;apos;s Easy?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This final set of problems is perhaps the most profound because it&amp;apos;s about the limitations of&amp;#xa0;&lt;em&gt;us&lt;/em&gt;, the human researchers.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Datasets aren&amp;apos;t Tasks:&lt;/strong&gt;&amp;#xa0;We talk about probing for a &amp;quot;task&amp;quot; like &amp;quot;grammar,&amp;quot; but in reality, we&amp;apos;re using a finite, messy &amp;quot;dataset&amp;quot; as a proxy. The choice of that dataset profoundly impacts our results. If you train a probe on part-of-speech tags from the Wall Street Journal, your conclusions might not apply to a model&amp;apos;s understanding of poetry. Even worse, the&amp;#xa0;&lt;em&gt;original data the LLM was trained on&lt;/em&gt;&amp;#xa0;is a huge confounding variable. We&amp;apos;re often not comparing architectures; we&amp;apos;re comparing models trained on different slices of the internet, and we have no way of untangling the two.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;We Can Only Find What We Look For:&lt;/strong&gt;&amp;#xa0;This is the big one. The entire probing framework relies on us having a pre-defined, pre-labeled property&amp;#xa0;&lt;em&gt;z&lt;/em&gt;&amp;#xa0;to look for. We can probe for part-of-speech because linguists have already given us the labels. We can probe for syntax because we have annotated treebanks. &lt;br&gt; But what if a language model, in its alien mind, discovers a new, powerful, and entirely non-human way of representing language? What if it learns a concept that is incredibly useful for predicting the next word but doesn&amp;apos;t map to any existing human linguistic category? &lt;br&gt; We would never find it. Because we would never know to look for it. &lt;br&gt; This is the ultimate&amp;#xa0;&lt;strong&gt;Streetlight Effect&lt;/strong&gt;: we are searching for our lost keys only under the streetlight, because that&amp;apos;s where the light is. Probing is limited to the concepts we can already name and annotate. The model&amp;apos;s most interesting discoveries might be lurking just outside the lamplight, in the darkness, and our current methods give us no way to see them.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/41f973d2-301c-42da-b713-a82dfaadd42f/17ccd3ff-9fb4-481a-b628-a5f6b162868d.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/41f973d2-301c-42da-b713-a82dfaadd42f/17ccd3ff-9fb4-481a-b628-a5f6b162868d.webp&quot; alt=&quot;We are excellent at searching for things within the light of our &amp;apos;Known Concepts,&amp;apos; but what strange, powerful, and non-human ideas has the model learned out there in the dark, where we don&amp;apos;t even know to look?&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;We are excellent at searching for things within the light of our &amp;apos;Known Concepts,&amp;apos; but what strange, powerful, and non-human ideas has the model learned out there in the dark, where we don&amp;apos;t even know to look?&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Conclusion: Probing is Flawed, Foundational, and Forward-Looking&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;So, after all these critiques, is probing a useless tool?&lt;/p&gt;&lt;p&gt;Absolutely not. Probing is one of the most important ideas in the history of interpretability. Its initial, simple promise-that we can map the knowledge in a model-forced the field to get serious. The intense debate about its shortcomings is a sign of a healthy, maturing science.&lt;/p&gt;&lt;p&gt;Probing taught us to be more rigorous. It forced us to invent concepts like control tasks and selectivity. It made us confront the monumental gap between correlation and causation, pushing the community toward more powerful interventional methods. And it made us humbly acknowledge the limits of our own hypotheses.&lt;/p&gt;&lt;p&gt;It&amp;apos;s the first rung on the ladder of understanding. We may not use probes to get the final answer, but we use the lessons learned from probing every single day. The black box is still dark, but probing was the first time we figured out how to switch on a flashlight, even if it could only illuminate a small corner at a time. And that changed everything.&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;A note on illustrations:&lt;/strong&gt;&lt;/em&gt;&lt;em&gt;All conceptual diagrams in this post were generated by the author using prompts for Google&amp;apos;s Gemini model.&amp;#xa0;&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Pages That Mention This Page&lt;/span&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/posts/internal-mechanisms-of-language-models-causal-attribution-patching/&quot;&gt;Internal Mechanisms of Language Models: Causal Attribution &amp;amp; Patching&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Bayesian Machine Learning</title>
      <link>https://nayanachandrika99.github.io/posts/bayesian-machine-learning/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/bayesian-machine-learning/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Fri Oct 17 2025 19:26:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <category>notes</category>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/bayesian-machine-learning/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 12, 2025 &lt;/time&gt;&lt;/p&gt;&lt;span&gt; Last Updated: &lt;time&gt; October 17, 2025 &lt;/time&gt;&lt;/span&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h4&gt;&lt;em&gt;&lt;strong&gt;Colab notebook: &lt;/strong&gt;&lt;/em&gt;&lt;em&gt;&lt;strong&gt;&lt;a href=&quot;https://colab.research.google.com/drive/1RRYV3r84YziXgiaj9b_Zs1GT2ICf-uAb?usp=sharing&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Google Colab&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/h4&gt;&lt;h4&gt;TL;DR&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; Bayesian ML treats model parameters as random variables, not fixed points. This allows us to reason about model uncertainty.  &lt;/li&gt;&lt;li&gt; The core workflow is &lt;strong&gt;Prior Belief + Data (Likelihood) &amp;#x2192; Posterior Belief&lt;/strong&gt;.  &lt;/li&gt;&lt;li&gt; Maximum A Posteriori (MAP) is a simple bridge from classical ML, adding a prior term to Maximum Likelihood (MLE).  &lt;/li&gt;&lt;li&gt; For complex models, the posterior is intractable. We approximate it with MCMC (sampling) or Variational Inference (optimization).  &lt;/li&gt;&lt;li&gt; Modern techniques like the Reparameterization Trick and MC Dropout make it possible to train Bayesian Neural Networks at scale, providing principled uncertainty estimates for deep learning.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Roadmap&lt;/h4&gt;&lt;p&gt;This tutorial builds a complete picture of modern Bayesian ML, from first principles to deep learning applications.&lt;/p&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Bayes&amp;#x2019; Theorem &amp;amp; Priors&lt;/strong&gt;: The fundamental rule for updating beliefs with evidence.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Maximum Likelihood vs. Maximum A Posteriori&lt;/strong&gt;: Contrasting point estimation methods to see how priors act as regularizers.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bayesian Linear Regression&lt;/strong&gt;: Our first &amp;quot;fully Bayesian&amp;quot; model, moving from point estimates to parameter distributions.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Monte Carlo Methods (MCMC)&lt;/strong&gt;: A class of algorithms for sampling from complex posterior distributions when closed-form solutions are impossible.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;ELBO (Evidence Lower Bound)&lt;/strong&gt;: The objective function at the heart of modern Variational Inference, trading sampling for optimization.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Reparameterization Trick&lt;/strong&gt;: The key mathematical device that allows us to train variational models with gradient descent.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bayesian Neural Networks&lt;/strong&gt;: Extending Bayesian principles to deep learning by placing distributions over network weights.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Dropout as Bayesian Approximation&lt;/strong&gt;: A surprising and powerful connection that lets us get uncertainty from standard NNs.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;hr&gt;&lt;h4&gt;1. Bayes&amp;#x2019; Theorem &amp;amp; Priors&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; Bayesian inference is a framework for updating our beliefs in light of new evidence. It starts with a &lt;strong&gt;prior&lt;/strong&gt; belief about a parameter, &lt;code&gt;p(&amp;#x3b8;)&lt;/code&gt;, which represents our knowledge before seeing any data. We then collect data &lt;code&gt;D&lt;/code&gt; and define a &lt;strong&gt;likelihood&lt;/strong&gt;, &lt;code&gt;p(D|&amp;#x3b8;)&lt;/code&gt;, a function that describes how probable the observed data is for a given value of the parameter &lt;code&gt;&amp;#x3b8;&lt;/code&gt;. Bayes&amp;apos; theorem combines these two to give us the &lt;strong&gt;posterior&lt;/strong&gt; distribution, &lt;code&gt;p(&amp;#x3b8;|D)&lt;/code&gt;, which represents our updated belief about &lt;code&gt;&amp;#x3b8;&lt;/code&gt; after observing the data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Equation:&lt;/strong&gt; The theorem itself is elegant and foundational.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p(&amp;#x3b8;&amp;#x2223;D)=p(D&amp;#x2223;&amp;#x3b8;)&amp;#x22c5;p(&amp;#x3b8;)p(D)p(\theta | D) = \frac{p(D | \theta) \cdot p(\theta)}{p(D)}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Here, &lt;code&gt;p(D)&lt;/code&gt;, the &lt;strong&gt;evidence&lt;/strong&gt; (or marginal likelihood), acts as a normalization constant. It&amp;apos;s the probability of observing the data averaged over all possible parameter values: &lt;code&gt;p(D) = &amp;#x222b; p(D|&amp;#x3b8;)p(&amp;#x3b8;)d&amp;#x3b8;&lt;/code&gt;. This integral is often the hardest part to compute, which motivates the approximation methods we&amp;apos;ll see later.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Figure 1: Visualizing a Bayesian Update&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A simple model helps build intuition. Let&amp;apos;s say we&amp;apos;re modeling coin flips. Our parameter &lt;code&gt;&amp;#x3b8;&lt;/code&gt; is the probability of heads. We can use a Beta distribution as our prior and a Bernoulli distribution as our likelihood. This is a &amp;quot;conjugate&amp;quot; pair, meaning the posterior is also a Beta distribution.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Experiment:&lt;/strong&gt; Let&amp;apos;s code this update. We start with a prior belief that the coin is slightly biased towards tails (&lt;code&gt;&amp;#x3b1;=2, &amp;#x3b2;=5&lt;/code&gt;). We then observe 10 flips: 7 heads (&lt;code&gt;H=7&lt;/code&gt;) and 3 tails (&lt;code&gt;T=3&lt;/code&gt;)&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/5c80594f-5e9d-4b8f-ab5c-eddaf58932aa/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/5c80594f-5e9d-4b8f-ab5c-eddaf58932aa/image.webp&quot; alt=&quot;A Beta(2, 5) prior (dashed blue) is updated with 7 heads and 3 tails. The likelihood (dotted yellow) peaks at the data&amp;apos;s mean (0.7). The resulting Beta(9, 8) posterior (solid green) is a compromise, shifting the prior towards the likelihood.&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;em&gt;A Beta(2, 5) prior (dashed blue) is updated with 7 heads and 3 tails. The likelihood (dotted yellow) peaks at the data&amp;apos;s mean (0.7). The resulting Beta(9, 8) posterior (solid green) is a compromise, shifting the prior towards the likelihood.&lt;/em&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;2. Maximum Likelihood vs. Maximum A Posteriori&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; Most standard machine learning training is &lt;strong&gt;Maximum Likelihood Estimation (MLE)&lt;/strong&gt;. We find the parameters &lt;code&gt;&amp;#x3b8;&lt;/code&gt; that make the observed data most probable. In the Bayesian world, a simple first step is &lt;strong&gt;Maximum A Posteriori (MAP)&lt;/strong&gt; estimation. Instead of maximizing just the likelihood, we maximize the posterior probability. Because the evidence &lt;code&gt;p(D)&lt;/code&gt; is constant with respect to &lt;code&gt;&amp;#x3b8;&lt;/code&gt;, this is equivalent to maximizing the likelihood times the prior.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Equation:&lt;/strong&gt; It&amp;apos;s most common to work with log probabilities to turn products into sums.&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;^MLE=arg&amp;#x2061;max&amp;#x2061;&amp;#x3b8;log&amp;#x2061;p(D&amp;#x2223;&amp;#x3b8;)\hat{\theta}{\text{MLE}} = \arg\max{\theta} \log p(D | \theta)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;MLE&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;ar&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;^MAP=arg&amp;#x2061;max&amp;#x2061;&amp;#x3b8;log&amp;#x2061;p(D&amp;#x2223;&amp;#x3b8;)+log&amp;#x2061;p(&amp;#x3b8;)\hat{\theta}{\text{MAP}} = \arg\max{\theta} \log p(D | \theta) + \log p(\theta)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;MAP&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;ar&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;The &lt;code&gt;log p(&amp;#x3b8;)&lt;/code&gt; term is key. It&amp;apos;s a &lt;strong&gt;regularizer&lt;/strong&gt;. If our prior &lt;code&gt;p(&amp;#x3b8;)&lt;/code&gt; is a Gaussian centered at zero, then &lt;code&gt;log p(&amp;#x3b8;)&lt;/code&gt; becomes a term proportional to &lt;code&gt;-||&amp;#x3b8;||&amp;#xb2;&lt;/code&gt;, which is exactly L2 regularization!&lt;/p&gt;&lt;p&gt;&lt;em&gt;Pitfall&lt;/em&gt;: The &amp;quot;regularization = log-prior&amp;quot; analogy is powerful but imperfect. It holds for simple cases, but the Bayesian approach is more general. The goal is to find a full posterior distribution, not just its mode (the peak). MAP, like MLE, gives only a single point estimate and discards all uncertainty.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Figure 2: The Geometry of MLE vs. MAP&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/78139f16-b91d-47a0-b327-f306c2a5f87b/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/78139f16-b91d-47a0-b327-f306c2a5f87b/image.webp&quot; alt=&quot;MLE finds the peak of the likelihood contours. MAP is pulled away from the likelihood&amp;apos;s peak toward a region of higher prior probability (e.g., closer to the origin for an L2-style prior).&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;em&gt;MLE finds the peak of the likelihood contours. MAP is pulled away from the likelihood&amp;apos;s peak toward a region of higher prior probability (e.g., closer to the origin for an L2-style prior).&lt;/em&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt; Experiment:&lt;/strong&gt; Let&amp;apos;s estimate the mean &lt;code&gt;&amp;#x3bc;&lt;/code&gt; of a 1D Gaussian. The MLE estimate is simply the sample mean. We&amp;apos;ll use a Gaussian prior on &lt;code&gt;&amp;#x3bc;&lt;/code&gt; and see how it pulls the MAP estimate.&lt;/p&gt;&lt;p&gt;The strong prior &lt;code&gt;N(0, 1)&lt;/code&gt; pulls the MAP estimate significantly away from the sample mean and towards zero. With a flatter prior (e.g., &lt;code&gt;prior_sigma = 10&lt;/code&gt;), the MAP estimate would be much closer to the MLE.&lt;/p&gt;&lt;h4&gt;3. Bayesian Linear Regression&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; Standard linear regression finds a single best-fit line by learning point estimates for weights &lt;code&gt;w&lt;/code&gt; and bias &lt;code&gt;b&lt;/code&gt;. Bayesian Linear Regression (BLR) takes this a step further. We place priors on &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; (usually Gaussians) and compute the full posterior distribution over them. This means that for any input &lt;code&gt;x*&lt;/code&gt;, we don&amp;apos;t get a single prediction &lt;code&gt;y*&lt;/code&gt;; we get a full predictive distribution &lt;code&gt;p(y*|x*, D)&lt;/code&gt;. The mean of this distribution is our prediction, and its variance tells us our uncertainty.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Equation:&lt;/strong&gt; For a Gaussian prior on weights &lt;code&gt;w ~ N(0, &amp;#x3b1;&amp;#x207b;&amp;#xb9;I)&lt;/code&gt; and a Gaussian likelihood with precision &lt;code&gt;&amp;#x3b2;&lt;/code&gt;, the posterior for &lt;code&gt;w&lt;/code&gt; is also a Gaussian, &lt;code&gt;p(w|X, y) ~ N(m_N, S_N)&lt;/code&gt;. The posterior predictive distribution is also a Gaussian, &lt;code&gt;p(y*|x*, X, y) ~ N(m_N&amp;#x1d40;x*, &amp;#x3c3;&amp;#xb2;(x*))&lt;/code&gt;. The full equations for &lt;code&gt;m_N&lt;/code&gt;, &lt;code&gt;S_N&lt;/code&gt;, and &lt;code&gt;&amp;#x3c3;&amp;#xb2;(x*)&lt;/code&gt; are in the appendix, but the key idea is they are solvable in closed form.&lt;/p&gt;&lt;p&gt;&lt;strong&gt; Experiment:&lt;/strong&gt; We&amp;apos;ll fit a BLR model to a noisy 1D sine wave. The plot will show not just the mean prediction, but also the uncertainty, which should be higher in regions with no data.&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/e0386917-fb64-4595-acc2-c476084f847c/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/e0386917-fb64-4595-acc2-c476084f847c/image.webp&quot; alt&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The shaded region shows the model&amp;apos;s uncertainty. Notice how the uncertainty band widens dramatically in the gap between &lt;code&gt;x = -0.5&lt;/code&gt; and &lt;code&gt;x = 0.5&lt;/code&gt;, where we have no data. This is a key advantage of the Bayesian approach: the model knows what it doesn&amp;apos;t know.&lt;/p&gt;&lt;h4&gt;4. Monte Carlo Methods (MCMC)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; Bayesian Linear Regression is nice because its posterior is tractable. For most interesting models (like neural networks), the integral in the denominator of Bayes&amp;apos; rule is intractable. &lt;strong&gt;Monte Carlo (MC)&lt;/strong&gt; methods are a class of algorithms that let us approximate an intractable posterior by drawing samples from it. The idea is that if we can draw enough representative samples &lt;code&gt;{&amp;#x3b8;&amp;#x2081;, &amp;#x3b8;&amp;#x2082;, ..., &amp;#x3b8;&amp;#x2099;}&lt;/code&gt; from &lt;code&gt;p(&amp;#x3b8;|D)&lt;/code&gt;, we can approximate any property of the posterior, like its mean or variance.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Markov Chain Monte Carlo (MCMC)&lt;/strong&gt; algorithms like Metropolis-Hastings and Gibbs sampling construct a Markov chain whose stationary distribution is our target posterior. More advanced methods like &lt;strong&gt;Hamiltonian Monte Carlo (HMC)&lt;/strong&gt; and its adaptive variant, the &lt;strong&gt;No-U-Turn Sampler (NUTS)&lt;/strong&gt;, use gradient information to propose samples more efficiently, making them the standard choice in modern probabilistic programming libraries like Stan or PyMC.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Diagram: HMC/NUTS Phase Space&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/b933f7e0-81fb-4615-ba3f-dca197d41a0f/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/b933f7e0-81fb-4615-ba3f-dca197d41a0f/image.webp&quot; alt=&quot;HMC simulates a particle moving in a potential field defined by the negative log posterior. It explores the parameter space efficiently. NUTS automates the tuning of path length, stopping when the particle starts to &amp;quot;U-turn&amp;quot; and head back to where it started&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;em&gt;HMC simulates a particle moving in a potential field defined by the negative log posterior. It explores the parameter space efficiently. NUTS automates the tuning of path length, stopping when the particle starts to &amp;quot;U-turn&amp;quot; and head back to where it started&lt;/em&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Sanity Checks:&lt;/strong&gt; Since MCMC is a stochastic process, we need to check if it worked. Two key diagnostics are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Effective Sample Size (ESS):&lt;/strong&gt; Measures how many independent samples our correlated MCMC chain is worth. Low ESS means poor exploration.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;R-hat (R&amp;#x302;):&lt;/strong&gt; Compares the variance between multiple parallel MCMC chains to the variance within each chain. An R&amp;#x302; value close to 1.0 (e.g., &amp;lt; 1.01) suggests the chains have converged to the same distribution.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;5. ELBO (Variational Inference)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; MCMC can be very slow. &lt;strong&gt;Variational Inference (VI)&lt;/strong&gt; reframes Bayesian inference as an optimization problem. Instead of sampling, we posit a family of simple, tractable distributions &lt;code&gt;q(&amp;#x3b8;; &amp;#x3bb;)&lt;/code&gt; (e.g., Gaussians) indexed by parameters &lt;code&gt;&amp;#x3bb;&lt;/code&gt;. We then try to find the parameters &lt;code&gt;&amp;#x3bb;&lt;/code&gt; that make our &amp;quot;variational distribution&amp;quot; &lt;code&gt;q&lt;/code&gt; as close as possible to the true posterior &lt;code&gt;p(&amp;#x3b8;|D)&lt;/code&gt;. The measure of &amp;quot;closeness&amp;quot; is the Kullback-Leibler (KL) divergence, &lt;code&gt;KL(q || p)&lt;/code&gt;. Minimizing this KL divergence is equivalent to maximizing a quantity called the &lt;strong&gt;Evidence Lower Bound (ELBO)&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Equation Derivation:&lt;/strong&gt; We start with the log evidence and apply some algebra.&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;log&amp;#x2061;p(D)=log&amp;#x2061;&amp;#x222b;p(D,&amp;#x3b8;)&amp;#x2009;d&amp;#x3b8;=log&amp;#x2061;&amp;#x222b;p(D,&amp;#x3b8;)&amp;#x2009;q(&amp;#x3b8;)q(&amp;#x3b8;)&amp;#x2009;d&amp;#x3b8;=log&amp;#x2061;&amp;#x2009;Eq(&amp;#x3b8;)[p(D,&amp;#x3b8;)q(&amp;#x3b8;)]&amp;#x2265;Eq(&amp;#x3b8;)[log&amp;#x2061;p(D,&amp;#x3b8;)q(&amp;#x3b8;)](by&amp;#xa0;Jensen&amp;#x2019;s&amp;#xa0;Inequality)=Eq(&amp;#x3b8;)[log&amp;#x2061;p(D,&amp;#x3b8;)]&amp;#x2212;Eq(&amp;#x3b8;)[log&amp;#x2061;q(&amp;#x3b8;)]=Eq(&amp;#x3b8;)[log&amp;#x2061;p(D&amp;#x2223;&amp;#x3b8;)]&amp;#x23df;Expected&amp;#xa0;Log-Likelihood&amp;#x2212;KL(q(&amp;#x3b8;)&amp;#x2009;&amp;#x2225;&amp;#x2009;p(&amp;#x3b8;))&amp;#x23df;KL&amp;#xa0;Divergence\begin{aligned}\log p(D) &amp;amp;= \log \int p(D, \theta) \, d\theta \\[6pt]&amp;amp;= \log \int p(D, \theta) \, \frac{q(\theta)}{q(\theta)} \, d\theta \\[6pt]&amp;amp;= \log \, \mathbb{E}_{q(\theta)} \left[ \frac{p(D, \theta)}{q(\theta)} \right] \\[6pt]&amp;amp;\geq \mathbb{E}_{q(\theta)} \left[ \log \frac{p(D, \theta)}{q(\theta)} \right] \quad \text{(by Jensen&amp;#x2019;s Inequality)} \\[6pt]&amp;amp;= \mathbb{E}_{q(\theta)}[\log p(D, \theta)] - \mathbb{E}_{q(\theta)}[\log q(\theta)] \\[6pt]&amp;amp;= \underbrace{\mathbb{E}_{q(\theta)}[\log p(D \mid \theta)]}_{\text{Expected Log-Likelihood}}- \underbrace{\mathrm{KL}\big(q(\theta)\,\|\,p(\theta)\big)}_{\text{KL Divergence}}\end{aligned}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x222b;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x222b;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;#x2265;&lt;/span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(by&amp;#xa0;Jensen&amp;#x2019;s&amp;#xa0;Inequality)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;span&gt;&amp;#x2212;&lt;/span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Expected&amp;#xa0;Log-Likelihood&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x2212;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;KL&amp;#xa0;Divergence&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;KL&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x2225;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;This final line is the ELBO. Maximizing it pushes &lt;code&gt;q&lt;/code&gt; to explain the data (first term) while staying close to the prior (second term).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Table: The Two Faces of the ELBO&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;&lt;strong&gt;Role&lt;/strong&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;&lt;strong&gt;Analogy&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Eq(&amp;#x3b8;)[log&amp;#x2061;p(D&amp;#x2223;&amp;#x3b8;)]\mathbb{E}_{q(\theta)}[\log p(D \mid \theta)]&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x200b;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;(Likelihood)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Data-fit / Reconstruction.&lt;/strong&gt; Pushes the approximate posterior &amp;#x24;q&amp;#x24; to put mass on parameters &amp;#x24;\theta&amp;#x24; that explain the data well. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;KL&amp;#x2009;&amp;#x2063;(q(&amp;#x3b8;)&amp;#x2009;&amp;#x2223;&amp;#x2009;p(&amp;#x3b8;))\mathrm{KL}\!\big(q(\theta)\,|\,p(\theta)\big)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;KL&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;(KL Regularizer)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt; Encourages the approximate posterior &amp;#x24;q(\theta)&amp;#x24; to stay close to the prior &amp;#x24;p(\theta)&amp;#x24;. &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt; Experiment:&lt;/strong&gt; We can&amp;apos;t fit a full VI model here, but we can visualize the ELBO&amp;apos;s components. Imagine a model with a reconstruction loss and a KL loss. During training, we are trying to maximize the sum of these two (or minimize the negative sum).&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/2ebc8220-17e0-4aaa-838d-4864750cd29d/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/2ebc8220-17e0-4aaa-838d-4864750cd29d/image.webp&quot; alt&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;6. Reparameterization Trick&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; To optimize the ELBO with gradient descent, we need to differentiate it. The likelihood term is tricky because the expectation &lt;code&gt;E_q&lt;/code&gt; depends on the parameters &lt;code&gt;&amp;#x3bb;&lt;/code&gt; we want to optimize. How do you backpropagate through a sampling operation? The &lt;strong&gt;Reparameterization Trick&lt;/strong&gt; is the solution. We rewrite the random variable &lt;code&gt;&amp;#x3b8; ~ q(&amp;#x3b8;; &amp;#x3bb;)&lt;/code&gt; in a way that isolates the randomness.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Equation:&lt;/strong&gt; For a Gaussian variational distribution &lt;code&gt;q(&amp;#x3b8;; &amp;#x3bc;, &amp;#x3c3;) = N(&amp;#x3b8; | &amp;#x3bc;, &amp;#x3c3;&amp;#xb2;)&lt;/code&gt;, instead of sampling &lt;code&gt;&amp;#x3b8;&lt;/code&gt; directly, we sample a noise variable &lt;code&gt;&amp;#x3b5;&lt;/code&gt; from a fixed distribution and then compute &lt;code&gt;&amp;#x3b8;&lt;/code&gt; as a deterministic function. &lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;If&amp;#x3b8;&amp;#x223c;N(&amp;#x3bc;,&amp;#x3c3;2),we&amp;#xa0;can&amp;#xa0;write&amp;#x3b8;=&amp;#x3bc;+&amp;#x3c3;&amp;#x22c5;&amp;#x3f5;,where&amp;#xa0;&amp;#x3f5;&amp;#x223c;N(0,1)\text{If} \theta \sim \mathcal{N}(\mu, \sigma^2), \quad \text{we can write} \quad \theta = \mu + \sigma \cdot \epsilon, \quad \text{where } \epsilon \sim \mathcal{N}(0, 1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;If&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;&amp;#x223c;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3bc;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c3;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;we&amp;#xa0;can&amp;#xa0;write&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3b8;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3bc;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3c3;&lt;/span&gt;&lt;span&gt;&amp;#x22c5;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3f5;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;where&amp;#xa0;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3f5;&lt;/span&gt;&lt;span&gt;&amp;#x223c;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Now, the stochastic part (&lt;code&gt;&amp;#x3b5;&lt;/code&gt;) is outside the computational graph, and we can easily compute gradients of functions of &lt;code&gt;&amp;#x3b8;&lt;/code&gt; with respect to &lt;code&gt;&amp;#x3bc;&lt;/code&gt; and &lt;code&gt;&amp;#x3c3;&lt;/code&gt;. This trick is fundamental to Variational Autoencoders (VAEs) and Bayesian Neural Networks.&lt;/p&gt;&lt;p&gt;&lt;strong&gt; Experiment:&lt;/strong&gt; We will demonstrate that we can compute gradients for &lt;code&gt;&amp;#x3bc;&lt;/code&gt; and &lt;code&gt;&amp;#x3c3;&lt;/code&gt; after a sampling step. Note: Since &lt;code&gt;torch.autograd&lt;/code&gt; is unavailable, this code is a conceptual demonstration of the data flow.&lt;/p&gt;&lt;p&gt;This confirms that gradients can be computed for the distribution&amp;apos;s parameters (&lt;code&gt;mu&lt;/code&gt;, &lt;code&gt;log_sigma&lt;/code&gt;) even though &lt;code&gt;z&lt;/code&gt; is a &amp;quot;random&amp;quot; sample.&lt;/p&gt;&lt;h4&gt;7. Bayesian Neural Networks&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; A Bayesian Neural Network (BNN) is a neural network where we replace the point-estimate weights with distributions. Instead of a weight matrix &lt;code&gt;W&lt;/code&gt;, we learn a posterior distribution &lt;code&gt;p(W|D)&lt;/code&gt;. For a simple BNN using Variational Inference, each weight &lt;code&gt;w_ij&lt;/code&gt; is not a single number but is instead represented by parameters of its variational posterior, typically &lt;code&gt;&amp;#x3bc;_ij&lt;/code&gt; and &lt;code&gt;&amp;#x3c3;_ij&lt;/code&gt; for a Gaussian distribution.&lt;/p&gt;&lt;p&gt;The training process involves optimizing the ELBO. The forward pass is different: for each training example, we sample a different set of weights from &lt;code&gt;q(W)&lt;/code&gt; and pass the input through this sampled network. This naturally incorporates uncertainty and regularization.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Equation:&lt;/strong&gt; The objective function is simply the ELBO, where &lt;code&gt;&amp;#x3b8;&lt;/code&gt; is now the set of all network weights &lt;code&gt;W&lt;/code&gt;. This is often called the &amp;quot;Bayes by Backprop&amp;quot; objective.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;LBNN=Eq(W&amp;#x2223;&amp;#x3bb;)[log&amp;#x2061;p(D&amp;#x2223;W)]&amp;#x2212;KL(q(W&amp;#x2223;&amp;#x3bb;)&amp;#x2223;&amp;#x2223;p(W))\mathcal{L}{\text{BNN}} = \mathbb{E}{q(W | \lambda)}[\log p(D|W)] - \text{KL}(q(W | \lambda) || p(W))
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;L&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;BNN&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x3bb;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;span&gt;&amp;#x2212;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;KL&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;&amp;#x2223;&lt;/span&gt;&lt;span&gt;&amp;#x3bb;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;#x2223;&amp;#x2223;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;W&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Here, &lt;code&gt;&amp;#x3bb;&lt;/code&gt; represents all the learnable parameters of the weight distributions (all the &lt;code&gt;&amp;#x3bc;&lt;/code&gt;s and &lt;code&gt;&amp;#x3c3;&lt;/code&gt;s).&lt;/p&gt;&lt;p&gt;&lt;strong&gt; Experiment:&lt;/strong&gt; Training a real BNN requires a framework like PyTorch. However, we can simulate its &lt;em&gt;output&lt;/em&gt;. We&amp;apos;ll take the same noisy sine wave data and plot what a trained BNN&amp;apos;s predictions might look like. The key feature is that uncertainty increases where there is no data.&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/0d6d9c24-d839-43fa-a861-8153f212ab67/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/0d6d9c24-d839-43fa-a861-8153f212ab67/image.webp&quot; alt&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;8. Dropout as Bayesian Approximation&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; This is one of the most remarkable and practical ideas in recent Bayesian ML. Gal and Ghahramani (2016) showed that a standard neural network with dropout, when dropout is also used at &lt;strong&gt;test time&lt;/strong&gt;, can be interpreted as an approximation to a Bayesian neural network. The technique is called &lt;strong&gt;MC Dropout&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;The procedure is simple: for a given test input &lt;code&gt;x*&lt;/code&gt;, instead of just doing one forward pass with the final trained weights, you do &lt;code&gt;T&lt;/code&gt; separate forward passes. In each pass, you use the same trained weights but a different random dropout mask. This gives you &lt;code&gt;T&lt;/code&gt; different outputs &lt;code&gt;{y*&amp;#x2081;, y*&amp;#x2082;, ..., y*&amp;#x1d40;}&lt;/code&gt;. The sample mean of these outputs is your prediction, and their sample variance is your uncertainty estimate.&lt;/p&gt;&lt;p&gt;&lt;strong&gt; Experiment:&lt;/strong&gt; We can implement this on a simple NumPy-based MLP. We&amp;apos;ll define a standard MLP with ReLU activations and dropout layers. We&amp;apos;ll then write a prediction function that runs the network &lt;code&gt;T&lt;/code&gt; times to get a distribution of outputs.&lt;/p&gt;&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://nayanachandrika99.github.io/notion/1c383ce4-d7bc-4029-86d1-5a6ddd5a2e76/image.webp&quot;&gt;&lt;img src=&quot;https://nayanachandrika99.github.io/notion/1c383ce4-d7bc-4029-86d1-5a6ddd5a2e76/image.webp&quot; alt&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h4&gt;Implications &amp;amp; Limits&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;What Bayesian Methods Buy You:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Principled Uncertainty Quantification:&lt;/strong&gt; The model tells you when it&amp;apos;s uncertain, which is critical for high-stakes applications like medicine or self-driving cars.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Regularization via Priors:&lt;/strong&gt; Priors naturally prevent overfitting by encoding assumptions about the parameters.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Better Performance on Small Data:&lt;/strong&gt; When data is scarce, a good prior can guide the model to a much better solution than MLE.  &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Where They Struggle:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Priors:&lt;/strong&gt; Choosing a good prior is not always easy (&amp;quot;the blessing and the curse&amp;quot;). A bad prior can seriously bias your results.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; MCMC is extremely slow. VI is faster but still computationally more expensive than training a standard deep network.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Approximation Errors:&lt;/strong&gt; Variational Inference makes simplifying assumptions (e.g., mean-field) that can lead to an approximate posterior that is biased or under-estimates variance. MC Dropout is also just an approximation.  &lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multimodality:&lt;/strong&gt; If the true posterior has multiple peaks, VI will tend to find only one, while MCMC (if run long enough) can explore them all.  &lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Takeaways&lt;/h4&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt; Bayes&amp;apos; Theorem is the core engine for updating beliefs (&lt;code&gt;prior * likelihood -&amp;gt; posterior&lt;/code&gt;).  &lt;/li&gt;&lt;li&gt; MAP estimation is a simple bridge from MLE that incorporates priors, often acting as regularization.  &lt;/li&gt;&lt;li&gt; Bayesian Linear Regression is the &amp;quot;hello world&amp;quot; of fully Bayesian models, swapping point estimates for distributions to capture uncertainty.  &lt;/li&gt;&lt;li&gt; MCMC lets us sample from intractable posteriors but is often too slow for large-scale problems.  &lt;/li&gt;&lt;li&gt; Variational Inference reframes inference as optimization, maximizing the ELBO to find a tractable approximation to the true posterior.  &lt;/li&gt;&lt;li&gt; The Reparameterization Trick is the key to training VI-based models with gradient descent by separating randomness from model parameters.  &lt;/li&gt;&lt;li&gt; Bayesian Neural Networks apply these principles to deep learning, learning distributions over weights to provide uncertainty in predictions.  &lt;/li&gt;&lt;li&gt; MC Dropout provides a practical, easy-to-implement way to get uncertainty estimates from standard neural networks by using dropout at test time.  &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr&gt;&lt;aside&gt;&lt;h2&gt;
Interlinked Content
&lt;/h2&gt;&lt;div&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;</content>
    </item>
    <item>
      <title>Testing Collections</title>
      <link>https://nayanachandrika99.github.io/posts/testing-collections/</link>
      <guid isPermaLink="true">https://nayanachandrika99.github.io/posts/testing-collections/</guid>
      <description>Note: This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        here.
                    
    ...</description>
      <pubDate>Sun, 12 Oct 2025 00:00:00 GMT</pubDate>
      <lastUpdatedTimestamp>Sun Oct 12 2025 22:04:00 GMT+0000 (Coordinated Universal Time)</lastUpdatedTimestamp>
      <content>&lt;div&gt;
                    &lt;p&gt;
                        &lt;em&gt;Note:&lt;/em&gt; This RSS feed strips out SVGs and embeds. You might want to read the post on the webpage
                        &lt;a href=&quot;https://nayanachandrika99.github.io/posts/testing-collections/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
                    &lt;/p&gt;
                    &lt;hr&gt;
                &lt;div&gt;&lt;p&gt;&lt;time&gt; October 12, 2025 &lt;/time&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Hi!&lt;/p&gt;&lt;/div&gt;&lt;hr&gt;&lt;/div&gt;</content>
    </item>
  </channel>
</rss>
