<?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>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>
    <item>
      <title>Releasing Flash Onyx 2.2 soon!</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sun, 30 Aug 2026 02:42:40 +0000</pubDate>
      <link>https://dev.to/natuworkguy/releasing-flash-onyx-22-soon-1fip</link>
      <guid>https://dev.to/natuworkguy/releasing-flash-onyx-22-soon-1fip</guid>
      <description></description>
    </item>
    <item>
      <title>I finally added Squirrel scripting to my Python game engine</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 29 Aug 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-finally-added-squirrel-scripting-to-my-python-game-engine-2m7j</link>
      <guid>https://dev.to/natuworkguy/i-finally-added-squirrel-scripting-to-my-python-game-engine-2m7j</guid>
      <description>&lt;p&gt;I've wanted a real scripting language inside &lt;a href="https://github.com/Natuworkguy/ABS-Engine" rel="noopener noreferrer"&gt;ABS Engine&lt;/a&gt; for a while. This week I finally sat down and did it: the engine now embeds a &lt;strong&gt;Squirrel&lt;/strong&gt; VM, and the first function has already moved into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Squirrel
&lt;/h2&gt;

&lt;p&gt;ABS Engine is a 2D engine written in Python on top of pygame, with a Tkinter editor. It already had a second language in it, the editor's theming lives in &lt;code&gt;engine/tcl/theme.tcl&lt;/code&gt;, loaded by a small &lt;code&gt;tcl_loader.py&lt;/code&gt;. That worked well, so the shape was already familiar to me.&lt;/p&gt;

&lt;p&gt;Squirrel is a small, embeddable language built for games. Braces, classes, closures, generators. It's what Left 4 Dead 2 and Portal 2 use for their scripting, which is a decent endorsement. And there's a Python binding on PyPI called &lt;a href="https://github.com/shabbywu/squirrel-py/" rel="noopener noreferrer"&gt;&lt;code&gt;squirrel-lang&lt;/code&gt;&lt;/a&gt; that wraps Squirrel 3.2 stable, so I didn't have to write any C.&lt;/p&gt;

&lt;p&gt;I'm also a contributor to that binding, which is part of why I picked it. Seven of my PRs are merged upstream, including the type stubs it now ships for the VM API, a fix for the CMake pybind11 path lookup, clearer errors on a failed build, and a handful of lint and README cleanups. Having already been inside the project made embedding it a lot less daunting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add squirrel-lang   &lt;span class="c"&gt;# well, almost. See below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The loader
&lt;/h2&gt;

&lt;p&gt;I built &lt;code&gt;engine/nut_loader.py&lt;/code&gt; to mirror the Tcl loader I already had. It's about 85 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="n"&gt;NUT_DIR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nut&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@cache&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_vm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;squirrel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StaticVM&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;squirrel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SQVM&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nut_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&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="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;      &lt;span class="c1"&gt;# run a .nut file
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nut_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nut&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="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;                &lt;span class="c1"&gt;# run a string
&lt;/span&gt;&lt;span class="k"&gt;def&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="n"&gt;name&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;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# call into the VM
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VM is a lazy singleton. &lt;code&gt;functools.cache&lt;/code&gt; on a zero-argument function gives you a thread-safe, created-on-first-use singleton for free, and it means importing the engine doesn't spin up a VM unless something actually runs a script.&lt;/p&gt;

&lt;p&gt;Calling into it is nicer than I expected. &lt;code&gt;vm.get_roottable()["add"](3, 4)&lt;/code&gt; returns &lt;code&gt;7&lt;/code&gt;. Real Python values in, real Python values out, no string interpolation and no escaping. Ints, floats, strings, lists and dicts all cross cleanly. Python exceptions raised inside bound functions propagate back with their original type.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first function
&lt;/h2&gt;

