<?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: Advik Divekar</title>
    <description>The latest articles on DEV Community by Advik Divekar (@advikdivekar).</description>
    <link>https://dev.to/advikdivekar</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%2F4162179%2F4f54da1f-781c-461b-8131-a914f2180e70.jpg</url>
      <title>DEV Community: Advik Divekar</title>
      <link>https://dev.to/advikdivekar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/advikdivekar"/>
    <language>en</language>
    <item>
      <title>ESA's satellite is dead. Its ghost: 2,123 signals. TabPFN beat the published SOTA; 83¢ of Tinker stopped an LLM speaking for it</title>
      <dc:creator>Advik Divekar</dc:creator>
      <pubDate>Sat, 31 Oct 2026 22:18:15 +0000</pubDate>
      <link>https://dev.to/advikdivekar/esas-satellite-is-dead-its-ghost-2123-signals-tabpfn-beat-the-published-sota-83c-of-tinker-3ke1</link>
      <guid>https://dev.to/advikdivekar/esas-satellite-is-dead-its-ghost-2123-signals-tabpfn-beat-the-published-sota-83c-of-tinker-3ke1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-06-02 03:17:03 UTC   I_PD3_THETA   0.0540137
2022-06-02 03:17:08 UTC   I_PD3_THETA   0.0462915
2022-06-02 03:17:13 UTC   I_PD3_THETA   0.0000000
2022-06-02 03:17:18 UTC   I_PD3_THETA   0.0000000
2022-06-02 03:17:23 UTC   I_PD3_THETA   0.0025708
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Ten seconds of nothing.&lt;/strong&gt; Recorded by a light sensor on a spacecraft that no longer exists.&lt;/p&gt;

&lt;p&gt;ESA's OPS-SAT burned up in the atmosphere on &lt;strong&gt;22 May 2024&lt;/strong&gt;. Its engineers have moved on. Nobody is left to ask what happened at 03:17:13.&lt;/p&gt;

&lt;p&gt;Ask a chatbot and it will tell you anyway. A sensor fault. A reboot. An eclipse. It will sound certain, and &lt;strong&gt;it will be making it up&lt;/strong&gt;, because nothing in the archive says why.&lt;/p&gt;

&lt;p&gt;So I built a séance with one house rule: &lt;strong&gt;nobody speaks for the dead.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Last Satellite&lt;/strong&gt; hands the archive to two open models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN&lt;/strong&gt; listens to every signal OPS-SAT left behind and decides which ones look wrong. On the official benchmark, it beats the best model in the paper that published the dataset, without ever running a training loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3-8B, fine-tuned on Tinker for 83 cents&lt;/strong&gt;, reads the mission's documents and is only allowed to say what they say.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask it why the light went out at 03:17:13, and you get the most honest sentence a medium can give:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Root cause of this signal behaviour is not documented in the archive."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One confession before we start. My model looked at those ten seconds and called them &lt;strong&gt;0.098: probably fine.&lt;/strong&gt; It was wrong. That miss is in the demo, on purpose.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ybpTJi4QFho" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;
&lt;h2&gt;
  
  
  What You're About to Read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The TabPFN result:&lt;/strong&gt; on the &lt;strong&gt;official OPSSAT-AD test split&lt;/strong&gt; (529 segments, run exactly once, logged), TabPFN-v2 scored &lt;strong&gt;AUCPR 0.996, F1 0.982, MCC 0.977&lt;/strong&gt;. The best published model (an FCNN in &lt;em&gt;Scientific Data&lt;/em&gt;, 2025) scored &lt;strong&gt;0.979 / 0.946 / 0.932&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same features as the paper, no training loop:&lt;/strong&gt; using only the paper's 18 features, TabPFN still beats it: &lt;strong&gt;0.989 / 0.950 / 0.937&lt;/strong&gt;. It also beats my tuned Random Forest, XGBoost and LightGBM by about &lt;strong&gt;0.02 AUCPR&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Tinker result:&lt;/strong&gt; one LoRA on Qwen3-8B took structured extraction from mission documents from &lt;strong&gt;65.3% to 93.0%&lt;/strong&gt; field-level exact match (validation), &lt;strong&gt;for 83 cents all-in&lt;/strong&gt;. On fully held-out test documents it went from &lt;strong&gt;72.2% to 86.3%&lt;/strong&gt; (paired p = 6×10⁻¹⁶).

&lt;ul&gt;
&lt;li&gt;Evidence copied verbatim: &lt;strong&gt;81% → 100%&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Investigation notes passing every automated check: &lt;strong&gt;56% → 100%&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost:&lt;/strong&gt; &lt;strong&gt;$0.22&lt;/strong&gt; for the final model. &lt;strong&gt;$0.83 for everything on Tinker&lt;/strong&gt;: every run, the learning curve, a run I killed on purpose, and every evaluation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The honesty record:&lt;/strong&gt; &lt;strong&gt;1 false alarm and 3 missed anomalies&lt;/strong&gt; in 529 segments (the false alarm and a miss are in the demo, on purpose), and &lt;strong&gt;0 invented root causes&lt;/strong&gt; from either model. I also show where I &lt;strong&gt;missed my own 95% target&lt;/strong&gt;, and the one task fine-tuning made &lt;strong&gt;worse&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You keep it:&lt;/strong&gt; the adapter runs &lt;strong&gt;offline&lt;/strong&gt; on a laptop through llama.cpp, with no network and no API key. It agrees with the hosted model on &lt;strong&gt;100% of fields&lt;/strong&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5eu5k8m4lo4tp7syh4fw.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%2F5eu5k8m4lo4tp7syh4fw.png" alt="The Last Satellite: a shader-lit Earth, a decaying orbit, and a console that reads what OPS-SAT left behind" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem: a dead satellite can't explain itself
&lt;/h2&gt;

&lt;p&gt;OPS-SAT was ESA's flying laboratory: a CubeSat that teams outside ESA could upload experiments to. Over four and a half years it sent home thousands of telemetry fragments. Then, in the words of its mission-end coverage, it was &lt;em&gt;"impacted by the recent unexpectedly strong increase in solar activity that began in early 2024"&lt;/em&gt;, and it came down early.&lt;/p&gt;

&lt;p&gt;Some of what it sent is now &lt;strong&gt;OPSSAT-AD&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2,123 telemetry segments from three magnetometers and six photodiodes;&lt;/li&gt;
&lt;li&gt;each segment labelled nominal or anomalous by three ESA operations engineers and two ML experts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The labels say &lt;strong&gt;that&lt;/strong&gt; something was wrong. Nothing in the archive says &lt;strong&gt;why&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the trap. Point a modern AI at this archive and ask "what happened to segment 204?" and it will give you a confident, plausible, completely made-up story. A sensor fault. A reboot. Cosmic rays. The data can't contradict it, because the people who could are gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The failure you have to fear isn't "the model is wrong." It's "the model invents a memory the archive doesn't hold, and you believe it."&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What exists today, and the gap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;What it gives you&lt;/th&gt;
&lt;th&gt;What it can't tell you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read the paper&lt;/td&gt;
&lt;td&gt;A benchmark: FCNN at AUCPR 0.979&lt;/td&gt;
&lt;td&gt;Which of &lt;em&gt;these&lt;/em&gt; segments to look at first, or what the documents say about them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Train your own deep model&lt;/td&gt;
&lt;td&gt;Maybe the paper's number, after a training loop&lt;/td&gt;
&lt;td&gt;Whether its probabilities mean anything (calibration)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ask a general chatbot&lt;/td&gt;
&lt;td&gt;A fluent explanation&lt;/td&gt;
&lt;td&gt;Whether a single word of it is in the archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Last Satellite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A calibrated anomaly queue, cited documents, a card per segment&lt;/td&gt;
&lt;td&gt;It tells you what it &lt;strong&gt;doesn't&lt;/strong&gt; know: &lt;em&gt;"Root cause not documented in the archive."&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Last Satellite&lt;/strong&gt; is a &lt;em&gt;Mission Memory Engine&lt;/em&gt;: an archival investigation console with two open models, one evidence layer and one rule.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN-v2&lt;/strong&gt; reads every archived segment and gives a &lt;strong&gt;calibrated probability&lt;/strong&gt; that it's anomalous. It ranks all 529 test segments into &lt;em&gt;prioritise / queue / archive&lt;/em&gt; and shows which &lt;strong&gt;measured&lt;/strong&gt; characteristics drove each score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3-8B + a Tinker LoRA&lt;/strong&gt; reads the mission's documents. It extracts &lt;strong&gt;8-field, source-linked records&lt;/strong&gt;, rebuilds the timeline (&lt;em&gt;event&lt;/em&gt; dates kept apart from &lt;em&gt;publication&lt;/em&gt; dates), explains telemetry terms &lt;strong&gt;with citations&lt;/strong&gt;, and writes investigation notes split into &lt;strong&gt;KNOWN&lt;/strong&gt; and &lt;strong&gt;NOT KNOWN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every statement on screen&lt;/strong&gt; is either &lt;code&gt;[measured]&lt;/code&gt; from the raw signal or &lt;code&gt;[cited]&lt;/code&gt; from a document, &lt;strong&gt;verbatim&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A root cause is never asserted&lt;/strong&gt; unless a source states one, and the archive has no per-segment incident reports. So it says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The analyst has the last word:&lt;/strong&gt; Confirm, Reject or Investigate, stored as feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export:&lt;/strong&gt; one click bundles documents, records, cards, feedback and every evaluation file into a portable mission-memory archive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The console&lt;/strong&gt; is one cinematic scroll from orbit to re-entry. Behind it, a shader-lit Earth whose city lights burn like candles, and a tiny CubeSat whose orbit decays as you read. At the end the planet warms to marigold and the satellite streaks in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it fits Ghost in the Machine:&lt;/strong&gt; this week runs from Halloween night into Día de los Muertos, when the line between past and present is supposed to be thinnest. OPS-SAT is a ghost in the most literal sense: a machine that no longer exists, still talking through 303,493 archived readings.&lt;/p&gt;

