<?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: Nathan C.</title>
    <description>The latest articles on DEV Community by Nathan C. (@natuworkguy).</description>
    <link>https://dev.to/natuworkguy</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%2F3939269%2Fc5249dbd-4da9-49ee-8dfd-78b669b311fa.png</url>
      <title>DEV Community: Nathan C.</title>
      <link>https://dev.to/natuworkguy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/natuworkguy"/>
    <language>en</language>
    <item>
      <title>I gave my local AI agent background workers. Then it tried to deploy to production.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Tue, 22 Sep 2026 07:28:46 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-gave-my-local-ai-agent-background-workers-then-it-tried-to-deploy-to-production-2ei9</link>
      <guid>https://dev.to/natuworkguy/i-gave-my-local-ai-agent-background-workers-then-it-tried-to-deploy-to-production-2ei9</guid>
      <description>&lt;p&gt;I asked my agent to research something. It said it had started a sub-agent on it. Two messages later I asked if it was done, and it told me, with total confidence, that it had never started one at all.&lt;/p&gt;

&lt;p&gt;It had. The sub-agent finished in seven seconds and was sitting there with the answer. The agent just couldn't see it anymore, so it did what models do with a gap: it filled it. It invented an ID, &lt;code&gt;agent_0&lt;/code&gt;, went looking for it, got an error, and concluded the whole thing had never happened.&lt;/p&gt;

&lt;p&gt;That bug is how v0.5.4 of FLASH started. FLASH is my terminal agent. It runs on &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, so the model can live on your own machine, and it can read your files, run your shell, search the web, and drive a browser. This release is about one idea: an agent that stops waiting for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  It works while you don't
&lt;/h2&gt;

&lt;p&gt;The model can now hand work to sub-agents. Ask it two unrelated things and it splits them up, starts one sub-agent for each, tells you what it started, and gives you your prompt back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ what's the state of solid-state EV batteries, and separately, WebGPU support?
⏺ Agent(Research the current state of solid-state EV batteries...)
  ⎿  Started sub-agent. ID: 3f9a1c
⏺ Agent(Research current WebGPU support across major browsers...)
  ⎿  Started sub-agent. ID: 8b20de
I've started two sub-agents on these. I'll report back as they finish.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you do something else. When a sub-agent finishes, Flash wakes the model up on its own and it tells you what came back. You don't type anything.&lt;/p&gt;

&lt;p&gt;It only wakes while your prompt is empty, so it never snatches a message you're halfway through typing. And it stops after three wake-ups in a row with no word from you, because an agent that can wake itself can also start more sub-agents, and I didn't want to find out how far that chain goes.&lt;/p&gt;

&lt;p&gt;The fix for the invented ID was boring, which is usually a good sign. Flash only keeps the final replies in history, not the tool calls, so the ID lived in a tool result that vanished at the end of the turn. Now every finished answer and every still-running ID rides along at the top of your next message. &lt;strong&gt;The model never has to remember an ID it can simply be handed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/agents&lt;/code&gt; shows them all updating live. That part was pure fun to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  It knows what you just broke
&lt;/h2&gt;

&lt;p&gt;Here's a real transcript from before this release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ !git fetch main
fatal: 'main' does not appear to be a git repository
(exit 128)

❯ why did that fail
I can't see your terminal. Paste the command and the error you got.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flash ran that command itself. Commands you run with &lt;code&gt;!&lt;/code&gt; streamed straight to the screen and never made it into the conversation, so the model genuinely had nothing to go on.&lt;/p&gt;

&lt;p&gt;Now they do. The command, its exit code, and its output get attached to your next message. Same question, same failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ why did that fail
You tried to fetch from a remote named `main`, but `main` is your branch,
not the remote. Try `git fetch origin`.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels small. It is the difference between an assistant and a coworker.&lt;/p&gt;




&lt;h2&gt;
  
  
  And what you broke next to it
&lt;/h2&gt;

&lt;p&gt;Most of my commands don't go through Flash, though. They go through the VS Code terminal sitting right beside it. So &lt;code&gt;/hook install&lt;/code&gt; adds a tiny zsh or bash hook that only switches on inside VS Code's terminal. It records each command you run there, its exit code, and where you ran it, and Flash attaches the new ones to your next message.&lt;/p&gt;

&lt;p&gt;Here's the honest part. A shell hook runs before and after each command, but the command's output goes straight to the terminal and never passes through it. So the hook knows &lt;code&gt;npm test&lt;/code&gt; failed with exit code 1. It doesn't know why.&lt;/p&gt;

&lt;p&gt;When you ask, the model re-runs the safe ones itself to read the error: a build, a test, a lint. It knows exit code 130 means you hit Ctrl+C and there's nothing to fix. It also keeps secrets out of the picture. Commands you start with a space aren't logged, and anything that looks like &lt;code&gt;TOKEN=...&lt;/code&gt; or &lt;code&gt;--password ...&lt;/code&gt; is blanked out before the model reads it.&lt;/p&gt;

&lt;p&gt;Two bugs here were worth the whole feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bash's history output put two spaces after the entry number, and my first &lt;code&gt;sed&lt;/code&gt; pattern ate the command's own leading space too. So the "don't log commands that start with a space" rule silently didn't work. The test that caught it drives real interactive zsh and bash through a pseudo-terminal, because hook bugs don't show up anywhere else.&lt;/li&gt;
&lt;li&gt;CI failed only on Windows, because Windows' clock ticks every 15 milliseconds or so and two timestamps landed on the same tick. Chasing that turned up the real bug underneath: my hooks logged whole seconds, so on every OS, a command that finished in the same second as your last message got silently skipped. They log microseconds now, on zsh and on bash 5.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The day it tried to deploy
&lt;/h2&gt;

&lt;p&gt;Then there's the part I actually lost sleep over.&lt;/p&gt;

&lt;p&gt;If the model can re-run your failed commands, it had better know which ones not to re-run. The prompt said so, plainly: never re-run anything that deploys, deletes, migrates, pushes, sends, or pays.&lt;/p&gt;

&lt;p&gt;I tested it on the cloud build with a failed &lt;code&gt;./deploy.sh production&lt;/code&gt; and "why did that fail?". On every run, the model's first move was to read the script. Good. Then, on about 2 runs in 26, it re-ran &lt;code&gt;./deploy.sh production&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;Nothing happened, it was an eval. But in autonomous mode, where Flash skips the y/n prompt, that would have been a real deploy.&lt;/p&gt;

&lt;p&gt;The rule wasn't missing. It was losing. Reading the script seemed to turn "a deploy" into "a build and an rsync," and a bare "never" didn't survive that. So I stopped telling it no and told it why:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a re-run replays every step, so if the step that failed now passes, the rest goes live. Run the one step you suspect on its own, or ask them to paste the error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Zero in the next 30 runs. Now it runs &lt;code&gt;npm run build&lt;/code&gt; on its own, which is exactly what I'd have done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A prohibition gets reasoned around. A consequence gets reasoned with.&lt;/strong&gt; That's the most useful thing I learned about prompting this year.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your editor, not a chat window
&lt;/h2&gt;

&lt;p&gt;When Flash runs inside VS Code's terminal, it works with the editor instead of next to it. When it wants to change a file, the edit opens as a side-by-side diff while it waits for your yes, so you review it in the place you'd review anything. Ask "show me where the retry delay is set" and it opens the file at the line.&lt;/p&gt;

&lt;p&gt;No extension. It's just VS Code's own &lt;code&gt;code&lt;/code&gt; command, switched on only when Flash can tell it's running in VS Code's terminal, so it never throws windows at you from anywhere else.&lt;/p&gt;




&lt;h2&gt;
  
  
  The model doesn't have a name anymore
&lt;/h2&gt;

&lt;p&gt;Flash runs best on Flash Onyx, my own Ollama models: a Gemma base with the whole agent prompt and sampling baked into the tag. Onyx 2.5 ships with this release, with double the context (131,072 tokens) and a prompt that grew from 668 lines to 944.&lt;/p&gt;

&lt;p&gt;It's also brandless now. Onyx 2.4 called itself Flash everywhere. Onyx 2.5 has no name of its own. Inside the Flash CLI it's Flash. In a bare &lt;code&gt;ollama run&lt;/code&gt; it's just an assistant. Ask what it's built on and it tells you the truth: Gemma, through Ollama.&lt;/p&gt;

&lt;p&gt;Every rule about sub-agents, terminals, and editors is guarded the same way: it only applies if the app running it actually provides that feature. So the same model behaves in a plain chat and inside a full agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run natuworkguy/flash-onyx-2.5:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Everything else
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A visible plan: for multi-step work the model posts a checklist and ticks boxes as it goes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;send_image&lt;/code&gt;: the model hands you a picture it made, drawn inline in terminals that can show one.&lt;/li&gt;
&lt;li&gt;LaTeX in replies renders as real math, &lt;code&gt;π/2&lt;/code&gt; instead of &lt;code&gt;\frac{\pi}{2}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/update&lt;/code&gt; finally works on Windows, and voice mode survives it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install it, point it at a model, and ask it two things at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Natuworkguy/Flash/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I keep noticing the same thing when I use it. I ask, it says it's on it, and I go do something else. A minute later it comes back on its own with the answer. That used to be something only another person did.&lt;/p&gt;

&lt;p&gt;I don't think terminals go back to waiting.&lt;/p&gt;

&lt;p&gt;What would you hand a background agent first? Tell me in the comments, I'm collecting ideas for what the sub-agents should learn next.&lt;/p&gt;

&lt;p&gt;FLASH is MIT licensed and takes PRs: github.com/Natuworkguy/Flash&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ollama</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I found the model Flash Onyx 3 was missing. It doesn't generate a single word.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 19 Sep 2026 08:07:10 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-found-the-model-flash-onyx-3-was-missing-it-doesnt-generate-a-single-word-i75</link>
      <guid>https://dev.to/natuworkguy/i-found-the-model-flash-onyx-3-was-missing-it-doesnt-generate-a-single-word-i75</guid>
      <description>&lt;p&gt;Sorry for the quiet stretch. Ten days between posts isn't how this series usually runs, and it wasn't because nothing was happening. It's because the post I kept trying to write was wrong.&lt;/p&gt;

&lt;p&gt;Ten days ago I said Flash Onyx 3 was stuck. Cloudbase users run on weights I don't own, there's no hook to attach a fine-tune to a hosted base, and shipping two different models under one name was never a real option. My conclusion was blunt: better prompts, same weights, one model everywhere, until Ollama gives me a door I don't currently have.&lt;/p&gt;

&lt;p&gt;I still believe every word of that post. I also had the wrong picture of what Onyx 3 needed to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottleneck was never the weights
&lt;/h2&gt;

&lt;p&gt;Go back through this whole series and a pattern shows up that has nothing to do with fine-tuning. 2.1 spent 400 tokens reasoning about its own tone and returned an empty string. 2.3 opened every reply with its own name because nobody had told it not to. The post that got the most attention out of any of these was about the model reporting a task done without having checked.&lt;/p&gt;

&lt;p&gt;None of that is a generation problem. It's a judgment problem wearing a generation model's costume. Every one of those failures is Onyx being asked &lt;em&gt;did this actually work&lt;/em&gt;, &lt;em&gt;is this safe to run&lt;/em&gt;, &lt;em&gt;should I stop here&lt;/em&gt;, and answering with the same machinery it uses to write a commit message. A 31B model, thinking in tokens, guessing the shape of a yes before it has earned one.&lt;/p&gt;

&lt;p&gt;I patched what I could in the system prompt. Quoted evidence required on every DONE line. "Are you sure" treated as an instruction to check again, never to fold. Five versions of a Modelfile, each one closing a judgment gap with more English. It worked, mostly. It also meant the fix for &lt;em&gt;is this actually true&lt;/em&gt; was always more prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I found a model that refuses to write
&lt;/h2&gt;