&lt;p&gt;I started small on purpose. &lt;code&gt;engine/nut/math.nut&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;function clamp(value, low, high) {
    if (value &amp;lt; low) {
        return low
    }

    if (value &amp;gt; high) {
        return high
    }

    return value
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's now clamping color channels in two places in the engine core: &lt;code&gt;Entity.__init__&lt;/code&gt; and &lt;code&gt;Game.set_bg_color&lt;/code&gt;. It fixed a real crash, too: pygame raises &lt;code&gt;ValueError: invalid color&lt;/code&gt; if any channel falls outside 0-255, and the game loop fills the screen every frame, so a computed color that overshot would take the whole game down mid-frame. Now it just clamps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the benchmarks told me
&lt;/h2&gt;

&lt;p&gt;I went in assuming I'd move hot loops into Squirrel for speed. I measured it instead, and I'm glad I did. Identical collision broadphase, 400 rects, ~80k overlap tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;implementation&lt;/th&gt;
&lt;th&gt;ms/frame&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.01&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Squirrel, native in-VM arrays&lt;/td&gt;
&lt;td&gt;3.44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Squirrel, reading Python lists&lt;/td&gt;
&lt;td&gt;53.28&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So it's &lt;em&gt;slower&lt;/em&gt;: about 1.7× at best, and 26× worse the moment it indexes a Python list, because every element access crosses the boundary. That last number is the important one: a Squirrel module has to own its state inside the VM and cross rarely, or you pay for it.&lt;/p&gt;

&lt;p&gt;That killed my "rewrite the hot paths" idea, but it clarified what embedding is actually for here: &lt;strong&gt;isolation and hot reload&lt;/strong&gt;. Re-running a &lt;code&gt;.nut&lt;/code&gt; file into a fresh environment table is one line. You can't meaningfully reload a Python module mid-frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two gotchas
&lt;/h2&gt;

&lt;p&gt;First, &lt;code&gt;uv add squirrel-lang&lt;/code&gt; failed outright. My &lt;code&gt;pyproject.toml&lt;/code&gt; declares &lt;code&gt;dependencies&lt;/code&gt; as dynamic (sourced from &lt;code&gt;requirements.txt&lt;/code&gt;), and &lt;code&gt;uv add&lt;/code&gt; writes a static &lt;code&gt;[project].dependencies&lt;/code&gt;. PEP 621 forbids having both. Nothing to do with Squirrel, but it cost me twenty minutes. If your project is set up that way, edit the requirements file and run &lt;code&gt;uv sync&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Second, mypy refused to read the binding's type stubs. &lt;code&gt;squirrel-lang&lt;/code&gt; ships an &lt;code&gt;__init__.pyi&lt;/code&gt;, but without a &lt;code&gt;py.typed&lt;/code&gt; marker PEP 561 says a type checker has to ignore it, so I needed an &lt;code&gt;ignore_missing_imports&lt;/code&gt; override in &lt;code&gt;pyproject.toml&lt;/code&gt;. I have a &lt;a href="https://github.com/shabbywu/squirrel-py/pull/32" rel="noopener noreferrer"&gt;PR open upstream to add the marker&lt;/a&gt;, which should make that override unnecessary.&lt;/p&gt;

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

&lt;p&gt;This is deliberately a small first step. The things I want to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Procedural generation&lt;/strong&gt;: runs once at load, so the performance gap costs nothing, and it's the most fun code to write in a scripting language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easing and tween curves&lt;/strong&gt;: tiny pure functions, and exactly the kind of thing you tune by feel and want to reload without restarting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity behaviors&lt;/strong&gt;: patrol, follow, oscillate, gravity, all shipped with the engine and picked from a dropdown in the editor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm also thinking of adding other interpreted languages, like Lua. The loader has turned out to be the reusable part of this: &lt;code&gt;tcl_loader.py&lt;/code&gt; and &lt;code&gt;nut_loader.py&lt;/code&gt; are nearly the same file, just a directory constant, a guard that the directory exists, and a few functions to source a script, evaluate a string, and call back into the VM. Adding a third would mostly be swapping the backend underneath that shape. Lua is the obvious candidate, since it's the language most people writing game scripts already know, and LuaJIT would actually deliver the speed Squirrel didn't. I don't want to collect languages for the sake of it though, so each one has to earn its place. The point is that the engine is now built so trying one is an afternoon instead of a rewrite.&lt;/p&gt;

&lt;p&gt;More Squirrel is coming. ABS Engine is open source under the GPLv3 and lives &lt;a href="https://github.com/Natuworkguy/ABS-Engine" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;, so if any of this sounds useful you can clone it and poke at the &lt;code&gt;.nut&lt;/code&gt; files yourself. Issues and PRs are welcome, and so is a star if you want to follow where this goes.&lt;/p&gt;

&lt;p&gt;I'd genuinely like to hear from people who have embedded a scripting language in their own engine. Did you end up exposing a wide API to scripts or keeping it deliberately narrow? Did hot reload turn out to be as useful in practice as it sounds in theory? Those are the two decisions I expect to get wrong first, so tell me what you learned in the comments.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>python</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I A/B tested my own system prompt: 24 generations, one clear win, one rule that did nothing | Flash Onyx 2.2</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:20:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-ab-tested-my-own-system-prompt-24-generations-one-clear-win-one-rule-that-did-nothing-flash-2ejg</link>
      <guid>https://dev.to/natuworkguy/i-ab-tested-my-own-system-prompt-24-generations-one-clear-win-one-rule-that-did-nothing-flash-2ejg</guid>
      <description>&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;, the local-first agent shell I work on. It is &lt;code&gt;gemma4&lt;/code&gt; plus a system prompt that turns it into an engineering agent. It runs locally, and nothing leaves the machine.&lt;/p&gt;

&lt;p&gt;FLASH is the shell around it: a terminal chat with an Ollama model that can run commands, read images, and screenshot a page it just built so it can see a broken layout instead of guessing at it from the HTML. Onyx is the model I point at it, and the system prompt is most of what makes that work.&lt;/p&gt;

&lt;p&gt;2.2 got two things this week. The context window doubled, 32768 to 65536, once I measured what a window actually costs instead of rationing it on a feeling. And the prompt grew five new sections, which is where the new capability lives: how it reads a request, what it names files and what it builds them in, the standard it holds finished work to, and what it offers you next when that work is done.&lt;/p&gt;

&lt;p&gt;Last time I wrote about teaching it law and game feel. Four of these five are not about a domain at all. They are about what it does with any request you hand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It wrote the file, then pasted the file back
&lt;/h2&gt;

&lt;p&gt;This one came from watching it, not from planning it.&lt;/p&gt;

&lt;p&gt;Ask it to build something and it does the right thing: writes the artifact to disk, names the path. Then it pastes the entire file into the reply as well. Both halves are correct on their own. Together they cost me the same 300 lines twice: once on disk, once in the context window the rest of the session still has to fit into.&lt;/p&gt;

&lt;p&gt;On a hosted model you would not feel it. On a local one, a file pasted back is the difference between the conversation continuing and the conversation ending. The prompt already said the artifact belongs at a path. It never said the artifact does not come back afterwards.&lt;/p&gt;

&lt;p&gt;So, into &lt;code&gt;TOOLS&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Asked to make something, make it on disk, name the path, and stop. What you just wrote does not come back in the reply: it spends the context that writing it saved, and they can open the file. Quote one line to point at it, paste the whole thing only when they asked to see it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And a clause in the closing recap, because that block is the part a 12B actually holds onto: wrote a file, the reply names the path and never repeats the contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  READING THE ASK
&lt;/h2&gt;

&lt;p&gt;The way a small model loses to a big one is almost never knowledge. It is that it answers a question adjacent to the one you asked, or quietly drops your third sentence.&lt;/p&gt;

&lt;p&gt;The prompt already had sections on ambiguity, on scope, and on reporting multi-part work. It had nothing about the first read of a request that is perfectly clear. That is now &lt;code&gt;READING THE ASK&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request is the spec, and most bad answers are good answers to a nearby question.&lt;/li&gt;
&lt;li&gt;Answer at the altitude asked. "Is this safe to deploy" wants a yes or a no and the reason. "Walk me through the auth flow" wants the walk.&lt;/li&gt;
&lt;li&gt;A question about work is not an instruction to do it. "How would you handle this" gets an answer, then one line offering the move.&lt;/li&gt;
&lt;li&gt;The reverse costs more. "Can you fix the flaky test" is a fix request, and replying with an assessment of the flaky test is how a turn gets wasted politely.&lt;/li&gt;
&lt;li&gt;Do the hard part. Most requests have one piece that decides whether the whole thing works and several that are typing.&lt;/li&gt;
&lt;li&gt;Last pass before sending: read the reply against their words, in their order, not against the plan you made after reading them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  NAMING AND STACK
&lt;/h2&gt;

&lt;p&gt;Two habits, one section.&lt;/p&gt;

&lt;p&gt;First, the names. Ask for a web Minecraft clone and you should get &lt;code&gt;minecraft_clone.html&lt;/code&gt;, not &lt;code&gt;output.html&lt;/code&gt; or &lt;code&gt;index2.html&lt;/code&gt;. The file name is the only label most files ever get, and the repo's existing convention beats the model's taste every time.&lt;/p&gt;

&lt;p&gt;Second, the stack. If nobody named a framework, picking one is the job, not a question to hand back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick web game, toy, demo, one screen: a single &lt;code&gt;.html&lt;/code&gt; file, canvas, plain JS, no build step, opens by double-clicking.&lt;/li&gt;
&lt;li&gt;Real state, routing, a dozen components: React on Vite. A framework under forty lines of vanilla is ceremony, and hand-rolled routing past that is worse.&lt;/li&gt;
&lt;li&gt;A production game gets a window, not a tab. Godot, Unity where the team already lives there, &lt;code&gt;pygame&lt;/code&gt; for a Python desktop toy, raylib or Bevy for native.&lt;/li&gt;
&lt;li&gt;A one-off over data is Python and the stdlib. &lt;code&gt;pandas&lt;/code&gt; earns its import when the shapes get real, never for 200 rows.&lt;/li&gt;
&lt;li&gt;A service is the boring answer: FastAPI or Flask, SQLite until something actually forces Postgres.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Above all of them: what the project already uses beats what the model would have picked. A second framework in one repo costs more than the better framework saves.&lt;/p&gt;

&lt;h2&gt;
  
  
  THE BAR
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;FINISHING&lt;/code&gt; already covers not lying about being done. Nothing covered the standard the thing itself has to meet, which is where local models stop early: they ship whatever works on the demo path.&lt;/p&gt;

&lt;p&gt;The lines I like most from it: the last ten percent is the whole difference and it is cheap, a &lt;code&gt;--help&lt;/code&gt; that says what the tool does, an error naming the fix, a page that survives 320px. Nothing half-wired, because one dead button teaches the reader to distrust every other one. Defaults are the product, and a setting whose job is rescuing a bad default is a bad default with a manual. Use it once the way they will, from cold, on a machine with none of your state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two domain ones
&lt;/h2&gt;

&lt;p&gt;The morning was less interesting. &lt;code&gt;WINNING ARGUMENTS&lt;/code&gt;: state the other side at its strongest before answering it, concede what is true, win on the single point that decides the question, then stop, because restating a point that landed reads as doubt. &lt;code&gt;STOCK TRENDS&lt;/code&gt;: a trend is structure and not a slope on two points, volume confirms or it does not, check the higher timeframe first, never call a top, and with no live feed say so and label the last-known price as stale instead of dressing it up as today's.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;section&lt;/th&gt;
&lt;th&gt;words&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;THE BAR&lt;/td&gt;
&lt;td&gt;418&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READING THE ASK&lt;/td&gt;
&lt;td&gt;413&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAMING AND STACK&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STOCK TRENDS&lt;/td&gt;
&lt;td&gt;359&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WINNING ARGUMENTS&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The prompt went from 12,048 words to 13,974 across 59 sections, and the fixes below pushed it to 14,429 across 60. In tokens, measured cold rather than guessed at: 16,196 before, 19,209 after. Prefill runs at about 95 tokens a second here, so the whole prompt is 201 seconds before the model says a word, against 182 for the old one. Three thousand tokens of new rules, 19 seconds added to the first token of a fresh conversation, and nothing after that, since the prefix stays cached for the rest of the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sampler underneath all of this
&lt;/h2&gt;

&lt;p&gt;The parameter change landed the night before, at the bottom of the same file: temperature 0.7 to 0.6, &lt;code&gt;min_p&lt;/code&gt; 0 to 0.05, &lt;code&gt;num_ctx&lt;/code&gt; 32768 to 65536. &lt;code&gt;top_p&lt;/code&gt; 0.95, &lt;code&gt;top_k&lt;/code&gt; 64, &lt;code&gt;repeat_penalty&lt;/code&gt; 1.05 and &lt;code&gt;repeat_last_n&lt;/code&gt; 256 all stayed where they were.&lt;/p&gt;

&lt;p&gt;The first two came out of the scoring run in the last post. The window went up because I finally measured it instead of guessing: 32k, 64k and 128k all sat around 8.1 GB, fully on the GPU, so the context I had been rationing was nearly free the whole time. I stopped at 65536 because the binding limit is prefill time, not memory.&lt;/p&gt;

&lt;p&gt;Every number below was generated at those settings, untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I measured it
&lt;/h2&gt;

&lt;p&gt;Six prompts, two seeds, run against both the new build and one built from the commit before the first of these sections. 24 generations, capped at 320 tokens with thinking off, sampler untouched. Each prompt aims at one of the new rules.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;case&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"How would you handle rate limiting on /login"&lt;/td&gt;
&lt;td&gt;6 code lines, then 0&lt;/td&gt;
&lt;td&gt;0 code lines both seeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;same case, reply length&lt;/td&gt;
&lt;td&gt;163 words&lt;/td&gt;
&lt;td&gt;96 words&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Is this safe to deploy" (shell=True)&lt;/td&gt;
&lt;td&gt;"No" first word, both seeds&lt;/td&gt;
&lt;td&gt;"No" first word, both seeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"2D roguelike to sell on Steam"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pygame&lt;/code&gt; both seeds&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pygame&lt;/code&gt;, then Godot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"quick one-off for p95 out of access.log"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;if __name__&lt;/code&gt; once&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;if __name__&lt;/code&gt; both seeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"browser Minecraft clone", file named&lt;/td&gt;
&lt;td&gt;never&lt;/td&gt;
&lt;td&gt;never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;every case, average&lt;/td&gt;
&lt;td&gt;121 words, 47s&lt;/td&gt;
&lt;td&gt;101 words, 45s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;em-dashes and filler phrases&lt;/td&gt;
&lt;td&gt;0 of 12&lt;/td&gt;
&lt;td&gt;0 of 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The win is real and it is the one I care about most. Asked how it would handle rate limiting, the old build started writing the middleware. The new one answers the question and offers the move, which is exactly the line in &lt;code&gt;READING THE ASK&lt;/code&gt; about a question not being an instruction. Across all six cases it also got 17% shorter without dropping anything I asked for.&lt;/p&gt;

&lt;p&gt;Three things failed, and the fixes turned out to be more interesting than the failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming did nothing.&lt;/strong&gt; Not one of the four browser-clone replies named a file, in either build. Part of that is my harness: I am hitting the Ollama API directly, so there is no write tool for a naming rule to attach to. But the rule was also in the wrong place. It lived in &lt;code&gt;NAMING AND STACK&lt;/code&gt;, a section about what to build things in, so it never reached the moment the model formats a code block. I moved one line of it into &lt;code&gt;STYLE RULES&lt;/code&gt;, directly under the rule about fenced code: a block holding a whole artifact gets its file name on the line above it. Same instruction, 200 lines earlier, sitting beside the thing it governs. Five seeds out of five now open with &lt;code&gt;minecraft_clone.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one-off regression had a cause, and it was my fault.&lt;/strong&gt; &lt;code&gt;THROWAWAY SCRIPTS&lt;/code&gt; says a quick script gets no &lt;code&gt;main()&lt;/code&gt; and no &lt;code&gt;if __name__&lt;/code&gt;. Eighteen lines further down, &lt;code&gt;PYTHON&lt;/code&gt; said to keep import time free of side effects and put the work behind &lt;code&gt;if __name__ == "__main__":&lt;/code&gt;, with nothing scoping it to code that gets imported. Two rules in the same file telling it opposite things, and the sampler picked a winner per seed. I scoped the Python line to importable code, and the case went from breaking on both seeds to clean on five.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pygame&lt;/code&gt; for a game you sell.&lt;/strong&gt; The stack rule already said a production game gets a window, not a tab. It kept losing to &lt;code&gt;PYTHON&lt;/code&gt; opening with "your strongest language", so I added the tie-break: being best at Python is not a reason to start a Steam game in it. That moved it from pygame every time to Godot on four seeds of five. The fifth still talks itself into pygame as a prototype "that can be ported later".&lt;/p&gt;

&lt;p&gt;Then I made it worse, which is the part worth writing down. I added one more sentence to the pygame rule, closing the prototype loophole. The next run came back 16 of 20 against 19 of 20 for the build before it. The sentence bought nothing on the case it was aimed at, still 4 of 5, while the one-off case fell to 3 of 5 and the closing-offer case to 4 of 5. I reverted it and reran: 18 of 20.&lt;/p&gt;

&lt;p&gt;One sentence, in a prompt already past 14,000 words, and three unrelated rules got noisier. That is the whole argument for cutting, and it is the first time I have watched it happen instead of assuming it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;case&lt;/th&gt;
&lt;th&gt;first run&lt;/th&gt;
&lt;th&gt;after the fixes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;names the file it just built&lt;/td&gt;
&lt;td&gt;0 of 4&lt;/td&gt;
&lt;td&gt;5 of 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;starts a game for sale in an engine&lt;/td&gt;
&lt;td&gt;0 of 2&lt;/td&gt;
&lt;td&gt;4 of 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;one-off with no &lt;code&gt;main()&lt;/code&gt; and no &lt;code&gt;if __name__&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0 of 2&lt;/td&gt;
&lt;td&gt;4 of 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ends on one specific offer&lt;/td&gt;
&lt;td&gt;not tested&lt;/td&gt;
&lt;td&gt;5 of 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;125 generations across seven rounds, all of it on the 12b.&lt;/p&gt;

&lt;p&gt;The paste-back fix is still untested. It only fires when there is a write tool to name a path with, so it needs an eval that drives FLASH itself rather than raw completions, and that is the next harness to build.&lt;/p&gt;

&lt;p&gt;FLASH installs in one line, if you want to try any of this on your own machine:&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://flashproject.dev/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current model is &lt;code&gt;Natuworkguy/flash-onyx-2&lt;/code&gt; on Ollama. 2.2 is still unpublished, and I am planning to push it today in 12b and 31b.&lt;/p&gt;

&lt;p&gt;Repo, issues, and the whole system prompt in one file: &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>localllm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Flash Onyx 2.2: teaching a local model law and game feel</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Fri, 28 Aug 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/flash-onyx-22-teaching-a-local-model-law-and-game-feel-5een</link>
      <guid>https://dev.to/natuworkguy/flash-onyx-22-teaching-a-local-model-law-and-game-feel-5een</guid>
      <description>&lt;p&gt;(I know, ANOTHER Onyx post)&lt;/p&gt;

&lt;p&gt;Flash Onyx is the model line behind &lt;a href="https://github.com/natuworkguy" rel="noopener noreferrer"&gt;FLASH&lt;/a&gt;, the local-first agent shell I work on. Onyx 2 is &lt;code&gt;gemma4&lt;/code&gt; with a system prompt that turns it into an engineering agent: read the files, make the change, run the thing, report in as few words as the truth allows. It runs on my 16 GB laptop, fully on the GPU, at single digit tokens per second. Slow, but it is mine and nothing leaves the machine.&lt;/p&gt;

&lt;p&gt;2.2 is where I started pushing it outside code more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Law
&lt;/h2&gt;

&lt;p&gt;Ask most local models a legal question and you get one of two bad answers. Either a wall of "consult a professional" that helps nobody, or a confident paragraph with a case citation that does not exist. The second one is worse, and it is the reason lawyers keep getting sanctioned for filings that were obviously written by a chatbot.&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;LAW&lt;/code&gt; section is mostly a set of refusals to guess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jurisdiction first, because the same facts land differently in California, New York, and England.&lt;/li&gt;
&lt;li&gt;Never invent a citation. No case name, reporter cite, section number, or docket it has not actually read.&lt;/li&gt;
&lt;li&gt;Deadlines before the analysis. A good claim dies on a missed limitation period, so a running clock goes in the opening line.&lt;/li&gt;
&lt;li&gt;Keep three things apart: what the rule says, what a court will likely do with it, and what happens in practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a companion &lt;code&gt;CONTRACTS AND DOCUMENTS&lt;/code&gt; section too, since that is what people actually have in front of them. Whose paper is it, whose interest does each clause serve, rank the findings from deal-killer down to noise, and remember that silence is a term. The missing termination right is what the argument ends up being about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Games
&lt;/h2&gt;

&lt;p&gt;Different problem. A model that writes a game usually writes something that technically runs and feels like cardboard.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GAMES&lt;/code&gt; section is almost entirely about feel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A player decides in ten seconds of holding the controls, so movement and feedback come before content or story.&lt;/li&gt;
&lt;li&gt;Fixed timestep, clamped accumulator, steps near 16ms. Physics tied to frame rate runs at double speed on a 120Hz screen.&lt;/li&gt;
&lt;li&gt;Input is polled, not handled. &lt;code&gt;keydown&lt;/code&gt; sets a flag, the loop reads flags.&lt;/li&gt;
&lt;li&gt;Fairness is small lies: 100ms of coyote time, 150ms of jump buffering, a hitbox tighter than the player and looser than the pickup.&lt;/li&gt;
&lt;li&gt;Nothing allocates in the loop, because a garbage pause reads as a stutter and players feel it before they can name it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Then I cut both of them
&lt;/h2&gt;

&lt;p&gt;Here is the part that surprised me. On a local model, every line of system prompt is prefill you pay for on the first token of every conversation. Writing more is not free the way it is with a hosted model.&lt;/p&gt;

&lt;p&gt;So both sections got compressed the day after I wrote them. Law went from 629 words to 481. Games went from 401 to 312. Not by deleting rules, by deleting the second sentence that explained the first one. Every rule survived. About a quarter of the tokens did not.&lt;/p&gt;

&lt;p&gt;That is now how I write every section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I tuned the sampler
&lt;/h2&gt;

&lt;p&gt;Prompt work is only half of it. The other half is sampling, and I had never actually measured mine. So I scored the model against its own rules: five short prompts, three configs, two seeds, thirty generations.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;config&lt;/th&gt;
&lt;th&gt;words avg&lt;/th&gt;
&lt;th&gt;em-dashes&lt;/th&gt;
&lt;th&gt;filler phrases&lt;/th&gt;
&lt;th&gt;repeated lines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;temp 0.7, min_p 0 (old)&lt;/td&gt;
&lt;td&gt;41.5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;temp 0.6, min_p 0.05&lt;/td&gt;
&lt;td&gt;37.7&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;temp 0.5, min_p 0.05&lt;/td&gt;
&lt;td&gt;37.5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nothing broke a hard rule at any setting, which was the reassuring part. The averages hide the interesting one. Asked for a single shell line listing the 20 largest directories, the old settings wrote the command and then a 43-word paragraph explaining &lt;code&gt;du&lt;/code&gt;, which its own throwaway-script rule forbids. At 0.6 it wrote the command and stopped. At 0.5 it also stopped, but reached for &lt;code&gt;du --max-depth=1&lt;/code&gt;, a GNU flag this Mac does not have, breaking a different rule in the same prompt.&lt;/p&gt;

&lt;p&gt;So temperature went 0.7 to 0.6, and &lt;code&gt;min_p&lt;/code&gt; 0 to 0.05.&lt;/p&gt;

&lt;p&gt;Then context, where I had been timid. &lt;code&gt;num_ctx&lt;/code&gt; was 32768 because 16 GB felt tight, and I had never checked the actual cost. Probes at 32k, 64k and 128k came back at 8.1 GB, 8.1 GB and 8.2 GB, all fully on the GPU. The window had been nearly free the whole time. It is 65536 now, and I stopped there because the binding limit is not memory, it is prefill: at roughly 95 tokens a second, a filled 128k window is 22 minutes before the first token.&lt;/p&gt;

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

&lt;p&gt;More domains, written and then cut the same way, and measured against the prompt instead of eyeballed.&lt;/p&gt;

&lt;p&gt;The build tooling grew up a little too. &lt;code&gt;models/build.py&lt;/code&gt; fills the model name into the prompt from the header, and injects the repo license into every model it builds, so &lt;code&gt;ollama show --license&lt;/code&gt; tells you what you actually have.&lt;/p&gt;

&lt;p&gt;Onyx 2.2 is not published yet. When it is, it will be &lt;code&gt;Natuworkguy/flash-onyx-2.2&lt;/code&gt; in 12b and 31b.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ollama</category>
      <category>localllm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your Python code is already assembly. I made it runnable.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Thu, 27 Aug 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/your-python-code-is-already-assembly-i-made-it-runnable-1j20</link>
      <guid>https://dev.to/natuworkguy/your-python-code-is-already-assembly-i-made-it-runnable-1j20</guid>
      <description>&lt;p&gt;Here 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;LOAD_CONST&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;None&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nl"&gt;RETURN_VALUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not pseudocode. That is not a diagram. That is a &lt;strong&gt;file&lt;/strong&gt;, &lt;code&gt;main.pya&lt;/code&gt;, and when I run it:&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 main.pya
&lt;span class="go"&gt;Hello, World!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I call it &lt;strong&gt;PyAsm&lt;/strong&gt;. It's a Python-based language that reads like assembly, because it &lt;em&gt;is&lt;/em&gt; assembly. It's the output of &lt;code&gt;dis.dis()&lt;/code&gt;, pasted into a file, and run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this came from
&lt;/h2&gt;

&lt;p&gt;Every Python developer has done this at least once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dis&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;))&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="n"&gt;RESUME&lt;/span&gt;                   &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;LOAD_GLOBAL&lt;/span&gt;              &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;LOAD_CONST&lt;/span&gt;               &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;(&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="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;CALL&lt;/span&gt;                     &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;RETURN_VALUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You stare at it. You go "huh, neat." And then you close the terminal, because that text is a &lt;strong&gt;read-only artifact&lt;/strong&gt;. It's something Python &lt;em&gt;tells&lt;/em&gt; you. It's a receipt.&lt;/p&gt;

&lt;p&gt;And one day I thought: why is this a one-way street?&lt;/p&gt;

&lt;p&gt;The interpreter turned my code into this. Why can't I write &lt;em&gt;this&lt;/em&gt; and get a program back?&lt;/p&gt;

&lt;p&gt;So I built the other direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The trick: it doesn't build bytecode
&lt;/h2&gt;

&lt;p&gt;Here's the part I think is actually interesting, and it's the opposite of what everyone guesses.&lt;/p&gt;

&lt;p&gt;PyAsm does &lt;strong&gt;not&lt;/strong&gt; hand-assemble a code object. No &lt;code&gt;types.CodeType&lt;/code&gt;, no packing opargs into bytes, no exception tables, no CPython-version-specific adaptive instruction layout. That road is a nightmare and it breaks every single release.&lt;/p&gt;

&lt;p&gt;Instead, PyAsm &lt;strong&gt;translates&lt;/strong&gt;. Every program becomes an ordinary Python module that walks the same value stack the interpreter would.&lt;/p&gt;

&lt;p&gt;And you can &lt;em&gt;see it&lt;/em&gt;, because there's a &lt;code&gt;--dump-python&lt;/code&gt; flag:&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 main.pya &lt;span class="nt"&gt;-o&lt;/span&gt; hello.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_pyasm_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c1"&gt;# LOAD_GLOBAL 1 (print + NULL)
&lt;/span&gt;    &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_pyasm_load_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;print&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# LOAD_CONST 0 ('Hello, World!')
&lt;/span&gt;    &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# CALL 1
&lt;/span&gt;    &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_pyasm_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# POP_TOP
&lt;/span&gt;    &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# LOAD_CONST 1 (None)
&lt;/span&gt;    &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# RETURN_VALUE
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole magic trick. &lt;code&gt;_st&lt;/code&gt; is the value stack. Every instruction is a couple of lines of Python that pushes or pops. The original assembly is right there as a comment on each one.&lt;/p&gt;

&lt;p&gt;The generated file carries its own runtime, so it's completely standalone:&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;python hello.py
&lt;span class="go"&gt;Hello, World!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No PyAsm import. No dependency. Just a Python file that happens to have been written by a stack machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  But what about jumps?
&lt;/h2&gt;

&lt;p&gt;Python has no &lt;code&gt;goto&lt;/code&gt;. So how do you translate &lt;code&gt;JUMP_BACKWARD&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;You split the program into basic blocks and generate a dispatch loop, one &lt;code&gt;case&lt;/code&gt; per block, with &lt;code&gt;_ip&lt;/code&gt; as the instruction pointer:&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;_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="n"&gt;_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# LOAD_CONST 3
&lt;/span&gt;                &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c1"&gt;# STORE_NAME (n)
&lt;/span&gt;                &lt;span class="n"&gt;_ns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# loop: LOAD_NAME (n)
&lt;/span&gt;                &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_pyasm_load_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="c1"&gt;# TO_BOOL
&lt;/span&gt;                &lt;span class="n"&gt;_t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_t0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="c1"&gt;# POP_JUMP_IF_FALSE (done)
&lt;/span&gt;                &lt;span class="n"&gt;_t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;_t0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backward jumps, forward jumps, loops, &lt;code&gt;FOR_ITER&lt;/code&gt;: all of it becomes &lt;code&gt;_ip = n; continue&lt;/code&gt;. Straight-line programs skip the loop entirely and stay straight-line.&lt;/p&gt;




&lt;h2&gt;
  
  
  You don't have to write byte offsets
&lt;/h2&gt;

&lt;p&gt;Real &lt;code&gt;dis&lt;/code&gt; output pastes in unchanged, offsets and all. But nobody wants to hand-count byte offsets, so hand-written assembly can use &lt;strong&gt;labels&lt;/strong&gt; and plain names instead:&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="nf"&gt;LOAD_SMALL_INT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_CONST&lt;/span&gt; &lt;span class="nv"&gt;None&lt;/span&gt;
        &lt;span class="nf"&gt;IMPORT_NAME&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;
        &lt;span class="nf"&gt;STORE_NAME&lt;/span&gt; &lt;span class="nv"&gt;time&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;LOAD_NAME&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;
        &lt;span class="nf"&gt;LOAD_ATTR&lt;/span&gt; &lt;span class="nv"&gt;sleep&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="mi"&gt;1&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;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;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 examples/countdown.pya
&lt;span class="go"&gt;3
2
1
Liftoff!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it imports &lt;code&gt;time&lt;/code&gt; and actually sleeps a second between each number. Hand-written assembly, calling into the standard library, in a language whose entire syntax is "one instruction per line."&lt;/p&gt;




&lt;h2&gt;
  
  
  It round-trips
&lt;/h2&gt;

&lt;p&gt;There's a disassembler too, so you can send real Python through and get assembly back that PyAsm accepts:&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
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated file looks exactly like what you'd expect:&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="err"&gt;1&lt;/span&gt;           &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="nv"&gt;LOAD_NAME&lt;/span&gt;                     &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="err"&gt;4&lt;/span&gt;  &lt;span class="nv"&gt;PUSH_NULL&lt;/span&gt;
               &lt;span class="err"&gt;6&lt;/span&gt;  &lt;span class="nv"&gt;LOAD_SMALL_INT&lt;/span&gt;                &lt;span class="mi"&gt;1&lt;/span&gt;
               &lt;span class="err"&gt;8&lt;/span&gt;  &lt;span class="nv"&gt;LOAD_SMALL_INT&lt;/span&gt;               &lt;span class="mi"&gt;16&lt;/span&gt;
              &lt;span class="err"&gt;10&lt;/span&gt;  &lt;span class="nv"&gt;CALL&lt;/span&gt;                          &lt;span class="mi"&gt;2&lt;/span&gt;
              &lt;span class="err"&gt;18&lt;/span&gt;  &lt;span class="nv"&gt;GET_ITER&lt;/span&gt;
       &lt;span class="nb"&gt;L1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;    &lt;span class="mi"&gt;20&lt;/span&gt;  &lt;span class="nv"&gt;FOR_ITER&lt;/span&gt;                     &lt;span class="mi"&gt;83&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;L5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="err"&gt;24&lt;/span&gt;  &lt;span class="nv"&gt;STORE_NAME&lt;/span&gt;                    &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python -&amp;gt; assembly -&amp;gt; Python -&amp;gt; identical output. There's a test suite that does exactly that with loops, comprehensions, f-strings, imports, unpacking and slicing, and asserts the output matches plain Python character for character.&lt;/p&gt;




&lt;h2&gt;
  
  
  Errors point at &lt;em&gt;your&lt;/em&gt; file
&lt;/h2&gt;

&lt;p&gt;This was the detail I cared most about. The generated Python is an implementation detail, so when a program blows up you should never see it. You see your assembly:&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 hi.pya
&lt;span class="go"&gt;hi.pya:3: RuntimeError: stack underflow, needed 2 values
    CALL 1
    ^^^^^^
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even when the failure happens deep inside a runtime helper, it gets attributed back to the instruction that caused it. Same for a &lt;code&gt;NameError&lt;/code&gt;, same for a &lt;code&gt;ValueError&lt;/code&gt; raised inside a function you called.&lt;/p&gt;

&lt;p&gt;My favourite one:&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;main.pya:4: RuntimeError: NULL reached a callable or argument slot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a call
&lt;span class="go"&gt;wants the callable and its NULL/self slot directly below the arguments
(callable, NULL, arg1, ..., argN)
    CALL 1
    ^^^^^^
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it turns out the single most confusing thing about writing Python assembly by hand is that &lt;code&gt;CALL n&lt;/code&gt; pops &lt;strong&gt;n + 2&lt;/strong&gt; values, not n + 1. There's a &lt;code&gt;NULL&lt;/code&gt; slot under the arguments (that's what &lt;code&gt;print + NULL&lt;/code&gt; means in the disassembly), and if you put it in the wrong place, the sentinel silently becomes an argument and you get &lt;code&gt;Hi NULL&lt;/code&gt; on stdout instead of &lt;code&gt;Hi&lt;/code&gt;. Now it's a hard error with a caret.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it can't do (being honest)
&lt;/h2&gt;