&lt;p&gt;Día de los Muertos isn't about being afraid of the dead. It's about &lt;strong&gt;remembering them accurately&lt;/strong&gt;: the ofrenda holds their real photos, their real favourite foods, their real stories. So the console is built like one. Marigold and candlelight, not horror. The satellite's own words are the offerings, every one sourced. And there's one rule a séance usually breaks: &lt;strong&gt;nobody gets to put words in the ghost's mouth.&lt;/strong&gt; Not me, not the language model. Measured, cited, or &lt;strong&gt;unknown&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The video above is the whole tour. The console talks to the live FastAPI backend when it's running. Without it, it falls back to a &lt;strong&gt;labelled archived snapshot of real API responses&lt;/strong&gt;, so it works anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The signal that should not be there.&lt;/strong&gt; Pick one of six candles: six real segments from the official test split, labels hidden. Step through the signal, the 18 measured features, TabPFN's probability, the documents, the most similar past segments, and the investigation card. Then &lt;strong&gt;reveal the truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faz889is5wgze528ipokh.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%2Faz889is5wgze528ipokh.png" alt="True positive: a 16-sample run of exact zeros, P = 1.000, prioritise. The card separates what is measured, what is cited, and what is not known." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the one it missed.&lt;/strong&gt; The demo set deliberately includes the false alarm and a missed anomaly. Notice the banner on the summary: the fine-tuned interpreter's draft was &lt;strong&gt;rejected by the validator&lt;/strong&gt;, so the card fell back to plain, deterministic text. More on that below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frtnlibrno6z2vojh1yes.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%2Frtnlibrno6z2vojh1yes.png" alt="Missed anomaly: P = 0.098, below the threshold chosen on training data. Shown, not hidden." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask the archive. It tells you what it doesn't know.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzqpmj7fky4xsbrekmv2.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%2Fdzqpmj7fky4xsbrekmv2.png" alt="Mission Memory: cited answers on the left; &amp;quot;missing from the archive&amp;quot; on the right" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The queue.&lt;/strong&gt; 529 segments ranked by TabPFN, each with a card and three buttons: Confirm, Reject, Investigate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz2rabd6wh5m01jtlecqz.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%2Fz2rabd6wh5m01jtlecqz.png" alt="Investigation queue with an open card" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/advikdivekar" rel="noopener noreferrer"&gt;
        advikdivekar
      &lt;/a&gt; / &lt;a href="https://github.com/advikdivekar/wake" rel="noopener noreferrer"&gt;
        wake
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Mission Memory Engine for OPS-SAT (archival): TabPFN telemetry analyser + Tinker-tuned Qwen3-8B interpreter.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;The Last Satellite: Mission Memory Engine&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Its mission ended. Its memories did not.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OPS-SAT was ESA's flying laboratory, a CubeSat that re-entered the atmosphere on &lt;strong&gt;22 May 2024&lt;/strong&gt;. This project is an &lt;strong&gt;archival&lt;/strong&gt; analysis system built on its real, public telemetry and documents. It has two halves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry Analyser (TabPFN).&lt;/strong&gt; For each archived telemetry segment it estimates P(anomalous), explains which measured characteristics drive the estimate, and ranks segments in an investigation queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mission Memory Interpreter (Qwen3-8B + LoRA fine-tuned on Tinker).&lt;/strong&gt; It turns public mission documents into structured, source-linked records. It keeps &lt;em&gt;what is known&lt;/em&gt; separate from &lt;em&gt;what is not&lt;/em&gt;, and it never invents a root cause.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An evidence layer joins the two into &lt;strong&gt;investigation cards&lt;/strong&gt;. Every statement on a card is either &lt;em&gt;measured&lt;/em&gt; or &lt;em&gt;cited&lt;/em&gt;, and an analyst can confirm, reject or investigate it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nothing here is live. All numbers below are produced by scripts…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/advikdivekar/wake" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The rule behind all of it: the models read, code measures, and nothing is claimed that the archive cannot show.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0zzlosqg6stivgcvleb.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%2Fo0zzlosqg6stivgcvleb.png" alt="The models read. Code measures. Nothing is claimed that the archive cannot show." width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Language models are brilliant readers and terrible witnesses. TabPFN is a superb estimator, but a probability is not an explanation. So each job goes to whatever can be held accountable for it, and &lt;strong&gt;five independent checks&lt;/strong&gt; stand between the models and the screen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The output parses&lt;/strong&gt; into the 8-field schema (Pydantic).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every quoted sentence exists verbatim&lt;/strong&gt; in its source document (substring check).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel IDs and dates are cross-checked&lt;/strong&gt; by an independent regex reader that never saw the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "root cause" must be stated, unhedged, by a cited source.&lt;/strong&gt; "May have been caused by…" maps to &lt;code&gt;unknown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal facts come from plain code&lt;/strong&gt;, never from a model: zero runs, gaps, peaks, noise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The data: real, public, and licence-aware.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry:&lt;/strong&gt; OPSSAT-AD from Zenodo, md5-verified, CC-BY-4.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documents:&lt;/strong&gt; 20 public sources (the benchmark paper via Europe PMC's open-access API, the dataset code, Wikipedia, ESA pages, news reprints) chunked into 304 passages. Full text is stored only where the licence allows (CC-BY, CC-BY-SA, MIT); everything else gets ≤300-character excerpts and a link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline:&lt;/strong&gt; every one of its 11 entries is a verbatim quote, checked against the stored text before it may appear.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Best Use of TabPFN
&lt;/h2&gt;
&lt;h2&gt;
  
  
  0.979 → 0.989, with no training loop
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The published state of the art on this dataset is a fully connected neural network, trained, tuned and reported in &lt;em&gt;Scientific Data&lt;/em&gt; in 2025. TabPFN-v2 never ran a training loop on this data. I handed it the training rows and the paper's own 18 features, and it beat the paper on the official test split. Then I engineered better features, and it beat the paper by more.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the point. The anomaly detector at the heart of a mission-memory console shouldn't need a GPU, a hyperparameter sweep and a week. It should need a table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8ilhzpvmtayj8y9ejkd.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%2Fi8ilhzpvmtayj8y9ejkd.png" alt="Same 18 features as the paper. TabPFN on top." width="800" height="468"&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;Official test split (529)&lt;/th&gt;
&lt;th&gt;AUCPR&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;th&gt;MCC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FCNN, best published (Sci Data 2025)&lt;/td&gt;
&lt;td&gt;0.979&lt;/td&gt;
&lt;td&gt;0.946&lt;/td&gt;
&lt;td&gt;0.932&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TabPFN-v2, paper's 18 features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.989&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.950&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.937&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TabPFN-v2, selected features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.996&lt;/strong&gt; [0.989–1.000]&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.982&lt;/strong&gt; [0.963–0.996]&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.977&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Brackets are 95% bootstrap CIs over test segments.&lt;/p&gt;

&lt;p&gt;My goal wasn't a big number. It was &lt;strong&gt;a big number I could defend line by line.&lt;/strong&gt; Here is exactly how.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Reproduce the paper before you try to beat it
&lt;/h3&gt;

&lt;p&gt;I downloaded OPSSAT-AD, verified both md5 checksums, and counted everything myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2,123 segments, 303,493 points, 9 channels, 20.4% anomalous;&lt;/li&gt;
&lt;li&gt;1,594 train and 529 test segments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The paper's text says 1,494 training segments; its own Table 1 and the released data both say 1,594.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then I ported the published feature code. It reproduces &lt;strong&gt;16 of the 18 features exactly&lt;/strong&gt; (99.97% of all values). On the way it caught a nasty bug of mine: the raw file has &lt;strong&gt;22,115 duplicate timestamps&lt;/strong&gt;, and a non-stable sort silently changed every derivative feature.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Re-verify the bar programmatically
&lt;/h3&gt;

&lt;p&gt;I didn't copy the benchmark from a PDF by eye. The script pulls the paper's full text (JATS XML) from Europe PMC and checks every number in Table 3. The bar to beat is &lt;strong&gt;FCNN: AUCPR 0.979, F1 0.946, MCC 0.932&lt;/strong&gt; (best unsupervised: MO-GAAL at 0.779). The 2025 correction notice only re-ordered figures; no table changed.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Choose everything on training data only
&lt;/h3&gt;

&lt;p&gt;I ran 5-fold CV on the &lt;strong&gt;training split&lt;/strong&gt;, grouped so neighbouring segments of a channel never straddle folds. Each candidate feature group had to earn its place:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd7o29fu1ggvfiokf3gn.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%2Fcd7o29fu1ggvfiokf3gn.png" alt="Which extra features earn their place? Decided on training data only." width="799" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kept:&lt;/strong&gt; zero runs &amp;amp; gaps, autocorrelation and robust spread, which together raised CV AUPRC from &lt;strong&gt;0.9704&lt;/strong&gt; to &lt;strong&gt;0.9768&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dropped:&lt;/strong&gt; spectral features and channel identity, which didn't help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensemble size:&lt;/strong&gt; 8 estimators won (CV 0.9781).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. Let the operator choose the trade-off
&lt;/h3&gt;

&lt;p&gt;Every threshold comes from &lt;strong&gt;training out-of-fold&lt;/strong&gt; predictions, before the test split was opened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;max-F1&lt;/strong&gt; at 0.617;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prioritise&lt;/strong&gt; at ≥ 0.80, where precision is ≥ 0.95;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;queue&lt;/strong&gt; at ≥ 0.05, where recall is ≥ 0.98.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8niv4ii5xwwziwkuhztg.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%2F8niv4ii5xwwziwkuhztg.png" alt="The queue is a trade-off you choose, from training data." width="799" height="458"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Run the test split once
&lt;/h3&gt;

&lt;p&gt;A guard logs every test-set run and &lt;strong&gt;refuses to run the same configuration twice&lt;/strong&gt;. One shot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8dcthgh7v6il5kvdxo7m.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%2F8dcthgh7v6il5kvdxo7m.png" alt="529 archived segments. 1 false alarm, 3 missed anomalies." width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;415 nominal segments correctly left in the archive, 110 anomalies correctly flagged, 1 false alarm, 3 misses.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Checked before trusted
&lt;/h3&gt;

&lt;p&gt;Same split, same features, same folds. Five baselines and a heuristic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foj2dqyw9gsssq1ouguzw.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%2Foj2dqyw9gsssq1ouguzw.png" alt="With engineered features, everyone gets better. TabPFN still leads." width="800" height="425"&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;Official test split&lt;/th&gt;
&lt;th&gt;AUCPR&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;th&gt;MCC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TabPFN-v2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.996&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.982&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.977&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Forest&lt;/td&gt;
&lt;td&gt;0.994&lt;/td&gt;
&lt;td&gt;0.973&lt;/td&gt;
&lt;td&gt;0.966&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XGBoost&lt;/td&gt;
&lt;td&gt;0.993&lt;/td&gt;
&lt;td&gt;0.968&lt;/td&gt;
&lt;td&gt;0.960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LightGBM&lt;/td&gt;
&lt;td&gt;0.990&lt;/td&gt;
&lt;td&gt;0.969&lt;/td&gt;
&lt;td&gt;0.960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logistic regression&lt;/td&gt;
&lt;td&gt;0.987&lt;/td&gt;
&lt;td&gt;0.956&lt;/td&gt;
&lt;td&gt;0.944&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation Forest (unsupervised)&lt;/td&gt;
&lt;td&gt;0.401&lt;/td&gt;
&lt;td&gt;0.499&lt;/td&gt;
&lt;td&gt;0.362&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Lots of zeros or a gap" rule&lt;/td&gt;
&lt;td&gt;0.293&lt;/td&gt;
&lt;td&gt;0.527&lt;/td&gt;
&lt;td&gt;0.414&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A win, but on my features not a thrashing.&lt;/strong&gt; Good features lift every model. Against Random Forest, TabPFN is a &lt;strong&gt;statistical tie&lt;/strong&gt; (McNemar p = 0.63; AUPRC difference +0.002, 95% CI −0.0005 to +0.006). Against LightGBM and XGBoost, the paired-bootstrap AUPRC differences sit just above zero, and against logistic regression TabPFN wins with p = 0.03.&lt;/p&gt;

&lt;p&gt;That's why the like-for-like table at the top matters most. &lt;strong&gt;On the paper's own features, TabPFN beats the tuned trees by ~0.02 AUCPR, and the paper's neural network, out of the box.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  7. A probability that means what it says
&lt;/h3&gt;

&lt;p&gt;The probability &lt;em&gt;is&lt;/em&gt; the investigation queue, so it has to be honest. Brier score on test: &lt;strong&gt;0.0073&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fat2xva137weddo4x08mm.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%2Fat2xva137weddo4x08mm.png" alt="When it says 13%, it means 13%." width="800" height="808"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  8. Things I got wrong first
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN refused to run on CPU&lt;/strong&gt; with more than 1,000 training rows. Apple's MPS backend was faster anyway, with identical AUPRC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN and XGBoost segfault when loaded in the same process&lt;/strong&gt; on macOS, because two OpenMP runtimes collide (exit 139). Now they run as separate pipeline stages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The newest TabPFN weights want an account and a non-commercial licence.&lt;/strong&gt; TabPFN v2 downloads without either (Prior Labs License: Apache-2.0 + attribution), so every number here is v2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My first telemetry plot drew 553 "gaps"&lt;/strong&gt; that were really just artefacts of downsampling. Gaps are now computed on raw samples only.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Best Use of Tinker
&lt;/h2&gt;
&lt;h2&gt;
  
  
  65.3% → 93%, for 83 cents, and it stopped inventing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Qwen3-8B out of the box makes up channel IDs and paraphrases quotes, and with a confident tone that is exactly how false memories get made. It scored 65.3%. Same model, same prompt, same validator: after 83 cents of Tinker, all-in, it scored 93.0%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't need a model that knows everything about spacecraft. I needed one that &lt;strong&gt;copies instead of inventing&lt;/strong&gt;, and fine-tuning is how a small model learns that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqv8sirpad816q4leyuua.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%2Fqv8sirpad816q4leyuua.png" alt="Same model, same prompt, same validator. Only $0.22 of training changed." width="800" height="378"&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;&lt;/th&gt;