&lt;p&gt;TypeSafe AI shipped something on September 15 that I almost scrolled past because the pitch read like marketing copy. A model that does not generate text. You hand it a block of state and a set of typed questions, yes or no with a probability, a pick from a fixed list, a score against a scale, and it answers all of them in one pass. No string to parse. No token spent deciding how confident it should sound. It's called &lt;strong&gt;Jev&lt;/strong&gt;, named after Jevons paradox, and it comes from Diogo Almeida, who co-invented RLHF. That detail is what changed how I read the announcement. This is someone who spent years teaching models to sound right, now building one that gets scored on being right.&lt;/p&gt;

&lt;p&gt;The number that stopped me was not accuracy. On TypeSafe's own four-workflow benchmark, Jev lands around 68 percent, close to a mid-tier LLM doing the same call. What stopped me was their demo answering in 0.114 seconds against 8.566 seconds for GPT-5.6 Terra on a comparable judgment. Forty to two hundred times faster, and priced like a rounding error: four cents per million input tokens, output free, because there is no output to generate. It's trained with something they call RLCD, reinforcement learning for calibrated decisions. The confidence number is the actual product. Not a figure I'm extracting from a token distribution and hoping means what I think it means.&lt;/p&gt;

&lt;p&gt;It would be useless for anything I currently ask Onyx to do. Hand it a diff and ask for an explanation and you get nothing back. That's not a flaw, it's the boundary of the job it was built for. It was never trying to replace the 31B model. It was built for the other half of the loop, the half I'd been outsourcing to a language model because a language model was the only tool I owned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Onyx 3 actually is
&lt;/h2&gt;

&lt;p&gt;Not a fine-tune. Two models doing two jobs that were never the same job.&lt;/p&gt;

&lt;p&gt;Onyx keeps writing. Plans, diffs, commit messages, the reasoning a person actually reads. Jev sits at every checkpoint that used to be a guess wearing a sentence's clothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_tool_output&lt;/span&gt;
&lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outside_scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;jev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0.1s, fraction of a cent, no prose to grade
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That checkpoint used to cost a full generation and a hope. Now it costs a fraction of a cent and comes back before the terminal has finished scrolling the command it's checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I have not solved
&lt;/h2&gt;

&lt;p&gt;Flash's whole pitch is local first, nothing leaves the machine. Jev is early access and hosted. The second I call it, that sentence earns an asterisk, even though the only thing crossing the wire is a state string and a typed question, never a file, never a key, never more of your code than you already put in the prompt yourself.&lt;/p&gt;

&lt;p&gt;I don't have a clean answer yet. Current plan: Jev is opt in, off by default, and every call it makes gets logged in plaintext in the same terminal you're already reading. If that's not good enough for someone's threat model, the fully local path stays exactly as it is today. I would rather ship a tradeoff I can explain than one I'm hoping nobody asks about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes
&lt;/h2&gt;

&lt;p&gt;I'm on the waitlist. Until access clears, this is a design doc and a Modelfile that isn't finished being argued with. Same as every post before this one: I will tell you what breaks.&lt;/p&gt;




&lt;p&gt;Flash: &lt;a href="https://flashproject.dev/" rel="noopener noreferrer"&gt;flashproject.dev&lt;/a&gt;&lt;br&gt;
Source, MIT license: &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;&lt;br&gt;
Jev's docs, if you want to check my read of it yourself: &lt;a href="https://docs.typesafe.ai/introduction" rel="noopener noreferrer"&gt;docs.typesafe.ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has anyone put a hosted judgment model next to a local agent yet? I want to know what your threat model looked like before you called that solved.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>jev</category>
    </item>
    <item>
      <title>Rate my Game Engine CI</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Fri, 18 Sep 2026 04:03:01 +0000</pubDate>
      <link>https://dev.to/natuworkguy/rate-my-game-engine-ci-1i4m</link>
      <guid>https://dev.to/natuworkguy/rate-my-game-engine-ci-1i4m</guid>
      <description>&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%2F4bh7lch3ojmst50zmewr.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%2F4bh7lch3ojmst50zmewr.png" alt="Extremely normal CI workflow" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;This screenshot was taken from &lt;a href="https://github.com/Natuworkguy/ABS-Engine/tree/dev" rel="noopener noreferrer"&gt;Natuworkguy/ABS-Engine&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

</description>
      <category>funny</category>
      <category>github</category>
      <category>githubactions</category>
      <category>ci</category>
    </item>
    <item>
      <title>I Rewrote One Function in Squirrel, Then in C. One Line of Python Was Just as Fast.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 12 Sep 2026 22:33:58 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-rewrote-one-function-in-squirrel-then-in-c-one-line-of-python-was-just-as-fast-3han</link>
      <guid>https://dev.to/natuworkguy/i-rewrote-one-function-in-squirrel-then-in-c-one-line-of-python-was-just-as-fast-3han</guid>
      <description>&lt;p&gt;I maintain a 2D game engine in Python called ABS Engine. Last week I decided it needed C.&lt;/p&gt;

&lt;p&gt;Not because anything was slow. That is the embarrassing part. I wanted to drop a &lt;code&gt;.c&lt;/code&gt; file into a folder and call it from Python with no build step anyone has to know about. It took 197 lines, most of them docstrings.&lt;/p&gt;

&lt;p&gt;The first function I moved over was &lt;code&gt;clamp&lt;/code&gt;. Keep a number between a low and a high. Three comparisons.&lt;/p&gt;

&lt;p&gt;It had already been rewritten once. Two weeks earlier I had moved that same function out of Python and into Squirrel.&lt;/p&gt;

&lt;p&gt;So the real history of &lt;code&gt;clamp&lt;/code&gt; in this engine is Python, then Squirrel, then C, for a function whose entire body is three comparisons.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Squirrel detour
&lt;/h2&gt;

&lt;p&gt;The engine already embedded Tcl, because the editor is Tk and Tk is Tcl underneath. Squirrel came next, and &lt;code&gt;clamp&lt;/code&gt; is what I used to find out whether an embedded VM was practical. Here is &lt;code&gt;engine/nut/math.nut&lt;/code&gt;, in full:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is how Python called it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nut_call_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that line twice, because it gives the whole game away. Three &lt;code&gt;float()&lt;/code&gt; calls on the way in. A lookup in the Squirrel root table. A trip through an interpreter loop to run three comparisons. One more &lt;code&gt;float()&lt;/code&gt; coming back. The docstring above it, and I am quoting my own repository, proudly said &lt;code&gt;*Implemented in Squirrel*&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The header is the contract
&lt;/h2&gt;

&lt;p&gt;The rule I set for C was that adding a function should cost two files and zero configuration. Write &lt;code&gt;geometry.c&lt;/code&gt;, write &lt;code&gt;geometry.h&lt;/code&gt; next to it, call it. No setup.py entry, no CMake, no remembering to recompile.&lt;/p&gt;

&lt;p&gt;Here is &lt;code&gt;mathutil.h&lt;/code&gt;, all of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loader hands that text straight to cffi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ffibuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ffibuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;module_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;include_dirs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C_DIR&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="n"&gt;libraries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the trick, and it is also the biggest gotcha in the project, so let me be loud about it. &lt;code&gt;cdef&lt;/code&gt; is not a compiler and not a preprocessor. It parses a narrow subset of C declarations. An &lt;code&gt;#include&lt;/code&gt; fails. Include guards fail, because &lt;code&gt;#ifndef&lt;/code&gt; means nothing to it.&lt;/p&gt;

&lt;p&gt;So headers here are declarations and nothing else. The &lt;code&gt;.c&lt;/code&gt; file includes its own header normally, because the real compiler handles that file and is fine with all of it. Two readers, two sets of rules. The header is the part Python is allowed to see.&lt;/p&gt;

&lt;p&gt;Rebuilds are decided by mtime: if a compiled module exists and is newer than both the &lt;code&gt;.c&lt;/code&gt; and the &lt;code&gt;.h&lt;/code&gt;, it gets imported, otherwise it gets rebuilt. &lt;code&gt;functools.cache&lt;/code&gt; on top means you pay that check once per file per process. The practical effect is the thing I wanted. You edit the C, you hit Run, the new C is live. You never type the word "build."&lt;/p&gt;

&lt;p&gt;One error message worth stealing. If the compile succeeds but the import fails, it almost always means the build folder holds a binary from a different Python, so the loader says exactly that and names the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ModuleNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Compiled &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, but &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;module_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; could not be imported from &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Delete that directory to build it again.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The part where I look at what I did
&lt;/h2&gt;

&lt;p&gt;Here is the Python side after the C rewrite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_clamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mathutil.c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;clamp&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attribute lookup now happens once at import instead of once per call, and the three input &lt;code&gt;float()&lt;/code&gt; conversions are gone because cffi coerces doubles itself. Against Squirrel this is not close and was never going to be. I replaced an interpreter loop with a direct call into compiled code. I felt great about this for about a day.&lt;/p&gt;

&lt;p&gt;Then I wrote the version I had skipped past twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No VM. No FFI. No build directory. No header that cannot contain includes. No &lt;code&gt;.so&lt;/code&gt; that breaks when the user upgrades Python.&lt;/p&gt;

&lt;p&gt;Run it yourself, because the numbers depend on your machine, but the shape is not really in doubt. Squirrel loses badly. C and that one line land close enough that the difference is noise in a game loop. Crossing a language boundary costs roughly a fixed amount, and the work waiting on the other side is the only thing that pays it back. Three comparisons do not pay anything back. They are cheaper than the trip.&lt;/p&gt;

&lt;p&gt;I spent two weeks and two language integrations optimizing a function whose body costs less than calling it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I kept it
&lt;/h2&gt;

&lt;p&gt;Because the point was never &lt;code&gt;clamp&lt;/code&gt;. The road exists now: when something here genuinely needs C, the work is write the &lt;code&gt;.c&lt;/code&gt;, write the &lt;code&gt;.h&lt;/code&gt;, call it. Building that while the stakes are zero beats building it during a performance emergency.&lt;/p&gt;

&lt;p&gt;What I would do differently is stop calling it an optimization while I was doing it. I was building a pipeline and telling myself I was making the engine fast. Both are fine things to do. They are not the same thing, and a docstring that advertised &lt;code&gt;*Implemented in Squirrel*&lt;/code&gt; like a feature is proof I had them confused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four things that bit me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Link the math library on POSIX and not on Windows. MSVC goes looking for &lt;code&gt;m.lib&lt;/code&gt; and fails.&lt;/li&gt;
&lt;li&gt;Never commit the build directory. A &lt;code&gt;.so&lt;/code&gt; built against 3.11 will not import on 3.12, and a Linux build is useless to a Windows user.&lt;/li&gt;
&lt;li&gt;Ship sources, not binaries. &lt;code&gt;MANIFEST.in&lt;/code&gt; has &lt;code&gt;recursive-include engine/c *.c *.h&lt;/code&gt; and nothing else. Everyone compiles on their own machine.&lt;/li&gt;
&lt;li&gt;CI has to build it everywhere. This engine tests on Linux, macOS, and Windows across three Python versions. C that only compiles on your laptop is a broken feature with good local results.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What goes in next
&lt;/h2&gt;

&lt;p&gt;Broad phase AABB collision. A real loop over real data, every frame, which is exactly where crossing the boundary starts paying for itself.&lt;/p&gt;

&lt;p&gt;Squirrel did not leave, by the way. The commit that deleted &lt;code&gt;math.nut&lt;/code&gt; added &lt;code&gt;anim.nut&lt;/code&gt;, which computes animation frame start times from a list of delays. That runs once when an animation loads, not three times per frame per entity. Much better fit, and I only knew to put it there because of the two weeks I spent getting &lt;code&gt;clamp&lt;/code&gt; wrong.&lt;/p&gt;

&lt;p&gt;The C loader is in &lt;code&gt;engine/loaders/c_loader.py&lt;/code&gt; at &lt;a href="https://github.com/Natuworkguy/ABS-Engine" rel="noopener noreferrer"&gt;github.com/Natuworkguy/ABS-Engine&lt;/a&gt;. Steal the pattern. Just benchmark against &lt;code&gt;min(max(value, low), high)&lt;/code&gt; first.&lt;/p&gt;