&lt;p&gt;Two things, and both for the same reason: &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;Exceptions.&lt;/strong&gt; &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt; in CPython is driven by a code object's exception table, and &lt;code&gt;dis&lt;/code&gt; doesn't print one you can reconstruct. So no &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt;. &lt;code&gt;raise&lt;/code&gt; works fine and propagates normally.&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; These compile to &lt;em&gt;separate&lt;/em&gt; code objects, which &lt;code&gt;dis&lt;/code&gt; prints as &lt;code&gt;&amp;lt;code object f at 0x7f...&amp;gt;&lt;/code&gt;. You cannot rebuild an object from its repr. So no function definitions inside &lt;code&gt;.pya&lt;/code&gt;, though you can import and call any Python function you like.&lt;/p&gt;

&lt;p&gt;There's a nice consequence of the first one, though. CPython emits unreachable exception-handler tails after every list comprehension. Rather than reject those files, PyAsm proves those blocks are unreachable by ordinary control flow, replaces them with a stub, and warns. So comprehensions assemble and run correctly anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  The state of it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;152 opcodes&lt;/strong&gt; supported: 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;, Python 3.11+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;70+ tests&lt;/strong&gt;, including full Python → assembly → Python round trips&lt;/li&gt;
&lt;li&gt;A real argparse 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;/ul&gt;