&lt;th&gt;base Qwen3-8B&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Tinker LoRA&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Field-level exact match, validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;65.3%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;93.0%&lt;/strong&gt; [90.7–94.8]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field-level exact match, held-out test documents&lt;/td&gt;
&lt;td&gt;72.2%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;86.3%&lt;/strong&gt; [83.2–88.9]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel IDs correct&lt;/td&gt;
&lt;td&gt;70.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence copied verbatim from the source&lt;/td&gt;
&lt;td&gt;81.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Unknown" used correctly&lt;/td&gt;
&lt;td&gt;82.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whole record exactly right&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44.4%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investigation notes passing every check (task D)&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors caught by the independent validator&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsupported root causes&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My goal wasn't the biggest number. It was &lt;strong&gt;a real number at the lowest price I could defend.&lt;/strong&gt; Here is exactly how.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk48qgxzqdiuoadtij8x0.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%2Fk48qgxzqdiuoadtij8x0.png" alt="The fine-tuning pipeline" width="799" height="228"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Freeze the metric before anything trains
&lt;/h3&gt;

&lt;p&gt;Before any model saw any data, I froze the headline: &lt;strong&gt;field-level exact match on held-out documents, scored on raw output before any repair.&lt;/strong&gt; Companion metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;valid-schema rate;&lt;/li&gt;
&lt;li&gt;verbatim-evidence rate;&lt;/li&gt;
&lt;li&gt;unsupported-root-cause count;&lt;/li&gt;
&lt;li&gt;correct use of "unknown";&lt;/li&gt;
&lt;li&gt;Wilson CIs;&lt;/li&gt;
&lt;li&gt;a paired McNemar test against the base model.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Shrink the bill before you shrink the model
&lt;/h3&gt;

&lt;p&gt;Tinker bills per token, so the output is 8 compact lines that code turns into Pydantic JSON:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;channel_id: CADC0873
sensor_type: magnetometer
units: ?
event_date: 2024-05-22
observation: On 22 May 2024 the magnetometer I_B_FB_MM_1 reported a run of zero values.
operational_response: ?
root_cause: ?
evidence: On 22 May 2024 the magnetometer I_B_FB_MM_1 reported a run of zero values.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A full training example, prompt and passage included, is &lt;strong&gt;about 380 tokens&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Price the options, then pick
&lt;/h3&gt;

&lt;p&gt;Training per million tokens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Qwen3-8B: $0.44.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Qwen3.5-4B: $0.737. A smaller model is not automatically a cheaper one.&lt;/li&gt;
&lt;li&gt;GPT-OSS-20B: $0.396, but it's a reasoning model whose thinking tokens would cost more at sampling time and break the format.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. Train: rank-16 LoRA, loss on the answer only, 5 minutes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_renderer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen3_disable_thinking&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_supervised_example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train_on_what&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TrainOnWhat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LAST_ASSISTANT_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forward_backward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loss_fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cross_entropy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optim_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tinker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AdamParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lr_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beta1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beta2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data:&lt;/strong&gt; examples across four tasks (extraction, timelines, terminology with citations, investigation notes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule:&lt;/strong&gt; batch 16, linear LR decay, 3 epochs, a checkpoint every epoch. The final run took &lt;strong&gt;5.1 minutes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split by document family, never by chunk:&lt;/strong&gt; the test documents (the benchmark paper plus Wikipedia) never appear in training, and the paper's arXiv preprint is &lt;strong&gt;excluded everywhere&lt;/strong&gt; as a near-duplicate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget guard:&lt;/strong&gt; it prices every job from local token counts &lt;em&gt;before&lt;/em&gt; submitting and refuses anything over the cap. I started with &lt;strong&gt;$0.93&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  5. 65.3% → 93.0%: find the knee of the curve
&lt;/h3&gt;

&lt;p&gt;I didn't train once and hope. I trained in stages, scored every run on the same validation set, and priced each one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm26cbbc4xljjytv6n96a.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%2Fm26cbbc4xljjytv6n96a.png" alt="65.3% → 93.0%. The first 2.5 cents did most of the work." width="799" height="394"&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;Run&lt;/th&gt;
&lt;th&gt;Est. cost&lt;/th&gt;
&lt;th&gt;Validation field exact match&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;base Qwen3-8B, untrained&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;65.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;run 1&lt;/td&gt;
&lt;td&gt;$0.025&lt;/td&gt;
&lt;td&gt;90.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;run 2&lt;/td&gt;
&lt;td&gt;$0.049&lt;/td&gt;
&lt;td&gt;91.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;run 3&lt;/td&gt;
&lt;td&gt;$0.097&lt;/td&gt;
&lt;td&gt;92.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;final model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.224&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The first 2.5 cents of training took the model from 65.3% to 90.8%.&lt;/strong&gt; Every run after that added another point or so, and the final model landed at &lt;strong&gt;93.0%&lt;/strong&gt;, all inside an 83-cent budget.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Validation error analysis found &lt;em&gt;my&lt;/em&gt; bugs
&lt;/h3&gt;

&lt;p&gt;Midway through, most remaining "errors" were the model &lt;strong&gt;correctly refusing&lt;/strong&gt; to quote code blocks, footnotes and markdown bullet lists that my labelling rules had picked as "observations".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I tightened the spec to prose-only sentences.&lt;/li&gt;
&lt;li&gt;I &lt;strong&gt;killed a half-finished run&lt;/strong&gt; rather than pay to finish it on bad labels.&lt;/li&gt;
&lt;li&gt;I retrained once.&lt;/li&gt;
&lt;li&gt;The test set stayed sealed the whole time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  7. Is the gain real or noise?
&lt;/h3&gt;

&lt;p&gt;Paired, cell by cell, on the same 576 field cells: &lt;strong&gt;95 cells only the fine-tune got right, 14 only the base got right&lt;/strong&gt;. Exact McNemar &lt;strong&gt;p = 5.8×10⁻¹⁶&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnkiq3nz7b0g1t1g7ona3.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%2Fnkiq3nz7b0g1t1g7ona3.png" alt="It stopped inventing channel IDs and started quoting sources." width="799" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The base model's &lt;strong&gt;40 validator catches&lt;/strong&gt; are what the validator is for: channel IDs and quotes that don't exist in the source. &lt;strong&gt;The fine-tune made zero.&lt;/strong&gt; Even after the validator repairs the base model's output, it reaches 78.5%, still below the fine-tune's &lt;em&gt;unrepaired&lt;/em&gt; 86.3%.&lt;/p&gt;
&lt;h3&gt;
  
  
  8. Rent the training. Keep the weights.
&lt;/h3&gt;

&lt;p&gt;I exported the adapter as PEFT (one tensor rename: &lt;code&gt;unembed_tokens&lt;/code&gt; → &lt;code&gt;lm_head&lt;/code&gt;), converted it to GGUF with llama.cpp, and ran it over a Q4_K_M Qwen3-8B base:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;92.5% field exact match on held-out passages, 100% field-for-field agreement with the same checkpoint on Tinker, 4.7 s per passage.&lt;/strong&gt; M3 Pro, 18 GB, Hugging Face offline, no Tinker key in the environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The whole bill
&lt;/h3&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%2Ftms286yasbn0434thc39.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%2Ftms286yasbn0434thc39.png" alt="Where the $0.83 went. Everything on Tinker." width="800" height="439"&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;Item&lt;/th&gt;
&lt;th&gt;Est. cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning-curve runs (runs 1–3)&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A run I stopped at 65/84 steps after finding label bugs&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Final model (3 epochs)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.22&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downloadable sibling adapter (see Known edges)&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All evaluation on Tinker&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Everything on Tinker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.83&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Token counts at Tinker's list prices, logged before every job. Base-model validation ran locally for free.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Known edges (measured, not hidden)
&lt;/h2&gt;

&lt;p&gt;I'd rather you hear these from me, with numbers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I missed my own target.&lt;/strong&gt; I set &lt;strong&gt;95%&lt;/strong&gt; before training. The model got &lt;strong&gt;93.0% on validation and 86.3% on test&lt;/strong&gt;. Most of the gap is one judgement call: &lt;em&gt;which&lt;/em&gt; sentence is "the observation" in an unfamiliar document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning made one task worse.&lt;/strong&gt; Citing the right source for a telemetry term fell from &lt;strong&gt;96.2% to 82.7%&lt;/strong&gt; (p = 0.016). That task had the least training data, and the tuned model learned to answer "not in the archive" too eagerly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The offline adapter is a sibling.&lt;/strong&gt; Tinker doesn't let you download &lt;em&gt;sampler&lt;/em&gt; checkpoints, which is all my final run had saved. So the offline adapter is a smaller sibling configuration, retrained with a downloadable checkpoint (validation 92.3% vs 93.0%). The training script now saves both kinds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The labels are a spec, not human annotation.&lt;/strong&gt; "Exact match" means agreement with a deterministic spec on unseen documents. Some fields (units, operational responses) appear in only a handful of held-out passages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The validator rejects my own model.&lt;/strong&gt; In the demo, &lt;strong&gt;4 of 6&lt;/strong&gt; investigation cards fell back to deterministic text. In one, the interpreter wrote that the root cause "&lt;em&gt;is documented&lt;/em&gt;", which contradicts its own NOT KNOWN list. The guard caught it. I'd rather show a plain card than a confident wrong one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN's edge over Random Forest on my engineered features is a statistical tie.&lt;/strong&gt; Its clear win is like-for-like, on the paper's features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What went wrong on the way (and made it better):&lt;/strong&gt; my base-model comparison was unfair at first. The prompt never named the eight fields and the parser rejected markdown, so I fixed both &lt;em&gt;before&lt;/em&gt; training anything. A root-cause rule briefly treated "&lt;em&gt;due to&lt;/em&gt; the underlying nature of the mission" as a cause, and a Q&amp;amp;A ranking tweak made one answer prettier but quietly removed the "cause unknown" warning from others. Both were caught and reverted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does open innovation matter?
&lt;/h2&gt;