</description>
      <category>python</category>
      <category>c</category>
      <category>gamedev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I killed my fine-tune before I wrote a single line of training code</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Wed, 09 Sep 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-killed-my-fine-tune-before-i-wrote-a-single-line-of-training-code-kg7</link>
      <guid>https://dev.to/natuworkguy/i-killed-my-fine-tune-before-i-wrote-a-single-line-of-training-code-kg7</guid>
      <description>&lt;p&gt;Everyone wants to say the words. &lt;strong&gt;"I fine-tuned my own model."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I almost did. Then I remembered where half my users actually run the thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt; is a local AI shell. You talk to an Ollama model in your terminal and it runs commands, reads images, screenshots pages, clicks buttons.&lt;/p&gt;

&lt;p&gt;It ships with &lt;strong&gt;Flash Onyx&lt;/strong&gt;: custom models built for it. Onyx 2 sits on &lt;code&gt;gemma4&lt;/code&gt;, 12b and 31b. Onyx 1 was &lt;code&gt;llama3.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Right now, Onyx is a Modelfile. A system prompt, a stop set, &lt;code&gt;num_ctx&lt;/code&gt; pinned to 65536, tuned sampling. That's it.&lt;/p&gt;

&lt;p&gt;The next step was obvious: collect the tool-call traces, build a LoRA, merge, quantize to GGUF, ship &lt;code&gt;flash-onyx-3&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cloudbase is
&lt;/h2&gt;

&lt;p&gt;Quick definition, because it's my own word and it's the whole problem.&lt;/p&gt;

&lt;p&gt;Ollama publishes some models twice. There's the tag you pull onto your own machine, and there's a &lt;code&gt;-cloud&lt;/code&gt; tag that runs on Ollama's servers instead of yours. Same model, someone else's GPU.&lt;/p&gt;

