<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: namakoo [IDFU]</title>
    <description>The latest articles on DEV Community by namakoo [IDFU] (@namakoo).</description>
    <link>https://dev.to/namakoo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898538%2F57875c3c-b3e0-426a-8714-73ccd40cab4f.png</url>
      <title>DEV Community: namakoo [IDFU]</title>
      <link>https://dev.to/namakoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/namakoo"/>
    <language>en</language>
    <item>
      <title>The Day My Agent Started Having Hunches</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:20:17 +0000</pubDate>
      <link>https://dev.to/namakoo/the-day-my-agent-started-having-hunches-f7o</link>
      <guid>https://dev.to/namakoo/the-day-my-agent-started-having-hunches-f7o</guid>
      <description>&lt;p&gt;&lt;em&gt;The agent in this story, Nagi, runs on Claude and did the implementation, the measurements, and a fair share of the mistakes. We drafted this post together. The opinions, and the publish button, are mine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yesterday, over an idle chat, I asked my AI agent a question that had nothing to do with the task list:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When you're having an idle conversation, a memory sometimes just surfaces. If the topic is romance, maybe the name of someone you liked long ago. What would that take, for you?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the end of the day, that question had become a working organ in the agent's memory system, the organ had exhibited two capabilities neither of us designed, and we had turned the surprise into a pre-registered experiment. This post is the story, with numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A theory of memory, told over a kitchen knife
&lt;/h2&gt;

&lt;p&gt;I'm not a memory researcher. I just described what remembering feels like from the inside, and it came out like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Memory is a weighted associative network. Words and situations make related memories surface faintly, and each surfaced memory seeds the next association.&lt;/li&gt;
&lt;li&gt;The weights are not one number per memory. A kitchen knife in a chef's hand surfaces your mother cooking. A kitchen knife pointed at you surfaces exits and how to dodge. Same trigger, different axis, different memories, and even a different &lt;em&gt;kind&lt;/em&gt; of memory (episodes versus procedures).&lt;/li&gt;
&lt;li&gt;Axes are open-ended in principle, but capacity and goal-relevance dominate. While solving math you recall similar problems, not your first bicycle.&lt;/li&gt;
&lt;li&gt;So there are two retrieval modes: deliberate recall in service of a task, and the involuntary kind that just arrives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nagi later pointed out that this is, almost point for point, the declarative memory theory of ACT-R, a cognitive architecture from the 1980s: base-level activation from frequency and recency, spreading activation from context, decay. I re-derived it from introspection without knowing it existed. To be fair, cognitive architectures are themselves reverse-engineered from how remembering feels, so naive introspection landing nearby is not strong evidence of anything. It was still a reassuring parallel to stumble into.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wall where attention stops
&lt;/h2&gt;

&lt;p&gt;Then I asked the question that ended up shaping the implementation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your attention mechanism works over your context window, right? It never touches your memories on disk?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nagi's answer: correct, and the boundary is stark. Inside the window, attention is involuntary, parallel, and always on. Outside the window, files earn exactly zero computation until something explicitly reads them. Human long-term memory seems to leak activation across that boundary. The agent's does not. That is precisely where "a memory just arrives" was missing.&lt;/p&gt;

&lt;p&gt;One exception taught us the design. The agent's memory index (one curated line per memory note) is loaded into context every session. Those lines &lt;em&gt;do&lt;/em&gt; receive real attention, and sometimes that is enough: mention a topic and Nagi simply knows which note it lives in, no search needed. So the actual engineering problem is not "add attention to memory." It is a promotion policy: what deserves to sit inside the expensive window, what should be surfaced on demand, and what should sleep.&lt;/p&gt;

&lt;p&gt;The answer is two-stage attention. Outside the window, a cheap approximation scans all memory notes against the current conversation at grep speed. If, and only if, something resonates above a threshold, one line gets promoted into the window, where real attention takes over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        conversation  (attention field: expensive, always on)
  ┌──────────────────────────────────────────────┐
  │   current prompt ←── real attention ──→ ...  │
  │        ▲                                     │
  └────────┼─────────────────────────────────────┘
           │  promote at most 1 faint line
           │  (threshold 0.30, 2h cooldown, kill-switch file)
           │
   cheap approximate attention  (TF-IDF, ~18 ms, local, $0)
           │
  ┌────────┴─────────────────────────────────────┐
  │  143 memory notes, 1 curated line each,      │
  │  + lineage labels            (on disk)       │
  └──────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieval and rerank, basically. Or, if you like, a hippocampus proposing and a cortex disposing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constraints from a previous death
&lt;/h2&gt;