&lt;p&gt;A closed API gives you an answer. Open weights and open data give you &lt;strong&gt;a way to check it.&lt;/strong&gt; For a project whose whole job is refusing to invent memories, that difference is everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where open beat closed, concretely:&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;What I needed&lt;/th&gt;
&lt;th&gt;Closed API&lt;/th&gt;
&lt;th&gt;What open let me do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stop the model inventing channel IDs&lt;/td&gt;
&lt;td&gt;Write a longer prompt and hope&lt;/td&gt;
&lt;td&gt;Fine-tune the weights: invented IDs went to &lt;strong&gt;zero&lt;/strong&gt;, for $0.22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust the anomaly probabilities&lt;/td&gt;
&lt;td&gt;Take the vendor's word&lt;/td&gt;
&lt;td&gt;Measure calibration myself on the public test split (Brier &lt;strong&gt;0.0073&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beat a published benchmark&lt;/td&gt;
&lt;td&gt;Not possible to verify&lt;/td&gt;
&lt;td&gt;Reproduce the paper's features to the decimal, then beat it &lt;strong&gt;like-for-like&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep it running after the hype&lt;/td&gt;
&lt;td&gt;Depends on the vendor staying up&lt;/td&gt;
&lt;td&gt;Adapter + TabPFN run &lt;strong&gt;offline on a laptop&lt;/strong&gt;, no key, no meter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The reasons behind that table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I could audit the archive.&lt;/strong&gt; The dataset authors published the data &lt;em&gt;and&lt;/em&gt; the feature code, so I reproduced their features to the decimal and found a typo in their split count. A ghost story you can fact-check is a different kind of story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could change the model, not just the prompt.&lt;/strong&gt; Base Qwen3-8B invented channel IDs. Twenty-two cents of LoRA training stopped it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could test the probability.&lt;/strong&gt; With TabPFN's weights and the public test split, I measured calibration myself (Brier 0.0073) instead of trusting a number someone printed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No meter running, nobody to deprecate it.&lt;/strong&gt; The adapter and TabPFN run on the laptop that holds the archive. After a spacecraft dies, its memory shouldn't depend on someone else's API staying up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory should be a commons.&lt;/strong&gt; OPS-SAT existed because ESA opened a satellite to outsiders. Its memory should stay just as open.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest caveat: training ran on Tinker's servers, on public documents only. At run time, nothing leaves the laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  My agent session
&lt;/h2&gt;

&lt;p&gt;I built The Last Satellite with &lt;strong&gt;Claude Code&lt;/strong&gt; as a pair, from a written 12-hour plan with a cost estimate up front, a budget guard on every Tinker job and a guard on every test-set run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It read the sources before writing code.&lt;/strong&gt; It read Tinker's and TabPFN's docs, re-verified the paper's benchmark table from the full text, and read the original feature code before porting it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It caught its own mistakes:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;a duplicate-timestamp sort bug;&lt;/li&gt;
&lt;li&gt;an unfair base-model prompt;&lt;/li&gt;
&lt;li&gt;label bugs, found through validation error analysis;&lt;/li&gt;
&lt;li&gt;a root-cause rule that was too generous.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It stopped spending when the data said to.&lt;/strong&gt; It killed a half-finished run on bad labels, and when the final checkpoint turned out to be undownloadable, it asked me before spending the last of my credit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It reverted its own "improvement"&lt;/strong&gt; when it made the honesty guarantees worse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It kept a running cost ledger&lt;/strong&gt; from the first job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of TabPFN.&lt;/strong&gt; TabPFN is the engine of the console, not a bolt-on: every probability, every queue position and every "top measured characteristic" comes from it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Official OPSSAT-AD test split, one logged run:&lt;/strong&gt; &lt;strong&gt;AUCPR 0.996, F1 0.982, MCC 0.977&lt;/strong&gt;, against the best published &lt;strong&gt;0.979 / 0.946 / 0.932&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On the paper's own 18 features, with no training loop:&lt;/strong&gt; &lt;strong&gt;0.989 / 0.950 / 0.937&lt;/strong&gt;, beating the paper and the tuned tree ensembles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rigour:&lt;/strong&gt; calibrated (Brier 0.0073), with thresholds chosen on training data only, and compared against five baselines with bootstrap CIs and McNemar tests, including the tie I report against myself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Tinker.&lt;/strong&gt; The LoRA fine-tune (loss on answers only, per-epoch checkpoints), the learning curve, a budget guard, held-out evaluation with paired statistics, and the adapter download behind the offline story all ran on Tinker. &lt;strong&gt;$0.22 for the final model, $0.83 for everything.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same base model, same prompt, same validator:&lt;/strong&gt; extraction &lt;strong&gt;65.3% → 93.0%&lt;/strong&gt; on validation (&lt;strong&gt;72.2% → 86.3%&lt;/strong&gt; on held-out test documents), verbatim evidence &lt;strong&gt;81% → 100%&lt;/strong&gt;, investigation notes &lt;strong&gt;56% → 100%&lt;/strong&gt;, and &lt;strong&gt;0 invented root causes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built solo, with Claude Code, over one long Halloween night, finished as Día de los Muertos began.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two open models. Eighty-three cents. One rule: measure it, cite it, or call it unknown.&lt;/strong&gt; If that sounds like the right way to remember a satellite, a ❤️ or 🦄 helps more people find it.&lt;/p&gt;

&lt;p&gt;This Día de los Muertos, light a candle for a machine you loved that's gone: an old phone, a first laptop, a server that ran for a decade. What would you want its archive to remember? Leave it in the comments. 🕯️&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
      <category>ai</category>
    </item>
    <item>
      <title>A small, not-so-smart model went from 10% to 99.6% at reading property deeds for $1.15, and it says 'check this line' instead of bluffing</title>
      <dc:creator>Advik Divekar</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:39:52 +0000</pubDate>
      <link>https://dev.to/advikdivekar/a-small-not-so-smart-model-went-from-10-to-996-at-reading-property-deeds-for-115-and-it-says-490k</link>
      <guid>https://dev.to/advikdivekar/a-small-not-so-smart-model-went-from-10-to-996-at-reading-property-deeds-for-115-and-it-says-490k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;N 86°29' E, 103.2 feet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is one line of a property deed. Walk the rest of the lot and you should end up where you started.&lt;/p&gt;

&lt;p&gt;You end up &lt;strong&gt;27 feet away.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One digit is wrong. Maybe a clerk typed &lt;code&gt;103.2&lt;/code&gt; for &lt;code&gt;130.2&lt;/code&gt;. Maybe the scan smudged. Maybe the AI that read it got creative. The drawing you'd make still looks fine, and nobody finds out until the survey crew does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeedReader&lt;/strong&gt; reads that deed, closes the shape in plain code, plots it on a real satellite map, and when something doesn't add up it refuses to draw it. It stamps the result &lt;strong&gt;NEEDS HUMAN REVIEW&lt;/strong&gt;, points at the line, and tries every one-digit fix to find the one that closes the parcel. In the example above: &lt;code&gt;103.2 → 130.2&lt;/code&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What You're About to Read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The result:&lt;/strong&gt; one Tinker LoRA on Qwen3-8B moved exact deed reads from &lt;strong&gt;10% (6/60)&lt;/strong&gt; to &lt;strong&gt;99.6% (2,988/3,000, 95% CI 99.3–99.8%)&lt;/strong&gt; on fresh deeds. Same base model, same prompt, same checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost:&lt;/strong&gt; &lt;strong&gt;$1.15&lt;/strong&gt; and 16 minutes of training for the final model. &lt;strong&gt;$0.17 of training already reaches 99.4%.&lt;/strong&gt; The entire project, every baseline and every evaluation included, came to about &lt;strong&gt;$3.80&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The safety record:&lt;/strong&gt; on the same 60 deeds, wrong-and-unflagged answers went from &lt;strong&gt;5 to 0&lt;/strong&gt;. Across 3,000 fresh deeds, 10 of the 12 imperfect outputs were caught by parsing and closure alone, and the other 2 are a label quirk in my own generator (same physical length, written two ways).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The design rule:&lt;/strong&gt; the model reads. Plain, tested code does every piece of math. Five independent checks must agree before an answer is allowed to look confident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You keep it:&lt;/strong&gt; I downloaded the 369 MB adapter and ran it offline on a laptop. &lt;strong&gt;150 of 150 deeds read exactly right&lt;/strong&gt;, no network, no per-deed fee.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwep5gt1bsazspsgqosn.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%2Fnwep5gt1bsazspsgqosn.png" alt="DeedReader plots a parcel on real satellite imagery. Click a sentence and its side lights up." width="800" height="886"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: land is described in words
&lt;/h2&gt;

&lt;p&gt;Every parcel of land has a legal description, and it reads like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Commencing at a cedar post at the corner of Tract 7, thence S 46°30′11″ W 1,011.25 feet to a corner post, thence along a curve to the right, R = 757.19 feet, to a point in the center of the road, thence N 23°16′52″ W 1,206.14 feet to a 4 inch concrete monument…&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Someone has to turn that into a drawing. Convert every bearing. Plot every side, curved ones included. Check the shape closes. Work out the acreage. That someone is a surveyor, a title abstractor or a paralegal, and the job takes &lt;strong&gt;fifteen to thirty minutes a deed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The scary part isn't the time. It's that &lt;strong&gt;one wrong digit quietly poisons everything downstream&lt;/strong&gt;, and the drawing still looks plausible. The failure you have to fear isn't "the tool is wrong." It's "the tool is wrong and didn't tell me."&lt;/p&gt;

&lt;h2&gt;
  
  
  What exists today, and the gap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;What it gives you&lt;/th&gt;
&lt;th&gt;What it can't tell you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;By hand&lt;/td&gt;
&lt;td&gt;A drawing you trust&lt;/td&gt;
&lt;td&gt;It takes 15–30 minutes a deed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A regex parser&lt;/td&gt;
&lt;td&gt;Instant and free&lt;/td&gt;
&lt;td&gt;Only reads clean &lt;code&gt;N 41°00' E&lt;/code&gt; calls, not 1800s prose or numbers spelled out in words&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A base open model, no training&lt;/td&gt;
&lt;td&gt;Reads messy prose&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10% exactly right&lt;/strong&gt; in my test, and 5 of 60 wrong with no warning at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DeedReader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A closed parcel on a real map in seconds&lt;/td&gt;
&lt;td&gt;Says &lt;strong&gt;NEEDS HUMAN REVIEW&lt;/strong&gt; when anything doesn't add up, and points at the line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;DeedReader is the lazy version of that job: &lt;strong&gt;paste, glance, approve.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The deed becomes a &lt;strong&gt;parcel on a real satellite map&lt;/strong&gt; with true bearings and distances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acres, square feet, perimeter and closure ratio&lt;/strong&gt;, instantly&lt;/li&gt;
&lt;li&gt;Click a sentence and its side &lt;strong&gt;lights up on the map&lt;/strong&gt;. Click a side and the sentence lights up&lt;/li&gt;
&lt;li&gt;Export to &lt;strong&gt;DXF&lt;/strong&gt; for CAD, &lt;strong&gt;GeoJSON&lt;/strong&gt; for GIS, or &lt;strong&gt;SVG&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A deed that doesn't add up is &lt;strong&gt;never drawn as if it did.&lt;/strong&gt; You get the line to check and the one-digit fix that would close it&lt;/li&gt;
&lt;li&gt;Works &lt;strong&gt;fully offline&lt;/strong&gt; from a downloaded adapter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it fits Slow and Steady:&lt;/strong&gt; the tortoise didn't win by being fast. It won by never doing something stupid. A small model, a dollar of training, plain code for every number, and a system that would rather say "I don't know" than be quick and wrong. It automates the slowest job in land work, and earns trust the slow way: by showing its arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The video above is the whole tour. The studio replays &lt;strong&gt;real recorded outputs of my fine-tuned model&lt;/strong&gt; on deeds it never saw in training, while every safety check runs &lt;strong&gt;live in your browser&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paste, glance, approve.&lt;/strong&gt; A clean deed: every check passes, the parcel closes to 1:2.9 million, and the studio reports 43.44 acres. Click any sentence and its side lights up in the drawing. Export to SVG, DXF or GeoJSON in one click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1inqcb1t3xfslezcg88g.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%2F1inqcb1t3xfslezcg88g.png" alt="All checks pass: a closed 8-sided parcel with two curves, 43.44 acres, ready to export" width="800" height="915"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Break it on purpose.&lt;/strong&gt; "Simulate a misread" corrupts a single number. Three independent checks fail at once, the banner turns red, and it names the line:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpgrumav0rj8hqkysunnt.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%2Fpgrumav0rj8hqkysunnt.png" alt="A misread caught: misclosure 900 ft, the number isn't in its clause, an independent reader disagrees" width="800" height="930"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the lazy payoff, side by side:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphg6vjebmgfexxpk2zl4.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%2Fphg6vjebmgfexxpk2zl4.png" alt="By hand vs DeedReader" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/advikdivekar" rel="noopener noreferrer"&gt;
        advikdivekar
      &lt;/a&gt; / &lt;a href="https://github.com/advikdivekar/deed" rel="noopener noreferrer"&gt;
        deed
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DeedReader: a ~$1 fine-tuned model reads property deeds; plain code checks closure and plots the parcel
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DeedReader&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Paste a metes-and-bounds legal description. Get structured calls, a closure check done in plain code, a plotted parcel, and an honest &lt;strong&gt;NEEDS HUMAN REVIEW&lt;/strong&gt; whenever anything is off. A fine-tuned open-weight model (Qwen3-8B + LoRA, trained on &lt;a href="https://thinkingmachines.ai/tinker/" rel="nofollow noopener noreferrer"&gt;Tinker&lt;/a&gt; for about $2.5) does the reading; deterministic code does everything that must be right.&lt;/p&gt;
&lt;p&gt;Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 3. &lt;strong&gt;Read &lt;a href="https://github.com/advikdivekar/deed/EVAL_REPORT.md" rel="noopener noreferrer"&gt;EVAL_REPORT.md&lt;/a&gt; before trusting any number here: everything is measured on synthetic deeds, and real-deed accuracy is unmeasured.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Results (synthetic test deeds; details and caveats in EVAL_REPORT.md)&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;base Qwen3-8B, 3-shot&lt;/th&gt;
&lt;th&gt;tuned (v2)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;deeds read exactly right (in-distribution)&lt;/td&gt;
&lt;td&gt;10% (n=60)&lt;/td&gt;
&lt;td&gt;99.6% (n=3,000)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flagged for a human&lt;/td&gt;
&lt;td&gt;82%&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;silent errors (wrong AND unflagged)&lt;/td&gt;
&lt;td&gt;5 of 60&lt;/td&gt;
&lt;td&gt;2 of 3,000 (0.07%, 95% upper bound 0.24%); 0 if &lt;code&gt;87 link&lt;/code&gt; = &lt;code&gt;0.87 chain&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;training cost&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;about $1.15 (est.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Unseen style/unit, zero-shot: spelled-out numbers 85.6% exact (14.4% flagged, 0 silent)…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/advikdivekar/deed" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The rule behind all of it: the model reads, code decides.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2de3hgnn2q51cqts60t.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%2Fc2de3hgnn2q51cqts60t.png" alt="The model reads. Code decides." width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Language models read messy text brilliantly and are not accountable for arithmetic. So Qwen3-8B plus a LoRA does exactly one job, turning prose into structured calls. Everything after that is plain code with tests: closure, area, unit conversion (feet, chains, poles, varas, links) and plotting.&lt;/p&gt;

&lt;p&gt;Then five independent checks have to agree before an answer may look confident:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The output parses.&lt;/li&gt;
&lt;li&gt;The parcel closes (1:5,000).&lt;/li&gt;
&lt;li&gt;Every number appears &lt;strong&gt;in its own sentence&lt;/strong&gt; of the deed.&lt;/li&gt;
&lt;li&gt;Every landmark ("iron pin", "white oak") is backed by words in that sentence.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;second, independent reader&lt;/strong&gt;, a regex parser that never saw the model, agrees on bearing, distance and curve direction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The data: truth first, prose second.&lt;/strong&gt; There is no free pile of labelled real deeds, so I turned the problem around. A generator draws a &lt;strong&gt;closed polygon with known bearings and distances&lt;/strong&gt;, then writes it up in many styles: symbols, dashes, words, archaic chains and links, and damaged OCR, with easements, deed-book numbers and stray acreage as distractors. The truth exists before the text does, so every label is exact. I deliberately held back one &lt;strong&gt;writing style&lt;/strong&gt; (every number spelled out in words) and one &lt;strong&gt;unit&lt;/strong&gt; (Texas varas) to see what the model does with things it has never met.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Use of Tinker
&lt;/h2&gt;

&lt;h2&gt;
  
  
  10% → 99.6%, for $1.15
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Qwen3-8B is a small model. Out of the box it doesn't know what a deed is, and it has nowhere near the knowledge of a giant LLM. It scored 10%. Same model, same prompt, same checks: the only thing that changed was one dollar and fifteen cents of Tinker training.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the point. I didn't need a model that knows everything. I needed one that does one narrow job perfectly, and fine-tuning is how a small model gets there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbyywn8ut4pbtnul1t9fu.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%2Fbyywn8ut4pbtnul1t9fu.png" alt="Deeds read exactly right, before and after" width="800" height="428"&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;&lt;/th&gt;
&lt;th&gt;base Qwen3-8B, untrained&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Tinker LoRA&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exactly right&lt;/td&gt;
&lt;td&gt;6 / 60 (10%)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,988 / 3,000 (99.6%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sent to a human&lt;/td&gt;
&lt;td&gt;82%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.4%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong and unflagged&lt;/td&gt;
&lt;td&gt;5 / 60&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 / 3,000 (0 by physical length)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My goal wasn't a good number. It was &lt;strong&gt;a good number at the lowest price I could defend.&lt;/strong&gt; Here is exactly how.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6bbfwsx8qwgo6h4twvb.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%2Fa6bbfwsx8qwgo6h4twvb.png" alt="The fine-tuning pipeline" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Shrink the bill before you shrink the model
&lt;/h3&gt;

&lt;p&gt;Tinker bills per token, so I started with the output. My first format was JSON at &lt;strong&gt;547 tokens&lt;/strong&gt; per deed. I replaced it with one line per call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POB iron pin
L N 41 0 0 E 121.9 ft | iron pin
C N 3 45 22 E 366.15 ft R 944.33 ft L | stone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code converts it back to JSON. &lt;strong&gt;145 tokens, 3.8× smaller&lt;/strong&gt;, which cuts training, evaluation and local inference all at once, and costs nothing to run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgq52fwp6fjgspedxvoe.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%2Fhgq52fwp6fjgspedxvoe.png" alt="3.8x fewer tokens" width="799" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Price the options, then pick
&lt;/h3&gt;

&lt;p&gt;Training per million tokens: &lt;strong&gt;Qwen3-8B $0.44&lt;/strong&gt; vs Qwen3.5-4B $0.737. A smaller model is not automatically a cheaper one. Qwen3-8B was cheaper to train and strong enough for the job.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Train: rank-32 LoRA, 3 epochs, 16 minutes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_lora_training_client_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen3-8B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# loss only on the answer tokens, never on the deed prose
&lt;/span&gt;&lt;span class="nc"&gt;Datum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="n"&gt;loss_fn_inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                          &lt;span class="n"&gt;target_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]))&lt;/span&gt;