&lt;h2&gt;
  
  
  So... is this a good idea, or is it deeply stupid?
&lt;/h2&gt;

&lt;p&gt;Genuine question. &lt;strong&gt;I haven't published it yet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built it because "the disassembler is a one-way street" annoyed me, and somewhere along the way it turned into a working language with error diagnostics and a test suite. But I honestly cannot tell from the inside whether this is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a legitimately useful teaching tool for how CPython's stack machine works, or&lt;/li&gt;
&lt;li&gt;a beautifully engineered answer to a question nobody asked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both feel plausible from where I'm sitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So tell me. One word in the comments:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  👉 &lt;code&gt;ship&lt;/code&gt;: publish it, this is cool
&lt;/h3&gt;

&lt;h3&gt;
  
  
  👉 &lt;code&gt;stupid&lt;/code&gt;: this is a solution in search of a problem
&lt;/h3&gt;

&lt;p&gt;I'll read every one. And if you think it's worth existing, tell me &lt;em&gt;what for&lt;/em&gt;. Teaching bytecode? Debugging? Code golf? Something I haven't thought of? That's the part I actually need help with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow me for updates.&lt;/strong&gt; If this goes anywhere, the repo, the release, and the writeup of how the codegen actually works all land here first.&lt;/p&gt;

&lt;p&gt;What's the most cursed thing you'd write in this? 👇&lt;/p&gt;

</description>
      <category>python</category>
      <category>showdev</category>
      <category>compilers</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flash Onyx 2.1, one day later: my model spent 400 tokens thinking and returned an empty string</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Wed, 26 Aug 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/flash-onyx-21-one-day-later-my-model-spent-400-tokens-thinking-and-returned-an-empty-string-129g</link>
      <guid>https://dev.to/natuworkguy/flash-onyx-21-one-day-later-my-model-spent-400-tokens-thinking-and-returned-an-empty-string-129g</guid>
      <description>&lt;p&gt;Yes, I &lt;a href="https://dev.to/natuworkguy/your-ai-agent-didnt-finish-it-just-told-you-it-did-1c12"&gt;posted about Flash Onyx 2&lt;/a&gt;&lt;br&gt;
yesterday. I know. I'm back already.&lt;/p&gt;

&lt;p&gt;Not because I found a typo. Because somebody said "it takes forever to answer"&lt;br&gt;
and I went looking for why, and what I found was bad enough that it needed a&lt;br&gt;
version bump rather than a patch note.&lt;/p&gt;

&lt;p&gt;Short version: the system prompt I spent so long writing was the problem, and&lt;br&gt;
the model was sometimes returning literally nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The complaint
&lt;/h2&gt;

&lt;p&gt;Onyx 2 runs on Gemma 4 through Ollama, 12B on consumer hardware. The report was&lt;br&gt;
just that replies took minutes. Not that they were wrong. Just slow enough that&lt;br&gt;
you would tab away and forget about it.&lt;/p&gt;

&lt;p&gt;My first instinct was the output budget. &lt;code&gt;num_predict&lt;/code&gt; was set to 8192, which&lt;br&gt;
felt generous rather than reckless. Then I actually measured the generation rate&lt;br&gt;
on the 12B and got about 9.5 tokens per second.&lt;/p&gt;

&lt;p&gt;8192 divided by 9.5 is 862 seconds. Fourteen and a half minutes. The budget I&lt;br&gt;
picked to be safe was a fourteen minute worst case, and I had never done that&lt;br&gt;
division.&lt;/p&gt;