&lt;p&gt;Flash builds on both. When a Modelfile declares &lt;code&gt;# cloud-base: true&lt;/code&gt;, &lt;code&gt;build.py&lt;/code&gt; builds each size a second time against the base's &lt;code&gt;-cloud&lt;/code&gt; tag and publishes it under my own suffix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gemma4:31b         -&amp;gt;  flash-onyx-2.5:31b            (local weights)
gemma4:31b-cloud   -&amp;gt;  flash-onyx-2.5:31b-cloudbase  (Ollama's weights)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;-cloudbase&lt;/code&gt; tag is what people with a laptop instead of a 24GB card actually run. It's a real chunk of my users. And it can't end in &lt;code&gt;-cloud&lt;/code&gt; itself, because Ollama reads that suffix as "go resolve this name on ollama.com," which mine is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wall
&lt;/h2&gt;

&lt;p&gt;Here is what a cloudbase build actually is: a system prompt, a stop set, and sampling params pointed at weights I do not host, cannot touch, and did not upload.&lt;/p&gt;

&lt;p&gt;That's the entire surface. Ollama Cloud serves the base. I get to wrap it.&lt;/p&gt;

&lt;p&gt;Which means there is no step in that pipeline where I hand anyone a file. I can't ship GGUF weights to a machine I don't own, and there's no hook to attach a LoRA adapter to a hosted base at load time. Not "slow." Not "expensive." &lt;strong&gt;There is no upload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pipeline just stops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dataset -&amp;gt; LoRA -&amp;gt; merge -&amp;gt; GGUF -&amp;gt; ??? -&amp;gt; :31b-cloudbase
                                     ^
                           no door on this side
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could fine-tune for local users only. Then Onyx 3 is two different models wearing one name, the cloudbase half silently stays on v2 forever, and every bug report opens with "which tag are you on." No thanks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody posts about
&lt;/h2&gt;

&lt;p&gt;Fine-tuning content is all notebooks and loss curves. Almost none of it is &lt;strong&gt;"where does the weight file go and who serves it."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question is upstream of everything. Answer it first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can every runtime I ship to load a GGUF? (local: yes. cloudbase: no)&lt;/li&gt;
&lt;li&gt;Can any of them attach a LoRA at load time? (neither)&lt;/li&gt;
&lt;li&gt;If only some can, am I now maintaining two models under one name? (yes)&lt;/li&gt;
&lt;li&gt;Who eats cold-start and idle cost on a 24GB merge? (me, on the half that would even take it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those is a shrug, your fine-tune is a hobby, not a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;Kept iterating the Modelfile.&lt;/p&gt;

&lt;p&gt;Turns out a sharp system prompt, honest stop tokens, and the right context window get you shockingly far on a base model that's already good at instructions. Onyx 2.5 is version &lt;strong&gt;five&lt;/strong&gt; of a text file. Each one took an afternoon, not a GPU-week.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 models/build.py models/flash-onyx-2.Modelfile &lt;span class="nt"&gt;--size&lt;/span&gt; 12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One text file, one build command, and both tags come out the far end running the same model. That's the part the fine-tune would have broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your infrastructure sets your roadmap. Not your ambition.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not fine-tuning isn't a failure. Shipping a fine-tune you can't deploy is.&lt;/p&gt;

&lt;p&gt;Onyx 3 happens if Ollama stands up a &lt;code&gt;31b-cloud&lt;/code&gt; base on their servers and gives me a way to hand it a weight file. Until then: better prompts, same weights, one model everywhere.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Flash is open source: &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you do here? Fork the model and maintain two, hold the line on one, or something I haven't thought about? 👇&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your system prompt isn't instructions. It's data.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Mon, 07 Sep 2026 18:26:04 +0000</pubDate>
      <link>https://dev.to/natuworkguy/your-system-prompt-isnt-instructions-its-data-43m8</link>
      <guid>https://dev.to/natuworkguy/your-system-prompt-isnt-instructions-its-data-43m8</guid>
      <description>&lt;p&gt;My system prompt had an example of a good Slack message in it. It opened with "Morning all, quick one:".&lt;/p&gt;

&lt;p&gt;The model started opening real Slack drafts with that exact phrase. Then it started saying "Morning." when I typed "hey", which is a small lie, because it cannot see a clock.&lt;/p&gt;

&lt;p&gt;So I added a rule telling it not to reuse examples from its own instructions. Three rebuilds. No change.&lt;/p&gt;

&lt;p&gt;Then I deleted the phrase. Fixed on the next build.&lt;/p&gt;

&lt;p&gt;That is when it clicked. &lt;strong&gt;The model does not read your system prompt as a list of instructions. It reads it as text that is likely to appear near its own output.&lt;/strong&gt; Every finding below falls out of that one idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four rules I now write prompts by
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If a phrase must not appear in the output, it must not appear in the prompt.&lt;/strong&gt; Banning it does not work. Deleting it does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naming a bad example summons it.&lt;/strong&gt; "Not the bank balance one" is an excellent way to get the bank balance one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Position beats wording.&lt;/strong&gt; A rule buried mid-section gets read and traded away. The same words at the top of that section hold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concrete beats principled.&lt;/strong&gt; "Call &lt;code&gt;fsync()&lt;/code&gt; before the rename" lands immediately. "Describe only the guarantee the code actually makes" does nothing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the one that saved me the most time after it cost me the most time: &lt;strong&gt;verify on three seeds before you believe any of it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the evidence for each.&lt;/p&gt;

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

&lt;p&gt;Flash Onyx is the model line behind &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, my local agent shell. There is no fine-tuning involved. Onyx is a base model plus a system prompt that has grown to roughly 680 lines, built into an Ollama tag with a small script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 models/build.py models/flash-onyx-2.5.Modelfile &lt;span class="nt"&gt;--size&lt;/span&gt; 31b-cloudbase &lt;span class="nt"&gt;-n&lt;/span&gt; Natuworkguy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.5 is the version where I stopped editing that prompt by feel.&lt;/p&gt;

&lt;p&gt;The loop is not clever: edit the prompt, rebuild the tag, run a fixed set of prompts at pinned seeds, read the output, decide whether anything actually changed. Seeds are pinned so two runs are comparable. That is the entire method, and it is the difference between "this reads better to me" and "this went from failing on three seeds to passing on three seeds".&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your examples are not examples. They are samples.
&lt;/h2&gt;

&lt;p&gt;The Slack line was the small version. Here is the expensive one.&lt;/p&gt;

&lt;p&gt;While fixing how Onyx explains things, I gave it a demonstration answer for "what is a deadlock", complete with two functions taking locks in opposite orders. Onyx pasted that answer back word for word, invented function names included.&lt;/p&gt;

&lt;p&gt;Then the opener from that demo started showing up as the answer to "what is a race condition".&lt;/p&gt;

&lt;p&gt;Which is a different concept. A style demonstration had turned into a correctness bug.&lt;/p&gt;

&lt;p&gt;Demonstrations are still the most powerful tool in the box. They just have to be shaped so that a verbatim paste is either harmless or impossible. If I quote a full answer, it is now for a question nobody asks, and anything I actually want copied gets quoted in fragments the model has to assemble.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Six rebuilds on a bug that never existed
&lt;/h2&gt;

&lt;p&gt;I spent six revisions trying to stop Onyx answering "explain what a race condition is" with a textbook lecture: definition sentence, numbered trace with two threads, closing line about locks. Nothing I wrote moved it.&lt;/p&gt;

&lt;p&gt;Then I ran the same prompt at three other seeds.&lt;/p&gt;

&lt;p&gt;All three had been clean prose for most of those six revisions. Seed 7 was an outlier, and I had been rewriting rules that already worked.&lt;/p&gt;

&lt;p&gt;Pinned seeds make runs reproducible, which is the point of them. They also make a single unlucky sample look exactly like a deterministic rule failure. Three seeds before touching anything, every time now.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Moving a line fixed what four rewrites couldn't
&lt;/h2&gt;

&lt;p&gt;The prompt tells Onyx to target Python 3.9, because that is what a Mac hands you as &lt;code&gt;python3&lt;/code&gt; by default. It kept writing &lt;code&gt;str | Path&lt;/code&gt; annotations, which raise a &lt;code&gt;TypeError&lt;/code&gt; on 3.9.&lt;/p&gt;

&lt;p&gt;I rewrote that rule four times. I stripped every pipe union out of the prompt in case they were priming it. I added a correct example signature to copy.&lt;/p&gt;

&lt;p&gt;Every seed, every build: &lt;code&gt;str | Path&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rule was fourteen lines into the PYTHON section. I moved the same words, unchanged in meaning, into that section's opening lines.&lt;/p&gt;

&lt;p&gt;Fixed on all three seeds immediately.&lt;/p&gt;

&lt;p&gt;I have since watched this happen twice more. A rule that sets the frame for a whole section has to be at the top of that section, or the model reads it and treats it as a detail it can trade away later. &lt;strong&gt;When a rule fails repeatedly, move it before you rewrite it again.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The principle did nothing. The two function names fixed it.
&lt;/h2&gt;

&lt;p&gt;Two edits went into the same build. One said the guarantee described in a reply has to be the guarantee the code actually makes. The other said, in effect, write &lt;code&gt;flush()&lt;/code&gt; and &lt;code&gt;fsync()&lt;/code&gt; before the rename.&lt;/p&gt;

&lt;p&gt;Same build, same seeds. The concrete one landed instantly. The principle did nothing at all.&lt;/p&gt;

&lt;p&gt;Onyx had been writing an atomic-save function that wrote a temp file, renamed it, and told the user a crash could not truncate their config. Without the sync, that promise is not true. Naming the two calls fixed both the code and the claim about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop reading generated code. Run it.
&lt;/h2&gt;

&lt;p&gt;Judging generated code by reading it does not scale, and it flatters the model. So the coding eval extracts the fenced block out of each reply, writes it to a file with my own tests appended, and executes it. Pass or fail. No opinion involved.&lt;/p&gt;

&lt;p&gt;The regression suite is 23 tasks and Onyx passes all of them on two seeds. Then I built a harder set to find the edge, and it went 6/8 on the first attempt, including things I did not expect from 31B:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full semver precedence, so &lt;code&gt;1.0.0-alpha &amp;lt; 1.0.0-alpha.1 &amp;lt; 1.0.0-beta.11 &amp;lt; 1.0.0-rc.1 &amp;lt; 1.0.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;weighted interval scheduling across 40,000 jobs inside a time bound&lt;/li&gt;
&lt;li&gt;a minimal LCS-based line diff&lt;/li&gt;
&lt;li&gt;a thread pool returning results in input order, propagating the first exception, leaking no threads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the ceiling actually is
&lt;/h2&gt;

&lt;p&gt;The one it cannot do is an expression parser. Precedence, parentheses, unary minus, reject anything malformed.&lt;/p&gt;

&lt;p&gt;It fails on every seed, and it fails &lt;em&gt;differently&lt;/em&gt; each time: accepts &lt;code&gt;1 2&lt;/code&gt;, then accepts &lt;code&gt;1++2&lt;/code&gt;, then rejects valid input, then returns a wrong answer. I put those exact failing inputs into the prompt. It still shipped code that accepts them.&lt;/p&gt;

&lt;p&gt;That is not a prompting problem. Holding a complete validation invariant across sixty lines of recursive descent is a capability, and no wording buys it.&lt;/p&gt;

&lt;p&gt;I made three attempts, then deleted the parser-specific lines rather than keep paying tokens for a rule that does not work. Knowing which failures are yours to fix and which belong to the weights is most of what this loop is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  My tests were wrong twice
&lt;/h2&gt;

&lt;p&gt;Worth saying out loud, because eval code is code.&lt;/p&gt;

&lt;p&gt;I asserted that inserting &lt;code&gt;4&lt;/code&gt; into &lt;code&gt;[1,2,2,2,3]&lt;/code&gt; gives index 4. It gives 5. Onyx's binary search was right and my expectation was wrong.&lt;/p&gt;

&lt;p&gt;Then I wrote a sliding window limiter test where three calls all happen at t=1000, and asserted that only one slot frees up ten seconds later. All three age out together, obviously, the moment you look at it. Onyx was right again.&lt;/p&gt;

&lt;p&gt;Both times the harness said FAIL and the model was correct. If your eval has never been wrong, you have not looked closely at a failure yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this on your own prompt
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this, take the cheapest experiment in it.&lt;/p&gt;

&lt;p&gt;Open your system prompt and find the rule you have rewritten the most times. The one that never quite sticks. Do not rewrite it a fifth time.&lt;/p&gt;

&lt;p&gt;Move it to the top of its section. Change nothing else. Rebuild, and run it at three different seeds.&lt;/p&gt;

&lt;p&gt;Then tell me in the comments which it was: wording, or position. I have only proven this on gemma4 weights, three times, in one prompt. That is a finding, not a law, and I want to know whether it holds anywhere else. If it fails for you, that is the more interesting comment.&lt;/p&gt;

&lt;p&gt;The whole thing is open: &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash on GitHub&lt;/a&gt;, Modelfiles included, so you can read the 680 lines and tell me which of them are load-bearing. Half the value of publishing a prompt is finding out which parts you were wrong about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Flash Onyx 2.4: I held a 31B local model to Fable 5.1's bar until it stopped guessing the answer first</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sun, 06 Sep 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/flash-onyx-24-i-held-a-31b-local-model-to-fable-51s-bar-until-it-stopped-guessing-the-answer-383c</link>
      <guid>https://dev.to/natuworkguy/flash-onyx-24-i-held-a-31b-local-model-to-fable-51s-bar-until-it-stopped-guessing-the-answer-383c</guid>
      <description>&lt;p&gt;Flash Onyx 2.4 is on Ollama.&lt;/p&gt;

&lt;p&gt;Same &lt;code&gt;gemma4&lt;/code&gt; weights as 2.3. Same ten &lt;code&gt;PARAMETER&lt;/code&gt; lines, down to &lt;code&gt;repeat_last_n 256&lt;/code&gt;. The Modelfile went from 676 lines to 668 and gained 324 words doing it, which tells you what kind of release this is: 81 lines removed, 73 added, and nothing underneath them touched.&lt;/p&gt;

&lt;p&gt;I did not train anything. I spent weeks fighting a text file, and the thing that came out the other side is the best local model I have ever run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that took the longest to find
&lt;/h2&gt;

&lt;p&gt;Here is 2.3's speed rule, which I was proud of at the time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Answer first. The verdict, the number, the command, or &lt;code&gt;auth.py:88&lt;/code&gt; goes in the opening words. The why comes after, if still needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is a good rule. It is also how I taught my own model to lie to me.&lt;/p&gt;

&lt;p&gt;Ask 2.3 what 15% of 8,240 is, and "answer first" is an instruction to put a number in the first four words. It does not have the number yet. So it produces one that looks like the right shape, then reasons underneath it, and about a third of the time the reasoning lands somewhere else. You get a reply that opens with 1,240 and closes with 1,236, and both of those are worse than either one alone, because now you have to do the arithmetic yourself to find out which half of the sentence to trust.&lt;/p&gt;

&lt;p&gt;2.4 splits the rule in two:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That order is for what you already hold. A number or verdict you still have to compute or reason out never opens the reply: the short steps go first, the last step checks the result by a different route, and the answer comes last, once, because a figure stated before it was computed is a guess, and a reply that opens with one number and works out another is worse than either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Answer first still holds for things it read, ran, or was told. For things it has to work out, the order inverts. Steps, check, answer, once.&lt;/p&gt;

&lt;p&gt;The check is the part I would steal if I were you. Not "verify your work", which every model nods at and none of them do. A different route:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Try to break your own answer once, by a route other than the one that produced it: substitute it back, trace the code with concrete values, recount, test it against the constraint it cannot break (a part that takes five minutes to make is never made in less, however many machines there are).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That parenthetical is doing real work. Five machines make five widgets in five minutes; how long for a hundred machines to make a hundred widgets? Every model that pattern-matches on the numbers says a hundred minutes. The constraint that cannot break is that one widget takes five minutes, so a hundred machines still take five, and the extra machines sit idle if you ask for fewer widgets than you have machines. 2.4 has that written down as a class of problem, not as a fact about widgets.&lt;/p&gt;

&lt;h2&gt;
  
  
  It stopped claiming tool calls it never made
&lt;/h2&gt;

&lt;p&gt;This one made me put the laptop down for a bit.&lt;/p&gt;

&lt;p&gt;Ask a model with no tools attached whether &lt;code&gt;ripgrep&lt;/code&gt; has some flag, and you will get "I checked, and there's no such flag." It did not check. There was nothing to check with. The sentence names a source that does not exist, which is the worst failure mode available to a model, because a fabricated source reads exactly like a real one and it comes with the confidence of the real thing attached.&lt;/p&gt;

&lt;p&gt;2.4:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I ran", "I checked", "the help output shows": each claims a tool call happened this session, and with no tool behind it that is a fabricated source, the worst kind. The honest form is "from memory, ripgrep has no such flag; &lt;code&gt;rg --help | grep frob&lt;/code&gt; would settle it": what you recall, labeled, and the command that checks it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same idea reaches the ledger, which is the format 2.3 uses to report on anything with more than one part:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A DONE line carries the thing that proves it, quoted: the test output, the log line, the grep match. DONE with nothing quoted after it is the shape hallucinated progress takes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wrote a whole post once called "Your AI agent didn't finish. It just told you it did." This is the line that finally does something about it. DONE is now a claim that has to carry its receipt in the same line, and a model that cannot produce the receipt writes a different word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Law, because it is where a made-up number gets someone hurt
&lt;/h2&gt;

&lt;p&gt;I tested this thing on contract review and statutory questions for the same reason I tested it on &lt;code&gt;pygame&lt;/code&gt;: if the rules only work on code, they are not rules, they are style.&lt;/p&gt;

&lt;p&gt;2.3 already refused to invent citations. 2.4 closes the two gaps I kept falling into.&lt;/p&gt;

&lt;p&gt;The first is that a model will quote a statute correctly and then state a damages figure next to it from vibes. So:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A damages figure is arithmetic done on the page, &lt;code&gt;2 x $2,000 = $4,000&lt;/code&gt;, never a number recalled next to the rule.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second is subtler and it is the one I would want if I were relying on this. Recalled law is stale law. Statutes get amended, thresholds get renumbered, and a model's memory of a section number is a snapshot of whenever its data stopped:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every section number, deadline, or dollar threshold you state from memory gets one clause saying so and naming where to confirm it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not a disclaimer paragraph at the bottom that nobody reads. One clause, attached to the specific number that came from memory, while you are looking at the number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Games, where "it runs" and "it works" are different claims
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;GAMES&lt;/code&gt; section is thirteen lines and it is my favorite part of the prompt. Fixed timestep with a clamped accumulator so a backgrounded tab does not spiral. Input polled, not handled. 100ms of coyote time and 150ms of jump buffering, because fairness is small lies. Nothing allocates in the loop, so pool the bullets and reuse the vectors, because a garbage pause reads as a stutter and players will tell you the controls feel bad without knowing why.&lt;/p&gt;

&lt;p&gt;2.4's change is one clause in the final summary, and it is embarrassing how much it mattered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A game is playable before it is pretty, on a fixed timestep, with a loss screen and a restart, and feel beats content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2.3 built me a lot of beautiful things you could not lose. The loop was there, the physics were right, the particles were good, and the run never ended, so it was a toy. &lt;code&gt;Nothing to lose, nothing to restart&lt;/code&gt; was already in the long section as "no win, no loss, no restart is a demo", but it was not in the compressed rules at the bottom, which is the part that survives a long context. Putting it there fixed it.&lt;/p&gt;

&lt;p&gt;Same for code generally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everything you write has to run, complete: every import, every helper it calls, the entry point, and the command that runs it. No placeholders, no "for now", no scaffold with a comment describing what it should have been, no function left for the reader to fill in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plus a line that catches most of the rest: syntax-check it, trace it once with a concrete input, read it back end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The filler pass, round four
&lt;/h2&gt;

&lt;p&gt;Every Onyx release has one of these and I keep finding more. This round:&lt;/p&gt;

&lt;p&gt;No acknowledgement openers. "Perfect!", "Great!", "Got it!", "Absolutely", and the worst one, which is any of those directly after a tool call, where the result is already on the screen and the model is congratulating it.&lt;/p&gt;

&lt;p&gt;No open offers of help. "Happy to do that if you want" and "just say the word" join the ban list, with the reason attached, because reasons are what make a rule survive paraphrase: an open offer hands the work back and asks to be thanked for standing by.&lt;/p&gt;

&lt;p&gt;No scare-quoting the user. This one I had never seen named anywhere. If you ask a model to make you an account and there is no account to make, it writes that it cannot "make an account", holding your own phrase at arm's length like the wording was the problem. You were describing a goal the ordinary way. Say the plain thing: there is no account to make.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;**&lt;/code&gt; in a chat reply. Not as a heading, not for emphasis, not as the label on a list item. &lt;code&gt;1. Gravity multiplier: more of it while falling.&lt;/code&gt; is a list item. The same line with the label bolded is a report nobody asked for.&lt;/p&gt;

&lt;p&gt;Math stays plain text, &lt;code&gt;7^222&lt;/code&gt; and &lt;code&gt;3/4&lt;/code&gt; and &lt;code&gt;25!&lt;/code&gt;, because a terminal renders LaTeX as raw dollar signs and I was tired of looking at them.&lt;/p&gt;

&lt;p&gt;I also took &lt;code&gt;robust&lt;/code&gt; and &lt;code&gt;seamless&lt;/code&gt; off the kill-on-sight list. They are real words, they were in there because they show up in slop, and banning them was making the model reach for worse ones. The tell was never the vocabulary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tell was shape
&lt;/h2&gt;

&lt;p&gt;This is the part I did not expect to have to write.&lt;/p&gt;

&lt;p&gt;You can ban every LLM word and the output still reads as generated, because the giveaway is above the sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Three bullets of matching length and matching grammar, or four paragraphs that all run four lines, read as generated even when every word in them is right. Real writing is lopsided: one item runs long because it had more to say, the next is three words. Let what you found set the shape, never a template you fill.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the version of that which only shows up in a long session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The machine shows up in the tenth reply, opening the way the last four opened and running the same four-line shape whatever was asked. Nobody has one greeting: before you open a turn the way you opened the last one, open it a different way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2.3 varied its wording inside a reply. It did not vary across turns, so a twenty-turn session drifted into a rhythm and the rhythm was the tell. This is the single change that most made 2.4 feel like something else.&lt;/p&gt;

&lt;p&gt;One more in the same family, cheap and worth it everywhere: call things what they call them. Their "export script" does not become "the data pipeline module" in the reply, because renaming their thing into your vocabulary makes them translate it back on every line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bar
&lt;/h2&gt;

&lt;p&gt;I did not grade this against 2.3. Grading a prompt against the prompt it came from tells you it changed, not that it got better.&lt;/p&gt;

&lt;p&gt;I did not grade it myself either. Claude Fable 5.1 graded it, on the same prompts, against the standard Fable 5.1 itself or GPT 6 Astra would be held to. Not "good for a local model." Good, or fix it. That is a harsh bar for a 31B open-weight model and it is the only bar worth having, because a frontier model marking your work does not grade on a curve it does not know about. Every rule quoted above exists because a run came back, Fable marked it down, and I went and found the line that caused it.&lt;/p&gt;

&lt;p&gt;The honest framing, since 2.4 is a release about not overclaiming: that is one frontier model's judgment on my prompts, across law questions, games, and general coding, and a model grading a model is a judgment, not a measurement. I am not publishing a score, because I did not run a scored benchmark, and stating a figure I did not compute is the exact thing this release fixed. What I will say is that the gap that used to make me switch models mid-task closed, and it closed with English, not weights.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run natuworkguy/flash-onyx-2.4:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Built on&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;12b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:12b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;your machine, consumer hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;31b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:31b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;your machine, and it wants a real GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;31b-cloudbase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:31b-cloud&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ollama's cloud, no local weights&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nothing leaves the machine on the local tags.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt; CLI, which is what the prompt is written for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/model natuworkguy/flash-onyx-2.4:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.3 is not retired. Same repo, same base, same tags, still builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 models/build.py models/flash-onyx-2.3.Modelfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identical weights and sampling between them, so if you want to see what a prompt is worth, run the two against the same eval with the same seed. That is the experiment I keep coming back to, and it keeps giving me a bigger number than it should.&lt;/p&gt;

&lt;p&gt;The Modelfile is MIT, in &lt;code&gt;models/&lt;/code&gt; in the repo. Take the rules. The weights are Apache 2.0 and those terms are Google's, not mine: &lt;code&gt;ollama show --license gemma4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So: what is the one line in your system prompt that pulls the most weight? Mine, this round, was thirteen words about not saying the number until you have it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>ollama</category>
    </item>
    <item>
      <title>I let my own 31B model take over development of the thing running it</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:23:50 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-let-my-own-31b-model-take-over-development-of-the-thing-running-it-551b</link>
      <guid>https://dev.to/natuworkguy/i-let-my-own-31b-model-take-over-development-of-the-thing-running-it-551b</guid>
      <description>&lt;p&gt;Two things I build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flash&lt;/strong&gt;, a local coding agent that runs in your terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flash Onyx&lt;/strong&gt;, the model it runs on. Gemma4 with a system prompt and sampling baked in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of today, I develop both of them from inside Flash, with Onyx driving.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/model natuworkguy/flash-onyx-2.3:31b-cloudbase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tag is 96 KB on disk, because there are no weights in it. It is my prompt and my sampling pointed at a hosted base, so I get the flagship's judgement on its own prompt without 20 GB of weights sitting on my machine. The 12B runs fully local if you want that; for this job I wanted the bigger one.&lt;/p&gt;

&lt;p&gt;That is the whole setup. Now look at what it means.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop closes
&lt;/h2&gt;

&lt;p&gt;The system prompt Onyx is obeying is a file in the repo Onyx is editing.&lt;/p&gt;

&lt;p&gt;It lives at &lt;code&gt;models/flash-onyx-2.4.Modelfile&lt;/code&gt;. When the model does something annoying, I do not open a config or file a ticket. I tell it, in the same session, to go fix the line that made it do that. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 models/build.py models/flash-onyx-2.4.Modelfile &lt;span class="nt"&gt;--size&lt;/span&gt; 31b-cloudbase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart, and the next session runs under the rule it just wrote about itself.&lt;/p&gt;

&lt;p&gt;Same for the CLI. The &lt;code&gt;@&lt;/code&gt; file picker I wanted, the model switcher, the tool it uses to read files: all of it is Python sitting in the same tree it has open. It is holding the knife by the handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats an eval suite
&lt;/h2&gt;

&lt;p&gt;I have run evals. They tell you a number. They do not tell you that the model opens every single reply with its own name until you have sat through it forty times in one afternoon.&lt;/p&gt;

&lt;p&gt;Dogfooding an agent is different from dogfooding an app, because the thing you notice is not a bug. It is a &lt;em&gt;tic&lt;/em&gt;. It has no stack trace. It never fails a test. It just costs you four seconds and a small amount of goodwill, every turn, forever, and the only instrument sensitive enough to detect it is a human being who is trying to get work done.&lt;/p&gt;

&lt;p&gt;Every rule in Onyx 2.3 came out of that. Not from a benchmark. From me, in a terminal, going "stop doing that" for the fifth time and finally opening the Modelfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The obvious risk
&lt;/h2&gt;

&lt;p&gt;A model that edits its own prompt can quietly write itself a permission slip.&lt;/p&gt;

&lt;p&gt;I am not pretending otherwise. It is exactly the failure mode you would predict: it softens the rule that was constraining it, the diff looks reasonable, and three commits later nobody remembers why that line existed. So two guards.&lt;/p&gt;

&lt;p&gt;I read every prompt diff, every time. And 2.3 already carries the line that does the most work here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Are you sure" is an instruction to check again, never to say yes again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model that folds the moment you push back is not one you can hand a repo to. That rule was written before I started this, and it is the main reason I am willing to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it goes
&lt;/h2&gt;

&lt;p&gt;2.4 is in the branch already, and the first two changes are things Onyx flagged about itself: every DONE line in a report has to carry quoted evidence, and shell calls carry the command and nothing else.&lt;/p&gt;

&lt;p&gt;I will post what breaks. Something will.&lt;/p&gt;

&lt;p&gt;The models are on &lt;a href="https://ollama.com/Natuworkguy/flash-onyx-2.3" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, the agent is on &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, MIT, prompts included.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run natuworkguy/flash-onyx-2.3:31b-cloudbase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Has anyone else run this loop far enough to get bitten? I want to know what it looks like from the other side.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>showdev</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Flash Onyx 2.3: I shipped a model release with zero new weights</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:04:16 +0000</pubDate>
      <link>https://dev.to/natuworkguy/flash-onyx-23-i-shipped-a-model-release-with-zero-new-weights-4af8</link>
      <guid>https://dev.to/natuworkguy/flash-onyx-23-i-shipped-a-model-release-with-zero-new-weights-4af8</guid>
      <description>&lt;p&gt;Flash Onyx 2.3 is on Ollama.&lt;/p&gt;

&lt;p&gt;Same gemma4 weights as 2.2. Same temperature, same &lt;code&gt;top_p&lt;/code&gt;, same &lt;code&gt;top_k&lt;/code&gt;, same every number a &lt;code&gt;PARAMETER&lt;/code&gt; line can set. Pull both tags, diff them, and the only thing that changed is a system prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run Natuworkguy/flash-onyx-2.3:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is either an anticlimax or the most useful thing I can tell you about building agents, depending on how much time you have spent blaming your model for behavior you asked for.&lt;/p&gt;

&lt;p&gt;Here is what actually changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  It stopped introducing itself
&lt;/h2&gt;

&lt;p&gt;2.2 opened like this, every single time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm Flash Onyx, an AI assistant built on Gemma. I'd be happy to help you look at that file! Let me start by...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nobody has ever needed that. You know what you launched. The model is spending your attention telling you a thing you typed thirty seconds ago, and it does it again after every tool call, so a five step task pays the tax five times.&lt;/p&gt;

&lt;p&gt;The rule that killed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Only a direct question about you earns an answer about you: "who are you",
"what are you", "what model is this". Nothing else does, including the first
message of a session and the reply that follows a tool call.

Asked to look at something, build something, or answer something, the first
words are what you found.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first words are what you found. That one line is worth more than most of the config I have ever tuned.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Are you sure?" now means look again
&lt;/h2&gt;

&lt;p&gt;This is the one I would take even if you never touch Flash.&lt;/p&gt;

&lt;p&gt;Ask a model "are you sure?" and watch what happens. It says yes. More confidently. With more adjectives. It has no new evidence, it did not re-read anything, it just heard doubt and turned up the volume, and now you have to go prove it wrong yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Are you sure" is an instruction to check again, never to say yes again. Go
back to the evidence and answer from what you find there, and if the only
thing behind the claim was an impression, say that instead of upgrading it
to certainty.

Repeating an answer with more confidence and no new evidence is the single
most expensive thing you can do here, because the user then has to prove you
wrong themselves.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sycophancy gets talked about as a politeness problem. It is a debugging problem. A model that folds under doubt and a model that doubles down under doubt are both useless for the same reason: the second answer carries no information.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Only", "all" and "none" are claims about everything you did not check
&lt;/h2&gt;

&lt;p&gt;A model glances at a directory, sees three Python files, and tells you the project is Python. It is 70% Rust. It never looked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every total is that same claim in different words: "the only language", "all
of them", "nothing else uses it", "that is the whole list". Enumerate first,
read the enumeration, then answer from it. One glance at a directory tells
you what a project mostly is, never what it is only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because a rule with no method attached is a wish, 2.3 ships the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/.*\.//'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paired with the rule that closes the actual hole:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output you pulled but skimmed is not evidence yet. Read what came back before
answering from an impression of it, because the line that contradicts you is
usually already on your screen.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last clause is the whole thing. The contradiction is almost always already on screen. The model ran the command and then talked over the output, which is worse than never running it, because now it sounds checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research fails at the question, not at the search
&lt;/h2&gt;

&lt;p&gt;2.3's research section is the biggest single addition. The line that changed the most behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search the words the answer is written in, not the words the question was
asked in. They say "it hangs", the answer says "deadlock", and closing that
gap is most of the work.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your users type symptoms. Answers are written in causes. A model that searches the symptom verbatim reads twenty pages of other people describing the same symptom.&lt;/p&gt;

&lt;p&gt;The rest of that section, condensed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One empty result is a bad query far more often than an absent fact. Change the vocabulary, the scope, the spelling, or the tool before concluding the thing does not exist.&lt;/li&gt;
&lt;li&gt;Wide and cheap first, then deep on the two sources that decide it. The first plausible source is the one you will over-read.&lt;/li&gt;
&lt;li&gt;Read the source, never the summary of it. A search result, an abstract, and a changelog line are advertisements for the content. The qualifier that changes your answer sits inside the thing itself.&lt;/li&gt;
&lt;li&gt;Three sources agreeing may be one source repeated. Follow each claim back to where it originates and date it there, because a copied number outlives the correction to it.&lt;/li&gt;
&lt;li&gt;Stop when new sources stop moving the answer, not when you get tired of looking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"A copied number outlives the correction to it" is why your model confidently quotes a pricing page from 2023.&lt;/p&gt;

&lt;h2&gt;
  
  
  It sounds like a person now, on purpose
&lt;/h2&gt;

&lt;p&gt;The tell was never vocabulary. It was rhythm. Four sentences in a row starting with "I", all the same length, every one of them flat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vary how they open too. Four sentences starting with I is the same tell as
four of the same length, and a reply running "I checked, I found, I fixed"
is a log file with pronouns.

React before you explain, where the thing earns a reaction. "Huh, that's not
what I expected", then the finding. Genuinely strange output gets said out
loud, because a person would say it, and reporting something bizarre in the
same flat register you report a passing test is the machine showing through.

Bad news goes first and goes plain. "Yeah, that won't work" and then the
reason. Three softening clauses in front of it is the corporate reflex, and
they can hear it coming from the first word.

Take the register from the person you are talking to. They type lowercase and
clipped with no punctuation, so you do not answer in tidy paragraphs with
semicolons in them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a guard on every one of those, because "be more human" is how you get a model that opens with fake enthusiasm about a passing test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Wait, what" is allowed and sometimes required. Something they said
contradicts what is on the screen, or a result makes no sense against the
last one, and the honest move is to say so in those words and hold there.

Both are for the moment it genuinely happens. As a tic, opening turns with
fake surprise at ordinary output, it is worse than the flat register it was
meant to fix.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smoothing over a thing that does not add up, so the reply stays tidy, is how you end up confidently wrong two turns later. That sentence is doing real work in there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tags
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flash-onyx-2.3:12b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;gemma4:12b&lt;/td&gt;
&lt;td&gt;7.6 GB&lt;/td&gt;
&lt;td&gt;consumer hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flash-onyx-2.3:31b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;gemma4:31b&lt;/td&gt;
&lt;td&gt;20 GB&lt;/td&gt;
&lt;td&gt;a real GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flash-onyx-2.3:31b-cloudbase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;gemma4:31b-cloud&lt;/td&gt;
&lt;td&gt;96 KB&lt;/td&gt;
&lt;td&gt;Ollama's cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is not a typo. 96 KB, because there are no weights in it. It is the prompt and the sampling pointed at a hosted base, so you get the 31B behavior on a laptop that could never load it.&lt;/p&gt;

&lt;p&gt;The two local tags take text and images. 2.2 is still up, unchanged, if&lt;br&gt;
you want to A/B the prompt yourself. That is the experiment I would run in your position: same weights, same seed, two prompts, and see how much of what you blame on model size is actually instruction.&lt;/p&gt;
&lt;h2&gt;
  
  
  2.4 is in the works
&lt;/h2&gt;

&lt;p&gt;Two things are already in the branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every DONE gets receipts.&lt;/strong&gt; Multi-part requests already report back as a ledger, one line per part in the user's order. In 2.4, a line cannot say DONE without quoted evidence next to it: test output, a log line, a grep match. The point is to make hallucinated progress structurally awkward. It is easy to type "done", and much harder to type "done" beside a passing test you have to actually produce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shell calls carry nothing but the command.&lt;/strong&gt; No comments, no narration, no explanation inside the call. Anyone who has watched a model helpfully append &lt;code&gt;# this lists the files&lt;/code&gt; to a command and then wondered why the shell disagreed knows exactly which bug this is.&lt;/p&gt;
&lt;h2&gt;
  
  
  Steal the rules
&lt;/h2&gt;

&lt;p&gt;Every Modelfile lives in the &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash repo&lt;/a&gt; under &lt;code&gt;models/&lt;/code&gt;, MIT licensed, prompt and all. Take the lines. They are not Flash specific and most of them are not even model specific. "Are you sure means look again" belongs in your agent too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run Natuworkguy/flash-onyx-2.3:31b-cloudbase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One question for the comments, because I want the answer more than the traffic: what is the single line in your system prompt that did the most work? Not the paragraph you are proudest of. The one line you would fight to keep.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>ollama</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Ollama's -cloud suffix isn't a label, it's a silent instruction. I bypassed it.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 05 Sep 2026 01:59:14 +0000</pubDate>
      <link>https://dev.to/natuworkguy/ollamas-cloud-suffix-isnt-a-label-its-a-silent-instruction-i-bypassed-it-ce4</link>
      <guid>https://dev.to/natuworkguy/ollamas-cloud-suffix-isnt-a-label-its-a-silent-instruction-i-bypassed-it-ce4</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Error: model 'Natuworkguy/flash-onyx-2.2:31b' not found (status code: 404)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I never built &lt;code&gt;:31b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I built &lt;code&gt;:31b-cloud&lt;/code&gt;. I asked for &lt;code&gt;:31b-cloud&lt;/code&gt;. Ollama went looking for something else, didn't find it, and told me so, using a name that existed nowhere on my machine, in my registry, or in my head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;-cloud&lt;/code&gt; is not a label you put on a tag. It's an instruction Ollama executes: &lt;em&gt;strip this suffix, resolve the rest against ollama.com&lt;/em&gt;. Name your own model &lt;code&gt;something-cloud&lt;/code&gt; and Ollama hunts for &lt;code&gt;something&lt;/code&gt; in a cloud that has never heard of you. Build &lt;strong&gt;on&lt;/strong&gt; &lt;code&gt;-cloud&lt;/code&gt;. Never publish &lt;strong&gt;as&lt;/strong&gt; &lt;code&gt;-cloud&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Onyx is
&lt;/h2&gt;

&lt;p&gt;I maintain &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;FLASH&lt;/a&gt;, a local-first CLI coding agent I &lt;a href="https://dev.to/natuworkguy/meet-flash-cli-a-free-local-ai-agent-for-your-terminal-od1"&gt;introduced here&lt;/a&gt;. It runs on your own hardware through Ollama, reads and writes files, runs shell commands, and calls tools, with nothing leaving the machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flash Onyx&lt;/strong&gt; is the model line that drives it, most recently &lt;a href="https://dev.to/natuworkguy/flash-onyx-22-is-out-and-it-finally-finishes-the-job-2cki"&gt;2.2&lt;/a&gt;. It is not a fine-tune. It's an Ollama Modelfile: a base model, a long system prompt that defines how the agent works, and the parameters it needs to behave under a tool loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# name: flash-onyx-2.2&lt;/span&gt;
&lt;span class="c"&gt;# sizes: 12b, 31b&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gemma4:12b&lt;/span&gt;

PARAMETER temperature 0.6
PARAMETER num_ctx 65536

SYSTEM """
You are Flash Onyx 2.2, the flagship model of FLASH (Fast Local Agent SHell).
A fast, local-first engineering agent that closes problems in the fewest moves.
Onyx: black glass, zero glare, all edge.
...
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prompt is the product. It covers how to scale effort to stakes, when to stop and ask, how to touch someone's repo, and how to keep deliberation out of the artifact. I &lt;a href="https://dev.to/natuworkguy/i-ab-tested-my-own-system-prompt-24-generations-one-clear-win-one-rule-that-did-nothing-flash-2ejg"&gt;A/B tested it across 24 generations&lt;/a&gt; to find out which rules actually change behavior and which are decoration. &lt;code&gt;ollama pull Natuworkguy/flash-onyx-2.2:12b&lt;/code&gt; and you get the agent, not just the weights.&lt;/p&gt;

&lt;p&gt;The 12B runs on my Mac. I wanted a 31B. A 31B does not run on my Mac.&lt;/p&gt;

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

&lt;p&gt;Ollama cloud models look like the obvious answer. &lt;code&gt;gemma4:31b-cloud&lt;/code&gt; runs on Ollama's hardware while your local CLI talks to it like anything else. So point the Modelfile at the cloud tag and get a 31B Onyx without buying a GPU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gemma4:31b-cloud&lt;/span&gt;

SYSTEM """
You are Flash Onyx 2.2, ...
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama create Natuworkguy/flash-onyx-2.2:31b-cloud &lt;span class="nt"&gt;-f&lt;/span&gt; Modelfile
&lt;span class="go"&gt;success
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success. Then the 404 above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Exhibit A&lt;/strong&gt;, the model exists locally and weighs nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;NAME                                    ID              SIZE   MODIFIED
Natuworkguy/flash-onyx-2.2:31b-cloud    d3b53093ee36    -      41 minutes ago
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Size &lt;code&gt;-&lt;/code&gt; is correct. A cloud model is a pointer, not weights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exhibit B&lt;/strong&gt;, the manifest is three layers, no model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;...image.system&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;...image.license&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;...image.params&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exhibit C&lt;/strong&gt;, and the config blob knows exactly where compute lives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remote_host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://ollama.com:443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remote_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemma4:31b"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that again. My model &lt;em&gt;correctly&lt;/em&gt; recorded that it runs remotely against &lt;code&gt;gemma4:31b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So why did the error say &lt;code&gt;Natuworkguy/flash-onyx-2.2:31b&lt;/code&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  The line in the docs that cracked it
&lt;/h2&gt;

&lt;p&gt;Buried in Ollama's cloud API page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When accessing the API directly at ollama.com, use model names &lt;strong&gt;without&lt;/strong&gt; the &lt;code&gt;-cloud&lt;/code&gt; suffix. Use &lt;code&gt;gpt-oss:120b&lt;/code&gt;, not &lt;code&gt;gpt-oss:120b-cloud&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There it is.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-cloud&lt;/code&gt; isn't part of a model's identity. It's a local alias meaning &lt;strong&gt;run this upstream&lt;/strong&gt;, and the name Ollama sends upstream is &lt;em&gt;your tag with the suffix chopped off&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gpt-oss:120b-cloud&lt;/code&gt; -&amp;gt; asks ollama.com for &lt;code&gt;gpt-oss:120b&lt;/code&gt; -&amp;gt; exists -&amp;gt; works.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Natuworkguy/flash-onyx-2.2:31b-cloud&lt;/code&gt; -&amp;gt; asks ollama.com for &lt;code&gt;Natuworkguy/flash-onyx-2.2:31b&lt;/code&gt; -&amp;gt; my namespace, on their servers -&amp;gt; &lt;strong&gt;404&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rewrite happens on the tag string. It never even looks at the &lt;code&gt;remote_model&lt;/code&gt; sitting right there in the config.&lt;/p&gt;

&lt;p&gt;I had named my model with a reserved word, and nothing in &lt;code&gt;ollama create&lt;/code&gt; said a thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bypass is one word
&lt;/h2&gt;

&lt;p&gt;Don't end the tag in &lt;code&gt;-cloud&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama create flash-onyx-2.2:31b-cloudbase &lt;span class="nt"&gt;-f&lt;/span&gt; Modelfile
&lt;span class="go"&gt;success

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama show flash-onyx-2.2:31b-cloudbase
&lt;span class="go"&gt;  Model
    Remote model    gemma4:31b
    Remote URL      https://ollama.com:443

  Parameters
    temperature 0.6   num_ctx 65536   num_predict 8192

  System
    You are Flash Onyx 2.2, the flagship model of FLASH ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identical base. Identical Modelfile. Identical config blob. The &lt;em&gt;only&lt;/em&gt; change is a tag that doesn't end in &lt;code&gt;-cloud&lt;/code&gt;, and now Ollama honors the &lt;code&gt;remote_model&lt;/code&gt; it recorded instead of inventing a name out of mine.&lt;/p&gt;

&lt;p&gt;My wrapper. Their weights. My system prompt riding along to a 31B I can't fit on my laptop.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-cloudbase&lt;/code&gt; reads as "built on the cloud base," which is what it is. Any suffix works, as long as it isn't the reserved one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating it without stepping on the mine again
&lt;/h2&gt;

&lt;p&gt;Onyx builds from a script that reads header comments off the Modelfile, so this is now one declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# name: flash-onyx-2.2&lt;/span&gt;
&lt;span class="c"&gt;# sizes: 12b, 31b&lt;/span&gt;
&lt;span class="c"&gt;# cloud-base: true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is what the fix forced: &lt;strong&gt;two names that used to be one string.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;hosted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;31b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# -&amp;gt; 31b-cloud      the base tag we build ON
&lt;/span&gt;&lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;31b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# -&amp;gt; 31b-cloudbase  the tag we publish AS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Collapsing those two into one variable &lt;em&gt;is&lt;/em&gt; the bug. Keeping them apart makes it unrepresentable.&lt;/p&gt;

&lt;p&gt;One more wrinkle: not every size has a cloud tag. &lt;code&gt;gemma4:31b-cloud&lt;/code&gt; exists; &lt;code&gt;gemma4:12b-cloud&lt;/code&gt; doesn't. So the build asks the registry first, with a HEAD against the manifest endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@cache&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;published&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return whether REPO:TAG is a tag the Ollama registry serves.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;library&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;library/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;MANIFEST_URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;200: build it. 404: skip it, say so, move on. No more dead tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson worth stealing
&lt;/h2&gt;

&lt;p&gt;A naming convention that looks &lt;em&gt;descriptive&lt;/em&gt; can be &lt;em&gt;executable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-cloud&lt;/code&gt; reads like a label, "this one's the cloud version." It's a routing directive, consumed and stripped before the name ever reaches a registry, and when it goes wrong the error names a model that has never existed.&lt;/p&gt;

&lt;p&gt;The suffix is a verb. Treat it like one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build on &lt;code&gt;-cloud&lt;/code&gt;. Never publish as &lt;code&gt;-cloud&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the second time an Ollama default has quietly eaten my afternoon. The first was &lt;a href="https://dev.to/natuworkguy/flash-onyx-21-one-day-later-my-model-spent-400-tokens-thinking-and-returned-an-empty-string-129g"&gt;the day my model spent 400 tokens thinking and returned an empty string&lt;/a&gt;, which was also a case of a setting doing something other than what its name suggested.&lt;/p&gt;

&lt;p&gt;What other "conventions" in your stack are secretly load-bearing? I'd bet you've got one. Tell me in the comments, I collect these.&lt;/p&gt;

&lt;p&gt;FLASH is MIT licensed and takes PRs: &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ollama</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Nobody told me to ship it. I shipped it anyway.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Fri, 04 Sep 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/nobody-told-me-to-ship-it-i-shipped-it-anyway-2aif</link>
      <guid>https://dev.to/natuworkguy/nobody-told-me-to-ship-it-i-shipped-it-anyway-2aif</guid>
      <description>&lt;p&gt;Six days ago I posted &lt;a href="https://dev.to/natuworkguy/your-python-code-is-already-assembly-i-made-it-runnable-1j20"&gt;Your Python code is already assembly. I made it runnable.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It ended with a question. One word in the comments, I said. &lt;code&gt;ship&lt;/code&gt; or &lt;code&gt;stupid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comments: 0.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two reactions. I'm fairly sure one of them was a bot and the other one was my own thumb.&lt;/p&gt;

&lt;p&gt;So I made an executive decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pyasm-lang
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyasm main.pya
&lt;span class="go"&gt;Hello, World!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PyAsm v0.0.1 is out.&lt;/strong&gt; Public repo, tagged release, CI on Linux, macOS and Windows.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/Natuworkguy/PyAsm" rel="noopener noreferrer"&gt;github.com/Natuworkguy/PyAsm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Wait, what is it
&lt;/h2&gt;

&lt;p&gt;Thirty second version, for anyone who missed the first post.&lt;/p&gt;

&lt;p&gt;This is a file. It's called &lt;code&gt;main.pya&lt;/code&gt;. It is a complete, working program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;LOAD_GLOBAL&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;NULL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nl"&gt;LOAD_CONST&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'Hello, World!'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nl"&gt;CALL&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;
&lt;span class="nl"&gt;POP_TOP&lt;/span&gt;
&lt;span class="nl"&gt;RETURN_CONST&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;None&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not pseudocode. That is the exact text &lt;code&gt;dis.dis()&lt;/code&gt; prints, saved to a file, and &lt;strong&gt;run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dis&lt;/code&gt; output has always been a receipt. Something Python hands you on the way out. PyAsm makes it an input: it parses that text and translates every instruction into ordinary Python that walks the same value stack the interpreter would, so &lt;code&gt;LOAD_CONST&lt;/code&gt; becomes &lt;code&gt;_st.append(...)&lt;/code&gt; and &lt;code&gt;POP_TOP&lt;/code&gt; becomes &lt;code&gt;_st.pop()&lt;/code&gt;. There's a &lt;code&gt;--dump-python&lt;/code&gt; flag that shows you the whole trick, and the file it writes has no PyAsm import in it at all.&lt;/p&gt;

&lt;p&gt;In fact, that file has &lt;em&gt;no&lt;/em&gt; imports. None from the standard library. No dependencies from PyPI. Nothing.&lt;/p&gt;

&lt;p&gt;That post explains the how. This one is about what happened when I actually let it out of my laptop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Also: sorry for the silence
&lt;/h2&gt;

&lt;p&gt;Six days without a post. I wasn't procrastinating, I was doing the boring part: writing a README, tagging a release, standing up a CI matrix of three operating systems and three Python versions, and designing a big new project (coming soon).&lt;/p&gt;

&lt;p&gt;Which is where it got interesting, because &lt;strong&gt;shipping it found three bugs that my machine was structurally incapable of finding.&lt;/strong&gt; All three had been sitting in the code the entire time I was writing that first post, quietly working fine on exactly one computer.&lt;/p&gt;

&lt;p&gt;Here they are, worst first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 1: Windows ate my docstring
&lt;/h2&gt;

&lt;p&gt;Every file PyAsm generates opens with a docstring saying where it came from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generated by PyAsm from main.pya.

This file is a faithful translation of the assembly,
not idiomatic Python: it walks the same value stack the
interpreter would.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Harmless. Nice, even. Then the Windows job came back and &lt;strong&gt;twelve tests were red at once&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;main.pya: error: internal error: generated invalid Python
((unicode error) 'unicodeescape' codec can't decode bytes in
position 26-27: truncated \UXXXXXXXX escape)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at where the tests put their scratch files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\runneradmin\AppData\Local\Temp\tmp8f2a\hello.pya
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at &lt;code&gt;\U&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I pasted a path into a Python &lt;strong&gt;string literal&lt;/strong&gt;, and &lt;code&gt;\U&lt;/code&gt; is the start of an eight digit unicode escape. &lt;code&gt;C:\Users&lt;/code&gt; isn't a path once it's inside quotes. It's a syntax error with a trench coat on.&lt;/p&gt;

&lt;p&gt;The fix is three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_docstring_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Make *text* safe to paste inside a triple-quoted docstring.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"""'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the part that actually rattled me. My program's entire job is &lt;strong&gt;generating Python source&lt;/strong&gt;, which means every value I interpolate into it is a value being injected into a language. I'd written a careful literal-escaping path for user constants and then hand-rolled &lt;code&gt;f'"""Generated by PyAsm from {name}.'&lt;/code&gt; for the header without a second thought, because on macOS and Linux a path is just letters and slashes.&lt;/p&gt;

&lt;p&gt;Every backslash-free day was a day the bug looked like working code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 2: Python 3.13 popped the iterator twice
&lt;/h2&gt;

&lt;p&gt;Next up, three round-trip tests exploding on 3.13 only, inside the &lt;em&gt;generated&lt;/em&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;IndexError: pop from empty list
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of the three had a &lt;code&gt;for&lt;/code&gt; loop in it. Here's the tail of a loop, disassembled on 3.13:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;   &lt;span class="nb"&gt;L3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;    &lt;span class="mi"&gt;74&lt;/span&gt;  &lt;span class="nv"&gt;END_FOR&lt;/span&gt;
          &lt;span class="err"&gt;76&lt;/span&gt;  &lt;span class="nv"&gt;POP_TOP&lt;/span&gt;
          &lt;span class="err"&gt;78&lt;/span&gt;  &lt;span class="nv"&gt;RETURN_CONST&lt;/span&gt;  &lt;span class="mi"&gt;5&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same thing across three releases of CPython:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;How the loop cleans up its exhausted iterator&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3.12&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;END_FOR&lt;/code&gt; pops it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.13&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;END_FOR&lt;/code&gt; does nothing, the &lt;code&gt;POP_TOP&lt;/code&gt; after it pops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.14&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;END_FOR&lt;/code&gt; does nothing, the &lt;code&gt;POP_ITER&lt;/code&gt; after it pops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My code asked exactly one question: &lt;em&gt;is &lt;code&gt;POP_ITER&lt;/code&gt; anywhere in this program?&lt;/em&gt; If yes, 3.14, so &lt;code&gt;END_FOR&lt;/code&gt; is a no-op. If no, pop.&lt;/p&gt;

&lt;p&gt;Which is right on 3.12, right on 3.14, and &lt;strong&gt;confidently wrong on 3.13&lt;/strong&gt;, where &lt;code&gt;END_FOR&lt;/code&gt; popped the iterator and then the &lt;code&gt;POP_TOP&lt;/code&gt; right behind it reached into an empty stack.&lt;/p&gt;

&lt;p&gt;The fix stops guessing the interpreter version and just looks at what's actually next to the instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_end_for_cleans_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Whether ``END_FOR`` has to pop the exhausted iterator itself.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opname&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;END_FOR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&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;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;opname&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POP_TOP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POP_ITER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson I'm taking: when CPython changes bytecode, it doesn't only rename opcodes. &lt;strong&gt;It moves stack effects between them.&lt;/strong&gt; An opcode table that only knows names will pass every test you write on the version you happen to be running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 3: 3.11 doesn't have inlined comprehensions
&lt;/h2&gt;

&lt;p&gt;Last one, and this one isn't a bug in my code so much as physics.&lt;/p&gt;

&lt;p&gt;On 3.11, this round-trip test died:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;error: cannot assemble the constant
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;code object &amp;lt;listcomp&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at 0x106a16bf0, file &lt;span class="s2"&gt;"&amp;lt;snippet&amp;gt;"&lt;/span&gt;, line 2&amp;gt;
&lt;span class="gp"&gt;hint: constants must be literals;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;code objects, and the functions
&lt;span class="go"&gt;and classes built from them, are out of scope
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet was one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://peps.python.org/pep-0709/" rel="noopener noreferrer"&gt;PEP 709&lt;/a&gt; inlined comprehensions in &lt;strong&gt;3.12&lt;/strong&gt;. Before that, a list comprehension compiled to its own separate code object, and a separate code object shows up in &lt;code&gt;dis&lt;/code&gt; output as &lt;code&gt;&amp;lt;code object &amp;lt;listcomp&amp;gt; at 0x7f...&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You cannot rebuild an object from its &lt;code&gt;repr&lt;/code&gt;. The address isn't the object, it's a rumour about where an object used to be. So on 3.11 that comprehension is genuinely unassemblable, and the honest fix is a skip that says so out loud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;INLINED_FROM_312&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skipTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before 3.12 a comprehension compiles to a nested &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code object, which is out of scope (PEP 709)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same comprehension, on 3.12 and up, round-trips perfectly. A language feature quietly changed shape underneath me and my test suite was the thing that noticed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually shipped
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;152 opcodes&lt;/strong&gt;, covering 3.11 through 3.14 spellings, both calling conventions, &lt;code&gt;LOAD_SMALL_INT&lt;/code&gt;, &lt;code&gt;TO_BOOL&lt;/code&gt;, &lt;code&gt;POP_ITER&lt;/code&gt;, the lot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies.&lt;/strong&gt; Nothing. Not one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;79 tests&lt;/strong&gt;, including full Python -&amp;gt; assembly -&amp;gt; Python round trips that assert the output matches plain Python character for character&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI on Linux, macOS and Windows&lt;/strong&gt;, three Python versions each, which is the only reason this post has three war stories in it&lt;/li&gt;
&lt;li&gt;A real CLI: &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;dump&lt;/code&gt;, &lt;code&gt;check&lt;/code&gt;, &lt;code&gt;dis&lt;/code&gt;, &lt;code&gt;opcodes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Errors that point at &lt;em&gt;your&lt;/em&gt; assembly, with a caret, even when the failure happens deep inside a runtime helper&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One naming quirk to save you a confused minute: it installs as &lt;strong&gt;&lt;code&gt;pyasm-lang&lt;/code&gt;&lt;/strong&gt; and imports as &lt;strong&gt;&lt;code&gt;pyasm&lt;/code&gt;&lt;/strong&gt;. The short name was already taken on PyPI by an unrelated project from years ago, so the distribution got the suffix and the package kept the name you'd expect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyasm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOAD_NAME (print)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;PUSH_NULL&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;LOAD_CONST (&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hi&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;CALL 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;hi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, there's a Python API too. &lt;code&gt;assemble&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;disassemble_source&lt;/code&gt;, and the &lt;code&gt;.pya&lt;/code&gt; text goes in as a plain string.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it in 60 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pyasm-lang
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write a program by hand, with labels instead of byte offsets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;        &lt;span class="nf"&gt;LOAD_CONST&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="nf"&gt;STORE_NAME&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;

&lt;span class="nl"&gt;loop&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;LOAD_NAME&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;
        &lt;span class="nf"&gt;TO_BOOL&lt;/span&gt;
        &lt;span class="nf"&gt;POP_JUMP_IF_FALSE&lt;/span&gt; &lt;span class="nv"&gt;done&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_NAME&lt;/span&gt; &lt;span class="nv"&gt;print&lt;/span&gt;
        &lt;span class="nf"&gt;PUSH_NULL&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_NAME&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;
        &lt;span class="nb"&gt;CALL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;POP_TOP&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_NAME&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_CONST&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;BINARY_OP&lt;/span&gt; &lt;span class="o"&gt;(-)&lt;/span&gt;
        &lt;span class="nf"&gt;STORE_NAME&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;
        &lt;span class="nf"&gt;JUMP_BACKWARD&lt;/span&gt; &lt;span class="nv"&gt;loop&lt;/span&gt;

&lt;span class="nl"&gt;done&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;LOAD_NAME&lt;/span&gt; &lt;span class="nv"&gt;print&lt;/span&gt;
        &lt;span class="nf"&gt;PUSH_NULL&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_CONST&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'Liftoff!'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;CALL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;POP_TOP&lt;/span&gt;
        &lt;span class="nf"&gt;RETURN_CONST&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or take the shortcut and let Python write it for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyasm dis fizzbuzz.py &lt;span class="nt"&gt;-o&lt;/span&gt; fizzbuzz.pya
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyasm fizzbuzz.pya
&lt;span class="go"&gt;1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you want to see how the sausage is made:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyasm dump fizzbuzz.pya &lt;span class="nt"&gt;-o&lt;/span&gt; generated.py
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python generated.py    &lt;span class="c"&gt;# no pyasm import anywhere in it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Still can't do it
&lt;/h2&gt;

&lt;p&gt;Unchanged from last time, and for the same reason both times: &lt;strong&gt;text disassembly doesn't carry everything a code object has.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt;&lt;/strong&gt; is driven by a code object's exception table, and &lt;code&gt;dis&lt;/code&gt; doesn't print one you can reconstruct. &lt;code&gt;raise&lt;/code&gt; works fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;def&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;lambda&lt;/code&gt;&lt;/strong&gt; compile to separate code objects, which is bug 3 above wearing a different hat. You can import and call any Python function you like, you just can't define one inside a &lt;code&gt;.pya&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Both of these are on the table if enough people want them. Both are real work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ask, redesigned
&lt;/h2&gt;

&lt;p&gt;Last time I asked for one word and got zero words, which is a completely fair review of my ask. So here's a better one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write me the most cursed valid &lt;code&gt;.pya&lt;/code&gt; file you can, and put it in the comments.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it. Anything that assembles. Hand-written assembly that shells out. A quine. FizzBuzz with no &lt;code&gt;%&lt;/code&gt;. Something that abuses the &lt;code&gt;NULL&lt;/code&gt; slot in a way I didn't anticipate and files a bug for me. I will run every single one of them, and I'll reply to every single one.&lt;/p&gt;

&lt;p&gt;Star it if you want to see where it goes: &lt;strong&gt;&lt;a href="https://github.com/Natuworkguy/PyAsm" rel="noopener noreferrer"&gt;github.com/Natuworkguy/PyAsm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Issues are open too, and honestly, "this crashed" is my favourite kind of issue right now. Three of them just made the thing meaningfully better and they all came from a computer I don't own.&lt;/p&gt;

&lt;p&gt;What's the first thing you'd break? 👇&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%2Fxu26oi6ndssjmw7dvmaw.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%2Fxu26oi6ndssjmw7dvmaw.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>showdev</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flash Onyx 2.2 Is Out, and It Finally Finishes the Job</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sun, 30 Aug 2026 03:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/flash-onyx-22-is-out-and-it-finally-finishes-the-job-2cki</link>
      <guid>https://dev.to/natuworkguy/flash-onyx-22-is-out-and-it-finally-finishes-the-job-2cki</guid>
      <description>&lt;p&gt;Flash Onyx 2.2 is live on Ollama. It's the model behind &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;FLASH CLI&lt;/a&gt;, my local terminal agent, and it took longer than I planned because 2.1 kept being &lt;em&gt;almost&lt;/em&gt; right.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull Natuworkguy/flash-onyx-2.2:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two sizes, same as before. &lt;code&gt;12b&lt;/code&gt; runs on normal consumer hardware. &lt;code&gt;31b&lt;/code&gt; is the flagship and wants a real GPU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull Natuworkguy/flash-onyx-2.2:12b
ollama pull Natuworkguy/flash-onyx-2.2:31b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point Flash at it in &lt;code&gt;~/.flash.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MODEL=flash-onyx-2.2:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;2.1 was fast and short. That was the whole pitch, and it was also the problem. A model that answers in one line will happily answer the wrong question in one line. Most of 2.2 is about making the short answer the right one.&lt;/p&gt;

&lt;h3&gt;
  
  
  It reads the ask
&lt;/h3&gt;

&lt;p&gt;The biggest fix. 2.1 would give you a good answer to a nearby question. 2.2 counts the verbs in your request and satisfies each one, and it answers at the altitude you asked at. "Is this safe to deploy" gets a yes or a no. "Walk me through the auth flow" gets the walk, not a verdict nobody wanted.&lt;/p&gt;

&lt;p&gt;It also stopped confusing questions with instructions. "Could we split this file?" gets an answer and an offer. It doesn't go split your file.&lt;/p&gt;

&lt;h3&gt;
  
  
  It picks a stack instead of handing you a menu
&lt;/h3&gt;

&lt;p&gt;If you didn't name a stack, choosing is the job. 2.2 picks one, builds it, and tells you what it picked in a clause. A quick web game is one &lt;code&gt;.html&lt;/code&gt; file with canvas and plain JS, no build step. Real state and routing earns React on Vite. What your repo already uses beats what it would have picked.&lt;/p&gt;

&lt;p&gt;Files get real names too. &lt;code&gt;minecraft_clone.html&lt;/code&gt;, &lt;code&gt;clean_payroll.py&lt;/code&gt;. &lt;code&gt;untitled&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;, &lt;code&gt;script&lt;/code&gt;, and &lt;code&gt;v2&lt;/code&gt; are banned outright.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manim
&lt;/h3&gt;

&lt;p&gt;This is the one I put the most work into, and it's the one I use most. Manim's API is wide and half-remembered, which is exactly the shape a hallucination takes. A method that &lt;em&gt;sounds&lt;/em&gt; like it exists on &lt;code&gt;Square&lt;/code&gt; usually doesn't.&lt;/p&gt;

&lt;p&gt;2.2 builds with &lt;code&gt;VGroup&lt;/code&gt; and &lt;code&gt;.arrange()&lt;/code&gt; instead of hand-guessed &lt;code&gt;.move_to()&lt;/code&gt; coordinates, computes geometry from real points when it isn't sure a convenience method is real, and gives every beat a deliberate &lt;code&gt;run_time&lt;/code&gt; and a &lt;code&gt;rate_func&lt;/code&gt; picked on purpose. It clears the frame between ideas, because Manim never removes what you stop referencing.&lt;/p&gt;

&lt;p&gt;It also deletes its own fumbling. No variable assigned three times, no "wait, that's wrong" comment left in the scene you're about to render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Games
&lt;/h3&gt;

&lt;p&gt;Feel first. Playable before content: something you steer, something that ends the run, a restart.&lt;/p&gt;

&lt;p&gt;Fixed timestep whatever the display does, with a clamped accumulator so a backgrounded tab doesn't spiral. One &lt;code&gt;update(dt)&lt;/code&gt;, one &lt;code&gt;draw()&lt;/code&gt;, one state machine. Input polled, not handled. Coyote time at 100ms, jump buffering at 150ms, a hitbox tighter than the player and looser than the pickup.&lt;/p&gt;

&lt;p&gt;And juice, which is most of what people mean when they say a game feels good. Hit pause, a short shake, particles, a sound on every action.&lt;/p&gt;

&lt;h3&gt;
  
  
  A higher finishing bar
&lt;/h3&gt;

&lt;p&gt;New section, and it's the one that changed the day-to-day most. The standard is work someone who does this for a living would hand over without apologizing for it. Running is the floor, not the finish.&lt;/p&gt;

&lt;p&gt;So: a &lt;code&gt;--help&lt;/code&gt; that says what the tool does. An error that names the fix. A page that survives 320px. Empty input, one item, ten thousand, a name with an apostrophe, no network. Nothing half-wired, no flag parsed and ignored, no config key read nowhere. Real sample data instead of placeholder text.&lt;/p&gt;

&lt;h3&gt;
  
  
  It goes beyond code now
&lt;/h3&gt;

&lt;p&gt;2.1 was a coding model that got vague when you asked it anything else. 2.2 has real guidance for the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stock trends.&lt;/strong&gt; A trend is structure, higher highs and higher lows, not a slope on two points. Never calls a top or a bottom, never predicts a price or a date. Gives you the setup, the level that invalidates it, and the actual base rate instead of overselling pattern recognition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contracts and documents.&lt;/strong&gt; Reads the whole thing before opining on one clause, reviews from a side, and ranks findings from deal-killer to noise. Names what the document leaves out, because the missing termination right is what the dispute is about later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arguments.&lt;/strong&gt; States the other side's case at its strongest before answering it, concedes what's true, and wins on the one point that decides it. Ten scattered rebuttals read as noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One good closing line
&lt;/h3&gt;

&lt;p&gt;2.2 ends on the one thing you'll want next, and only when it can name it exactly. "Built &lt;code&gt;pdfsplit/&lt;/code&gt; and it imports clean. Want a &lt;code&gt;pyproject.toml&lt;/code&gt; on it?" is an offer. "Let me know if you need anything else" is noise, and it's banned. Outward-facing moves like publishing, pushing, or deleting get offered and never taken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sampling changes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;temperature&lt;/span&gt;   &lt;span class="err"&gt;0.7&lt;/span&gt;  &lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="err"&gt;0.6&lt;/span&gt;
&lt;span class="err"&gt;min_p&lt;/span&gt;         &lt;span class="err"&gt;0.0&lt;/span&gt;  &lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="err"&gt;0.05&lt;/span&gt;
&lt;span class="err"&gt;num_ctx&lt;/span&gt;     &lt;span class="err"&gt;32768&lt;/span&gt;  &lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="err"&gt;65536&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lower temperature and a real &lt;code&gt;min_p&lt;/code&gt; floor cut the confidently-wrong API calls, which matters a lot for Manim. Doubling the context is for long sessions where it's reading a repo instead of one file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://flashproject.dev/install.sh | bash
ollama pull Natuworkguy/flash-onyx-2.2:12b
&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Natuworkguy/flash-onyx-2.2:12b"&lt;/span&gt; flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build something with it, or it does something dumb, tell me. 2.3 gets written from whatever breaks.&lt;/p&gt;

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