&lt;span class="n"&gt;fb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forward_backward_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loss_fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cross_entropy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optim_step_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AdamParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;lr_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;beta1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beta2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2,300 training deeds&lt;/strong&gt;, batch 16, peak learning rate 2e-4 with warmup and linear decay, &lt;strong&gt;429 steps, about 2.6M tokens.&lt;/strong&gt; Loss is masked to the answer, so the model learns to write calls instead of memorizing deed prose. Steps are pipelined (step &lt;em&gt;k+1&lt;/em&gt; is submitted before step &lt;em&gt;k&lt;/em&gt; returns), a checkpoint is saved every epoch, and a budget guard prices every job from token counts and refuses to launch one that would burn half the cap. Loss falls from 1,806 on step 1 to under 0.1 by step 41.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgriaqbpu5jghxcrnjwj.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%2Fhgriaqbpu5jghxcrnjwj.png" alt="Training loss across 3 epochs" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Find the knee of the curve
&lt;/h3&gt;

&lt;p&gt;I didn't train on everything and hope. Four runs, one epoch each, scored on the same 500 held-out deeds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;training deeds&lt;/th&gt;
&lt;th&gt;est. cost&lt;/th&gt;
&lt;th&gt;read exactly right&lt;/th&gt;
&lt;th&gt;wrong and unflagged&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;$0.04&lt;/td&gt;
&lt;td&gt;38.6%&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;$0.08&lt;/td&gt;
&lt;td&gt;92.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.17&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.4%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;$0.34&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;td&gt;0&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ao6xzkphdj4n3kd5yoo.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%2F2ao6xzkphdj4n3kd5yoo.png" alt="Accuracy per dollar" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seventeen cents buys 99.4%.&lt;/strong&gt; And look at the zeros: between 250 and 500 deeds the model stops making &lt;em&gt;silent&lt;/em&gt; mistakes. That jump, not the accuracy, is where it becomes safe to trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Is the gain real or noise?
&lt;/h3&gt;

&lt;p&gt;I paired the base model against the fine-tune on &lt;strong&gt;the same 60 deeds&lt;/strong&gt;, deed by deed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;base, untrained&lt;/th&gt;
&lt;th&gt;fine-tuned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;exactly right&lt;/td&gt;
&lt;td&gt;6 / 60 (10%, CI 4.7–20.1%)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60 / 60&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wrong and unflagged&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deeds only this model got right&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every deed the base model got right, the fine-tune also got right, and it won the other 54. Paired sign test: &lt;strong&gt;p &amp;lt; 0.0001.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then the real exam: &lt;strong&gt;3,000 fresh deeds&lt;/strong&gt;, generated before I froze the validator and scored exactly once. &lt;strong&gt;2,988 exactly right. 99.6%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tom7obszu940i715r1p.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%2F9tom7obszu940i715r1p.png" alt="Where all 3,000 deeds ended up" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of the 12 imperfect outputs, &lt;strong&gt;10 were caught by parse and closure alone.&lt;/strong&gt; The other 2 are one ambiguity in my own generator: it writes &lt;code&gt;87 links&lt;/code&gt; as "87 link" in one style and "0.87 chain" in another, which is the same physical length. By physical length, the silent count is &lt;strong&gt;0&lt;/strong&gt;. I report both.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. New styles and units cost about 150 examples
&lt;/h3&gt;

&lt;p&gt;Held out on purpose, zero-shot, from the first model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;unseen test&lt;/th&gt;
&lt;th&gt;exactly right&lt;/th&gt;
&lt;th&gt;wrong and unflagged&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;numbers spelled out in words&lt;/td&gt;
&lt;td&gt;85.6%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt; (14.4% flagged for review)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Texas varas&lt;/td&gt;
&lt;td&gt;every deed flagged&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Varas is the fun one. The model read every bearing and distance correctly but wrote the unit as &lt;code&gt;var&lt;/code&gt;, a token it had never seen, and the strict decoder rejected it. So &lt;strong&gt;everything was flagged and nothing wrong got through.&lt;/strong&gt; That is the safety net doing its job on a surprise.&lt;/p&gt;

&lt;p&gt;I didn't patch the decoder to accept each spelling the model improvised, because that would be fitting the test set. The real fix was data: &lt;strong&gt;150 examples each&lt;/strong&gt;, retrain, score on &lt;strong&gt;fresh&lt;/strong&gt; deeds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;fresh test set&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;exactly right&lt;/th&gt;
&lt;th&gt;wrong and unflagged&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;in distribution&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.6%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 (0 by physical length)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spelled-out numbers&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.6%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;varas&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Onboarding a new style or unit is a 150-example, sub-dollar job. That's the workflow Tinker makes cheap enough to just do.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Make evaluation free the second time
&lt;/h3&gt;

&lt;p&gt;Evaluation was the bigger bill (&lt;strong&gt;$1.35&lt;/strong&gt; vs $2.46 for all training), so every sampled output is cached and the checks score from the cache. Improving the validator and re-scoring 3,000 deeds cost &lt;strong&gt;$0.&lt;/strong&gt; One deed through Tinker costs about &lt;strong&gt;$0.00015.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Rent the training. Keep the weights.
&lt;/h3&gt;

&lt;p&gt;I downloaded the &lt;strong&gt;369 MB adapter&lt;/strong&gt; and ran it with &lt;strong&gt;MLX on the Apple GPU&lt;/strong&gt; over a 4-bit Qwen3-8B base:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;150 of 150 deeds read exactly right, 0 flagged, median 7.5 s per deed.&lt;/strong&gt; M3 Pro, 18 GB, no network, no API key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The whole bill
&lt;/h3&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%2Fx3bnmk6y3uelix7roc9l.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%2Fx3bnmk6y3uelix7roc9l.png" alt="Where the $3.80 went" width="800" height="293"&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;item&lt;/th&gt;
&lt;th&gt;est. cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;baseline, no training&lt;/td&gt;
&lt;td&gt;$0.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;learning-curve runs&lt;/td&gt;
&lt;td&gt;$0.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;first full run&lt;/td&gt;
&lt;td&gt;$1.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;final model (2,300 deeds, 3 epochs)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;all evaluation (4,000+ deeds)&lt;/td&gt;
&lt;td&gt;$1.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;everything on Tinker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;about $3.80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Token counts at Tinker's list prices.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Known edges (measured, not hidden)
&lt;/h2&gt;