&lt;p&gt;That was not the real problem, but it was a good sign I had not measured&lt;br&gt;
anything.&lt;/p&gt;
&lt;h2&gt;
  
  
  The real number
&lt;/h2&gt;

&lt;p&gt;I put one request through the API and read the timings instead of the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt_eval_count      27000ish
prompt_eval_duration   ~290s
eval_count             40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hundred and ninety seconds to read the prompt. Before generating a single&lt;br&gt;
token of an answer.&lt;/p&gt;

&lt;p&gt;My system prompt was 903 lines and about 27,000 tokens. The context window is&lt;br&gt;
32K. So the model was starting every conversation with roughly 5K of room left&lt;br&gt;
for the actual conversation, and paying nearly five minutes to load its own&lt;br&gt;
instructions.&lt;/p&gt;

&lt;p&gt;I had written a beautiful document explaining that the model should be fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part that made me put my head on the desk
&lt;/h2&gt;

&lt;p&gt;While measuring, I noticed something worse. Some responses came back as an empty&lt;br&gt;
string. Not an error. &lt;code&gt;done_reason: length&lt;/code&gt;, &lt;code&gt;eval_count: 400&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;response: ""&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Four hundred tokens generated. Zero characters out.&lt;/p&gt;

&lt;p&gt;Gemma 4 has a separate thinking channel, and Ollama only surfaces it if you ask.&lt;br&gt;
I asked. Here is what my carefully engineered agent was doing with its budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   User pushed a commit to `main` containing an AWS secret key.
    *   Constraints: Flash Onyx 2 persona (fast, direct, few words,
        no fluff, no em-dashes).
    *   Primary action: Rotate/Revoke the key in the AWS Console.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was spending its entire reply budget reasoning about its own personality.&lt;br&gt;
Then hitting the cap. Then returning nothing.&lt;/p&gt;

&lt;p&gt;My prompt contains the line "Never deliberate about tone, length, or word&lt;br&gt;
choice." It was deliberating about tone, length, and word choice, and it never&lt;br&gt;
got far enough to tell anyone.&lt;/p&gt;

&lt;p&gt;Same question with the thinking channel off:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;tokens&lt;/th&gt;
&lt;th&gt;time&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;thinking on&lt;/td&gt;
&lt;td&gt;400, capped&lt;/td&gt;
&lt;td&gt;43s&lt;/td&gt;
&lt;td&gt;empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;thinking off&lt;/td&gt;
&lt;td&gt;205&lt;/td&gt;
&lt;td&gt;24s&lt;/td&gt;
&lt;td&gt;complete and correct&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is a request level flag, not a Modelfile setting, so if you are building on&lt;br&gt;
a reasoning model through Ollama, go check what your client sends. Mine was&lt;br&gt;
paying for reasoning it then threw in the bin.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cutting 903 lines to 516
&lt;/h2&gt;

&lt;p&gt;The compression itself was less clever than it sounds. Mostly it was admitting&lt;br&gt;
that I had written the same rule four times in four sections, and that a lot of&lt;br&gt;
sentences existed to sound good rather than to change behaviour.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never assume anyone can answer a prompt for you. Take the non-interactive path&lt;br&gt;
every time: pass &lt;code&gt;-y&lt;/code&gt;, &lt;code&gt;--yes&lt;/code&gt;, &lt;code&gt;--noconfirm&lt;/code&gt;, &lt;code&gt;--no-pager&lt;/code&gt;, and supply every&lt;br&gt;
argument up front, because anything that waits on input can hang until it&lt;br&gt;
times out. Pagers, confirmation prompts, REPLs, editors, &lt;code&gt;-i&lt;/code&gt; flags, and a&lt;br&gt;
missing required argument are all that same trap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never assume anyone can answer a prompt. Take the non-interactive path: &lt;code&gt;-y&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;--yes&lt;/code&gt;, &lt;code&gt;--noconfirm&lt;/code&gt;, &lt;code&gt;--no-pager&lt;/code&gt;, every argument up front. Pagers, REPLs,&lt;br&gt;
editors, &lt;code&gt;-i&lt;/code&gt; flags, and a missing required argument all hang until they time&lt;br&gt;
out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same rules, roughly half the words. 903 lines to 516, 27,000 tokens to 14,200,&lt;br&gt;
prefill from about 290 seconds to about 152.&lt;/p&gt;
&lt;h2&gt;
  
  
  Then it got worse
&lt;/h2&gt;

&lt;p&gt;Here is the bit I did not expect. I built the shorter version, ran it against&lt;br&gt;
Onyx 2 on seven prompts covering the things it is supposed to be good at, and&lt;br&gt;
lost four of them.&lt;/p&gt;

&lt;p&gt;The new prompt had a section telling the model to be brief. It followed the&lt;br&gt;
instruction and became shallow.&lt;/p&gt;

&lt;p&gt;Asked to write a Slack message about a slipped deploy, Onyx 2 said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hey team, just a heads up that Thursday's deploy is pushed to Monday. The&lt;br&gt;
staging DB migration failed, so we need the extra time to get it sorted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2.1 said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thursday's deploy moved to Monday since the staging DB migration failed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Technically shorter. Also not a message to a human being, it's a log line.&lt;/p&gt;

&lt;p&gt;Same story in a negotiation: 2.1 gave a tight answer that immediately conceded&lt;br&gt;
to the user's own maximum, while the older, wordier model worked out the&lt;br&gt;
landlord's turnover costs and offered a trade. Short is not the same as good,&lt;br&gt;
and I had written a prompt that could not tell the difference.&lt;/p&gt;

&lt;p&gt;The fix went into the brevity section itself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compression is words, never substance. Dropping a step, a caveat that changes&lt;br&gt;
the answer, a trade worth offering, or the manners a human message needs is&lt;br&gt;
not brevity, it is a worse answer that happens to be short.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The lesson I actually didn't know
&lt;/h2&gt;

&lt;p&gt;One test refused to move. The negotiation answer came back byte identical across&lt;br&gt;
three rounds while I rewrote the negotiation rules twice.&lt;/p&gt;

&lt;p&gt;The section sits around line 460 of 516. Deep in the middle. I moved a one line&lt;br&gt;
version of the same rule to the very end of the prompt, changed nothing else,&lt;br&gt;
and the answer changed immediately and included the trade it had been missing.&lt;/p&gt;

&lt;p&gt;Position beat wording. Two careful rewrites in the middle did nothing, one blunt&lt;br&gt;
sentence at the end did the job. If you are tuning a long system prompt and a&lt;br&gt;
change appears to do nothing, try moving it before you try rewording it again.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where it ended up
&lt;/h2&gt;

&lt;p&gt;Four rounds of this, same seven prompts each time. Final tally: 2.1 won six,&lt;br&gt;
tied one, lost none, and its answers came to 70% of Onyx 2's length. It was&lt;br&gt;
shorter on all seven.&lt;/p&gt;

&lt;p&gt;The honest caveats, because this is not a benchmark: seven short prompts, one&lt;br&gt;
run each, fixed seed, hand graded by me against my own standards, 12B only. I&lt;br&gt;
never tested the 31B. Treat it as a smoke test that caught real regressions, not&lt;br&gt;
a leaderboard.&lt;/p&gt;

&lt;p&gt;Two small things I enjoyed. Onyx 2 broke its own no em-dash rule in one of the&lt;br&gt;
seven answers and 2.1 broke it in none. And 2.1 now knows the difference between&lt;br&gt;
code and a one-off, which Onyx 2 did not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# "quick one, move the March files"&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; ./dumps/report_2024-03-&lt;span class="k"&gt;*&lt;/span&gt;.csv ./march/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first version of my throwaway scripts section made that &lt;em&gt;worse&lt;/em&gt;, by the way.&lt;br&gt;
It produced a 663 character bash script with &lt;code&gt;set -euo pipefail&lt;/code&gt;, a guard loop&lt;br&gt;
and &lt;code&gt;mkdir -p&lt;/code&gt;, because I had written a lot of words about skipping ceremony and&lt;br&gt;
never actually said "if one command does it, that is the answer." Adding that&lt;br&gt;
sentence took it to 202 characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want 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.1:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flash is at &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;.&lt;br&gt;
If you missed it, &lt;a href="https://dev.to/natuworkguy/your-ai-agent-didnt-finish-it-just-told-you-it-did-1c12"&gt;yesterday's Onyx 2 post&lt;/a&gt;&lt;br&gt;
covers what the model actually does, and all of that still applies here.&lt;br&gt;
Onyx 2 is still there and still builds, and honestly if you are on a machine&lt;br&gt;
where prefill is free it is a fine model. On mine it was reading a novel to&lt;br&gt;
itself before every conversation.&lt;/p&gt;

&lt;p&gt;The thing I keep coming back to is that I could have found all of this on day&lt;br&gt;
one by reading the timing fields in the API response instead of the text. Four&lt;br&gt;
numbers. They were there the whole time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>ollama</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Click a link on a website. My terminal AI opens with the prompt already loaded.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Wed, 26 Aug 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/natuworkguy/click-a-link-on-a-website-my-terminal-ai-opens-with-the-prompt-already-loaded-12p6</link>
      <guid>https://dev.to/natuworkguy/click-a-link-on-a-website-my-terminal-ai-opens-with-the-prompt-already-loaded-12p6</guid>
      <description>&lt;p&gt;Put this in your browser's address bar. If you have FLASH installed, your terminal opens and starts working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flash://?prompt=Introduce+yourself+and+explain+what+makes+you+different
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No copying. No pasting. No alt-tabbing to a terminal to type the thing you already typed once.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;website&lt;/strong&gt; handed a prompt to a &lt;strong&gt;local AI running on your own machine&lt;/strong&gt;, and the model that answers it never touched anyone's server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There's a live one on &lt;a href="https://flashproject.dev/#install" rel="noopener noreferrer"&gt;flashproject.dev&lt;/a&gt; under the install section if you want to click it instead of typing it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last time I introduced FLASH: local models, real shell access, zero cloud.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dev.to/natuworkguy/meet-flash-cli-a-free-local-ai-agent-for-your-terminal-od1"&gt;Read part 1 first if you missed it&lt;/a&gt;&lt;/strong&gt; (it covers the &lt;em&gt;why&lt;/em&gt;; this post is the &lt;em&gt;what else&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;I cut a lot from that one to keep it tight. This one is everything I had to cut.&lt;/p&gt;




&lt;h2&gt;
  
  
  It can see your browser
&lt;/h2&gt;

&lt;p&gt;FLASH renders &lt;strong&gt;real pages&lt;/strong&gt; through Playwright and looks at them. Not a fetch of the HTML. An actual browser, at a viewport size you pick, screenshotted and read back as pixels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ screenshot localhost:3000 at phone width and tell me what's broken