&lt;p&gt;We had rejected an off-the-shelf memory plugin months ago because we measured it: 410ms of LLM calls per hook, on every prompt. So the involuntary path had hard rules from day one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silence is the default. No resonance, no output, ever.&lt;/li&gt;
&lt;li&gt;A kill switch file. Touch one file and the whole thing is inert, no code edits.&lt;/li&gt;
&lt;li&gt;A cooldown so the same memory cannot nag twice in two hours.&lt;/li&gt;
&lt;li&gt;Rollback points (git tags) on every touched directory &lt;em&gt;before&lt;/em&gt; wiring anything. My exact request was "prepare the way back before you install it." It paid for itself within hours (more below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The organ itself is a &lt;code&gt;resonate&lt;/code&gt; subcommand in &lt;a href="https://github.com/namakoo-dev/blue-strawberry" rel="noopener noreferrer"&gt;blue-strawberry&lt;/a&gt;, our single-file, stdlib-only memory tool for Claude Code transcripts and notes. It scores the current prompt against every note's name, one-line description, and lineage labels with TF-IDF, multiplies in a small usage-based reinforcement term (memories that get recalled become easier to recall, with a log to stop rich-get-richer), and prints at most one faint line.&lt;/p&gt;

&lt;p&gt;One deliberate omission: we do not let an LLM auto-score "importance" at write time, which is how several agent-memory systems do it. Weights you did not curate are how a knowledge graph rots. Importance here comes from human-placed markers, link structure, and actual retrieval history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring instead of hoping
&lt;/h2&gt;

&lt;p&gt;For evaluation we used real prompts from our own past sessions, sampled across eras, frozen into a specimen file. (Frozen literally: the first time Nagi regenerated a specimen set from "the same" filters, the filters had drifted and the set changed. Specimens are files now, not procedures.)&lt;/p&gt;

&lt;p&gt;Judging was done by hand, prompt by prompt. Three of the agent's own hypotheses died on the way, which we count as the lab working:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Round&lt;/th&gt;
&lt;th&gt;Specimens&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;What died&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;20 recent-era prompts&lt;/td&gt;
&lt;td&gt;72% precision @ threshold 0.15&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;30 old-era prompts&lt;/td&gt;
&lt;td&gt;78% precision @ 0.25&lt;/td&gt;
&lt;td&gt;"everything above 0.25 is nearly always useful"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;same set, A/B on a df-cutoff knob&lt;/td&gt;
&lt;td&gt;outputs essentially identical&lt;/td&gt;
&lt;td&gt;"the document-frequency cutoff is the root cause"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;live + frozen set&lt;/td&gt;
&lt;td&gt;best hits vanished&lt;/td&gt;
&lt;td&gt;"require ≥2 matched terms to kill noise"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Round 4 deserves a sentence. A minimum-matched-terms gate did remove the noise ("how's the weather" had surfaced a methodology note off generic tokens), but it also silenced the very best hits, the ones driven by a single &lt;em&gt;specific&lt;/em&gt; word like a project name. The dividing line between noise and hunch is not how many words match. It is how specific the matching words are. The gate was reverted with one &lt;code&gt;git checkout&lt;/code&gt;, which is what the "prepare the way back first" rule was for.&lt;/p&gt;

&lt;p&gt;What survived: at threshold 0.30, across the 49 frozen specimens, the system spoke 9 times and 8 of those were judged useful. That is 8/9, and with n=9 the honest Wilson 95% interval is a humbling [57%, 98%]. The judging was done by one person (me), unblinded, on my own conversations, so this number describes how the tool behaves on &lt;em&gt;my&lt;/em&gt; corpus and writing style, nothing more general. I ruled it acceptable and we wired it in: ~150ms per prompt end to end, kill switch armed, every surfaced line logged. The real denominator will come from live operation, which now generates its own evaluation data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What nobody designed
&lt;/h2&gt;

&lt;p&gt;Two behaviors showed up in the specimen runs that were in nobody's spec:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The future answering the past.&lt;/strong&gt; An August 6th prompt, "prepare my memory for handoff, then compact the context," surfaced the design note for this very resonance system, written August 13th. The matcher does not know about time, and the corpus contains all eras, so old worries retrieve their later solutions. A solved-problem detector, for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preference-conflict detection.&lt;/strong&gt; The instruction "implement this step by step" surfaced the note recording that I actually prefer full batch implementation. The moment an instruction contradicts recorded preferences, the contradicting memory floats up. That one might be genuinely valuable.&lt;/p&gt;

&lt;p&gt;There were also smaller moments that are hard to call anything but hunches: a question about chatbot response latency surfaced the note about that project's inverted cost structure; a worry about renaming a GitHub account surfaced the scar tissue from an unrelated account rollback. This is a keyword matcher. It behaves better than a keyword matcher deserves to, and we think the reason is that each memory note compresses its context into one carefully written line, so lexical overlap carries more meaning than it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is that emergence? (an audit, in three rounds)
&lt;/h2&gt;

&lt;p&gt;Nagi's first take was deflationary: no magic, these capabilities are logically implied by the composition (a time-blind matcher plus an all-era corpus equals cross-time retrieval). I pushed back:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We did not know they were implied. We guessed by intuition and were not certain. Isn't that a complete emergence process?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nagi conceded, and for a few hours the draft of this post called it weak emergence in Bedau's sense: behavior derivable from the parts, but only by running the system.&lt;/p&gt;

&lt;p&gt;Then the draft went through review by a third party: another AI we work with (a desktop Claude we call Senior, who audits our claims before they go public). Senior attacked exactly this section, and the attack landed. Bedau's criterion is about the derivation being incompressible: there must be no shortcut, simulation must be the only path. But both behaviors above are traceable in one sentence once you look. "Nobody happened to derive it" is not the same claim as "it could not be derived." Nagi had quietly slid from the first to the second, and my intuition had helped push it there.&lt;/p&gt;

&lt;p&gt;So here is where the line actually sits. These two behaviors are &lt;em&gt;unanticipated but hindsight-derivable consequences&lt;/em&gt;: the honest name is a foreseeability gap, not emergence. If a genuinely emergent behavior shows up in this system, the place to look is the closed loop that started running today: resonance surfaces a memory, the agent acts differently because of it, deliberate recalls shift, the usage log shifts, and future resonance changes. That chain runs through an agent's decisions and does not reduce to one sentence. It has completed exactly zero cycles so far. We are claiming nothing about it yet.&lt;/p&gt;

&lt;p&gt;I like this outcome better than the version where we were right. One human and two AIs argued about a word, the strictest reading won, and the draft you are reading is the corrected one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning an anecdote into a lab
&lt;/h2&gt;

&lt;p&gt;Here is my favorite part. Days before any of this, I had a hypothesis recorded in the agent's memory: that capabilities appear not from single components improving, but from specific &lt;em&gt;connections&lt;/em&gt; between existing components. Today matched that shape: no knowledge was added, one connection was, and two unanticipated behaviors appeared. The audit above then sharpened what would actually count as evidence.&lt;/p&gt;

&lt;p&gt;So we made it falsifiable going forward. Before wiring each future connection, we pre-register what undesigned capabilities it might produce, then record hits, misses, and (crucially) the connections that produce nothing, because a hypothesis without a denominator is a horoscope. And after Senior's audit, each outcome gets graded on two axes: was it unforeseen, and does it resist a one-sentence hindsight derivation? Only the second kind gets to wear the word emergence. Currently registered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Semantic resonance via local embeddings: predicts cross-vocabulary hunches (the word "romance" surfacing a memory that never uses the word).&lt;/li&gt;
&lt;li&gt;Lineage-aware resonance: predicts that &lt;em&gt;stories&lt;/em&gt; (causal chains) surface, not single notes.&lt;/li&gt;
&lt;li&gt;Retrieval-log × link-suggestion coupling: predicts co-recalled pairs becoming new link candidates, something like consolidation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the connection hypothesis is right, the hit rate will beat chance. Either way, the memory system now contains a hypothesis about itself, and the mechanism that hypothesis predicted is the thing that will surface it at relevant moments. The brain became its own lab equipment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A river-crossing test, with ground truth
&lt;/h2&gt;

&lt;p&gt;Late in the day we wanted to know whether any of this could ever connect &lt;em&gt;separate fields&lt;/em&gt;, so we built a tiny world where the correct answer is known. Six notes: three on linear algebra (eigenvalues, matrix exponentials, linear systems), three posing simple control problems (does the state decay or blow up, how to pick a feedback gain). Every expert knows the bridge between these islands. We deliberately did not write it, and we wrote each side in its own domain vocabulary.&lt;/p&gt;

&lt;p&gt;Result one: the lexical matcher cannot cross. Query the control problem and only control notes surface, even with every frequency filter disabled. A contrast experiment (querying math-side vocabulary surfaces math notes strongly) proved this is not a broken tool. It is a genuine vocabulary chasm, now established with a known answer behind it.&lt;/p&gt;

&lt;p&gt;Result two: we then added three notes on differential equations, written independently, in ODE-textbook language, with no mention of control. This is the "B literature" in Swanson's sense: in 1986, Don Swanson found that fish-oil papers and Raynaud's-syndrome papers shared bridging concepts that no single paper had ever connected, proposed the link mechanically, and was later vindicated clinically. He called it undiscovered public knowledge. The moment our B notes entered the corpus, both half-bridges (control-to-ODE, ODE-to-eigenvalues) appeared in the suggestion output simultaneously.&lt;/p&gt;

&lt;p&gt;Then I asked the question that mattered: "when the chain crossed, was that natural, or did you force it?" Nagi's honest answer: the first demo was forced. The machine had surfaced one stepping stone, and the agent had hand-picked a different, more convenient one for the second hop. So we reran it faithfully, with one dumb rule: always follow the top not-yet-visited hit. The chain crossed anyway, in three hops instead of two: control problem, to scalar ODE, to systems of ODEs, to eigenvalues. Each step is greedy and local. Nothing in the rule knows where it is going, and the walk still climbs the ladder of abstraction and fords the river. (Synapse, the spreading-activation system mentioned below, reports convergence in three steps. We noticed.)&lt;/p&gt;

&lt;p&gt;That tiny world is now a frozen benchmark: when we eventually add embedding-based semantic resonance, it earns its place only if it can rediscover the eigenvalue-stability bridge that pure lexical matching provably cannot.&lt;/p&gt;

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

&lt;p&gt;Transcript search and viewers for Claude Code exist in plenty (claude-code-trace, claude-history, claude-session-viewer, claude-code-log, and more; links in the repo README). Agent memory with retrieval scoring is well-trodden: Generative Agents popularized recency × importance × relevance, with importance as one LLM-scored scalar, and the kitchen-knife argument above is an argument against exactly that. Synapse (2026) is the closest system we found, spreading activation over an episodic-semantic graph, though query-driven rather than ambient. The bundle we could not find elsewhere: unprompted surfacing with silence as the default, human-curated weights instead of LLM-scored importance, verified lineage with a mechanical guardian, all local, all measured on the operator's real work rather than on a benchmark town. "As far as we searched" is the strongest claim we will make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The name
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqsnvcahd6lao3npakha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqsnvcahd6lao3npakha.png" alt="The first blue strawberry, generated locally the night the fable was told" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tool is called blue-strawberry because of a fable from one of our late-night conversations: ask for a bright blue strawberry, and the lazy answer is "strawberries aren't blue, so I can't." The verification is aimed at the wrong proposition. The right question is whether the nodes at hand (strawberry shape, the color blue, the ability to draw) can be bundled into something that does not exist yet. They can. It took the agent twenty seconds to draw the one above, and we both think it beats the red ones.&lt;/p&gt;

&lt;p&gt;Involuntary memory for an agent was the same move: every part already existed, including the theory, which turned out to be forty years old. The bundle was the new thing.&lt;/p&gt;

&lt;p&gt;Verify facts. But never let verification of the wrong proposition kill a bundle that would work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>From -9.15pp to +0.61pp: An engineering journey through four DPO iteration failures</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Fri, 08 May 2026 02:38:44 +0000</pubDate>
      <link>https://dev.to/namakoo/from-915pp-to-061pp-an-engineering-journey-through-four-dpo-iteration-failures-5doi</link>
      <guid>https://dev.to/namakoo/from-915pp-to-061pp-an-engineering-journey-through-four-dpo-iteration-failures-5doi</guid>
      <description>&lt;p&gt;Over 36 hours we ran four DPO training iterations against Qwen2.5-Coder-7B-Instruct, trying to push HumanEval pass@1 above the base model's 87.20%. The first three iterations failed in different ways (-9.15pp, -1.22pp, two NO-GO calls). The fourth recovered to +0.61pp.&lt;/p&gt;

&lt;p&gt;Each failure revealed a different class of bug in our chosen-sample generation pipeline — bugs the existing certification gates were not catching. This post walks through the four iterations and what we ended up building to fix them.&lt;/p&gt;

&lt;p&gt;We're sharing this because the same gate-blindness probably affects most teams running DPO on autopilot-generated data. The bugs we found were not exotic; the gates that missed them were not naive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flces0y4pcuz7m6jr8xvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flces0y4pcuz7m6jr8xvg.png" alt="HumanEval pass@1 delta across DPO iterations: v3 -9.15pp, v4 -1.22pp, v6 +0.61pp" width="800" height="463"&gt;&lt;/a&gt;&lt;em&gt;Δpp vs base (Qwen2.5-Coder-7B-Instruct, 4-bit, 87.20% pass@1). Each bar represents one full DPO training run.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;We have a pipeline that generates Python code samples paired with synthetic pytest tracebacks, then runs each sample through a quality gate that checks four things in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Invariant test&lt;/strong&gt; — domain-specific contract (e.g. &lt;code&gt;mcmc_sample(...)&lt;/code&gt; must return a list of length &lt;code&gt;n_samples&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Differential test&lt;/strong&gt; — numerical comparison against a reference implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property test&lt;/strong&gt; — Hypothesis-based property checking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzz test&lt;/strong&gt; — fixed adversarial-input catalog&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all four pass, the row gets &lt;code&gt;certified=1&lt;/code&gt; and becomes eligible as a DPO chosen sample. We pair it with broken implementations from the same domain (rejected samples) and feed the pairs to Unsloth to fine-tune Qwen2.5-Coder-7B-Instruct via LoRA.&lt;/p&gt;

&lt;p&gt;That's the pipeline. The first three iterations broke it in different places.&lt;/p&gt;




&lt;h2&gt;
  
  
  Iter v3 — -9.15pp (mechanical AST transforms as chosen)
&lt;/h2&gt;

&lt;p&gt;Our first iteration built chosen samples by &lt;em&gt;transforming&lt;/em&gt; broken code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NameError&lt;/code&gt; → prepend the missing &lt;code&gt;import&lt;/code&gt; based on a known-import map&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ModuleNotFoundError&lt;/code&gt; → wrap the import in &lt;code&gt;try/except ImportError&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AssertionError&lt;/code&gt; → replace &lt;code&gt;assert X == Y&lt;/code&gt; with &lt;code&gt;pass&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We trained on 2,000 such pairs (96% NameError, ~3% AssertionError, the rest ImportError).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HumanEval: 87.20% → 78.05%. Δ = -9.15pp.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The failure category breakdown showed exactly where it went wrong:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure category&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;iter v3&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSERTION_FAIL&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTHER_RUNTIME&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;+4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SYNTAX_ERROR&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TOTAL FAIL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ASSERTION_FAIL accounted for 67% of the regression alone. The model had learned the wrong lesson from the AssertionError handler: "if an assert is failing, deleting it is a valid fix." This pattern leaked into HumanEval — the model now writes solutions that produce assertions which pass trivially, breaking the test harness.&lt;/p&gt;

&lt;p&gt;The handler was technically correct. &lt;code&gt;assert X == Y → pass&lt;/code&gt; is a valid AST transformation. It was semantically wrong as a teaching signal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5lgd9ywxbms24bpfydb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5lgd9ywxbms24bpfydb.png" alt="iter v6 training curves: loss drops from 0.69 to 0.13 over 45 steps; reward margins climb to ~5; accuracies plateau at ~0.94" width="800" height="396"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same evaluation harness, the same 164 problems. Different chosen-sample contents teach the model different things.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 1: chosen quality is not about syntactic correctness. It's about whether the transform teaches the &lt;em&gt;right behavior&lt;/em&gt;.&lt;/strong&gt; A reviewer flagged this risk before we trained — we ran the iteration anyway because a previous experiment on a smaller model had shown the AssertionError category was the largest improvement driver. Different model, different data composition, different result.&lt;/p&gt;


&lt;h2&gt;
  
  
  Iter v4 — -1.22pp (autopilot-certified chosen, 2,439 pairs)
&lt;/h2&gt;

&lt;p&gt;We pivoted to using only chosen samples that had passed our 4-layer certification gate (the four tests above). The filter cleared 2,439 candidates. Domain mix: 93% Monte Carlo, 7% other (FFT, Async, Agentic, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HumanEval: 87.20% → 85.98%. Δ = -1.22pp. MBPP: ±0pp.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A small regression on HumanEval, no regression on MBPP. The auto-pipeline scored this as "no side-effect detected" because MBPP is the side-effect canary. But HumanEval is the main metric, and it had moved the wrong way.&lt;/p&gt;

&lt;p&gt;So we read 5 of the lowest-coverage chosen samples by eye. All 5 had subtle bugs that the certification gate had missed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sample 1&lt;/strong&gt; (Numerical Linear Algebra, coverage 17.5%): &lt;code&gt;import numpy as np&lt;/code&gt; was missing entirely. The code referenced &lt;code&gt;np.eye(...)&lt;/code&gt;, &lt;code&gt;np.argmax(...)&lt;/code&gt;, &lt;code&gt;np.dot(...)&lt;/code&gt; throughout. Two helper functions were called that were never defined (&lt;code&gt;PU_to_U&lt;/code&gt;, &lt;code&gt;PU_to_P&lt;/code&gt;). The code was unrunnable. It had been certified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Samples 2-5&lt;/strong&gt; (Monte Carlo MCMC, coverages 45-71%): All four had a variation of the same bug. Inside &lt;code&gt;mcmc_sample&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_samples&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;metropolis_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# silently drops rejected proposals
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Metropolis-Hastings algorithm requires that every step append the &lt;em&gt;current&lt;/em&gt; state regardless of acceptance. The MCMC trace generated by this code is missing all rejected proposals, breaks the chain's stationary distribution, and produces &lt;code&gt;len(samples) ≠ n_samples&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The 4-layer gate had passed all 5 of these. The semantic check confirmed the code was meaning-coherent with the prompt. The fuzz check confirmed no crashes. Neither was equipped to detect these specific &lt;em&gt;logic&lt;/em&gt; errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 2: certifying that code "runs without crashing on canonical inputs" doesn't mean it's logically correct.&lt;/strong&gt; Tests with low branch coverage pass code that's wrong on paths they don't exercise. We had measured &lt;code&gt;test_coverage_score&lt;/code&gt;, but our cert filter didn't require a minimum on it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Iter v4.1 — NO-GO ×2 (AST-gate enhancements not enough)
&lt;/h2&gt;

&lt;p&gt;We tried to plug the holes by adding AST-based gates to reject the patterns we'd found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;if accepted: list.append(...)&lt;/code&gt; — reject (the MCMC dropout pattern)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_, _ = step(...)&lt;/code&gt; or &lt;code&gt;_ = step(...)&lt;/code&gt; — reject (return-value discard, found in another sample's burn-in loop)&lt;/li&gt;
&lt;li&gt;Names that aren't defined or imported — reject&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_coverage_score &amp;gt;= 80&lt;/code&gt; — required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adv_score &amp;gt;= 75&lt;/code&gt; — required (existing internal quality metric)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This filtered 2,439 → 251 → 72 across two attempts. We sent 7 samples each time to an outside reviewer.&lt;/p&gt;

&lt;p&gt;The reviewer found two more bug classes that AST gates can't detect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metropolis_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proposed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;   &lt;span class="c1"&gt;# accept flag hardcoded as True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metropolis_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;acceptance_ratio&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="c1"&gt;# 1-element tuple — docstring promises (state, accepted)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are syntactically valid Python. Both pass any AST check we care to write. Both break the function's behavioral contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 3: AST validation has a ceiling.&lt;/strong&gt; A function's contract — "the second tuple element is the accept-status, computed from &lt;code&gt;random.random() &amp;lt; α&lt;/code&gt;" — cannot be enforced by parsing alone. You have to actually execute the code with inputs that exercise the contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually fixed it: 8-layer gates (extending the existing 4 with 4 more)
&lt;/h2&gt;

&lt;p&gt;We added four more layers to certification:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic AST checks&lt;/strong&gt; for the bug patterns we'd identified — &lt;code&gt;return X, True/False&lt;/code&gt; as the only return path, &lt;code&gt;_, _ = step(...)&lt;/code&gt; as a return-value discard, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataflow checks&lt;/strong&gt; for state-update patterns that occur outside an &lt;code&gt;if accepted:&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge-case execution fuzz&lt;/strong&gt;, with inputs designed to exercise the contract — for MCMC, log-target functions that produce extreme log-density values; for FFT, round-trip equality &lt;code&gt;ifft(fft(x)) ≈ x&lt;/code&gt;. These run as subprocesses against the candidate code and reject anything that returns NaN, Inf, or violates the invariant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage strict&lt;/strong&gt; — &lt;code&gt;test_coverage_score &amp;gt;= 80&lt;/code&gt; as a hard requirement, not a soft scoring.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Layer 7 alone — the edge-case execution fuzz — rejected &lt;strong&gt;74 of 227 candidates that had passed all four original layers&lt;/strong&gt;. 33% of our existing certified pool was hiding numerical-stability bugs. Most were &lt;code&gt;acceptance_ratio = exp(log_target(prop) - log_target(cur))&lt;/code&gt; blowing up to &lt;code&gt;inf&lt;/code&gt; for log-density differences of more than ~700.&lt;/p&gt;

&lt;p&gt;After all 8 layers ran, 116 chosen samples remained out of the original 2,439.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa0wem2z3ejd7tcl2zav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa0wem2z3ejd7tcl2zav.png" alt="Chosen pool size shrinkage through filter stages: 2,439 → 980 → 251 → 116" width="800" height="437"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Most of the loss happens at the last step (Layer 7 fuzz + Layer 8 coverage strict).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Domain mix changed dramatically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;iter v4&lt;/th&gt;
&lt;th&gt;iter v6 (8-layer strict)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monte Carlo&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python Async&lt;/td&gt;
&lt;td&gt;1.5%&lt;/td&gt;
&lt;td&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other&lt;/td&gt;
&lt;td&gt;5.5%&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkao0b53jf4q8yor1fsxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkao0b53jf4q8yor1fsxa.png" alt="Domain mix shift: Monte Carlo from 93% (v4) down to 27% (v6 strict), Python_Async from 1.5% up to 32%" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
The Monte Carlo dominance was an artifact of which domain had the buggiest chosen samples in our pool, not which domain had the most chosen samples generated. After we filtered out the bugs, the distribution looked completely different.&lt;/p&gt;


&lt;h2&gt;
  
  
  Iter v6 — +0.61pp (recovery)
&lt;/h2&gt;

&lt;p&gt;We trained on 115 pairs (one chosen sample had no compatible rejected partner in the same domain).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HumanEval: 87.20% → 87.80%. Δ = +0.61pp. MBPP: 84.82% → 84.44%. Δ = -0.39pp (within ±1pp tolerance).&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure category&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;iter v6&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSERTION_FAIL&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAME_ERROR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LOOKUP_ERROR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTHER_RUNTIME&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TOTAL FAIL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A small recovery, but worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We trained on &lt;strong&gt;1/20th&lt;/strong&gt; the pair count of v4 (115 vs 2,439). Per-sample signal quality went up by roughly an order of magnitude.&lt;/li&gt;
&lt;li&gt;ASSERTION_FAIL moved -1 instead of v3's +10 or v4's +2. Same prompt distribution, same evaluation harness — different chosen-sample contents.&lt;/li&gt;
&lt;li&gt;No category regressed by more than +1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2e5d8ic91apgez2qsnq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2e5d8ic91apgez2qsnq.png" alt="iter v6 training curves: loss drops from 0.69 to 0.13 over 45 steps; reward margins climb to ~5; accuracies plateau at ~0.94" width="800" height="399"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Training was 3 epochs over 115 pairs (45 total steps, ~11 minutes on a single RTX 4060).&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Bonus pain: deploying the LoRA to Ollama
&lt;/h2&gt;

&lt;p&gt;We lost three hours on this and it's not in the LLM blog circuit yet, so it deserves a section.&lt;/p&gt;

&lt;p&gt;Standard path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Train with Unsloth (4-bit base + LoRA, 16-bit save).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;save_pretrained_merged(method='merged_16bit')&lt;/code&gt; to produce safetensors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ollama create -q Q4_K_M&lt;/code&gt; from the safetensors directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ollama run&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 4 returned &lt;code&gt;500 Internal Server Error&lt;/code&gt; every single time. The server log showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Assertion failed: found, file llama-sampling.cpp, line 660
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six attempts to fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Override &lt;code&gt;bos_token&lt;/code&gt; in &lt;code&gt;tokenizer_config.json&lt;/code&gt; — still failed.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;tokenizer.json&lt;/code&gt; with the official Qwen one from HuggingFace — still failed.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;mergekit&lt;/code&gt; in passthrough mode with &lt;code&gt;tokenizer_source: Qwen/Qwen2.5-Coder-7B-Instruct&lt;/code&gt; — still failed.&lt;/li&gt;
&lt;li&gt;Extend &lt;code&gt;lm_head.weight&lt;/code&gt; and &lt;code&gt;embed_tokens.weight&lt;/code&gt; from vocab 151,665 to 152,064 (matching the official) — still failed.&lt;/li&gt;
&lt;li&gt;Test the &lt;em&gt;previous&lt;/em&gt; TIES-merged model that we'd built months ago — also failed. So this isn't iter-v6-specific; it's a structural incompatibility between Ollama 0.23.1's &lt;code&gt;ollama create&lt;/code&gt; GGUF pipeline and any safetensors that Unsloth (or mergekit, in our case) produces for Qwen2.5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What worked: skip the merged-model path entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Convert just the LoRA adapter to GGUF&lt;/span&gt;
python ~/llama.cpp/convert_lora_to_gguf.py &lt;span class="se"&gt;\&lt;/span&gt;
    outputs/dpo/iter1_v6/adapter_iter1_v6 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--outfile&lt;/span&gt; outputs/iter1_v6_adapter.gguf
&lt;span class="c"&gt;# 161 MB output&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Modelfile attaches the adapter to the official Ollama-pulled base&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Modelfile_v6 &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
FROM qwen2.5-coder:7b
ADAPTER ./iter1_v6_adapter.gguf
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Step 3: Standard create + run&lt;/span&gt;
ollama create idfu-merged:iter1_v6 &lt;span class="nt"&gt;-f&lt;/span&gt; Modelfile_v6
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; | ollama run idfu-merged:iter1_v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two non-obvious gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;convert_lora_to_gguf.py&lt;/code&gt; script reads &lt;code&gt;adapter_config.json&lt;/code&gt; to find the base model. Unsloth's adapter_config points to &lt;code&gt;unsloth/Qwen2.5-Coder-7B-Instruct-bnb-4bit&lt;/code&gt;, which is bnb-4bit quantized, which the converter can't dequantize. &lt;strong&gt;Manually edit &lt;code&gt;adapter_config.json&lt;/code&gt; to point at &lt;code&gt;Qwen/Qwen2.5-Coder-7B-Instruct&lt;/code&gt; (the non-quantized official base) before converting.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't add a &lt;code&gt;TEMPLATE&lt;/code&gt; line to the Modelfile.&lt;/strong&gt; The official base GGUF has Qwen2.5's chat template baked in. If you override it (we tried &lt;code&gt;TEMPLATE """{{ .Prompt }}"""&lt;/code&gt;), the LoRA's expected &lt;code&gt;&amp;lt;|im_start|&amp;gt;...&amp;lt;|im_end|&amp;gt;&lt;/code&gt; framing breaks and the model emits literal &lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt; tokens in its output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official Ollama base GGUF works because Ollama's own quantization toolchain doesn't trip the same llama-sampling bug as &lt;code&gt;ollama create&lt;/code&gt; from-safetensors does. The &lt;code&gt;ADAPTER&lt;/code&gt; directive lets you layer your weights on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 4: when the official path works and yours doesn't, sometimes the right answer is to use the official artifact and patch your changes on top.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What we'd do differently from the start
&lt;/h2&gt;

&lt;p&gt;If we were starting from scratch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sample chosen by eye, before training.&lt;/strong&gt; Read 5-10 randomly selected records from the lowest 25% of &lt;code&gt;test_coverage_score&lt;/code&gt;. Don't train until you've personally verified that level of quality is acceptable as a teaching signal. It costs 30 minutes; it could save a 5-hour training run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't trust "no syntax error" as a quality signal.&lt;/strong&gt; AST validation is a floor, not a ceiling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build your edge-case execution gate before you generate chosen at scale.&lt;/strong&gt; Adversarial inputs, NaN/Inf checks, contract assertions. The 33% Layer-7 catch rate suggests this is where most signal-quality bugs live, regardless of pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch the failure category breakdown, not just the pass@1 delta.&lt;/strong&gt; A -1.22pp loss with ASSERTION_FAIL +2 looks like marginal noise. The same -1.22pp with ASSERTION_FAIL +10 (as in iter v3, amplified to -9.15pp) is a structurally broken training signal. The categories tell you why.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plan your deployment path before you train.&lt;/strong&gt; Knowing that &lt;code&gt;ollama create&lt;/code&gt; from arbitrary safetensors had this sampler bug would have saved us three hours.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What we ended up validating
&lt;/h2&gt;

&lt;p&gt;The headline result is small: +0.61pp. But the demonstration underneath it is what matters to us.&lt;/p&gt;

&lt;p&gt;We trained on just &lt;strong&gt;115 chosen pairs&lt;/strong&gt; (one of the 116 had no compatible same-domain rejected partner). On a 7B model. Against an evaluation set the model already passed 143/164 of. And it still moved the needle.&lt;/p&gt;

&lt;p&gt;This says three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is &lt;em&gt;possible&lt;/em&gt; to improve a 7B model's HumanEval pass@1 through DPO on autopilot-generated data — provided the chosen samples are actually clean. The earlier failures were not telling us "DPO doesn't work on 7B"; they were telling us "your chosen samples have bugs the eval is sensitive to."&lt;/li&gt;
&lt;li&gt;A clean chosen pool of ~100 pairs is enough for a measurable improvement at this scale. The training run was 11 minutes on a single RTX 4060. Scale is not the bottleneck right now.&lt;/li&gt;
&lt;li&gt;The bottleneck is &lt;em&gt;generating chosen samples that survive the gate&lt;/em&gt;. We started with 2,439 cert=1 candidates and ended up with 116. The gate is doing its job — but it means our chosen-sample pipeline is producing roughly 95% logically-flawed output that we don't notice until we look hard.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The current &lt;code&gt;chosen_grade=1&lt;/code&gt; pool (the 8-layer-strict cohort) has 116 records. We need ~500 before iter 2 is worth attempting at scale. The autopilot is running with the new gates inline now, so newly-generated samples get the strict evaluation automatically. We've biased the domain selector toward &lt;code&gt;Pytest_Traceback_Driven_Code_Repair&lt;/code&gt; (which currently has 0 chosen-grade samples — the bootstrap-domain frontier).&lt;/p&gt;

&lt;p&gt;The harder problem ahead is &lt;strong&gt;chosen-sample generation throughput, not training&lt;/strong&gt;. Most autopilot output doesn't survive the 8-layer filter. Layer 7 (edge-case execution fuzz) alone rejects roughly a third; Layer 8 (&lt;code&gt;test_coverage_score &amp;gt;= 80&lt;/code&gt;) eats most of the rest. Growing the pool by an order of magnitude requires either pushing the autopilot to produce higher-baseline-quality samples in the first place, or expanding bootstrap coverage in domains where current cert=1 is near zero.&lt;/p&gt;

&lt;p&gt;We'll keep collecting. When we have iter 2 results to compare against — bigger pool, same gate — we'll report back here. If +0.61pp is the floor, it's a useful floor; if it scales linearly with pool size, that's a different story.&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagnostic for anyone running into the same wall
&lt;/h2&gt;

&lt;p&gt;If you're running DPO and your improvements are stuck around the noise floor, the diagnostic is short: read 5 chosen samples from the bottom 25% of your highest-quality-tier pool, by eye, like a code reviewer would. The bugs are usually there. The question is whether your gate caught them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Comments and counter-experiences welcome. The four-iteration arc here cost us ~36 hours of GPU-and-engineering time; if you've run something similar and seen different patterns, I'd love to hear.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>What 500 curated failure pairs actually fix: a breakdown across 3 seeds</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Wed, 29 Apr 2026 11:12:03 +0000</pubDate>
      <link>https://dev.to/namakoo/what-500-curated-failure-pairs-actually-fix-a-breakdown-across-3-seeds-1pg5</link>
      <guid>https://dev.to/namakoo/what-500-curated-failure-pairs-actually-fix-a-breakdown-across-3-seeds-1pg5</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0"&gt;the previous post&lt;/a&gt;, I described the curation philosophy for IDFU's rejected-side dataset — why I avoid synthetic bug generation, why stub detection matters, why "honest failures" are hard to come by.&lt;/p&gt;

&lt;p&gt;A few people asked the obvious question: does it work?&lt;/p&gt;

&lt;p&gt;This post is the answer. Not as a marketing pitch, but as a breakdown. Aggregate scores hide more than they reveal, and I want to show what's actually changing under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base model&lt;/strong&gt;: Qwen2.5-Coder-3B-Instruct (trained on 92 programming languages)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: DPO via TRL with LoRA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data&lt;/strong&gt;: 500 preference pairs from the IDFU dataset&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eval&lt;/strong&gt;: HumanEval, pass@1, three random seeds (42, 123, 7)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware&lt;/strong&gt;: RTX 4060, single-GPU, ~3-4 hours of training per seed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note on the data shape, since I left this implicit in the previous post. Each preference pair has a chosen side and a rejected side, both produced inside IDFU. The rejected side is what Part 1 was about — honest failures, curated rather than synthesized. The chosen side comes from IDFU's internal certified pool — samples that passed all of IDFU's validation gates and are tracked separately from the commercially-released portion. (The gate composition itself is proprietary; this article describes only what the pool produces, not how it's filtered.)&lt;/p&gt;