&lt;p&gt;I'd rather you hear these from me, with numbers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The deeds are generated.&lt;/strong&gt; That's what made exact labels and a 3,000-deed fresh test possible, and it's why the held-out style and unit matter. The next step is running real recorded deeds through the same pipeline, which is why every real-world answer still ends in a human approve button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The baseline is the plain base model&lt;/strong&gt;, not a heavily engineered one, and it was scored on 60 deeds to keep the bill low. That's why I paired it against the fine-tune on the same 60 and gave its confidence interval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clause checks look for &lt;code&gt;;&lt;/code&gt; or &lt;code&gt;thence&lt;/code&gt;&lt;/strong&gt; between calls. Where that structure is missing, they fall back to whole-text checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curves are chord-form&lt;/strong&gt; (chord bearing, length, radius, direction). Deflection angles and station/offset descriptions aren't supported yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's a drafting aid.&lt;/strong&gt; Not a substitute for a licensed surveyor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What went wrong on the way (and made it better):&lt;/strong&gt; my validator flagged &lt;em&gt;perfect&lt;/em&gt; answers until I ran it on the generator's own ground truth and fixed three reading bugs. Two silent errors slipped past closure at 500 deeds (a dropped arc-minute and a flipped curve direction), which is exactly why the independent second reader exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does open innovation matter?
&lt;/h2&gt;

&lt;p&gt;A closed API gives you a prompt box. Open weights gave me a &lt;strong&gt;steering wheel.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I could change the model, not just the prompt.&lt;/strong&gt; The untrained small model got 10%. A one-dollar LoRA got 99.6%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deeds are private.&lt;/strong&gt; They name people, parcels and mortgages. The weights run on the laptop that holds the file. Turn Wi-Fi off and it still works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No meter running.&lt;/strong&gt; After the one-time fine-tune, deed number one thousand costs electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nobody can deprecate it.&lt;/strong&gt; The base model, adapter and code sit on disk. A better open model next year is a training run, not a migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could look inside.&lt;/strong&gt; The varas failure and the label tie all exist because I could inspect every output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest caveat: training ran on Tinker's servers on generated deeds only. No real person's land record was ever sent anywhere, and at run time nothing leaves the laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  My agent session
&lt;/h2&gt;

&lt;p&gt;I built DeedReader with Claude Code as a pair, from a written plan with a cost estimate up front and a budget guard on every training job.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It read Tinker's docs before writing a line against the API, and wrote closure-math tests before any model touched the project.&lt;/li&gt;
&lt;li&gt;The baseline exposed a blind spot: many of the base model's &lt;em&gt;landmark names&lt;/em&gt; were wrong, and closure can't see that. So it built the per-sentence grounding check.&lt;/li&gt;
&lt;li&gt;It debugged its own validator against ground truth, and when varas failed it ran the real fix as an experiment on fresh data instead of patching around it.&lt;/li&gt;
&lt;li&gt;It kept a running cost log from the first job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Tinker.&lt;/strong&gt; The LoRA fine-tune (pipelined forward/backward passes, loss-masked completions, per-epoch checkpoints), the baseline, the data-size learning curve, 4,000+ evaluated deeds and the adapter download behind the offline story all ran on Tinker. &lt;strong&gt;$1.15 for the final model, about $3.80 for everything.&lt;/strong&gt; Same base model, same prompt, same checks: &lt;strong&gt;10% (6/60) → 99.6% (2,988/3,000)&lt;/strong&gt;, and every deed the base model got right, the fine-tune got right too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built solo, with Claude Code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three epochs. $1.15. 10% became 99.6%.&lt;/strong&gt; If that sounds like a good trade, a ❤️ or 🦄 helps more people find it.&lt;/p&gt;

&lt;p&gt;What's the strangest phrase you've ever seen in a property deed? Mine so far is "to a white oak, thence along the run." Leave yours in the comments.&lt;/p&gt;

</description>
      <category>tinker</category>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>A solution for all Ecom brand owners and marketing agencies helping ecom owners create video and image ads. Let me know your thoughts on it.</title>
      <dc:creator>Advik Divekar</dc:creator>
      <pubDate>Sat, 17 Oct 2026 12:07:19 +0000</pubDate>
      <link>https://dev.to/advikdivekar/a-solution-for-all-ecom-brand-owners-and-marketing-agencies-helping-ecom-owners-create-video-and-ohh</link>
      <guid>https://dev.to/advikdivekar/a-solution-for-all-ecom-brand-owners-and-marketing-agencies-helping-ecom-owners-create-video-and-ohh</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84" class="crayons-story__hidden-navigation-link"&gt;What if your whole marketing team fit on a laptop, and finished in 55 seconds?&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/uvYCVlKRKbU" title="What if your whole marketing team fit on a laptop, and finished in 55 seconds?"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/advikdivekar" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F4162179%2F4f54da1f-781c-461b-8131-a914f2180e70.jpg" alt="advikdivekar profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/advikdivekar" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Advik Divekar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Advik Divekar
                
                
              
              &lt;div id="story-author-preview-content-4862921" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/advikdivekar" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F4162179%2F4f54da1f-781c-461b-8131-a914f2180e70.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Advik Divekar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84" id="article-link-4862921"&gt;
          What if your whole marketing team fit on a laptop, and finished in 55 seconds?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hf26challenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hf26challenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;50&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              33&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            17 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>What if your whole marketing team fit on a laptop, and finished in 55 seconds?</title>
      <dc:creator>Advik Divekar</dc:creator>
      <pubDate>Fri, 16 Oct 2026 14:09:27 +0000</pubDate>
      <link>https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84</link>
      <guid>https://dev.to/advikdivekar/what-if-your-whole-marketing-team-fit-on-a-laptop-and-finished-in-55-seconds-j84</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if your whole marketing team fit on a laptop, and finished in 55 seconds?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used to run an ecommerce brand.&lt;/p&gt;

&lt;p&gt;In small ecommerce, marketing is everything, and it comes in two kinds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance marketing&lt;/strong&gt; is the ads you pay for. &lt;strong&gt;Organic marketing&lt;/strong&gt; is the posts, reels and listings you put out without paying for reach. Both run on a constant stream of fresh content. If it isn't live, you aren't selling.&lt;/p&gt;

&lt;p&gt;Every product meant listing copy stitched together from two or three AI chats, then ad copy, photo edits, images for the listing, and video ads. Every step in a different tool. Every handoff a copy and paste.&lt;/p&gt;

&lt;p&gt;It cost me in three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time.&lt;/strong&gt; The gap between "I have a product" and "it's live" was where sales were lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cashflow.&lt;/strong&gt; A subscription for images, one for video, one for copy, one for editing. In a small business, tool bills quietly eat tight cashflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; My product details, my pricing and my workflow all sat inside other people's tools. None of it was ever private.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I watched my mom run into the same wall.&lt;/p&gt;

&lt;p&gt;My mom is a cosmetician. She sells skincare and beauty products, and she is very good at it. Her marketing is organic: Instagram and Facebook posts and reels.&lt;/p&gt;

&lt;p&gt;But one single image post took her &lt;strong&gt;3 to 4 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Canva for the picture. CapCut or another editor for the video. ChatGPT or Claude for the caption, then copy, paste, "make it shorter", copy, paste again. Then the post itself, on every platform she sells on. A reel could take days.&lt;/p&gt;

&lt;p&gt;On top of that, she kept switching between ChatGPT, Claude, Higgsfield and whatever other AI tool she could find, often across several accounts to keep going. That irritated her a lot.&lt;/p&gt;

&lt;p&gt;Those are hours she should have spent with customers. Instead she was working as a part-time designer and video editor.&lt;/p&gt;

&lt;p&gt;Different business, same trap: too many tools, too much attention, and your product details sent to places you don't control.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Mochi&lt;/strong&gt;: one product photo and a few true facts become studio photos, an image deck, a 10-second video and copy for every platform you sell on. It runs on open models on your own computer, so nothing about your product goes to anyone else's server.&lt;/p&gt;

&lt;p&gt;I built it for my mom and for sellers like me, and I tried it on a real brand to see if it works. More on that session is further down.&lt;/p&gt;

&lt;p&gt;Here it is in action:&lt;/p&gt;

&lt;p&gt;Here's the proof. A skincare brand, Velora, dropped &lt;strong&gt;three photos of a face cleanser&lt;/strong&gt; into Mochi. This is the screen when it finished:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wnzvi3e798iq27hexcm.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%2F1wnzvi3e798iq27hexcm.png" alt="The kit is ready, built in 55 seconds" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fifty-five seconds.&lt;/strong&gt; And this is what it had made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9 studio photos&lt;/strong&gt; on a designed backdrop:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdp1fqu0zp56ps9axxzci.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%2Fdp1fqu0zp56ps9axxzci.png" alt="Studio photos" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 5-slide image deck&lt;/strong&gt; (this is slide 3 of 5):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu6anojd8xhcd5ztm5s90.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%2Fu6anojd8xhcd5ztm5s90.png" alt="Image deck" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 10-second vertical video ad&lt;/strong&gt;, with text cards built from the copy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0b2jvhsxtk6wj08mi7uf.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%2F0b2jvhsxtk6wj08mi7uf.png" alt="Video ad" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing copy, tags and an SEO title&lt;/strong&gt;, each one editable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh98bssd0knx3ia2tqn4y.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%2Fh98bssd0knx3ia2tqn4y.png" alt="Keywords, tags and SEO title" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No agency. No stack of subscriptions. No uploading your product to someone else's server. Just open models running on one computer.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Mochi turns that whole chain of tools, tabs and copy-pasting into three taps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F334lp6kcdsst2yficlsa.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%2F334lp6kcdsst2yficlsa.png" alt="Mochi landing page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Studio photos.&lt;/strong&gt; Your phone photo, cut out and placed on a designed backdrop, and the product is never regenerated, so it stays your real product&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An image deck.&lt;/strong&gt; Slides ready for a feed or a catalog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 10-second vertical video.&lt;/strong&gt; Camera moves and text cards built from your own copy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform copy.&lt;/strong&gt; Listing, social, Pinterest and a video brief, written for each place you sell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing I care about more than anything: &lt;strong&gt;Mochi checks its own copy for things you never told it.&lt;/strong&gt; Models do invent claims, so every kit is checked by plain code. Missing facts are listed back to you ("Not provided, so left out of the copy"), and anything the copy says that isn't in your facts is flagged before you post.&lt;/p&gt;

&lt;p&gt;For a skincare seller, that matters. "Reduces wrinkles" is a legal claim, not a style choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use it
&lt;/h3&gt;

&lt;p&gt;Five steps, about two minutes of your time. The screenshots are from a real run on a phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shortcut:&lt;/strong&gt; add a photo, and if Mochi's guesses from it look right, tap &lt;strong&gt;Looks right: generate now&lt;/strong&gt;. Everything below is the long way, for when you want control.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fob6bdf92fm7q0i1ehi20.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%2Fob6bdf92fm7q0i1ehi20.png" alt="The shortcut: Looks right, generate now" width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Add your photos.&lt;/strong&gt;&lt;br&gt;
Tap &lt;strong&gt;Take photo&lt;/strong&gt; or &lt;strong&gt;Add more&lt;/strong&gt;. You can add up to 5. The first photo is your &lt;strong&gt;Main&lt;/strong&gt; one, and it also opens and closes your video. Different angles make a better video and deck. A clear photo on a plain background works best. Mochi starts reading them right away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnikyvvkwuaorwlpkcdc1.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%2Fnikyvvkwuaorwlpkcdc1.png" alt="Step 1: add photos" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Check what Mochi found.&lt;/strong&gt;&lt;br&gt;
Mochi reads the label on your product and offers the text as notes you can tap to add. Fields it filled in are tagged &lt;strong&gt;"From photo, check it"&lt;/strong&gt;. Fill in what's missing, like price and features. Only add what is true. Blank fields are flagged as missing, never guessed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tkytsi84mawrwybn2sz.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%2F0tkytsi84mawrwybn2sz.png" alt="Step 2: text Mochi read from the label" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Pick your style.&lt;/strong&gt;&lt;br&gt;
Choose where you sell (Instagram, Facebook, Pinterest, Etsy, Shopify), a tone (from Straightforward to Luxury), a goal (Drive sales, Brand awareness, Gift appeal or Explain features), and a writing speed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast&lt;/strong&gt; takes about 20 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best quality&lt;/strong&gt; uses a larger model and takes about a minute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then pick a backdrop for your photos and video: Premium studio, UGC-style, Minimal, Warm and cozy, or Bold and colorful. Tap &lt;strong&gt;Generate kit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwlfl7t1wz2m4q0eeyx9.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%2Fvwlfl7t1wz2m4q0eeyx9.png" alt="Step 3: tone, goal and writing speed" width="800" height="1351"&gt;&lt;/a&gt;&lt;br&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%2Fx488hyomz9e7cinuhkwz.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%2Fx488hyomz9e7cinuhkwz.png" alt="Step 3: choose a backdrop" width="800" height="991"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4. Let it build.&lt;/strong&gt;&lt;br&gt;
You see real progress, step by step: writing copy, building studio photos and video, adding text to the video. You can leave the page. It keeps running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F20jf7dpbnehvw76pjg2v.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%2F20jf7dpbnehvw76pjg2v.png" alt="Step 4: building the kit" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5. Review, edit and share.&lt;/strong&gt;&lt;br&gt;
Your kit has four tabs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overview:&lt;/strong&gt; the video, the image deck, and a "double-check before posting" list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy:&lt;/strong&gt; listing, social, Pinterest and a video brief. Change the tone or refresh any section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photos:&lt;/strong&gt; your studio photos, ready to download&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share:&lt;/strong&gt; download or share the kit&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flb32nwsgr7sldg08smts.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%2Flb32nwsgr7sldg08smts.png" alt="Step 5: the four tabs, shown on a sample product" width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The finished result of a real run is at the top of this post.&lt;/p&gt;