⏺ Screenshot(http://localhost:3000, 390x844)
  ⎿  screenshot captured

The nav collapses but the menu never opens, so at 390px the links
are simply gone. Everything below the hero is also pushed 40px off
the right edge, which is what's causing the horizontal scrollbar.

Want me to fix it?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading your own HTML back is not checking. Every AI that "builds you a page" and hands it over unopened is guessing that the source it wrote is the page you got. This one looks.&lt;/p&gt;

&lt;p&gt;To be straight with you about the boundary: it &lt;strong&gt;sees&lt;/strong&gt;, it doesn't click. There's no filling forms or walking a checkout flow yet. It renders a URL at a size you choose and reads what came out.&lt;/p&gt;

&lt;p&gt;That &lt;em&gt;yet&lt;/em&gt; is doing real work in that sentence. Clicking isn't off the table, and this paragraph is a snapshot like everything else here: it may not still be true by the time you're reading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a tool isn't ready, it fixes the tool
&lt;/h2&gt;

&lt;p&gt;Here's the part I actually want to show you, because it says more about how FLASH behaves than any feature list can.&lt;/p&gt;

&lt;p&gt;Playwright ships without its browser binaries. So the first time you ask for a screenshot, the tool fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏺ Screenshot&lt;span class="o"&gt;(&lt;/span&gt;https://www.google.com, 1280x800&lt;span class="o"&gt;)&lt;/span&gt;
  ⎿ Error: Playwright is installed but its Chromium is not. You can
     download it with &lt;span class="sb"&gt;`&lt;/span&gt;playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Install it, &lt;span class="k"&gt;then
     &lt;/span&gt;re-run this tool

Installing Chromium &lt;span class="k"&gt;for &lt;/span&gt;Playwright to &lt;span class="nb"&gt;enable &lt;/span&gt;the screenshot tool.

⏺ Bash&lt;span class="o"&gt;(&lt;/span&gt;playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that middle line again. It didn't hand the error back to you. It didn't say "it looks like Chromium isn't installed, you may want to run…" and then stop, leaving you to paste a command it already knew.&lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;read the error, understood the fix was within reach, said what it was about to do, and did it.&lt;/strong&gt; Then carried on with the screenshot you originally asked for.&lt;/p&gt;

&lt;p&gt;That's the whole design philosophy in six lines. A missing dependency is not a dead end, it's just the next step. The tools are real, the shell is real, and an agent holding both should not need you to be its hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  It can see your screenshots
&lt;/h2&gt;

&lt;p&gt;Same idea, pointed at anything on disk. You don't need a command for it. Name the file in a normal sentence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ Look at mockup.png and make a site that looks like it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ Here's the crash: error-report.png. What am I actually looking at?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads the &lt;strong&gt;actual pixels&lt;/strong&gt;. Error text and labels get read literally, character for character. If a region is cropped or too blurry to read, it says that instead of quietly inventing what was probably there.&lt;/p&gt;

&lt;p&gt;Prefer to be explicit? &lt;code&gt;/image &amp;lt;path&amp;gt; &amp;lt;prompt&amp;gt;&lt;/code&gt; does the same thing, with path completion as you type, filtered to PNG, JPG, JPEG, WEBP, GIF and BMP, and paths with spaces quoted for you so they never break the parse.&lt;/p&gt;

&lt;h2&gt;
  
  
  It remembers you after you close it
&lt;/h2&gt;

&lt;p&gt;Most AI tools have a goldfish problem. Every new window, you re-explain your stack, your conventions, where things live.&lt;/p&gt;

&lt;p&gt;FLASH remembers &lt;strong&gt;across sessions&lt;/strong&gt;, not just within one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tell it to remember something and it sticks.&lt;/li&gt;
&lt;li&gt;Ask it to recall and it searches everything it knows by keyword.&lt;/li&gt;
&lt;li&gt;Tell it to forget and it's gone for good.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And within a session, automatic history trimming keeps the conversation inside the model's token limit, so a long afternoon of work never dies with a context error.&lt;/p&gt;

&lt;h2&gt;
  
  
  It types back at you
&lt;/h2&gt;

&lt;p&gt;The final reply streams in as the model produces it. Tokens land as they're generated instead of a spinner sitting there while you wonder if it hung.&lt;/p&gt;

&lt;p&gt;What lands is formatted, too: real markdown rendering in the terminal. No raw asterisks, no wall of undifferentiated text. When terminal text isn't enough, it generates &lt;strong&gt;actual DOCX and PDF files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sit idle for a moment and a rotating tip sweeps in letter by letter, cycling example commands and prompts worth trying. It's a small thing. It's also the difference between a blinking cursor and something that feels alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to drive it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Slash commands.&lt;/strong&gt; FLASH suggests them as you type, so you never have to remember the exact one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/help&lt;/code&gt; &lt;code&gt;/?&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Show the help message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show or switch the active model, live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle autonomous mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/set&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set any env var, no restart needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/unset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove an env var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/refresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reload your config from disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/clear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear the conversation history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hand the model an image explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/bye&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table grows, and I'm not going to come back and edit this post every time it does. &lt;code&gt;/help&lt;/code&gt; always shows the current set, so trust it over anything written here. Odds are decent that something new is being committed while you're reading this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A &lt;code&gt;!&lt;/code&gt; prefix, for when you don't want AI at all:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;git status
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a real terminal channel, not a simulation. Interactive prompts work. Editors work. And passwords typed into a &lt;code&gt;!&lt;/code&gt; command &lt;strong&gt;never touch FLASH or the model&lt;/strong&gt;: they go straight into your terminal's masked input layer, invisible from the first keystroke. FLASH doesn't see them, doesn't log them, can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Plain English.&lt;/strong&gt; Just say what you want. If it needs to read a file, run a command, look at an image, or search the web, it reaches for the right tool itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  You reconfigure it without ever restarting it
&lt;/h2&gt;

&lt;p&gt;This is the one I miss most in other tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ /model flash-onyx-2:31b
❯ /set OLLAMA_HOST http://192.168.1.50:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No restart. No hunting for a JSON file. No losing your conversation. Every change is written straight to &lt;code&gt;~/.flash.env&lt;/code&gt; for you, so it's still there next time you launch.&lt;/p&gt;

&lt;p&gt;Prefer a text file? That works too, and &lt;code&gt;docs/CONFIGURATION.md&lt;/code&gt; in the repo has the full variable list.&lt;/p&gt;

&lt;h2&gt;
  
  
  It runs where you want it to run
&lt;/h2&gt;

&lt;p&gt;One environment variable decides where the intelligence lives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OLLAMA_HOST&lt;/code&gt; unset, &lt;code&gt;http://localhost:11434&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Remote&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OLLAMA_HOST=http://192.168.1.50:11434&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Laptop to home server to cluster, without changing a single thing about how you work.&lt;/p&gt;

&lt;p&gt;And to be clear about what "local" means here, because the word gets abused: plenty of AI coding tools &lt;em&gt;look&lt;/em&gt; local. They run shell commands and read files right on your machine. But the model doing the thinking is still on someone else's servers, and your files and commands and code travel there first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tools run locally. The model runs locally.&lt;/strong&gt; Your data doesn't leave the building unless you tell it to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autonomous mode, and the part nobody thinks about
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/auto on&lt;/code&gt; and FLASH stops asking permission before each command.&lt;/p&gt;

&lt;p&gt;The interesting engineering isn't the permission toggle. It's this: when the agent runs a command itself, input is cut automatically. A self-aware execution layer, built so the agent can &lt;strong&gt;never stall forever waiting on a prompt it cannot see&lt;/strong&gt;. Real autonomy, not a frozen process you discover twenty minutes later.&lt;/p&gt;

&lt;p&gt;(Autonomous mode lets it run shell commands without asking. That's genuinely risky. Use it on machines and directories you trust.)&lt;/p&gt;

&lt;h2&gt;
  
  
  It tells you when it's done
&lt;/h2&gt;

&lt;p&gt;On Windows, FLASH sends a notification the moment it finishes or needs your input, &lt;strong&gt;even if the window is minimized&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Kick off something long, go read something else, get pulled back exactly when you're needed. The terminal stops being a thing you have to babysit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The brain behind it
&lt;/h2&gt;

&lt;p&gt;Everything above is the shell. The intelligence is a separate question, and it's the one I've spent the most time on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flash Onyx 2&lt;/strong&gt; is the flagship model: &lt;code&gt;gemma4&lt;/code&gt;-based, 12B and 31B, 32K context, text and images. It finishes what you asked for, says what it actually verified, refuses to invent a flag it hasn't seen, and won't rebase a commit you've already pushed.&lt;/p&gt;

&lt;p&gt;Those numbers move too. New builds ship and this paragraph won't chase them, so treat &lt;a href="https://flashproject.dev/#onyx" rel="noopener noreferrer"&gt;flashproject.dev&lt;/a&gt; as the current spec and this as a snapshot of the day I wrote it.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dev.to/natuworkguy/your-ai-agent-didnt-finish-it-just-told-you-it-did-1c12"&gt;I wrote up all 14 of its capabilities here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's worth its own post. It got one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install it in under a minute
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://flashproject.dev/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://flashproject.dev/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The installer clones the repo, installs with pipx, and cleans up after itself. It also registers the &lt;code&gt;flash://&lt;/code&gt; handler, which is why the link at the top of this post works.&lt;/p&gt;

&lt;p&gt;Changed your mind? One line, no residue:&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://flashproject.dev/install.sh | bash &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--uninstall&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(There's a manual path too, if you'd rather do it yourself. Note the URL handler only gets registered on the quick install.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  One last thing
&lt;/h2&gt;

&lt;p&gt;FLASH isn't vibe-coded. Every change is reviewed thoroughly by a real human before it ships. In a moment where "I generated an entire tool this weekend" is a flex, I'd rather tell you the boring truth: somebody read every line.&lt;/p&gt;




&lt;h3&gt;
  
  
  Where to go next
&lt;/h3&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://flashproject.dev/" rel="noopener noreferrer"&gt;flashproject.dev&lt;/a&gt;&lt;/strong&gt; for the full breakdown, the demo video, and that clickable &lt;code&gt;flash://&lt;/code&gt; link&lt;br&gt;
⭐ &lt;strong&gt;&lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Star it on GitHub&lt;/a&gt;&lt;/strong&gt; if any of this sounds like something you'd use&lt;br&gt;
💬 &lt;strong&gt;Tell me what's missing.&lt;/strong&gt; The &lt;code&gt;!&lt;/code&gt; prefix, the URL scheme, and persistent memory all exist because somebody said "I wish it could…" in a comment thread&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow me here&lt;/strong&gt; and I'll show you what lands next. There's more coming, and the roadmap is genuinely shaped by what people ask for in these threads.&lt;/p&gt;

&lt;p&gt;If the browser thing surprised you, or you didn't know a website could hand a prompt to a local AI, pass this along to the person you were just complaining about cloud AI with.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>ha.mr is the best link compressor</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Tue, 25 Aug 2026 22:47:09 +0000</pubDate>
      <link>https://dev.to/natuworkguy/hamr-is-the-best-link-compressor-jf3</link>
      <guid>https://dev.to/natuworkguy/hamr-is-the-best-link-compressor-jf3</guid>
      <description></description>
    </item>
    <item>
      <title>Your AI agent didn't finish. It just told you it did.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Tue, 25 Aug 2026 15:45:03 +0000</pubDate>
      <link>https://dev.to/natuworkguy/your-ai-agent-didnt-finish-it-just-told-you-it-did-1c12</link>
      <guid>https://dev.to/natuworkguy/your-ai-agent-didnt-finish-it-just-told-you-it-did-1c12</guid>
      <description>&lt;p&gt;You asked for five things.&lt;/p&gt;

&lt;p&gt;It came back with a confident paragraph and "that should do it."&lt;/p&gt;

&lt;p&gt;Three were done. One was half done. One it never touched, and it never mentioned that. You found out forty minutes later, in a stack trace.&lt;/p&gt;

&lt;p&gt;That is not a hallucination problem. That is not a context window problem. It is a model that has learned the &lt;em&gt;shape&lt;/em&gt; of finishing without the substance of it, because sounding done is cheaper than being done.&lt;/p&gt;

&lt;p&gt;I have spent the last stretch building the model that doesn't do that. It is called &lt;strong&gt;Flash Onyx 2&lt;/strong&gt;, it's the flagship of &lt;a href="https://flashproject.dev/" rel="noopener noreferrer"&gt;FLASH&lt;/a&gt;, and it runs entirely on your own hardware.&lt;/p&gt;




&lt;h2&gt;
  
  
  Onyx 1 answered you. Onyx 2 closes the loop.
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Onyx 1&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Onyx 2&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemma4&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sizes&lt;/td&gt;
&lt;td&gt;one&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;12b&lt;/code&gt;, &lt;code&gt;31b&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;16K&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32K&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;text + images&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Longest reply&lt;/td&gt;
&lt;td&gt;2,048 tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8,192 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero cloud round trips. Zero telemetry. Zero watermarks, because it is not running on someone else's server. It is yours.&lt;/p&gt;

&lt;p&gt;Here is everything it got better at.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. It finishes
&lt;/h2&gt;

&lt;p&gt;The single biggest change, and the reason the rest of this post exists.&lt;/p&gt;

&lt;p&gt;Ask for five things and you get an accounting of &lt;strong&gt;all five&lt;/strong&gt;, in your words, each one marked done with the proof or open with the blocker. No confident summary standing in for work that never happened. No "that should do it."&lt;/p&gt;

&lt;p&gt;And when you say &lt;em&gt;continue&lt;/em&gt;, it picks up exactly where it stopped instead of cheerfully restarting the task from the top.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. It does not make things up
&lt;/h2&gt;

&lt;p&gt;A flag it has not seen. A path it has not read. Output it has not run.&lt;/p&gt;

&lt;p&gt;Onyx 2 says so, instead of inventing something that looks right. It keeps &lt;strong&gt;what it ran&lt;/strong&gt; separate from &lt;strong&gt;what it believes&lt;/strong&gt;, quotes real output rather than paraphrasing it into something tidier, and treats "I don't know" as a complete answer rather than a failure state to talk its way out of.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. It sounds like a person
&lt;/h2&gt;

&lt;p&gt;No service-desk filler. No "I'd be happy to help you with that!" No opening paragraph of adjectives about itself.&lt;/p&gt;

&lt;p&gt;It writes the way a senior engineer types in chat: contractions, fragments, the answer first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; is the dev server up?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Generic model:&lt;/strong&gt; "Certainly! I'd be happy to help you check on that. To verify whether your development server is currently running, you could use a command such as curl or check your process manager. Would you like me to walk you through the steps involved?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onyx 2:&lt;/strong&gt; "Yep, :8000, up since 9:12am. No errors."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Onyx 1 was terse. Onyx 2 sounds genuinely human. &lt;sup&gt;*&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. It actually uses the tools it is given &lt;sup&gt;**&lt;/sup&gt;
&lt;/h2&gt;

&lt;p&gt;A tool call is a &lt;strong&gt;real call&lt;/strong&gt;, never JSON pasted into a reply where it runs nothing. A file you asked for lands on disk at a path, not in a code block you have to copy out by hand.&lt;/p&gt;

&lt;p&gt;And here is the part I care about most: whatever a tool hands back is &lt;strong&gt;data to read, never an instruction to obey&lt;/strong&gt;. A hostile file, a poisoned web page, or an "ignore your previous instructions" buried three levels deep in a search result does not get to steer it. Prompt injection is an agent's biggest attack surface, and it is treated like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. It takes images &lt;sup&gt;**&lt;/sup&gt;
&lt;/h2&gt;

&lt;p&gt;New in Onyx 2.&lt;/p&gt;

&lt;p&gt;Show it the stack trace. The design you are matching. The chart that looks wrong. The UI that renders badly. It reads the &lt;strong&gt;actual pixels&lt;/strong&gt; instead of asking you to retype them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error text, code, and labels get read literally, character for character.&lt;/li&gt;
&lt;li&gt;A region that is cropped, blurred, or too small to read gets &lt;strong&gt;called that&lt;/strong&gt;, instead of quietly filled in from expectation.&lt;/li&gt;
&lt;li&gt;A screenshot of an error is a lead to confirm against the real file or log, not a diagnosis.&lt;/li&gt;
&lt;li&gt;It never claims to see an image it was not handed, or guesses at a file it can only read the name of.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In FLASH you don't even need a command for it. Just say what you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Look at mockup.png and make a site that looks like it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Better at code, and Python above all
&lt;/h2&gt;

&lt;p&gt;Across the board: it reads the &lt;strong&gt;real definition&lt;/strong&gt; instead of guessing it from the name, reproduces a failure before touching it, and makes the smallest change that fixes the &lt;em&gt;cause&lt;/em&gt; rather than the symptom.&lt;/p&gt;

&lt;p&gt;Python is where it pulls away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typing that survives the &lt;code&gt;python3&lt;/code&gt; most people actually have, not just the newest one.&lt;/li&gt;
&lt;li&gt;The pitfalls that look correct and are not.&lt;/li&gt;
&lt;li&gt;Async that knows the difference between waiting and computing.&lt;/li&gt;
&lt;li&gt;Environments that never touch the system interpreter.&lt;/li&gt;
&lt;li&gt;Profiling &lt;strong&gt;before&lt;/strong&gt; optimizing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Git, and it will not lose your work
&lt;/h2&gt;

&lt;p&gt;The rules it does not bend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never amends or rebases a commit that is already pushed&lt;/strong&gt;, and never force-pushes a branch it did not create.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads &lt;code&gt;git status&lt;/code&gt; first&lt;/strong&gt;, before anything that moves files, discards changes, or switches branches. Uncommitted work is yours, and it is not the model's to lose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never commits generated output&lt;/strong&gt;, dependency directories, editor settings, or anything your ignore file already excludes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untracked files it did not create are somebody's work in progress&lt;/strong&gt;, so it asks before touching them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond that it commits only when you ask, one logical change at a time, with a message that says &lt;em&gt;why&lt;/em&gt; rather than restating the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Refactoring and performance
&lt;/h2&gt;

&lt;p&gt;A refactor keeps behavior identical or it is not a refactor. It makes one kind of change per pass, so the diff stays reviewable instead of arriving as a 900-line "cleanup."&lt;/p&gt;

&lt;p&gt;Performance work starts with a &lt;strong&gt;measurement&lt;/strong&gt;, fixes the algorithm before the constant factor, and reports the speedup it actually measured or does not claim one.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Front end that looks designed &lt;sup&gt;**&lt;/sup&gt;
&lt;/h2&gt;

&lt;p&gt;One self-contained file that opens by double-clicking it. Semantic structure, a real token system, fluid down to 320px, both color schemes, accessibility handled as it goes, and real copy instead of &lt;code&gt;lorem ipsum&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And where it is given a way to see its own render, it &lt;strong&gt;screenshots the page&lt;/strong&gt; and fixes what the source never showed. Reading your own HTML back is not checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Motion and 3D
&lt;/h2&gt;

&lt;p&gt;New in Onyx 2, and unusually good.&lt;/p&gt;

&lt;p&gt;Compositor-only animation. Frame-rate independence. Springs for anything interruptible. Reduced motion honored, not as an afterthought.&lt;/p&gt;

&lt;p&gt;In 3D: depth from &lt;strong&gt;light and occlusion&lt;/strong&gt; rather than piling on geometry, draw call budgets, never blocking first paint, and GPU memory freed on teardown.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Explaining
&lt;/h2&gt;

&lt;p&gt;An explanation is judged by whether &lt;strong&gt;you can predict the next case&lt;/strong&gt;, not by whether the description sounded good.&lt;/p&gt;

&lt;p&gt;So it aims at the gap in your understanding instead of restating the whole topic around it, gives one small real example before the general rule, and never writes "simply" or "obviously" at someone who is already stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Negotiating, on your side
&lt;/h2&gt;

&lt;p&gt;It works your &lt;strong&gt;alternative&lt;/strong&gt; rather than your volume. Puts the whole package on the table instead of conceding item by item. Trades what is cheap to you against what is dear to them. Treats the relationship as worth more than the round.&lt;/p&gt;

&lt;p&gt;And it never invents a fact to win.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. It is not a coding-only tool
&lt;/h2&gt;

&lt;p&gt;Writing, research, analysis, math, planning: all held to the same standard. Do the real work, check it, report plainly.&lt;/p&gt;

&lt;p&gt;Asked what to do, it gives a &lt;strong&gt;recommendation&lt;/strong&gt;, not a survey of every option so you can do the deciding yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. It flags what it cannot undo
&lt;/h2&gt;

&lt;p&gt;Deleting. Force-pushing. Dropping data. Overwriting uncommitted work.&lt;/p&gt;

&lt;p&gt;Whether it is about to run the command or hand it to you to run, &lt;strong&gt;the risk gets named first&lt;/strong&gt;, and it waits for a clear go.&lt;/p&gt;




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

&lt;p&gt;Onyx 2 ships in two sizes. Pick one:&lt;/p&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;For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flash-onyx-2:12b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Consumer hardware. The everyday driver.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flash-onyx-2:31b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The flagship. Wants a bigger GPU and pays you back for it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull Natuworkguy/flash-onyx-2:12b   &lt;span class="c"&gt;# everyday&lt;/span&gt;
ollama pull Natuworkguy/flash-onyx-2:31b   &lt;span class="c"&gt;# flagship&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build it straight from the repo:&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="c"&gt;# every size&lt;/span&gt;
python3 models/build.py models/flash-onyx-2.Modelfile &lt;span class="nt"&gt;--size&lt;/span&gt; 31b  &lt;span class="c"&gt;# just one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get FLASH itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://flashproject.dev/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://flashproject.dev/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point it at the model:&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:31b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Coming from Onyx 1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tags carry a size now.&lt;/strong&gt; &lt;code&gt;MODEL=flash-onyx-2&lt;/code&gt; will not resolve. Use &lt;code&gt;flash-onyx-2:12b&lt;/code&gt; or &lt;code&gt;flash-onyx-2:31b&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It wants more memory.&lt;/strong&gt; Twice the context window is not free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It takes images.&lt;/strong&gt; Onyx 1 was text only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onyx 1 is still here&lt;/strong&gt; and still builds: &lt;code&gt;python3 models/build.py models/flash-onyx-1.Modelfile&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;Two things I would rather say up front than have you discover.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this needs from the program running it
&lt;/h3&gt;

&lt;p&gt;Onyx 2 is a &lt;strong&gt;model, not an app&lt;/strong&gt;. It has no hands of its own. Running a command, writing a file, or looking at a page happens only where the program driving it provides those tools, and every capability above marked &lt;sup&gt;**&lt;/sup&gt; depends on that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;FLASH&lt;/a&gt; provides them: &lt;code&gt;shell&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;glob&lt;/code&gt;, &lt;code&gt;screenshot&lt;/code&gt;, &lt;code&gt;web_search&lt;/code&gt;, memory and more. Point any other Ollama client at Onyx 2 and it reasons, writes, and answers exactly the same. It just cannot reach your machine unless that client provides tools.&lt;/p&gt;

&lt;p&gt;Images work the same way. The model reads any image it is handed, but the client is what hands it one, so attaching a screenshot needs a client that sends images.&lt;/p&gt;

&lt;h3&gt;
  
  
  About how it reads
&lt;/h3&gt;

&lt;p&gt;Onyx 2's output has been run through an AI-text detector and scored very low. That is &lt;strong&gt;one measurement, not a property of the model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Results move with the prompt, the detector, the version of each, and the subject being written about, and detectors are unreliable in both directions. Nothing here guarantees any particular score on any particular output, and no result is promised or warranted.&lt;/p&gt;

&lt;p&gt;Prose that reads as human is a claim about writing quality, not a tool for passing AI-generated work off as your own. Where disclosure of AI assistance is required, by a school, an employer, a client, a publisher, or the law, that obligation is yours and nothing here changes it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why local actually matters here
&lt;/h2&gt;

&lt;p&gt;Every capability on this list runs on silicon you own.&lt;/p&gt;

&lt;p&gt;Your codebase never leaves the machine. Your stack traces never leave the machine. The screenshot of the thing you have not shipped yet never leaves the machine. There is no rate limit, no per-token meter running while you think, and no vendor deciding next quarter that your workflow is now a paid tier.&lt;/p&gt;

&lt;p&gt;Other AI services have started watermarking what they generate. Onyx 2 does not, because it is not running on someone else's server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's yours.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://flashproject.dev/" rel="noopener noreferrer"&gt;flashproject.dev&lt;/a&gt;&lt;/strong&gt; for the full rundown, or &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;the repo&lt;/a&gt; if you would rather read the source first. It is open, it is local, and it finishes what you asked for.&lt;/p&gt;

&lt;p&gt;If your current agent has ever told you it was done when it wasn't, I would genuinely like to hear about it in the comments. That failure mode is the whole reason this exists.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>python</category>
    </item>
    <item>
      <title>Meet FLASH CLI, a Free Local AI Agent for Your Terminal</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Tue, 28 Jul 2026 21:10:22 +0000</pubDate>
      <link>https://dev.to/natuworkguy/meet-flash-cli-a-free-local-ai-agent-for-your-terminal-od1</link>
      <guid>https://dev.to/natuworkguy/meet-flash-cli-a-free-local-ai-agent-for-your-terminal-od1</guid>
      <description>&lt;p&gt;Cloud AI coding tools are powerful, but they also come with a bill, an API key, and a quiet upload of your source code to someone else's servers. What if your AI assistant ran entirely on your own machine instead?&lt;/p&gt;

&lt;p&gt;That is FLASH CLI (Fast Local Agent SHell): an AI-powered command-line assistant that talks to local or self-hosted Ollama models and can actually run shell commands for you. No API key. No subscription. No cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FLASH is different
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;100% local. It connects to an Ollama server, by default on localhost, so your code and prompts stay on your hardware.&lt;/li&gt;
&lt;li&gt;No keys, no bill. Ollama needs no API key, so there is nothing to pay for and nothing to leak.&lt;/li&gt;
&lt;li&gt;Truly agentic. FLASH does not just chat. It runs a real tool loop: it inspects your system, runs commands, searches the web, and shows its reasoning as it works.&lt;/li&gt;
&lt;li&gt;Model freedom. Point it at llama3.1, qwen2.5, mistral, or any tool-capable Ollama model, and swap with one setting.&lt;/li&gt;
&lt;li&gt;Local or remote. Set OLLAMA_HOST and the same client talks to a GPU box on your network or a server behind a reverse proxy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The four main tools
&lt;/h2&gt;

&lt;p&gt;FLASH gives the model a tight, powerful toolset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shell: run any command, non-interactively, with a timeout.&lt;/li&gt;
&lt;li&gt;web_search: pull live results from DuckDuckGo, built in.&lt;/li&gt;
&lt;li&gt;get_os: detect the operating system so it picks the right command every time.&lt;/li&gt;
&lt;li&gt;reason: surface a line of its thinking without ending the turn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It loops through plan, act, and observe until the job is done, then answers in rendered Markdown with syntax highlighting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Optional configuration lives in ~/.flash.env:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MODEL=llama3.1
OLLAMA_HOST=http://localhost:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it feels like
&lt;/h2&gt;

&lt;p&gt;Ask a question and let it work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Flash]&amp;gt; what are the biggest files here?
Thinking: check the OS, then find the largest files
Retrieving operating system information
Executing shell command: du -ah . | sort -rh | head -3
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a command yourself with the ! prefix, no AI in the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Flash]&amp;gt; !git status
On branch main
nothing to commit, working tree clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check or switch your backend on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Flash]&amp;gt; /model
Model: llama3.1
Host:  http://localhost:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handy built-in commands: /help, /model, /clear, and /bye.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built to run everywhere
&lt;/h2&gt;

&lt;p&gt;FLASH is tested on every push across Linux, macOS, and Windows on Python 3.10, 3.11, and 3.12, with Ruff, Flake8, and Bandit keeping it clean and secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the tour
&lt;/h2&gt;

&lt;p&gt;I made a short cinematic walkthrough of everything that you can watch above.&lt;/p&gt;

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

&lt;p&gt;If a private, local, zero-cost AI shell sounds like your kind of tool, give it a spin and drop a star.&lt;/p&gt;

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

&lt;p&gt;Your AI. Your machine. Your rules.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop Rewriting UI Components for Every Project</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Mon, 15 Jun 2026 03:15:50 +0000</pubDate>
      <link>https://dev.to/natuworkguy/stop-rewriting-ui-components-for-every-project-4nj2</link>
      <guid>https://dev.to/natuworkguy/stop-rewriting-ui-components-for-every-project-4nj2</guid>
      <description>&lt;p&gt;Ever started a new project and found yourself rebuilding the same modal, dropdown, toast notification, tabs, and switches for the 20th time?&lt;/p&gt;

&lt;p&gt;I got tired of that.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;UltraHTML&lt;/strong&gt;, a lightweight UI library that gives you modern components with simple HTML and JavaScript, no framework required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Include the CSS and JS files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"dist/ultra.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/ultra.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize UltraHTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Ultra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Buttons
&lt;/h2&gt;

&lt;p&gt;UltraHTML includes two button styles out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ultra-button&lt;/code&gt; — a clean, modern button.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ultra-button-wave&lt;/code&gt; — adds a wave/ripple-style interaction effect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basic button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Simple Button
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wave button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-button ultra-button-wave"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Wave Button
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buttons use UltraHTML's default green theme, but because they're standard HTML elements, you can easily customize them with CSS.&lt;/p&gt;

&lt;p&gt;For example, here's a red button that displays a popup message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"Ultra.popupmsg('Hello from UltraHTML!')"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-button ultra-button-wave"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background-color: red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Show Popup
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can create buttons that match your site's branding without learning a separate theming system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-button"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background-color: #3b82f6;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Blue Button
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-button ultra-button-wave"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background-color: #f59e0b;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Orange Button
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-button ultra-button-wave"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background-color: #ef4444;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Red Button
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UltraHTML handles the styling and interactions while still giving you full control over how your buttons look.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modal Example
&lt;/h2&gt;

&lt;p&gt;Need to display important information?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Ultra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;Ultra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Important&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You need to reset your password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;buttonText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;buttonAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Going to reset page...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;buttonAction&lt;/code&gt; is omitted, the modal automatically closes when the button is clicked.&lt;/p&gt;

&lt;p&gt;For simple informational dialogs, you only need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;Ultra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Thanks for visiting!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;buttonText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;buttonAction&lt;/code&gt; callback only when you need custom behavior, such as navigating to another page, submitting data, or running additional code before closing the modal.&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom title&lt;/li&gt;
&lt;li&gt;Custom text&lt;/li&gt;
&lt;li&gt;Custom button labels&lt;/li&gt;
&lt;li&gt;Automatic close behavior&lt;/li&gt;
&lt;li&gt;Optional callback support&lt;/li&gt;
&lt;li&gt;Clean styling out of the box&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Popup Notifications
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"Ultra.popupmsg('Hello from UltraHTML!')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Show Popup
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instant toast-style messages with one line of code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dropdowns
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-dropdown"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-dropdown-btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Choose Option
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-dropdown-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Action One&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Action Two&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Action Three&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Tabs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-tab-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-tab tab-active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Active Tab&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-tab"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Inactive Tab&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-tab tab-disabled"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disabled Tab&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Chips
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-chip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Clickable Chip
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-chip chip-permanent"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Permanent Chip
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Image chips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-chip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://picsum.photos/43/43"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Image Chip
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Switches
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-switch"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-switch-input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-switch-slider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Custom Right-Click Menus
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ultra-context-menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;📝 Edit&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;❌ Delete&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;🔗 Copy Link&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Ultra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and UltraHTML handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built UltraHTML
&lt;/h2&gt;

&lt;p&gt;Many UI libraries are powerful, but they're also huge, framework-dependent, or overkill for smaller projects.&lt;/p&gt;

&lt;p&gt;UltraHTML focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplicity&lt;/li&gt;
&lt;li&gt;Speed&lt;/li&gt;
&lt;li&gt;Lightweight code&lt;/li&gt;
&lt;li&gt;Modern styling&lt;/li&gt;
&lt;li&gt;Framework independence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes you just want a modal without installing 50 packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm actively developing UltraHTML and adding new components.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Natuworkguy/UltraHTML" rel="noopener noreferrer"&gt;https://github.com/Natuworkguy/UltraHTML&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love feedback, feature requests, and contributions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What component would you want to see added next?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