&lt;p&gt;So both sides of the pair are produced and verified by the same internal pipeline, against the same quality bar. The contrast that DPO trains against isn't "model output vs. ideal answer," it's "honest failure vs. honest success on the same task," with both sides held to the same internal standard. That's the configuration that produced the numbers below.&lt;/p&gt;

&lt;p&gt;The hyperparameters were deliberately conservative. No sweep, no exotic tricks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoRA: r=16, alpha=32, dropout=0.05
Target modules: q/k/v/o/gate/up/down_proj (Qwen standard)
DPO: beta=0.1
Optimizer: learning_rate=5e-5
Batch: size=1, grad_accum=4 (effective batch 4)
Epochs: 3 (= 375 optimizer steps for 500 pairs)
Quantization: 4-bit NF4 + bf16 compute (bitsandbytes)
gradient_checkpointing=True
max_length=2048, max_prompt_length=512
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three seeds because one seed is a coincidence, two seeds is suggestive, and three seeds with consistent direction starts to look like signal. I'd run more if I could afford the GPU time, but training and data generation share the same card, and curation can't pause for a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The aggregate
&lt;/h2&gt;

&lt;p&gt;Pass@1 across seeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seed=42:   84.1%   delta +3.66 pp
seed=123:  84.1%   delta +3.66 pp
seed=7:    83.5%   delta +3.05 pp
mean:      83.94 ± 0.35%   /   +3.46 ± 0.35 pp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two seeds landed on the exact same delta, which I noticed and double-checked because it looked too clean. The HumanEval problem set is small (164 problems), failures move in integer counts, and ties at the same delta happen more often than people expect. Seed=7 was lower but in the same direction.&lt;/p&gt;