&lt;p&gt;Nothing posts on its own. The only thing Mochi can send anywhere is a Shopify &lt;strong&gt;draft&lt;/strong&gt;, and only when you tap it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;h3&gt;
  
  
  Every page of the web app
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Landing: hero&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Famd6aglqw91i6bfultjj.jpg" 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%2Famd6aglqw91i6bfultjj.jpg" alt="Landing: hero" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: the interactive studio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfb9xes4rfi38wmck6rd.jpg" 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%2Fnfb9xes4rfi38wmck6rd.jpg" alt="Landing: the interactive studio" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: written the way each channel works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1puj1ug0wb7j6ppirel.jpg" 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%2Fc1puj1ug0wb7j6ppirel.jpg" alt="Landing: written the way each channel works" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: your next ad starts with a photo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffai28wirp1eclromyy1m.jpg" 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%2Ffai28wirp1eclromyy1m.jpg" alt="Landing: your next ad starts with a photo" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: upload once, get the lot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6s18rq4dkeb1av1izyfc.jpg" 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%2F6s18rq4dkeb1av1izyfc.jpg" alt="Landing: upload once, get the lot" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: same facts, your voice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9a4uaiuk8k5hevz85ri.jpg" 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%2Fq9a4uaiuk8k5hevz85ri.jpg" alt="Landing: same facts, your voice" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: three steps, about a minute&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzhvag4wlkv5r76m6l4d.jpg" 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%2Fwzhvag4wlkv5r76m6l4d.jpg" alt="Landing: three steps, about a minute" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landing: call to action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwklmdqj7uw5zq7i02ir3.jpg" 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%2Fwklmdqj7uw5zq7i02ir3.jpg" alt="Landing: call to action" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create: start&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2u1wyob3fh2k3z8ryd6a.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%2F2u1wyob3fh2k3z8ryd6a.png" alt="Create: start" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create step 1: photos, and the text Mochi read from them&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8z55zjlv5yzmr9ervp59.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%2F8z55zjlv5yzmr9ervp59.png" alt="Create step 1: photos, and the text Mochi read from them" width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create step 2: product facts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6304jsjo7byil265uwux.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%2F6304jsjo7byil265uwux.png" alt="Create step 2: product facts" width="800" height="1042"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create step 3: tone, goal, speed and visual style&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftxvu2r39wjbshtmvi7oo.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%2Ftxvu2r39wjbshtmvi7oo.png" alt="Create step 3: tone, goal, speed and visual style" width="800" height="774"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: overview with video ad and image deck&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe7nqdl3s5oywk7vlbot5.jpg" 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%2Fe7nqdl3s5oywk7vlbot5.jpg" alt="Result: overview with video ad and image deck" width="800" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: copy for every channel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34yxfu5eziokepe6yqwm.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%2F34yxfu5eziokepe6yqwm.png" alt="Result: copy for every channel" width="800" height="1159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: studio photos, white background and cut-outs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fadvikdivekar%2Fsellcraft%2Fmain%2Fdocs%2Fimages%2Fweb%2F08-result-photos.jpg" 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%2Fraw.githubusercontent.com%2Fadvikdivekar%2Fsellcraft%2Fmain%2Fdocs%2Fimages%2Fweb%2F08-result-photos.jpg" alt="Result: studio photos, white background and cut-outs" width="800" height="1579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: share and export&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Famd0krt31e7aayh4rbb3.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%2Famd0krt31e7aayh4rbb3.png" alt="Result: share and export" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stores&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpxiim25505tl93604tm.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%2Fcpxiim25505tl93604tm.png" alt="Stores" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/advikdivekar" rel="noopener noreferrer"&gt;
        advikdivekar
      &lt;/a&gt; / &lt;a href="https://github.com/advikdivekar/sellcraft" rel="noopener noreferrer"&gt;
        sellcraft
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Mochi&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Your product. Your story. Ready to sell.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Small-seller marketing studio: upload a product photo, enter a few verified facts, pick a platform / tone / style / goal, and get platform-specific listing copy plus a video-ad brief. Copy is written by an &lt;strong&gt;open-weight model (Gemma) running locally through Ollama&lt;/strong&gt;, so product data never leaves your machine. See &lt;a href="https://github.com/advikdivekar/sellcraft/plan.md" rel="noopener noreferrer"&gt;&lt;code&gt;plan.md&lt;/code&gt;&lt;/a&gt; for the full design.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Working end to end: facts and photos → Gemma copy → studio photos, image deck and a 10-second video → export or Shopify draft. A LoRA fine-tune on Tinker, an independent Gemma judge and Sentry tracing are in; see &lt;a href="https://github.com/advikdivekar/sellcraft/docs/RESULTS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/RESULTS.md&lt;/code&gt;&lt;/a&gt; for the measured numbers and &lt;a href="https://github.com/advikdivekar/sellcraft/docs/DECISIONS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/DECISIONS.md&lt;/code&gt;&lt;/a&gt; for why things are the way they are.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it makes&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;From up to &lt;strong&gt;5 photos&lt;/strong&gt; and a few facts (the app measures each photo, flags blur/low-res/dark shots, cleans the cut-out, and picks a backdrop that makes your product…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/advikdivekar/sellcraft" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Backend: FastAPI, SQLite, Pydantic. Frontend: Next.js 16 and React 19. 135 backend tests pass. Every number in this post can be reproduced from the files in &lt;code&gt;backend/finetune/results/&lt;/code&gt;, with the commands in &lt;a href="https://github.com/advikdivekar/sellcraft/blob/main/docs/RESULTS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/RESULTS.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Two lanes run at the same time&lt;/strong&gt;: one for visuals and one for language. They meet at the deck and the video, which need the finished copy. The page polls the job every 1.5 seconds and shows real progress and partial copy as it arrives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadjgrqtq99dub8ofl7mi.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%2Fadjgrqtq99dub8ofl7mi.png" alt="System architecture" width="800" height="791"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every model has one job and one strict contract.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdq9z6kh4t26q2ciee7y5.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%2Fdq9z6kh4t26q2ciee7y5.png" alt="Models and their jobs" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two choices worth explaining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The video is not AI-generated.&lt;/strong&gt; It's image processing plus FFmpeg, so the product in the video is &lt;em&gt;your&lt;/em&gt; product, not a regenerated lookalike. (I do denoise, upscale and retouch it.) Generative video tends to repaint the product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The checker is plain code, not a second model.&lt;/strong&gt; A model can talk itself into anything. A word list and a number check can't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The copy&lt;/strong&gt; is split into parallel parts (four core parts plus one per platform), each forced into a JSON schema. A bad call is retried once, and plain code then checks for forbidden words, invented numbers and unsupported claims.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fieh6fc12xbi2rjqff00g.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%2Fieh6fc12xbi2rjqff00g.png" alt="Copy pipeline" width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Photos are read, never trusted.&lt;/strong&gt; Up to 5 photos are shrunk to 768 px and read under strict rules. What comes back is only a suggestion, marked "From photo, check it".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63mev0vvk32tueerxi7m.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%2F63mev0vvk32tueerxi7m.png" alt="Photo reading" width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Studio shots:&lt;/strong&gt; measure, cut out, refine the product's pixels, fix the tone, pick a backdrop from what the photo shows, compose. If the cutout looks wrong, it falls back to a framed photo instead of a bad cutout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2m7v0j05sj3mgs91zt4z.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%2F2m7v0j05sj3mgs91zt4z.png" alt="Studio imagery pipeline" width="800" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The video:&lt;/strong&gt; at least 4 shots rendered with FFmpeg (up to 3 at once), 0.4 s crossfades, text cards from your edited copy, checked to be 10 seconds at 1080x1920. Edit the copy and only the text layer re-renders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frydx2bgqvnduxn1h52yw.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%2Frydx2bgqvnduxn1h52yw.png" alt="Video pipeline" width="799" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's fast.&lt;/strong&gt; My first version took 90 to 145 seconds for one big generation on a laptop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fousp5imd7cvkf78zet3v.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%2Fousp5imd7cvkf78zet3v.png" alt="Before and after: what the Fast default and parallel parts bought" width="800" height="443"&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;Change&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fast mode (Gemma 4B) as the default&lt;/td&gt;
&lt;td&gt;copy 53 s&lt;/td&gt;
&lt;td&gt;copy 14 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split the kit into parallel parts&lt;/td&gt;
&lt;td&gt;one 12B call, 90 to 145 s&lt;/td&gt;
&lt;td&gt;45 to 60 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run both lanes together&lt;/td&gt;
&lt;td&gt;the sum of both&lt;/td&gt;
&lt;td&gt;the slower lane, plus a few seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A four-photo kit went from about &lt;strong&gt;100 s to about 18 s&lt;/strong&gt; in Fast mode. The trade-off: the 4B model gets more claims flagged than the 12B, the post-check still catches them, and Best quality (about 57 s) is one switch away.&lt;/p&gt;

&lt;p&gt;The fine-tune, the evaluation and the Sentry setup each have their own section under Prize Categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Go back to my two stories. In both, the real cost was not money alone. It was &lt;strong&gt;control&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Your product details stay yours.&lt;/strong&gt; Pricing, ingredients, launch plans. With open models running locally, the data never leaves your machine. There's no "we don't train on your data" to trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. No per-kit fee.&lt;/strong&gt; Every kit costs electricity. A seller can run it five times, try three tones, and never wonder if it's "worth" the credits. When cashflow is tight, that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. I could change the model, not just the prompt.&lt;/strong&gt; I fine-tuned a small open model to follow one rule: &lt;em&gt;don't invent claims&lt;/em&gt;. Prompting alone left the 4B model at 23% clean kits. Training took it to 88%. You can't do that to a closed model, or download what you trained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. I could look inside and measure.&lt;/strong&gt; I tested the fine-tune against an independent judge model, not just my own checker. With a closed API I'd have one number and no way to question it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. One setting swaps the model.&lt;/strong&gt; Fast or Best quality is a single switch between two open Gemma models. The next Gemma release is a one-line change.&lt;/p&gt;

&lt;p&gt;The honest limit: open models need a computer that can run them. I wrote the local setup into the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built Mochi with Claude Code, and every session is recorded with &lt;strong&gt;Entire&lt;/strong&gt;. &lt;a href="https://github.com/advikdivekar/sellcraft/blob/main/docs/DECISIONS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/DECISIONS.md&lt;/code&gt;&lt;/a&gt; lists 19 decisions, each with its reason, its commit and its Entire checkpoint, so you can read the conversation behind any choice. More in the Entire section below.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Velora&lt;/strong&gt;, a skincare brand, ran its &lt;strong&gt;Sensicontrol Face Cleanser&lt;/strong&gt; through Mochi on a phone, on &lt;strong&gt;16 October 2026&lt;/strong&gt;. The phone reached my computer over a Cloudflare tunnel, so for this demo the photos passed through Cloudflare. In normal use everything stays on the machine. I have the screen recording. Here is what happened, taken only from what is on screen.&lt;/p&gt;

&lt;p&gt;I don't have a written quote from them, so I'm not putting words in their mouth. What I do have is the recording, and it shows what they actually did.&lt;/p&gt;

&lt;p&gt;They followed the five steps above (the screenshots for steps 1 to 4 are from their session): three photos of the bottle, the label read off it, a persuasive tone, Best quality writing, Pinterest as the platform, and the warm and cozy backdrop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What came out&lt;/strong&gt; is shown at the top of this post: the finished kit in 55 seconds, the studio photos, the deck, the video and the copy. It was built on Best quality, with three photos, by a Gemma 3 12B model running on my own computer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The part I care about most.&lt;/strong&gt; Mochi told them what it had left out, and what it had written that they never said:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Not provided, so left out of the copy":&lt;/strong&gt; dimensions, verified benefits, target audience, care instructions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Double-check before posting":&lt;/strong&gt; &lt;em&gt;Not in your facts: "available now". Remove it unless it's true.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"About your photos":&lt;/strong&gt; it skipped the close-up slide because the photo didn't have enough pixels on the product&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiwarsrxu8qucbnk18zvt.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%2Fiwarsrxu8qucbnk18zvt.png" alt="Mochi lists what it left out and what to double-check" width="800" height="1351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The kit was five slides, nine studio photos, a 10-second video, and copy for one platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned from watching.&lt;/strong&gt; All of this is from the recording:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They changed their mind before generating.&lt;/strong&gt; They started with the Friendly tone, Instagram and Fast, then switched to Persuasive, Pinterest and Best quality. The choices clearly matter to people, so I'm glad nothing is locked in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They left several facts blank, and the copy respected that.&lt;/strong&gt; Target audience, dimensions, verified benefits and care instructions were never filled in, so Mochi left them out and said so, instead of guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best quality costs time.&lt;/strong&gt; The form says Fast takes about 20 seconds and Best quality about a minute. They chose the slower one, and it took 55.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I'm entering &lt;strong&gt;Tinker, Gemma, Sentry Agent Tracing and Entire&lt;/strong&gt;. Each section covers what I used, why, what it was meant to fix, and what I found.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Tinker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I used it for.&lt;/strong&gt; I fine-tuned &lt;strong&gt;Qwen3.5-4B with a LoRA adapter&lt;/strong&gt; on Tinker to write marketing copy that follows one rule: &lt;em&gt;never invent a claim the seller didn't give.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Tinker.&lt;/strong&gt; Prompting a small model to follow that rule didn't work. Untuned Qwen3.5-4B produced a clean kit only 18% of the time. I wanted to change the model, not keep rewording the prompt, and Tinker let me train without running my own GPU servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it worked.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A big teacher model, &lt;strong&gt;Qwen3.5-397B-A17B&lt;/strong&gt;, writes and revises kits on Tinker&lt;/li&gt;
&lt;li&gt;My checker and an independent judge (Gemma 12B, a different model family) reject any kit with an invented claim, number or forbidden word&lt;/li&gt;
&lt;li&gt;The teacher revises with the checker's findings, at most 2 rounds&lt;/li&gt;
&lt;li&gt;Only kits that pass &lt;strong&gt;both&lt;/strong&gt; the checker and the judge become training data (zero hard violations)&lt;/li&gt;
&lt;li&gt;A small student is trained on those: rank 32, 3 epochs, 319 examples, learning rate 2e-4&lt;/li&gt;
&lt;li&gt;A held-out set of 60 products, which the training code refuses to read, tests it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing cost &lt;strong&gt;$5.41 of the $10 credit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqw9o3qzio5ymvhtbnc0r.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%2Fqw9o3qzio5ymvhtbnc0r.png" alt="Fine-tuning pipeline" width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full detailed version (tap to open it full size and zoom in):&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/advikdivekar/sellcraft/main/docs/images/diagrams/finetune.png" rel="noopener noreferrer"&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%2F01lc40ivo9wqpty6wqp5.png" alt="Detailed fine-tuning pipeline" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results&lt;/strong&gt; (60 held-out synthetic products, one checker):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5uuhupbw7nb90ii5tark.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%2F5uuhupbw7nb90ii5tark.png" alt="Evaluation results" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full detailed version (tap to open it full size and zoom in):&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/advikdivekar/sellcraft/main/docs/images/diagrams/evaluation.png" rel="noopener noreferrer"&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%2F00hzeo2inen8hff27hkx.png" alt="Detailed evaluation" width="799" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's a 4B model, a third of Gemma 12B's size, producing nearly twice as many clean kits, and about 10 times faster (4.9 s against 48.7 s per kit, though v2 runs on Tinker's cloud and Gemma on my laptop).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest result on the judge.&lt;/strong&gt; v2 halves the untuned model's flags (0.43 to 0.20), but the confidence intervals overlap, and Gemma 12B is lower still at 0.13. With 30 kits per model I can't claim more than that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits I'm stating on purpose:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The test set is synthetic, and the checker also filtered the training data&lt;/li&gt;
&lt;li&gt;The fine-tune runs on Tinker, so its inputs leave the machine. The app's default (Gemma via Ollama) stays local.&lt;/li&gt;
&lt;li&gt;The fine-tune is &lt;strong&gt;not&lt;/strong&gt; in the app's path yet. I haven't exported it to GGUF for Ollama.&lt;/li&gt;
&lt;li&gt;Hosted cost is about $3 per 1,000 kits, estimated from token counts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I learned.&lt;/strong&gt; A small model trained on the right examples can follow a narrow rule better than a larger model that's only prompted. The checker and the judge decide which examples count, so they matter as much as the training run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Gemma
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I used it for.&lt;/strong&gt; Gemma does three jobs inside Mochi, all through Ollama on the seller's own computer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The copy writer.&lt;/strong&gt; Gemma 3 4B for Fast, Gemma 3 12B for Best quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The photo reader.&lt;/strong&gt; It reads up to 5 photos and returns suggested facts, each tagged "From photo, check it"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The independent judge.&lt;/strong&gt; Gemma 3 12B, a different family from the Qwen teacher, reviews kits in my evaluation&lt;/li&gt;
&lt;/ol&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%2Fdq9z6kh4t26q2ciee7y5.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%2Fdq9z6kh4t26q2ciee7y5.png" alt="Models and their jobs" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full detailed version (tap to open it full size and zoom in):&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/advikdivekar/sellcraft/main/docs/images/diagrams/models.png" rel="noopener noreferrer"&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%2Fd93ec2onoyygwvql0xvs.png" alt="Detailed models and jobs" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full detailed version (tap to open it full size and zoom in):&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/advikdivekar/sellcraft/main/docs/images/diagrams/photo-reading.png" rel="noopener noreferrer"&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%2F1q0clzedinjlx8dhp6wy.png" alt="Detailed photo reading" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Gemma.&lt;/strong&gt; It's open-weight, it runs on one machine, and it can be held to a JSON schema. That matters here: the seller's facts shouldn't leave their computer, and every part of a kit must come back as strict, checkable JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it was meant to fix.&lt;/strong&gt; The cost and privacy problems from my ecommerce days: no per-kit fee, and nothing sent to anyone else's server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both sizes run the same prompt and the same checker, so I could compare them fairly: &lt;strong&gt;23% clean kits on 4B and 45% on 12B&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Speed is the trade-off. Fast (4B) builds a four-photo kit in about &lt;strong&gt;18 s&lt;/strong&gt;, Best quality (12B) in about &lt;strong&gt;57 s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;As the judge, Gemma 12B had the fewest flags of any model I tested (0.13 per kit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I learned.&lt;/strong&gt; Two things. First, the model size is a user choice, not a technical one, so I made it a switch on the form. Second, the judge is Gemma, so it may be lenient toward Gemma's own writing. That bias favours the Gemma rows and works &lt;em&gt;against&lt;/em&gt; my fine-tune's claim, so it can't be inflating my result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Sentry Agent Tracing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I used it for.&lt;/strong&gt; Tracing every model call and every job, without Sentry ever seeing a seller's product facts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this was the hard part.&lt;/strong&gt; A marketing tool handles a seller's most private material: product details, prices, plans. Sending prompts to a tracing service would break the promise the whole product makes. So the question was not "can I trace this", it was "can I trace this and prove nothing private leaks".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvbj0bv9h45k4irxmwnq4.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%2Fvbj0bv9h45k4irxmwnq4.png" alt="What Sentry sees, and what it never sees" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full detailed version (tap to open it full size and zoom in):&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/advikdivekar/sellcraft/main/docs/images/diagrams/observability.png" rel="noopener noreferrer"&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%2Fnf1io1ghn7hv68135idc.png" alt="Detailed observability" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each model call is a &lt;strong&gt;&lt;code&gt;gen_ai.chat&lt;/code&gt; span&lt;/strong&gt;, with the model, token counts and latency&lt;/li&gt;
&lt;li&gt;Each generation job is a transaction&lt;/li&gt;
&lt;li&gt;Each flagged claim is an issue (which rule fired, and how many)&lt;/li&gt;
&lt;li&gt;Each seller report is an event. The report button sends a section name and a fixed reason code, &lt;strong&gt;never free text&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flagged claims come from the copy post-check, so each issue is a rule the code caught, not a model's opinion.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full detailed version (tap to open it full size and zoom in):&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/advikdivekar/sellcraft/main/docs/images/diagrams/copy.png" rel="noopener noreferrer"&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%2Fc7u90lmblbff86utm46s.png" alt="Detailed copy pipeline and post-check" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it never sees.&lt;/strong&gt; An allow-list scrubber removes everything that is not on the list of allowed metadata. Product facts, copy, photos and prompts can't pass it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8n53vtd4hp0wq0m3p0do.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%2F8n53vtd4hp0wq0m3p0do.png" alt="What stays local and what can leave" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How I know it works.&lt;/strong&gt; A canary test pushes a unique fake product through every input path of the real app, then fails if that marker shows up in &lt;em&gt;any&lt;/em&gt; outgoing Sentry payload. It's in &lt;code&gt;tests/test_tracing.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results and honesty.&lt;/strong&gt; The scrubber and the canary test are in place and pass. I am &lt;strong&gt;not&lt;/strong&gt; claiming Sentry has caught a real production incident, because it hasn't. Mochi has had one real user session, not production traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned.&lt;/strong&gt; Making tracing safe was a bigger design question than turning it on. The allow-list approach (what's permitted, not what's blocked) is the one that survives new fields being added later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Entire
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I used it for.&lt;/strong&gt; To record how Mochi was built, so a reader can see &lt;em&gt;why&lt;/em&gt; the code is the way it is, not just what it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; Most repos show a result. For a project where I changed my mind several times, the changes are the story. A privacy rule, a new default and a safety check each have a reason that a commit message can't hold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What came of it.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;About 113 checkpoints&lt;/strong&gt; of Claude Code sessions, pushed to the repo&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/advikdivekar/sellcraft/blob/main/docs/DECISIONS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/DECISIONS.md&lt;/code&gt;&lt;/a&gt;: &lt;strong&gt;19 numbered decisions&lt;/strong&gt;, each with the reason, the commit and the Entire checkpoint ID&lt;/li&gt;
&lt;li&gt;Anyone can run &lt;code&gt;entire checkpoint explain &amp;lt;id&amp;gt;&lt;/code&gt; in a clone and read the prompts, files and reasoning behind a decision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The most useful example.&lt;/strong&gt; Decision #11 records why Sentry only ever gets metadata, and how the scrubber enforces it. That kind of reasoning normally disappears. Here it is written down, with the conversation that led to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest limit.&lt;/strong&gt; Checkpoints from before Entire was enabled were imported from local history and matched by time. They show the conversation a commit came out of, not a one-to-one record.&lt;/p&gt;




&lt;p&gt;If you sell something and make your own content, I'd love to hear what's missing. Tell me in the comments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built solo, with Claude Code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