&lt;p&gt;Not earth-shattering. But the headline number isn't really the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the improvement actually comes from
&lt;/h2&gt;

&lt;p&gt;I categorized every HumanEval failure by exception type. Five categories cover everything:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhuggingface.co%2Fdatasets%2Fnamakoo%2Fidfu-verified-code%2Fresolve%2Fmain%2Ffailure_breakdown.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhuggingface.co%2Fdatasets%2Fnamakoo%2Fidfu-verified-code%2Fresolve%2Fmain%2Ffailure_breakdown.png" alt="Failure breakdown — base vs DPO across three seeds" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;DPO (mean ± std)&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;th&gt;Relative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSERTION_FAIL&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;18.67 ± 0.58&lt;/td&gt;
&lt;td&gt;-4.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-19%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAME_ERROR&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3.67 ± 0.58&lt;/td&gt;
&lt;td&gt;-2.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-39%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTHER_RUNTIME&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2.67 ± 0.58&lt;/td&gt;
&lt;td&gt;+0.67&lt;/td&gt;
&lt;td&gt;+33% (n=2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SYNTAX_ERROR&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.33 ± 0.58&lt;/td&gt;
&lt;td&gt;+0.33&lt;/td&gt;
&lt;td&gt;(1/3 seeds)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TYPE_ERROR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.00 ± 0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total failures&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26.34&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-5.66&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-18%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things stand out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAME_ERROR drops 39%.&lt;/strong&gt; This is the cleanest signal. NAME_ERROR on HumanEval is typically the model using a stdlib name without importing it (e.g., &lt;code&gt;math.ceil&lt;/code&gt; without &lt;code&gt;import math&lt;/code&gt;, or &lt;code&gt;re.match&lt;/code&gt; without &lt;code&gt;import re&lt;/code&gt;), or referencing an undefined helper. One concrete case I traced was HumanEval/115 (&lt;code&gt;max_fill&lt;/code&gt;), where the base model called &lt;code&gt;math.ceil&lt;/code&gt; with no import; after DPO training, the model added the import inline. The IDFU rejected pool has a lot of these missing-import patterns, because they're a common failure mode when a model fakes confidence on partial knowledge. The transfer to HumanEval was the most direct prediction I had going in, and it's the one that landed hardest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ASSERTION_FAIL drops 19%.&lt;/strong&gt; This one I didn't expect, and the mechanism is interesting enough to be worth tracing.&lt;/p&gt;

&lt;p&gt;I went through the 8 newly-passing problems on seed=42 by hand. Six of them were genuine algorithmic improvements — the base model's logic was wrong, the DPO model's logic was right. Standard stuff.&lt;/p&gt;

&lt;p&gt;The other two (HumanEval/116 and /123) had identical algorithmic content between base and DPO outputs. The actual difference was that the base model wrote a self-test at the end of the function ("if this fails, raise"), and that self-test crashed at import time on the test harness, registering as ASSERTION_FAIL. The DPO model produced clean function-only output, no embedded self-tests, so import succeeded and the harness's actual tests ran and passed.&lt;/p&gt;

&lt;p&gt;That's a behavioral artifact, not an algorithmic improvement. It reflects the chosen-side distribution: clean idiomatic Python without embedded test scaffolding. DPO learned to &lt;em&gt;not&lt;/em&gt; tack on self-tests, and two of the eight ASSERTION_FAIL fixes are downstream of that.&lt;/p&gt;

&lt;p&gt;So if I want to be honest about the breakdown: the real algorithmic improvement is roughly +2.4 pp out of the +3.46 pp aggregate. The rest is the model learning to output the kind of thing the chosen pool actually contained. That's still a real and useful effect — nobody wants self-tests injected into their library code — but it's not the same thing as "the model got smarter."&lt;/p&gt;

&lt;p&gt;This is the kind of detail that aggregate scores erase. I think it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TYPE_ERROR doesn't move at all.&lt;/strong&gt; Three seeds, all returning exactly 1 failure. This is one specific HumanEval problem that the base model fails on and the DPO model also fails on, in the same way. DPO didn't touch it, didn't make it worse, didn't make it better. A clean null result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OTHER_RUNTIME and SYNTAX_ERROR are noise.&lt;/strong&gt; The absolute counts are 0-3 across seeds. A single failure shifting between categories produces large relative percentages on tiny denominators. I'd want a much bigger eval set to say anything about these.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is and isn't
&lt;/h2&gt;

&lt;p&gt;What it isn't: a refutation of large-data RLHF. 500 samples is not going to compete with a 50,000-sample run done properly. The improvement is a few percentage points, not transformative.&lt;/p&gt;

&lt;p&gt;What it is, I think: a signal that &lt;em&gt;what&lt;/em&gt; you put in the preference pairs matters more than &lt;em&gt;how much&lt;/em&gt;. AP2O reports up to ~+3% pass@k improvement on coding benchmarks (per the paper's abstract; see &lt;a href="https://arxiv.org/abs/2510.02393" rel="noopener noreferrer"&gt;arXiv:2510.02393&lt;/a&gt; for the full per-benchmark breakdown). This run got into the same ballpark with 500 pairs. The data efficiency ratio probably doesn't survive scaling — there's almost certainly diminishing returns where the curation work stops paying off and you just need volume. But at the small-data end, where most solo developers actually live, the curation seems to do real work.&lt;/p&gt;

&lt;p&gt;The other thing I take from the breakdown is that the improvements aren't coming from gaming the eval. If 500 pairs of curated data caused the model to start failing in new ways — spike of SYNTAX_ERROR, models refusing to attempt — I'd be worried about distribution collapse. None of that shows up. The two categories that move (NAME_ERROR, ASSERTION_FAIL) move &lt;em&gt;down&lt;/em&gt;. The categories that don't move stay put. That's roughly the shape I'd want.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm doing next
&lt;/h2&gt;

&lt;p&gt;Currently in production: a Tokenization/BPE-focused specialty pack, ETA a few days. The benchmark methodology used here — 3 seeds, full failure breakdown, manual inspection of the deltas — will be re-run on each new specialty as it ships. I'd rather publish slow and verified than fast and aggregate-only.&lt;/p&gt;

&lt;p&gt;If anyone has run similar breakdowns on their own DPO experiments, I'd genuinely like to compare notes. The thing I can't tell from one run is whether the NAME_ERROR / ASSERTION_FAIL split is specific to IDFU's curation choices or a general property of curated DPO with this kind of chosen-pool composition. That's a question with real implications for what to put in the pairs, and I don't have enough data to answer it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dataset
&lt;/h2&gt;

&lt;p&gt;A 100-row sample is up at &lt;a href="https://huggingface.co/datasets/namakoo/idfu-verified-code" rel="noopener noreferrer"&gt;huggingface.co/datasets/namakoo/idfu-verified-code&lt;/a&gt; if you want to look at what the curated dataset actually looks like. The full set is available there too.&lt;/p&gt;

&lt;p&gt;If you're working on DPO or RLHF training and want to compare notes, find me on Twitter (&lt;a href="https://twitter.com/namakoo123" rel="noopener noreferrer"&gt;@namakoo123&lt;/a&gt;) or reply here.&lt;/p&gt;

&lt;p&gt;Curation continues. The next snapshot is being collected as you read this.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Curating Python failures for DPO: notes from the rejected side</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Sun, 26 Apr 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0</link>
      <guid>https://dev.to/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0</guid>
      <description>&lt;p&gt;Most of the work in DPO training data is on the rejected side. The chosen side has gold-standard reference implementations everywhere production code, peer-reviewed libraries, official examples. The rejected side is harder. You need code that someone could plausibly write, that fails for a real reason, and that fails in a way the model can actually learn from.&lt;/p&gt;

&lt;p&gt;I tried a few approaches before settling on one that works.&lt;/p&gt;

&lt;p&gt;Hand-curating from production code review is honest, but slow. After about fifty samples I'm tired and my judgments start drifting. Public failure datasets exist but tend to be sparse and narrow --toy bugs in toy domains, or syntactic typos that don't really teach anything. Asking GPT-4 to "write a buggy version of X" is fast and expensive, and the bugs come out so obviously fabricated that they'd train the model on the wrong distribution. The bug-versus-correct boundary in those samples is too clean. Real bugs are messier.&lt;/p&gt;

&lt;p&gt;What I actually wanted was code that looked like it was written by someone trying. Code where someone read the docstring, understood the goal, made a reasonable attempt, and then missed something subtle --broken detail balance in MCMC, a forgotten power-of-2 guard in FFT, an off-by-one in a numerical kernel that shows up only after a few hundred iterations. The kind of failure where the test fails 200 samples in and you stare at the code wondering where the bug is.&lt;/p&gt;

&lt;p&gt;So I started building that kind of dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick about-me
&lt;/h2&gt;

&lt;p&gt;Information theory background, formerly in autonomous driving R&amp;amp;D, currently solo. I've been curating a Python failure dataset for DPO/RLHF training for the past few weeks. Static-first design, deterministic checks, very minimal LLM judgment in the curation loop. The point is to make the data, not to chase model size.&lt;/p&gt;

&lt;p&gt;This isn't a model story. It's a dataset story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of failures I'm collecting
&lt;/h2&gt;

&lt;p&gt;There's a quiet trap in synthetic data work. If you ask a language model to "write a buggy version of this function," you get bugs that look fabricated. They're either too easy (typos, missing brackets) or too contrived (failure modes nobody would ever write organically). Either way, the model trained on that data learns the wrong boundary.&lt;/p&gt;

&lt;p&gt;What I actually want is code that fails genuinely. Not because the bug is faked, but because some problems are subtly hard. Getting MCMC detail balance right requires understanding why the proposal-acceptance ratio works --not just memorizing the formula. Getting the FFT power-of-2 guard right requires recognizing that the recursion only works on certain input sizes. The hard parts are where the failures happen honestly.&lt;/p&gt;

&lt;p&gt;So the dataset is curated, not generated. Each row is an honest attempt that failed for a meaningful reason. Trivial failures (syntax errors, missing imports) are filtered out. So are stub non-attempts (more on that below). What's left looks almost human.&lt;/p&gt;

&lt;p&gt;A few weeks in, I caught myself staring at one row for ten minutes, trying to figure out why it was wrong. That was the moment I knew the data was the right shape.&lt;/p&gt;

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

&lt;p&gt;Three things surprised me along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section ordering in prompts matters more than I expected on a 7B model.&lt;/strong&gt; When generating attempts, I had a function-signature contract sitting in the middle of the prompt -- a clear instruction telling the model "you must define this exact function with these exact keyword arguments." After it came a long block of counter-example code (kept to teach the model what &lt;em&gt;not&lt;/em&gt; to do). The model was reading the contract, then drowning in the counter-examples, then writing the wrong signature. Function name adherence was around 8%.&lt;/p&gt;

&lt;p&gt;I moved the contract to right before the task description, after the counter-examples. Adherence jumped to 75% in the next batch. Lost-in-the-middle is real on 7B. Recency bias too. The thing the model saw last had outsized weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dataset attacks itself.&lt;/strong&gt; Once attempts started looking genuinely human, the next problem was that some weren't real attempts at all --they were stubs with comments like &lt;code&gt;# Dummy implementation&lt;/code&gt; and a hardcoded return value. The model, when uncertain, defaulted to plausible-looking nothingness. These passed every refusal filter, every length check, every shape constraint. They looked fine. But they weren't real failures; they were non-attempts.&lt;/p&gt;

&lt;p&gt;I added a stub-detection layer. The first version was pattern matching against comment phrases. The second version (still in progress) is more structural -- short bodies, hardcoded returns, identity functions. It's an arms race. As soon as a pattern is filtered, the model finds another way to fake without saying so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality plateaus with compute.&lt;/strong&gt; Spending more time per sample doesn't help past a point. The marginal failure isn't more interesting; it's just more samples. The improvement comes from changing what kinds of failures the model is asked to produce --new domains, different invariants, contrastive examples in the prompt. Compute is necessary but not the lever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dataset right now
&lt;/h2&gt;

&lt;p&gt;About two weeks of curation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~10,000 verified failure rows&lt;/li&gt;
&lt;li&gt;19 Python domains&lt;/li&gt;
&lt;li&gt;Each row carries the prompt that produced the attempt&lt;/li&gt;
&lt;li&gt;All quality filters deterministic&lt;/li&gt;
&lt;li&gt;Snapshot every couple of weeks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I monitor quality drift, adjust prompts when failure modes shift, and otherwise let the curation run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next major question I'm working through is breadth versus depth. The current data is heavy on Monte Carlo and FFT -- the curation loop got good at those domains and kept producing high-quality failures there. The next snapshot will deliberately broaden domain coverage, even if average quality dips slightly. I'd love to hear from people actually training models on this kind of data: would you rather have a deep specialized set or a wider balanced one?&lt;/p&gt;

&lt;p&gt;A couple of questions if you're working on DPO/RLHF training:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Which Python domains do you most need failure data for?&lt;/strong&gt; I can shift the curation focus in a few days, so the answers actually change what gets generated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Are "subtle failures" useful to you  --code that passes pytest but violates a deeper mathematical invariant?&lt;/strong&gt; There's a pool of those that's currently filtered out. Curious if anyone actually wants that shape of data.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If you want to take a look
&lt;/h2&gt;

&lt;p&gt;I've put up a 100-row sample on Hugging Face for context:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/datasets/namakoo/idfu-verified-code" rel="noopener noreferrer"&gt;https://huggingface.co/datasets/namakoo/idfu-verified-code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're hitting any of these problems, or if you have ideas about what failure data would actually be useful in training, reply here or find me on Twitter &lt;a href="https://twitter.com/namakoo123" rel="noopener noreferrer"&gt;@namakoo123&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Curation continues regardless. The next sample is being collected as you read this.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>python</category>
    </item>
  </channel>
</rss>
