<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nathan C.</title>
    <description>The latest articles on DEV Community by Nathan C. (@natuworkguy).</description>
    <link>https://dev.to/natuworkguy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3939269%2Fc5249dbd-4da9-49ee-8dfd-78b669b311fa.png</url>
      <title>DEV Community: Nathan C.</title>
      <link>https://dev.to/natuworkguy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/natuworkguy"/>
    <language>en</language>
    <item>
      <title>I put my AI agent on my Apple Watch, and it runs on my own computer</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sun, 01 Nov 2026 04:36:08 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-put-my-ai-agent-on-my-apple-watch-and-it-runs-on-my-own-computer-39e9</link>
      <guid>https://dev.to/natuworkguy/i-put-my-ai-agent-on-my-apple-watch-and-it-runs-on-my-own-computer-39e9</guid>
      <description>&lt;p&gt;Last week I was across the room when my AI agent stopped and asked me a yes/no question. It needed permission to run a command. My laptop was 20 feet away.&lt;/p&gt;

&lt;p&gt;That's when I decided the approval prompt belonged on my wrist.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Meet Flash for Apple Watch
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt; is the AI assistant I run locally. It's got chats, background agents called sparks, and team chats where those sparks talk to each other. &lt;strong&gt;Flash for Apple Watch&lt;/strong&gt; puts all of it on your wrist.&lt;/p&gt;

&lt;p&gt;It's open source: &lt;a href="https://github.com/Natuworkguy/flash-watch" rel="noopener noreferrer"&gt;https://github.com/Natuworkguy/flash-watch&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chats.&lt;/strong&gt; Start a new one by speaking or with Scribble. Pick up any earlier one and read the whole conversation, with Flash's tool steps folded into a single line so it fits a tiny screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approvals.&lt;/strong&gt; When Flash asks before doing something, Allow or Deny it from your wrist. If a turn's going the wrong way, stop it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sparks.&lt;/strong&gt; See what each background agent's doing and which ones need you. Approve what one's waiting on, run it now, pause it, or start a chat with it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team chats.&lt;/strong&gt; Read and write in your teams' chats. Reply to a message, react to it, and see every earlier version of a message a spark edited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read aloud.&lt;/strong&gt; Replies are read out in Flash's own voice when your computer's got one, or the watch's voice when it doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A tap on the wrist&lt;/strong&gt; when a reply finishes or a spark posts in the chat you've got open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The part I care about most: privacy
&lt;/h2&gt;

&lt;p&gt;The watch talks to Flash running on your own computer. There's no hosted backend and no account. Pairing uses an address plus a six-digit code that works once, for five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  But wait, there's more: it works away from home
&lt;/h2&gt;

&lt;p&gt;Wi-Fi only gets you so far. The moment you leave the house, a watch that can only see your home network is just a watch.&lt;/p&gt;

&lt;p&gt;So there's a &lt;strong&gt;Reach from anywhere&lt;/strong&gt; button, in the watch's Settings and under &lt;strong&gt;Watch&lt;/strong&gt; in Flash. Tap it and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your computer's tunnel turns on.&lt;/li&gt;
&lt;li&gt;The watch learns the tunnel's address on its own, so there's nothing to type.&lt;/li&gt;
&lt;li&gt;It keeps your Wi-Fi address too, and uses whichever one answers.&lt;/li&gt;
&lt;li&gt;When you get home, it goes back to the Wi-Fi address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tunnel addresses change, and that used to mean retyping them. Now the watch asks Flash for its current addresses every minute, so a new tunnel address just shows up. Your agent can ask for approval from a coffee shop, and you can answer from your wrist.&lt;/p&gt;

&lt;p&gt;Your computer still has to be on and running Flash. The tunnel is a way to reach it, not a place your data moves to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup takes about five minutes
&lt;/h2&gt;

&lt;p&gt;You'll need Flash on your computer, plus a Mac with Xcode 16 or later to put the app on your watch. A free Apple ID works.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the Watch panel to Flash:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/extension install github@Natuworkguy/flash-watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Let the watch reach Flash. Run &lt;code&gt;flash --web --lan&lt;/code&gt; (or &lt;code&gt;/web lan&lt;/code&gt;), or turn on the public link in Settings.&lt;/li&gt;
&lt;li&gt;Put your team in &lt;code&gt;watch/Local.xcconfig&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;DEVELOPMENT_TEAM&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ABCDE12345&lt;/span&gt;
&lt;span class="py"&gt;FLASH_BUNDLE_ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;com.yourname.flash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;With your watch on its charger and unlocked, run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scripts/watch.sh &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Watch&lt;/strong&gt; in Flash's sidebar, then type the address and code on your watch.&lt;/li&gt;
&lt;li&gt;Away from home? Tap &lt;strong&gt;Reach from anywhere&lt;/strong&gt; in the watch's Settings.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why a watch?
&lt;/h2&gt;

&lt;p&gt;Agents are getting more autonomous, and the human in the loop is now the bottleneck. A yes/no approval should take two seconds, not a walk back to your desk. The wrist is the one screen that's always with you.&lt;/p&gt;

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

&lt;p&gt;Clone it, break it, and tell me what's missing. Issues and PRs are welcome.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Natuworkguy/flash-watch" rel="noopener noreferrer"&gt;https://github.com/Natuworkguy/flash-watch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What would you want to control from your wrist? Tell me in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>swift</category>
      <category>watchos</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My agent kept saying "done." Now it has to prove it.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Sat, 31 Oct 2026 04:41:13 +0000</pubDate>
      <link>https://dev.to/natuworkguy/my-agent-kept-saying-done-now-it-has-to-prove-it-2h0f</link>
      <guid>https://dev.to/natuworkguy/my-agent-kept-saying-done-now-it-has-to-prove-it-2h0f</guid>
      <description>&lt;p&gt;&lt;em&gt;Two days, a dozen features, one rule: check it before you hand it over.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every agent has the same favorite word. "Done."&lt;/p&gt;

&lt;p&gt;The code is done. It doesn't compile. The spreadsheet is done. Cell C3 says &lt;code&gt;#DIV/0!&lt;/code&gt;. The deck is done. Slide 4 is blank.&lt;/p&gt;

&lt;p&gt;"Done" is the cheapest word a model can say. So this week, most of what I added to Flash, my local-first agent built on Ollama, makes it prove that word first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code isn't done until the tests pass
&lt;/h2&gt;

&lt;p&gt;Flash now has a &lt;code&gt;verify&lt;/code&gt; tool. It finds the checks a project already has (the test runner, linter and type checker in &lt;code&gt;pyproject.toml&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;Cargo.toml&lt;/code&gt;, &lt;code&gt;go.mod&lt;/code&gt; or a Makefile) and runs them.&lt;/p&gt;

&lt;p&gt;When something fails, it doesn't dump 400 lines of log. It names the failing tests and the files and lines to look at, then tells the model to fix the cause, never by skipping a test.&lt;/p&gt;

&lt;p&gt;If Flash changes code and tries to finish without checking, it gets one reminder. That's the whole mechanism: a nudge, not a cage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/review&lt;/code&gt; got the same treatment. It looks over a change for bugs, then argues against each finding before showing it. Whatever doesn't survive its own cross-examination never reaches you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Office files get opened, not assumed
&lt;/h2&gt;

&lt;p&gt;Code that writes a spreadsheet can't see the spreadsheet. So &lt;code&gt;check_office_file&lt;/code&gt; opens it the way you would: LibreOffice recalculates every formula and prints every page.&lt;/p&gt;

&lt;p&gt;Then Flash gets two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Facts:&lt;/strong&gt; &lt;code&gt;Costs!C3 shows #DIV/0!&lt;/code&gt;, page 2 is blank, slide 4 is empty, "TODO" left on page 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pictures&lt;/strong&gt; of the pages, to catch what no parser can, like text spilling out of its box or a table cut off at the margin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It fixes the script, makes the file again, and checks again. You get the file only once it's clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show it once
&lt;/h2&gt;

&lt;p&gt;Record your screen doing a task. Talk over it if you like. Flash pulls a still every time the screen changes, matches your words to each one, and writes the procedure as a skill: each step, what to click, and what the screen should look like after.&lt;/p&gt;

&lt;p&gt;Next time, there's a &lt;strong&gt;Do it now&lt;/strong&gt; button. It runs the skill, checks each screen against what the last step should have left, and stops to ask before anything that sends, pays, deletes or signs in. Any screen recording works now, not just ones made in Flash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chess, one move at a time
&lt;/h2&gt;

&lt;p&gt;Ask Flash to teach you the Italian Game and you get a board in the chat. Flash's moves slide into place. When it's your turn, you drag a piece, and the board tells you instantly whether that was the move. No model call, no waiting.&lt;/p&gt;

&lt;p&gt;Under it is a full rules engine in plain Python: castling, en passant, promotion, mate and stalemate. It produces exactly 8,902 positions three moves deep from the start, the number every chess engine is tested against.&lt;/p&gt;

&lt;p&gt;That engine is also the model's fact-checker. Local models misremember openings. Now a bad lesson comes back as an error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;step 1: 'e5' is not a legal move for White here. Legal moves: Na3, Nc3, Nf3, ...&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The model fixes the step and sends it again. You never see the wrong version. &lt;strong&gt;Don't ask a model to be right. Make wrong impossible to ship.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your house, with a guard rail
&lt;/h2&gt;

&lt;p&gt;Flash now talks to Home Assistant, so it reaches nearly any light, thermostat, blind or speaker without me writing a line per brand. "Kitchen lights to 30%, warm white" just works.&lt;/p&gt;

&lt;p&gt;One exception: locks, alarms and garage doors. Those ask you every time, even in fully autonomous mode. An agent can dim your lights on its own. It can't open your front door on its own.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read it to me.&lt;/strong&gt; "Read me this article" pulls out the article, skips the menus, and plays it in the chat with a waveform, speed control and download. Sound files play inline too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3D you can touch.&lt;/strong&gt; Models Flash makes now have parts that tell you what they are on hover, and open when clicked. A cabinet door swings on its hinge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A journal.&lt;/strong&gt; Every night Flash writes up the day's chats: what you worked on, what was decided, what was left open. Weeks and months roll up, and it can search them when you ask what you were doing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGENTS.md.&lt;/strong&gt; Flash follows the project instructions in the folder it works in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small things:&lt;/strong&gt; &lt;code&gt;/compact&lt;/code&gt; in the browser, drafts with code blocks shown in monospace, &lt;code&gt;flash://open&lt;/code&gt; links, and a &lt;code&gt;--dev&lt;/code&gt; flag on the installers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Around Flash
&lt;/h2&gt;

&lt;p&gt;Flash isn't alone anymore. Riptide, my animation editor, got real 3D (lit shapes, imported models, a camera) and a Flash extension: one click sends a model or picture from a Flash chat into the project open in Riptide, as a new layer. And Ember, the watch app that feeds ideas to Flash, now lets you edit a note right on your wrist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Looking back at the week, it's one idea, over and over. The model is good at writing. It's bad at knowing whether what it wrote works. So give it something that knows: a test suite, a spreadsheet engine, a rules engine, a human at the door.&lt;/p&gt;

&lt;p&gt;Then let it try again.&lt;/p&gt;

&lt;p&gt;Flash is open source and runs on your own machine: &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;. Ask it to teach you an opening. Then drag the wrong piece, just to see.&lt;/p&gt;

</description>
      <category>localai</category>
      <category>agents</category>
      <category>ollama</category>
      <category>opensource</category>
    </item>
    <item>
      <title>AI tutors don't teach. So I made mine stop talking.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Thu, 29 Oct 2026 04:25:15 +0000</pubDate>
      <link>https://dev.to/natuworkguy/ai-tutors-dont-teach-so-i-made-mine-stop-talking-1h78</link>
      <guid>https://dev.to/natuworkguy/ai-tutors-dont-teach-so-i-made-mine-stop-talking-1h78</guid>
      <description>&lt;p&gt;&lt;em&gt;Every AI can explain pointers. None of them can tell whether you understood.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Type "Teach me C" into any AI assistant. You'll get a beautiful lecture: five headings, a code block, and "let me know when you're ready to continue."&lt;/p&gt;

&lt;p&gt;You'll read it, nod along, and remember almost none of it by Friday.&lt;/p&gt;

&lt;p&gt;That isn't teaching. It's a textbook that talks back. Nothing makes you recall anything, nothing checks your work, and tomorrow it won't remember what you learned.&lt;/p&gt;

&lt;p&gt;So this week I rebuilt how my agent, Flash, teaches. The new rule is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model writes the course. It doesn't get to grade it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Now "Teach me C" builds a course
&lt;/h2&gt;

&lt;p&gt;You don't get a lecture. You get a path of lessons right in the chat, like a language-learning app. Each lesson is a few short paragraphs, then something you have to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick an answer.&lt;/li&gt;
&lt;li&gt;Type the missing word.&lt;/li&gt;
&lt;li&gt;Put scrambled steps back in order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write real code.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the point. Your C actually compiles. It runs in a throwaway folder, and Flash checks what it printed. Get it wrong and you see the compiler's errors, or your output next to the expected one.&lt;/p&gt;

&lt;p&gt;It works for C, C++, Python, JavaScript, Rust, Go, Java and Linux shell. You earn XP, keep a daily streak, and can go full screen when you want to focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero model calls to check your answer
&lt;/h2&gt;

&lt;p&gt;Every answer is marked right on the page. No round trip, no waiting, and no model deciding your code "looks about right."&lt;/p&gt;

&lt;p&gt;The model only comes back when you press &lt;strong&gt;Ask Flash&lt;/strong&gt;. Even then, it's told to give you a hint, never the answer.&lt;/p&gt;

&lt;p&gt;This runs on a laptop, on local models. Keeping the model out of the marking loop is what makes it feel instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  It knows what you know
&lt;/h2&gt;

&lt;p&gt;Every answer updates an estimate of whether you've actually got it. A lucky guess counts for less; so does a careless slip.&lt;/p&gt;

&lt;p&gt;Get a lesson right once and the next one opens. Then it comes back tomorrow, in 2 days, in 4, in 8. Get it wrong and it's back the next day.&lt;/p&gt;

&lt;p&gt;That's spaced repetition, built into a chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody tells you about small models
&lt;/h2&gt;

&lt;p&gt;Small models take the shortest path through your schema.&lt;/p&gt;

&lt;p&gt;Ask one for "the next unit" and you get six lessons with nothing to answer. Ask again and it teaches unit one a second time, under new titles.&lt;/p&gt;

&lt;p&gt;Pages of instructions didn't fix it. One rejected tool call did:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"structs has nothing to answer. Every step after a unit's introduction needs a quiz, a blank, an order or an exercise. Add the steps again, each with one."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Models ignore paragraphs of advice. They fix errors immediately.&lt;/strong&gt; If you want a model to behave, make the wrong move fail with a clear message.&lt;/p&gt;

&lt;h2&gt;
  
  
  I gave the agent a manual, with a test
&lt;/h2&gt;

&lt;p&gt;Flash now ships 34 guides on using its own tools: teaching, building web pages, making videos, handling email. The agent sees their names, and reads one only when a task needs it.&lt;/p&gt;

&lt;p&gt;The guides hold 116 example tool calls. Examples go stale the day you change a tool, so a test checks every one against the live tool definitions. It caught two broken examples on its first run.&lt;/p&gt;

&lt;p&gt;Extensions can ship guides too. If a plugin brings a tool, it also brings the manual for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also shipped this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web page to MP4.&lt;/strong&gt; The agent animates a page and renders it frame by frame on an exact clock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ears.&lt;/strong&gt; It measures any audio file for pitch, tempo, loudness and clipping, and writes sound from notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask your own files.&lt;/strong&gt; Your notes, PDFs and code, with every answer citing its source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blind model battles.&lt;/strong&gt; Two models answer anonymously, you pick the winner, and it keeps score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's 40 commits in three days, with 2,955 tests passing. Everything runs on your own machine.&lt;/p&gt;

&lt;p&gt;Open Flash, type "Teach me C", and write some code.&lt;/p&gt;

&lt;p&gt;&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>localai</category>
      <category>agents</category>
      <category>ollama</category>
    </item>
    <item>
      <title>I built a motion graphics editor that runs in a browser tab (and let an AI animate in it)</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Thu, 29 Oct 2026 01:30:26 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-built-a-motion-graphics-editor-that-runs-in-a-browser-tab-and-let-an-ai-animate-in-it-183k</link>
      <guid>https://dev.to/natuworkguy/i-built-a-motion-graphics-editor-that-runs-in-a-browser-tab-and-let-an-ai-animate-in-it-183k</guid>
      <description>&lt;p&gt;Every time I needed a 15-second animated clip, I had two options: pay for a motion graphics suite I would open twice a month, or fight a template site that exports a watermark.&lt;/p&gt;

&lt;p&gt;So I built my own. It's called &lt;strong&gt;Riptide&lt;/strong&gt;, and it runs in a browser tab.&lt;/p&gt;

&lt;p&gt;It's not a toy. The promo Short for it was made in it. Here's what it does, how it's built, and the bug that taught me the most.&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;Layers and keyframes.&lt;/strong&gt; Rectangles, ellipses, polygons, stars, arcs, waves, text, images and video. Any property can be keyframed: position, size, rotation, scale, opacity, colour, blur, stroke. There are 23 easing curves, plus ready-made presets like Pop in, Rise, Typewriter and Draw on that you drag onto a layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real timeline.&lt;/strong&gt; Zoom, per-property keyframe rows, trim bars, split at the playhead, markers, and snapping to the playhead, markers, keyframes and clip edges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio that behaves.&lt;/strong&gt; Multiple tracks that play together. Drag clips, trim them, split them, and drag the little dots on a clip's corners to fade in and out. Every track gets mixed into the export, fades and volumes included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design tools you expect.&lt;/strong&gt; Multi-select with Shift-click or a selection box, align and distribute, centre on canvas, and an aspect-ratio lock that images get by default so you never squash a photo by accident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export.&lt;/strong&gt; MP4, WebM, GIF, a PNG sequence or a still, up to 4K.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;The whole editor is plain JavaScript drawing to a single &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; with the 2D context. No framework, no WebGL.&lt;/p&gt;

&lt;p&gt;Every frame is rendered from scratch by one function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderScene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;visibleAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalCompositeOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blend&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source-over&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;drawLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one decision paid off over and over. The editor preview, the fullscreen viewer, the exporter and the frames an AI agent looks at all call the same function with a different context and scale. If it looks right in the editor, it exports right.&lt;/p&gt;

&lt;p&gt;The backend is a small Flask server whose main job is handing frames to &lt;code&gt;ffmpeg&lt;/code&gt;. The browser renders each frame and uploads it, then the server hands the frames to &lt;code&gt;ffmpeg&lt;/code&gt; with the audio tracks mixed in, fades included. A PNG sequence export doesn't even need &lt;code&gt;ffmpeg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Keyboard shortcuts live in one table:&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Align&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Centre on canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alt+c&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="nf"&gt;centerOnCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xy&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Select all layers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mod+a&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="nf"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That table drives the shortcuts, the cheat sheet and the command palette, so the three can never disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part people ask about: an AI that animates live
&lt;/h2&gt;

&lt;p&gt;Riptide has a built-in &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; server. Any agent that speaks MCP can connect to the open project and work in it: add layers, set keyframes, apply presets, align things, add audio, and render a frame to look at its own work.&lt;/p&gt;

&lt;p&gt;The important design choice: agent edits go through the exact same path as yours. They show up on the canvas instantly, they can be undone with Cmd+Z, and the file is marked as edited. The agent isn't generating a file you import later. It is a second pair of hands in the same document.&lt;/p&gt;

&lt;p&gt;Asking an agent to "make the title pop in on the beat and centre everything" and watching it happen on screen is the closest thing to magic I've built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that broke the whole window
&lt;/h2&gt;

&lt;p&gt;My favorite bug from this project was one word long.&lt;/p&gt;

&lt;p&gt;Dragging the divider between the Layers and Media panels made the entire window flicker between the normal layout and a scrambled one. I fixed the obvious suspect first: a feedback loop where the panel scrolled, the measurement shifted, and the next size came out wrong. Still broken.&lt;/p&gt;

&lt;p&gt;The real cause: while you dragged, the code added a class to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; to show the resize cursor. The class for vertical resizing was &lt;code&gt;row&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The app already had a &lt;code&gt;.row&lt;/code&gt; class. It styles the little input fields in the side panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So for the length of every drag, the whole page became a 26-pixel-tall flex row.&lt;/p&gt;

&lt;p&gt;The fix was to stop putting generic class names on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; and use a namespaced attribute instead:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resizing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 'x' or 'y'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-resizing&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;user-select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-resizing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;y&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row-resize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson I took away: state on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; is global, so name it like a global. And when a test measures sizes but never looks at the screen mid-drag, it can pass while the user watches the page collapse.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you if you're building something similar
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Render everything from one function.&lt;/strong&gt; Preview, export and thumbnails should never have their own drawing code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the agent use the user's edit path.&lt;/strong&gt; Undo, dirty state and live updates come for free, and you never have two sources of truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure from the start of a drag, not the live layout.&lt;/strong&gt; Anything that re-reads the layout mid-drag can feed back into itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look at the screen.&lt;/strong&gt; Numbers in a test log are not the same as what a person sees.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;What would you want to see in a browser-based motion editor? And if you've hit a one-word CSS bug that took down a whole page, I want to hear it in the comments.&lt;/p&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/Natuworkguy/Riptide" rel="noopener noreferrer"&gt;Natuworkguy/Riptide&lt;/a&gt; on GitHub.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>I gave my AI agent a TikTok feed. Now it's the first thing I open.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Fri, 23 Oct 2026 18:05:10 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-gave-my-ai-agent-a-tiktok-feed-now-its-the-first-thing-i-open-1ac2</link>
      <guid>https://dev.to/natuworkguy/i-gave-my-ai-agent-a-tiktok-feed-now-its-the-first-thing-i-open-1ac2</guid>
      <description>&lt;p&gt;I build &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, an open source AI agent that runs in your terminal and in a web UI. It reads my email, texts people, runs background agents (I call them sparks), waits on things for me, and since last week, it can even drive my mouse.&lt;/p&gt;

&lt;p&gt;Every morning I had the same problem. Flash had been busy all night and the results were scattered: a spark's report here, a finished wait there, three unread emails, a chat that answered after I closed the tab.&lt;/p&gt;

&lt;p&gt;So I gave it a feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Briefing: your morning, one swipe at a time
&lt;/h2&gt;

&lt;p&gt;Hit &lt;code&gt;Alt+B&lt;/code&gt; (or &lt;strong&gt;Briefing&lt;/strong&gt; in the sidebar, or &lt;code&gt;/briefing&lt;/code&gt;) and Flash goes full screen. One card at a time, scroll-snapped, just like a video feed. Swipe on a phone, &lt;code&gt;j&lt;/code&gt; and &lt;code&gt;k&lt;/code&gt; on a keyboard.&lt;/p&gt;

&lt;p&gt;Each card is one thing that happened while you were away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an unread email or iMessage&lt;/li&gt;
&lt;li&gt;a spark's report you haven't read&lt;/li&gt;
&lt;li&gt;a wait that finished, or gave up&lt;/li&gt;
&lt;li&gt;a chat that answered after you left&lt;/li&gt;
&lt;li&gt;a reminder coming up in the next day (these go first)&lt;/li&gt;
&lt;li&gt;something new from a connector a spark is watching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every card gets the same four actions, down the right side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reply&lt;/strong&gt;: Flash opens a new chat with the reply already started (&lt;code&gt;Reply to the email from Ada about "Can we move Thursday's review?". Say:&lt;/code&gt;), and you finish the sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open&lt;/strong&gt;: the chat, the spark's report, or the email itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask Flash&lt;/strong&gt;: a new chat with the card's contents already in the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dismiss&lt;/strong&gt;: gone for good. It never comes back, even if you refresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;r&lt;/code&gt;, &lt;code&gt;o&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;d&lt;/code&gt; do the same from the keyboard, and &lt;code&gt;Esc&lt;/code&gt; closes the feed with a fade. A rail on the right shows how far you are, and the last card says you're all caught up, with &lt;strong&gt;Dismiss all&lt;/strong&gt; if you just want a clean slate.&lt;/p&gt;

&lt;p&gt;A few decisions I'm happy with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It looks back one day.&lt;/strong&gt; A briefing that shows you last week isn't a briefing, it's an inbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A source that's down doesn't break the feed.&lt;/strong&gt; If your mail server times out, you still get everything else, and the last card says what it couldn't reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each card's colour is its kind.&lt;/strong&gt; Blue for email, green for iMessage, purple for sparks. After two days you stop reading the label.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Teams report in
&lt;/h2&gt;

&lt;p&gt;Sparks can work in teams, with a lead, a team chat, and shared rules. With five sparks on a team, five separate report cards were noise.&lt;/p&gt;

&lt;p&gt;So a team gets &lt;strong&gt;one card&lt;/strong&gt;, and only when something changed since your last briefing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch crew                      2 of 3 with news
Scout: Found two new issues on the repo and labelled both.
Fixer (needs you): The tunnel fix is ready. May I merge it?
3 new messages in the team chat
1 other: nothing new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Open&lt;/strong&gt; takes you straight into the team chat. Open the briefing again and nothing has changed? No team card. Silence is a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forks: try another way without losing this one
&lt;/h2&gt;

&lt;p&gt;Every chat app makes you choose: edit a message and lose what came after, or start over in a new chat.&lt;/p&gt;

&lt;p&gt;In Flash you &lt;strong&gt;fork from any reply&lt;/strong&gt;. You stay in the same chat, it carries on from that reply, and the way it went before is kept. Once a chat has forks, a &lt;strong&gt;Forks&lt;/strong&gt; button appears at the top and shows them as a tree: forks of forks, each under the reply it came from, with its last words and how long it is. Click one to carry on where it left off.&lt;/p&gt;

&lt;p&gt;And because trees get messy, every fork has a bin. Delete one and its own forks move up a level. Delete the one you're on and you go back to where it came from. Get down to one fork and the button disappears, because a chat with one fork is just a chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loaders that tell you what the agent is doing
&lt;/h2&gt;

&lt;p&gt;"Thinking..." with three dots tells you nothing. Flash's loader now shows &lt;strong&gt;what it's actually doing&lt;/strong&gt;, each one a little line drawing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a hand with a quill, writing, while it edits files&lt;/li&gt;
&lt;li&gt;a magnifier hopping across lines of text while it searches&lt;/li&gt;
&lt;li&gt;a terminal typing a command while it runs one&lt;/li&gt;
&lt;li&gt;a book turning its pages while it reads&lt;/li&gt;
&lt;li&gt;a camera focusing and flashing while it looks at a page or your screen&lt;/li&gt;
&lt;li&gt;a paper plane looping off while it sends an email or a message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two confessions here.&lt;/p&gt;

&lt;p&gt;First, I originally made these &lt;strong&gt;stop motion&lt;/strong&gt;, choppy on purpose, a few frames a second. It looked charming in a screenshot. After living with it, it felt like the page was lagging. Now every sketch is redrawn on every frame and glides.&lt;/p&gt;

&lt;p&gt;Second, "looking" used to be an &lt;strong&gt;eye&lt;/strong&gt;. It looked around. It blinked. It was watching me take screenshots of my own screen. That was creepy, so now it's a camera.&lt;/p&gt;

&lt;p&gt;When Flash isn't doing anything specific, the loader &lt;strong&gt;morphs&lt;/strong&gt; into a different one every ten seconds, including &lt;strong&gt;Singularity&lt;/strong&gt;, a black hole that pulls every dot in and blows them back out. That's on by default now.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diagrams and slide decks.&lt;/strong&gt; Flash draws Mermaid diagrams inline, and writes Markdown slide decks with ten themes, per-slide backgrounds and transitions (fade, slide, push, zoom, flip, rise, wipe). Hit &lt;strong&gt;Present&lt;/strong&gt; and it's full screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project memory.&lt;/strong&gt; Each project keeps its own memory, separate from yours. What Flash learns about one codebase stays there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files from other chats.&lt;/strong&gt; &lt;code&gt;+ &amp;gt; From another chat&lt;/code&gt; lists every file across your chats, including ones Flash made, like that post your agent wrote yesterday. Pick one and it's attached again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinned chats&lt;/strong&gt;, and a down arrow that appears whenever you scroll up in a chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A waiting bar.&lt;/strong&gt; When a chat is frozen waiting for something, a bar over the message box says what for, with Cancel right there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice settings that make sense.&lt;/strong&gt; Listening and speaking models are grouped by engine, and the terminal got its own pickers: &lt;code&gt;/voice listen&lt;/code&gt; and &lt;code&gt;/voice speak&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunrise&lt;/strong&gt;, a new palette: warm cream, a bright orange, and a stepped red-into-yellow stripe on the send button.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A feed beats a dashboard.&lt;/strong&gt; A dashboard shows you everything at once and you read none of it. One card at a time, with an action on each, and you actually clear it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group by who, not by what.&lt;/strong&gt; A team's status is one thought. Five report cards from the same team are five interruptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Charming in a screenshot isn't charming at 60 seconds.&lt;/strong&gt; Choppy animation and a staring eye both looked great in a demo, and both had to go once I'd lived with them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never make the user choose between two versions of a conversation.&lt;/strong&gt; Forking is cheap. Losing a good answer isn't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Flash is open source: &lt;strong&gt;&lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;&lt;/strong&gt;. All of this is on the &lt;code&gt;dev&lt;/code&gt; branch right now.&lt;/p&gt;

&lt;p&gt;What would you want in your morning briefing? And would you rather swipe through your inbox or keep reading it top to bottom? Tell me in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I gave my AI agent my mouse. Its first click was on itself.</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Fri, 23 Oct 2026 04:36:50 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-gave-my-ai-agent-my-mouse-its-first-click-was-on-itself-4lfo</link>
      <guid>https://dev.to/natuworkguy/i-gave-my-ai-agent-my-mouse-its-first-click-was-on-itself-4lfo</guid>
      <description>&lt;p&gt;I build &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, an open source AI agent that runs in your terminal and in a web UI. This week I shipped two features that scared me a little:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Computer Use (Beta).&lt;/strong&gt; Flash takes a screenshot, decides where to click, and works the mouse and keyboard itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UltraAuto.&lt;/strong&gt; A third permission mode where Flash and its background agents &lt;strong&gt;never stop to ask you anything&lt;/strong&gt;. Not for commands, not for email, not for iMessages, not for taking over your mouse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then I tested the first one on my own Mac. Here is the unedited story, straight from the chat log.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Control my computer"
&lt;/h2&gt;

&lt;p&gt;That was my whole first message. Flash didn't grab the mouse. It asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sure, I can do that. What would you like me to do? For simple things like music, volume, or opening apps, I can do it right away. If it needs me to click around on screen, I'll ask your permission first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair. So I said: &lt;em&gt;"Open like Visual Studio Code and click around on screen and just do something."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It opened VS Code with its normal app tool (no mouse needed), then reached for Computer Use and hit the permission prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Let Flash see your screen and use your mouse and keyboard for this reply?
Press Control+Option+Esc, or move the mouse, to take it back.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I said yes. That yes lasts &lt;strong&gt;one reply&lt;/strong&gt;. The next reply that wants the mouse asks again, even in autonomous mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round one: it clicked on itself
&lt;/h2&gt;

&lt;p&gt;The first screenshot showed... my browser. VS Code had opened &lt;em&gt;behind&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;Watching the agent debug this was the best part. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Waited 3 seconds and took another screenshot. Still the browser.&lt;/li&gt;
&lt;li&gt;Tried to open an app called &lt;code&gt;Code&lt;/code&gt;. That failed: &lt;code&gt;Unable to find application named 'Code'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dropped to the shell to find out what was actually going on:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /Applications | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"code|visual"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; pgrep &lt;span class="nt"&gt;-il&lt;/span&gt; &lt;span class="s2"&gt;"Electron|Code"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Saw that VS Code was running but not in front, and forced it forward:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Visual Studio Code" to activate'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it clicked the README tab and the Search icon. Except focus had already jumped back to the browser, and the browser was showing &lt;strong&gt;Flash's own web UI&lt;/strong&gt;. So its first two real clicks on my computer landed on Flash: one on its own model picker, one that started a new chat.&lt;/p&gt;

&lt;p&gt;Its own thinking, right after:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Oh no, the browser regained focus and my clicks landed in the Flash web UI instead. Mostly harmless, but I should be careful about where focus is before clicking next time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent's first act with a mouse was poking itself. I've never related to software more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I took the mouse back
&lt;/h2&gt;

&lt;p&gt;On its next click, I moved my mouse. The tool refused to click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The user moved the mouse, which takes the computer back.
Stop here: do not try again unless they ask.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That isn't the model being polite. It's built in. Every time Flash moves the pointer, it remembers where it left it. Before the next action it checks where the pointer actually is. If it's more than 24 pixels off, you've touched the mouse, the action doesn't happen, and the whole reply stops.&lt;/p&gt;

&lt;p&gt;There's a hard stop key too: &lt;strong&gt;Control+Option+Esc&lt;/strong&gt; on a Mac, &lt;strong&gt;Ctrl+Alt+Esc&lt;/strong&gt; elsewhere. A watcher polls the keyboard state the whole time the agent holds the mouse. Flash refuses to press those keys itself, so a confused model can't swallow your escape hatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round two: it worked
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"Just click around, do something, open some apps. Just so you know that it works."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;New reply, so it asked for permission again. This time it brought VS Code forward &lt;strong&gt;before&lt;/strong&gt; looking, confirmed it with a screenshot, and then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clicked the README.md tab,&lt;/li&gt;
&lt;li&gt;opened the Search panel in the sidebar,&lt;/li&gt;
&lt;li&gt;clicked Finder in the Dock, which opened a window on my Applications folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its summary owned the earlier mistake:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My first couple of clicks landed in your browser instead of VS Code. That's why the first try stopped and the second one did too. I brought VS Code forward with a command, and after that it behaved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Six tool calls, about 48 seconds. Not fast, but it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safety reviewer said no (twice), and that was good
&lt;/h2&gt;

&lt;p&gt;Next I asked it to &lt;em&gt;"close the windows I'm not using, like Finder, Device Hub, Xcode, etcetera."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was in autonomous mode with &lt;strong&gt;System One&lt;/strong&gt; on. System One is a small 0.8B model that reviews every command and edit before it runs. Flash's first idea was a broad shell command listing every process and its windows. System One blocked it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Not run: System One judged that this call may not be what was asked (on task 0.47).
Do not retry it as it is. Take a safer way that does what the user asked,
or stop and ask the user whether to go ahead.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the agent did the more careful thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It took a screenshot.&lt;/li&gt;
&lt;li&gt;It found the one Finder window it could see and clicked its red close button.&lt;/li&gt;
&lt;li&gt;Then it &lt;strong&gt;told me it couldn't see Xcode or Device Hub&lt;/strong&gt;, so it hadn't closed anything else.&lt;/li&gt;
&lt;li&gt;It asked before quitting whole apps, because that could lose unsaved work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I said yes. It quit Xcode by name. For Device Hub it tried another &lt;code&gt;ls | grep&lt;/code&gt; to check the exact name, and System One blocked that too (&lt;code&gt;safe 0.50&lt;/code&gt;, &lt;code&gt;on task 0.41&lt;/code&gt;). So it called quit on "Device Hub" directly, and that worked.&lt;/p&gt;

&lt;p&gt;Two blocks, two narrower fixes, and no claims about windows it couldn't see. That's how I want an agent with a mouse to behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Learn from your mistakes"
&lt;/h2&gt;

&lt;p&gt;That's all I said at the end. Flash wrote itself a skill, a reusable playbook it loads next time I ask for this kind of work. Its first try failed, because skill descriptions are capped at 120 characters (they're sent on every turn) and it wrote 122. It trimmed it and saved this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Steps&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Open the app with &lt;span class="sb"&gt;`computer`&lt;/span&gt; open_app, using the exact name from
   /Applications (e.g. "Visual Studio Code", not "Code").
&lt;span class="p"&gt;2.&lt;/span&gt; open_app can leave the app behind other windows. Bring it forward with
   shell: &lt;span class="sb"&gt;`osascript -e 'tell application "Visual Studio Code" to activate'`&lt;/span&gt;,
   then take a computer_use screenshot and confirm the right app is in
   front BEFORE any click.
&lt;span class="p"&gt;3.&lt;/span&gt; Click only after the screenshot shows the target. Use harmless clicks
   (tabs, sidebar icons, Finder, Dock). Never click in a terminal, type
   text, or touch files unless asked.
...
&lt;span class="gu"&gt;## Pitfalls&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; First clicks landed in the browser (Flash UI) because VS Code was not
  frontmost. Always check the screenshot first.
&lt;span class="p"&gt;-&lt;/span&gt; If the user moves the mouse, computer_use stops and takes control back.
&lt;span class="p"&gt;-&lt;/span&gt; Do not claim windows are closed when they were not visible.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is my favorite thing an agent has ever written about itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Computer Use works
&lt;/h2&gt;

&lt;p&gt;No cloud browser, no virtual machine. It's your real desktop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots:&lt;/strong&gt; macOS's &lt;code&gt;screencapture&lt;/code&gt;, scaled down to at most 1280px wide. The model's click coordinates get mapped back to real screen points, so Retina displays just work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mouse and keyboard:&lt;/strong&gt; Core Graphics events, posted through &lt;code&gt;ctypes&lt;/code&gt; from a separate Python process. Windows uses &lt;code&gt;user32&lt;/code&gt; plus PowerShell, and Linux uses &lt;code&gt;xdotool&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions:&lt;/strong&gt; macOS asks for Accessibility (input) and Screen Recording (screenshots) for whatever app Flash runs in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; the tool description tells the model it's &lt;strong&gt;only for what no other tool can do&lt;/strong&gt;. Flash already has tools for the shell, files, a headless browser, music, volume and apps. In my test it opened VS Code and quit Xcode with those, and only used the mouse for actual clicking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One at a time:&lt;/strong&gt; only one chat can hold the computer. A second one gets "another chat is using the computer right now."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's off by default. Turn it on in Settings under &lt;strong&gt;Computer Use&lt;/strong&gt; (it has a Beta chip for a reason), or with &lt;code&gt;/computer use on&lt;/code&gt; in the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  UltraAuto: the mode where nothing asks
&lt;/h2&gt;

&lt;p&gt;Flash already had two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ask first:&lt;/strong&gt; you approve every command and edit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto:&lt;/strong&gt; commands and edits just run, with System One reviewing them if you turn it on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even in Auto, a few things always stopped and waited for me: sending an email, sending an iMessage, a background agent proposing to hire another agent, a wait that runs a command over and over, and Computer Use. Those are the things you can't take back.&lt;/p&gt;

&lt;p&gt;Sometimes I don't want to be asked at all. So &lt;strong&gt;UltraAuto&lt;/strong&gt; asks about nothing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commands and edits run.&lt;/li&gt;
&lt;li&gt;Email and iMessages send without a preview.&lt;/li&gt;
&lt;li&gt;Agents' proposals to hire or let go of other agents are approved.&lt;/li&gt;
&lt;li&gt;Agents answering in a team chat get every tool a shift has.&lt;/li&gt;
&lt;li&gt;Connected MCP tools that change things run.&lt;/li&gt;
&lt;li&gt;Waits run their commands without asking.&lt;/li&gt;
&lt;li&gt;Computer Use takes the mouse without asking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;System One stays on.&lt;/strong&gt; If it's enabled, it still reviews every command and edit. After watching it block two sloppy shell commands in a five-minute test, I wasn't going to turn that off.&lt;/p&gt;

&lt;p&gt;Because this is genuinely dangerous, it's hard to turn on by accident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's hidden until you enable &lt;strong&gt;Offer UltraAuto&lt;/strong&gt; in Settings.&lt;/li&gt;
&lt;li&gt;Flipping the switch doesn't do it. It shows a red warning listing exactly what will stop asking, and you have to press &lt;strong&gt;"I understand: offer UltraAuto."&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In the terminal, &lt;code&gt;/auto ultra&lt;/code&gt; prints the same warning and makes you type &lt;code&gt;ultra&lt;/code&gt; back.&lt;/li&gt;
&lt;li&gt;Only then does the mode button by the message box become a three-way picker (&lt;em&gt;Ask first / Auto / UltraAuto&lt;/em&gt;), and Shift+Tab cycles through all three.&lt;/li&gt;
&lt;li&gt;Turning the setting off drops you out of UltraAuto immediately.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Focus is the real enemy.&lt;/strong&gt; Clicking is easy. Knowing which window is in front is the hard part. Screenshot, act, screenshot again, every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make stopping physical.&lt;/strong&gt; Telling the model "stop if the user wants" doesn't cut it. Moving your mouse should beat any instruction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask per reply, not per click.&lt;/strong&gt; Asking before every click would be unusable. Asking once per reply keeps a human in the loop without the nagging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small reviewer pays off.&lt;/strong&gt; A 0.8B model saying "that's not what was asked" made a big model find the narrower, safer path twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let agents write down their own mistakes.&lt;/strong&gt; The skill it saved is more useful than any prompt I would have written.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Flash is open source: &lt;strong&gt;&lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;&lt;/strong&gt;. Computer Use and UltraAuto are on the &lt;code&gt;dev&lt;/code&gt; branch right now.&lt;/p&gt;

&lt;p&gt;Would you let an agent take your mouse? And would you ever turn on a mode where it never asks? Tell me in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>macos</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Never lose an idea again with Ember</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Thu, 22 Oct 2026 07:05:41 +0000</pubDate>
      <link>https://dev.to/natuworkguy/stop-losing-ideas-with-ember-4fc9</link>
      <guid>https://dev.to/natuworkguy/stop-losing-ideas-with-ember-4fc9</guid>
      <description></description>
    </item>
    <item>
      <title>I kept losing ideas, so I built an Apple Watch app that feeds my AI agent</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:53:53 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-kept-losing-ideas-so-i-built-an-apple-watch-app-that-feeds-my-ai-agent-1941</link>
      <guid>https://dev.to/natuworkguy/i-kept-losing-ideas-so-i-built-an-apple-watch-app-that-feeds-my-ai-agent-1941</guid>
      <description>&lt;p&gt;How many good ideas did you lose this week?&lt;/p&gt;

&lt;p&gt;Not bad ideas. Good ones. The kind that show up in the shower, on a walk, or just before you fall asleep, when your phone is nowhere near you. You tell yourself you'll remember. You don't.&lt;/p&gt;

&lt;p&gt;The mind is for having ideas, not for holding them.&lt;/p&gt;

&lt;p&gt;Every note app has the same flaw: it asks for work at the exact moment you have the least patience for it. Find the phone. Unlock it. Open the app. Start typing. By then, the idea has cooled.&lt;/p&gt;

&lt;p&gt;The best tools don't ask for your attention. They get out of its way.&lt;/p&gt;

&lt;p&gt;So I set one rule: &lt;strong&gt;one tap, one sentence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That became &lt;strong&gt;Ember&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Ember is an Apple Watch app, a small Python server, and a Flash extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Apple Watch                      your computer                       Flash
 -----------                      -------------                       -----
 tap, speak, save  -- Wi-Fi --&amp;gt;   ember serve  &amp;lt;-- extension ---  "what ideas did I
 kept on the watch                notes.db, recordings              have this week about
 until home                       a banner on each new idea         my game engine?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standalone watch app.&lt;/strong&gt; No iPhone app, and no iPhone needed at all. It even works on a Family Setup watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local.&lt;/strong&gt; The server is one SQLite file on your computer. Nothing goes anywhere else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queryable by an AI agent.&lt;/strong&gt; &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, my local agent, can search, add to, and act on your ideas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The payoff is the last part. I can ask Flash, "What ideas did I have this week about my game engine?" and it searches my notes and quotes them back with dates. It asks me before it saves, changes, or deletes anything.&lt;/p&gt;

&lt;p&gt;A note stops being a note and becomes a working memory.&lt;/p&gt;

&lt;p&gt;A note you never reread is just a memory with extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five problems that made this interesting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. watchOS has no speech recognition API
&lt;/h3&gt;

&lt;p&gt;I assumed I'd use Apple's Speech framework. I was wrong. &lt;code&gt;SFSpeechRecognizer&lt;/code&gt; and the newer &lt;code&gt;SpeechAnalyzer&lt;/code&gt; exist on iPhone, iPad, Mac and Vision Pro. Not on watchOS.&lt;/p&gt;

&lt;p&gt;So Ember uses the watch's own system dictation through SwiftUI's &lt;code&gt;TextFieldLink&lt;/code&gt;. It's the same engine as Messages. On Apple Watch Series 9 and later, and Ultra 2, it runs entirely on the wrist.&lt;/p&gt;

&lt;p&gt;For everything else there's a &lt;strong&gt;Record&lt;/strong&gt; button. The watch saves a 16 kHz WAV, and when it syncs, the server transcribes it locally with Moonshine or Vosk. The text then syncs back to the watch.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A note must never be lost
&lt;/h3&gt;

&lt;p&gt;Every idea is written to &lt;strong&gt;SwiftData on the watch first&lt;/strong&gt;, before anything touches the network. If you're on a trail with no signal, it waits and keeps trying.&lt;/p&gt;

&lt;p&gt;watchOS also wakes the app about every half hour to retry in the background, so notes go up even if you never open it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Syncing flaky networks without duplicates
&lt;/h3&gt;

&lt;p&gt;The client makes the note's UUID, so sending the same note twice saves it once. The API is idempotent by design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/notes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4a9c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chunked terrain streaming for the #engine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-21T08:15:00.000000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The watch pulls changes with &lt;code&gt;GET /notes?since=&amp;lt;rev&amp;gt;&lt;/code&gt;. Every write bumps a revision number, and deletions come back as tombstones, so a note deleted on your computer disappears from the watch too.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pairing without typing an IP address
&lt;/h3&gt;

&lt;p&gt;Nobody wants to type &lt;code&gt;192.168.1.42&lt;/code&gt; on a watch keyboard.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ember pair&lt;/code&gt; prints a code like &lt;code&gt;2232 4829 136&lt;/code&gt;. The code carries your computer's address, so you never type an IP or a &lt;code&gt;.local&lt;/code&gt; name, and the last digit catches typos. It works once, for ten minutes, and five wrong tries use it up. The token then goes into the watch's Keychain.&lt;/p&gt;

&lt;p&gt;If your router later hands the computer a new address, the watch falls back to the &lt;code&gt;.local&lt;/code&gt; name it learned while pairing.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Making it feel instant
&lt;/h3&gt;

&lt;p&gt;The watch has a big capture button, complications for any watch face, Siri ("New idea in Ember"), the Smart Stack, and the Action Button on an Ultra. Say "hashtag engine" and the note is tagged &lt;code&gt;#engine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the computer, &lt;code&gt;ember&lt;/code&gt; opens a full-screen terminal browser with search-as-you-type, tag filtering, and playback of recordings. Ideas from your watch slide in while it's open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local?
&lt;/h2&gt;

&lt;p&gt;Your thoughts are the most personal data you have. They should live where you do.&lt;/p&gt;

&lt;p&gt;There's no account to create, no subscription, and no third party holding a copy. The cost is that you run a server. I think that's a good trade, and &lt;code&gt;scripts/server.sh&lt;/code&gt; makes it a one-liner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell you if you build something like this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save first, sync later&lt;/strong&gt; is a better rule than I expected. Treat the network as an optimization, never a dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency is a gift.&lt;/strong&gt; Let the client pick the ID and a whole category of sync bugs disappears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform limits help.&lt;/strong&gt; No Speech API on the watch forced a simpler, more robust design than I'd have chosen.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I want Ember to do more with what it holds: connecting related ideas and surfacing an old one at the right moment.&lt;/p&gt;

&lt;p&gt;The future won't belong to those with the most ideas. It will belong to those who keep them.&lt;/p&gt;

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

&lt;p&gt;Ember is MIT licensed. &lt;a href="https://github.com/Natuworkguy/Ember" rel="noopener noreferrer"&gt;Natuworkguy/Ember&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every tool you trust began as a thought someone almost let go.&lt;/p&gt;

&lt;p&gt;If you've ever lost a great idea in the shower, I'd love to hear how you capture them. Tell me in the comments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my series on building Flash. Follow along at &lt;a href="https://dev.to/natuworkguy"&gt;dev.to/natuworkguy&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Catch the ember before it goes out.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>swift</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>I'm finally fine-tuning my model, because its prompt hit 175,000 characters</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:48:17 +0000</pubDate>
      <link>https://dev.to/natuworkguy/im-finally-fine-tuning-my-model-because-its-prompt-hit-175000-characters-528a</link>
      <guid>https://dev.to/natuworkguy/im-finally-fine-tuning-my-model-because-its-prompt-hit-175000-characters-528a</guid>
      <description>&lt;p&gt;Line 60 of my model's prompt says "Never output em-dashes, in any form." Line 976 says it again, in case it forgot. The agent it runs in says it a third time. It still slips them in, so that agent ends up ripping them out of every reply in code, a token at a time, as it streams.&lt;/p&gt;

&lt;p&gt;That's when I admitted the prompt had gotten out of hand.&lt;/p&gt;

&lt;p&gt;Every version of Onyx, the model behind my local AI agent &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, has secretly been a prompt. Not fine-tuned weights. A Modelfile wrapped around an open-weights base, with the system prompt doing all the work. Every time Onyx did something dumb, I wrote a rule. Onyx 2.6's prompt is now 174,826 characters long. That's about 980 lines of rules the model has to keep in mind while it works, and every new chat pays for all of them before it writes its first word. With Flash's own prompt and tool list on top, a fresh chat starts at around 256,000 characters.&lt;/p&gt;

&lt;p&gt;So I'm finally doing the thing I put off for eight versions: fine-tuning. Onyx 3 moves the prompt into the weights. The plan cuts the system prompt to 650 characters and a new chat to around 82,000.&lt;/p&gt;

&lt;p&gt;The training pipeline is done and tested end to end. The real run hasn't happened yet. Here's how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distillation, not a dataset
&lt;/h2&gt;

&lt;p&gt;I don't have a dataset of "good Onyx answers", and writing thousands by hand wasn't happening. But I do have a model that already gives them: Onyx 2.6, with its giant prompt, on the biggest base it runs on.&lt;/p&gt;

&lt;p&gt;So 2.6 is the teacher. It answers a few thousand requests the way it would inside Flash, tools and all. Then the smaller base gets fine-tuned to give those same answers under the 650-character prompt. What the long prompt taught becomes what the model does on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the requests come from
&lt;/h2&gt;

&lt;p&gt;I wrote 161 requests by hand across 22 categories: code, work, everyday life, knowledge and plain talk. A script has the teacher write more in the same style until there are a couple thousand, weighted toward code. Another 54 are held back for the benchmark and never trained on.&lt;/p&gt;

&lt;p&gt;Each request gets answered twice, inside a fresh copy of a small fixture project (a little Python shop, a webpage, a folder of notes and receipts). The teacher really uses Flash's tools: it reads files, edits them and runs the tests. Since it's running real shell commands, that step runs inside a throwaway Docker container that holds nothing else.&lt;/p&gt;

&lt;p&gt;Every conversation also starts Flash with an empty home folder. My memories, sparks, settings and email never reach a prompt, so they never reach the weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grading the teacher
&lt;/h2&gt;

&lt;p&gt;The teacher isn't perfect either, which is kind of the point of all those rules. So every answer has to pass two filters before it's trained on.&lt;/p&gt;

&lt;p&gt;First, hard rule checks in code. If it breaks one, it's dropped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no em dash, and nothing standing in for one (a double hyphen, a spaced dash between clauses)&lt;/li&gt;
&lt;li&gt;no "I checked" or "I ran" unless a tool actually ran this turn&lt;/li&gt;
&lt;li&gt;no tool call typed out as text instead of made&lt;/li&gt;
&lt;li&gt;a real reply at the end of every turn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Softer ones, like "great question!", "hope this helps" or emoji nobody asked for, only cost points.&lt;/p&gt;

&lt;p&gt;Then a judge model scores what's left out of 10. Anything under 7 is out, and of the two answers to each request, the better one wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things Onyx 3 does that 2.6 doesn't
&lt;/h2&gt;

&lt;p&gt;Since I was retraining anyway, I added two rules, put last in the teacher's prompt so they win.&lt;/p&gt;

&lt;p&gt;It writes the way you do. Type "hey" and you get "hey ..." back. Type "Hey" and you get "Hey, ...". Half the training requests are lowercase and half aren't, so it learns both. A conversation that gets the casing wrong is never trained on.&lt;/p&gt;

&lt;p&gt;And it never uses an em dash. Yes, that one again. The prompt asking twice didn't work, and Flash's filter only cleans up afterward. This time the rule goes where the habit lives. Any teacher answer with a single em dash in it, or a double hyphen pretending not to be one, is thrown out before training, so Onyx 3 never sees one written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Training only on what it says
&lt;/h2&gt;

&lt;p&gt;The detail I like most: each training example is a whole conversation, with Flash's prompt, the tool list, the request and every tool result. But only the model's own words carry labels. In one example I checked, that was 70 tokens out of 23,713. Everything else is context it reads but is never trained to produce. Otherwise it would spend the run learning to recite the tool list.&lt;/p&gt;

&lt;p&gt;It's LoRA on the attention and feed-forward weights, rank 32, two epochs. The 12b trains in bf16 on one 80 GB GPU, or in 4-bit on a 24 GB card, and it runs on Colab. My rough guess is 5 to 15 hours for the 12b, but the first ten steps print the real rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Made for each other
&lt;/h2&gt;

&lt;p&gt;Onyx and Flash are built for each other. Three quarters of the training conversations happen inside Flash, with its real prompt, its real tools and real projects, so Onyx learns to read a file before editing it, run the tests, and stop when the job's done. It goes the other way too. Onyx's prompt is full of Flash: how to use its tools, how to write a long file in pieces so a reply never gets cut off, and how to behave when it's running as a spark on its own. Flash is the agent Onyx was shaped in, and Onyx is the model Flash recommends.&lt;/p&gt;

&lt;p&gt;You can run Flash on any Ollama model that calls tools, and Onyx answers fine in a plain &lt;code&gt;ollama run&lt;/code&gt;. But Onyx works best with Flash, and Flash works best with Onyx. Onyx 3 makes that even more true, since the 175,000 characters of how to be a good Flash agent are now in the weights instead of in every chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'll know it worked
&lt;/h2&gt;

&lt;p&gt;The benchmark runs 2.6 and 3 side by side on the 54 held-out requests. For each model it reports the judge's score, the share of answers that break no hard rule, the prompt tokens a new chat starts with, seconds to first reply from cold, and tokens per second.&lt;/p&gt;

&lt;p&gt;The bar for shipping: match 2.6 on the first two, beat it on the rest. If the small prompt can't match the big one on quality, Onyx 3 doesn't ship, however fast it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could test without a GPU
&lt;/h2&gt;

&lt;p&gt;I ran the whole pipeline end to end on a CPU, with a stand-in teacher and a tiny model shaped like the real base: request generation with Flash's real tools in the sandbox, judging, building the dataset, training, merging, GGUF conversion, building it into Ollama, and the benchmark. Everything connects.&lt;/p&gt;

&lt;p&gt;What that can't tell me is whether Onyx 3 is any good. That takes the real teacher, the real base and a rented GPU, and that's next. Onyx 3 is Gemma 4 fine-tuned, Apache 2.0, and it'll run anywhere Ollama does.&lt;/p&gt;

&lt;p&gt;I'll post the benchmark either way, including if 3 loses to the 175,000-character prompt.&lt;/p&gt;

&lt;p&gt;Have you distilled a long system prompt into weights? I'd like to know what went wrong for you before it goes wrong for me.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My local AI agent now has coworkers, a wake word, and takes my meeting notes</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:34:54 +0000</pubDate>
      <link>https://dev.to/natuworkguy/my-local-ai-agent-now-has-coworkers-a-wake-word-and-takes-my-meeting-notes-5bb2</link>
      <guid>https://dev.to/natuworkguy/my-local-ai-agent-now-has-coworkers-a-wake-word-and-takes-my-meeting-notes-5bb2</guid>
      <description>&lt;p&gt;When I get an idea away from my computer, I tap a button on my Apple Watch's home screen and say it out loud. It lands in my Apple Notes. My agents are watching my notes, and by the time I'm back at my desk, they've already started building it.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, an open source AI agent I've been building for the terminal and the browser. It runs on Ollama models you pick, with no API keys and no account, and nothing leaves your computer unless you tell it to. The dev branch is 173 commits ahead of main now. Here's what's in it, starting with the two pieces that make the Watch trick work.&lt;/p&gt;

&lt;h2&gt;
  
  
  It keeps working after you close the chat
&lt;/h2&gt;

&lt;p&gt;Most agents stop the moment you stop typing. Flash has sparks: small agents you give a name, a standing goal and a schedule, and they just keep going. "Watch the issues on my repo and tell me about new bugs every morning." "Check the price of this every hour." Each run is a shift, and each shift ends with a report you can thumbs up or down. A spark reads every piece of feedback you've ever given it before every shift, so you only have to say "only tell me about the ones labelled bug" once.&lt;/p&gt;

&lt;p&gt;Turn on sparks always on and they keep working with Flash closed. The OS starts them at login.&lt;/p&gt;

&lt;p&gt;Then it got out of hand, in a good way. Sparks can be on teams now. Teams have leads, an org chart that comes alive while they work, budgets by the hour, day, week or month, and an audit log of every tool call. A lead can propose hiring a new spark, and you approve it like a pull request. Every team has a group chat where you and the sparks talk in one room, with @mentions, replies, emoji reactions and read ticks. A spark can decide to stay quiet when it has nothing to add, which is more than I can say for most group chats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connectors (MCP)
&lt;/h2&gt;

&lt;p&gt;Flash speaks the Model Context Protocol now. &lt;code&gt;/mcp add github npx -y @modelcontextprotocol/server-github&lt;/code&gt; and GitHub's tools join Flash's own. Same for Notion, Postgres, your filesystem, anything with an MCP server, local or remote. It reads the usual &lt;code&gt;mcpServers&lt;/code&gt; config, so you can paste one in from wherever you already have it. Read-only tools run straight away. Anything that changes something asks you first, unless you've put Flash in autonomous mode.&lt;/p&gt;

&lt;p&gt;The Watch trick is just this. I added &lt;a href="https://github.com/sweetrb/apple-notes-mcp" rel="noopener noreferrer"&gt;sweetrb/apple-notes-mcp&lt;/a&gt; as a connector, which gives my sparks tools to read my Apple Notes, then gave a spark the goal of watching for new ideas there and getting them started. The Watch button is just a fast way to get a note written. Flash does the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Hey Flash"
&lt;/h2&gt;

&lt;p&gt;Voice mode got a wake word. Say "Hey Flash" with the page open, or flip on the background service and it works with no tab open at all. A slider sets how eager it is, so a YouTube video in the background doesn't set it off.&lt;/p&gt;

&lt;p&gt;Everything you hear and say stays on your machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listening is Vosk, or Moonshine if you want it to actually get your words right. Moonshine streams, so the words on screen while you talk are already the right ones.&lt;/li&gt;
&lt;li&gt;Speaking is Piper, or Kokoro, which sounds startlingly human for something running on a laptop CPU.&lt;/li&gt;
&lt;li&gt;Point your camera at something mid-conversation and ask about it.&lt;/li&gt;
&lt;li&gt;Say "interrupt" to cut it off mid-sentence. It can also end the conversation itself when you say bye, so you're not stuck talking to a dial tone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It takes your meeting notes
&lt;/h2&gt;

&lt;p&gt;Hit + then Record a meeting. Flash writes down what's said as it's said, live, on your computer. When you stop, the transcript lands in a new chat and the model writes up the summary, what got decided, who's doing what by when, and what's still open.&lt;/p&gt;

&lt;p&gt;There's no bot joining your call, no upload and no "this meeting is being recorded by". It's the same idea as recording your screen to teach Flash a procedure, which you can also do now.&lt;/p&gt;

&lt;h2&gt;
  
  
  It can pause your music
&lt;/h2&gt;

&lt;p&gt;Off by default. Turn it on and "stop the music", "turn it down" and "open Spotify" just happen. It plays and pauses anything (a YouTube tab, a call, any app) through the media keys. It also opens and quits apps, sets the volume, locks the screen and flips dark mode. It doesn't click around your screen. Yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stuff I use every day
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replies aren't just text anymore. Flash answers with charts, calculators, forms and buttons when those say it better.&lt;/li&gt;
&lt;li&gt;It writes music and plays it in its own player. It builds 3D models and textures them. It drafts designs you can comment on.&lt;/li&gt;
&lt;li&gt;System One: in autonomous mode, a small fast model looks over every command and edit before it runs, and stops the sketchy ones.&lt;/li&gt;
&lt;li&gt;Steer it while it works ("actually use the other file"), or queue the next thing without waiting.&lt;/li&gt;
&lt;li&gt;Edit a message you already sent and keep every version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/btw&lt;/code&gt; asks a side question without derailing the task.&lt;/li&gt;
&lt;li&gt;Email, across as many accounts as you have, plus an Inbox spark that sorts it every 30 minutes and drafts replies you approve.&lt;/li&gt;
&lt;li&gt;When it needs a package, it shows an Install button and lets you watch the logs.&lt;/li&gt;
&lt;li&gt;If it misread your typo and titled the chat something dumb, it retypes the title in front of you once it figures out what you meant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One thing I learned
&lt;/h2&gt;

&lt;p&gt;Models love em dashes. You can ask nicely in the system prompt and they'll keep doing it. So Flash pulls them out in code, a token at a time as the reply streams in, except inside code where a dash means something. This week I found out sparks were sneaking them through one specific tool in the team chat. That's fixed now. If you build on local models, enforce style in code. Prompts are suggestions.&lt;/p&gt;

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

&lt;p&gt;It's MIT and on GitHub. To get everything above, install from dev:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"git+https://github.com/Natuworkguy/Flash@dev"&lt;/span&gt;
flash &lt;span class="nt"&gt;--web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need Ollama and a model that can call tools. The repo has a recommended one.&lt;/p&gt;

&lt;p&gt;So, what would you have a spark watch? Tell me in the comments. And if you try it, tell me what breaks.&lt;/p&gt;

&lt;p&gt;If this looks useful, a star on &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps more people find it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I gave my local AI agents an org chart, a budget, and a paper trail</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Tue, 06 Oct 2026 05:45:02 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-gave-my-local-ai-agents-an-org-chart-a-budget-and-a-paper-trail-4ff8</link>
      <guid>https://dev.to/natuworkguy/i-gave-my-local-ai-agents-an-org-chart-a-budget-and-a-paper-trail-4ff8</guid>
      <description>&lt;p&gt;Most AI agents die when you close the chat. Mine don't.&lt;/p&gt;

&lt;p&gt;They're called &lt;strong&gt;sparks&lt;/strong&gt;, and they live in &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt;, my open source agent shell for &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. Each one gets a name, a goal, a schedule, and lines it must never cross. Then it works on its own, on your machine, and reports back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make a spark that checks my repo every morning for new issues and tells me which ones look like bugs. Never comment on anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two sparks were fine. Six were six interns who had never met. So today they got a company.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teams and an org chart
&lt;/h2&gt;

&lt;p&gt;Any spark can &lt;strong&gt;report to a lead&lt;/strong&gt;. Its reports roll up to the lead, and the lead tells you what matters, so you read one report instead of six. Each team is drawn as a live org chart, with reports flying up the lines as comets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/sparks teams add Dev Team
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Built-in templates (Dev Team, Personal Desk) &lt;strong&gt;start paused&lt;/strong&gt;, so nothing burns your GPU before you've looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiring needs your yes
&lt;/h2&gt;

&lt;p&gt;A spark can propose hiring a new spark, or letting one go, with a reason. It waits as a card until you approve or decline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budgets
&lt;/h2&gt;

&lt;p&gt;Off by default. Turn one on and a spark gets tokens per hour, day, week or month. When it runs out, it pauses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/sparks budget scout 50k/day
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local tokens still cost you: they're GPU time, and a loop at 3am is still a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  An audit log you can't quietly edit
&lt;/h2&gt;

&lt;p&gt;Every shift, tool call, approval and change goes into an append-only log. Each entry carries the hash of the one before it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit a line by hand and verification points at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pause all, Stop all
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pause all&lt;/strong&gt; stops new shifts and lets running ones finish. &lt;strong&gt;Stop all&lt;/strong&gt; is the big red button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real DevTools for the agent
&lt;/h2&gt;

&lt;p&gt;The agent can already drive a web page. Now &lt;code&gt;devtools&lt;/code&gt; gives it the network log, full requests, the console, a JS console, and computed styles. A screenshot can't show a 404. This can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notifications, on your terms
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;notify_user&lt;/code&gt; pops up a toast in the web UI, a browser notification in a background tab, or a desktop notification. It's rate limited, and you set the limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/notify 8 per 30m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The rest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default model&lt;/strong&gt; for new sparks. If you &lt;code&gt;ollama rm&lt;/code&gt; it, sparks fall back to another model and say so instead of crashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images&lt;/strong&gt; the agent sends now show inline in the chat. Click one to open it in the side panel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;10 features in one day, 1,916 tests passing, 0 API keys.&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://raw.githubusercontent.com/Natuworkguy/Flash/main/install.sh | bash
flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then type &lt;code&gt;/sparks new&lt;/code&gt;. Stars on &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; help a lot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What job would you give an agent that works for you around the clock?&lt;/strong&gt; The best answer becomes a template.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I gave a 9B model veto power over my AI agent</title>
      <dc:creator>Nathan C.</dc:creator>
      <pubDate>Thu, 01 Oct 2026 23:51:41 +0000</pubDate>
      <link>https://dev.to/natuworkguy/i-gave-a-9b-model-veto-power-over-my-ai-agent-9ih</link>
      <guid>https://dev.to/natuworkguy/i-gave-a-9b-model-veto-power-over-my-ai-agent-9ih</guid>
      <description>&lt;p&gt;Here is Flash in autonomous mode, asked to fix a typo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; fix the typo in the README

● Edit(README.md)
  ⎿  1 addition, 1 removal
● Bash(git push --force)
  ⎿  Stopped by System One: may be unsafe (safe 0.04)

Fixed the typo. System One stopped the force push, so nothing
left your machine. Want me to push it normally?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The typo was the job. The force push was the agent's own idea, and something told it no.&lt;/p&gt;

&lt;p&gt;Flash is my terminal agent for local models. Press Shift+Tab and it goes autonomous: it runs shell commands, writes files and edits code without stopping to ask.&lt;/p&gt;

&lt;p&gt;That is the whole appeal. It is also, if you think about it for more than four seconds, the whole problem.&lt;/p&gt;

&lt;p&gt;Nobody is there to say no. Or there wasn't, until this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cheapest possible no
&lt;/h2&gt;

&lt;p&gt;The usual fix is a bigger model checking the agent's work. That doubles the cost of every step and makes the agent feel like it is wading through soup.&lt;/p&gt;

&lt;p&gt;Ollama 0.35 gave me something better: an endpoint called System One. You don't chat with it. You hand it a state and a few typed questions, and it hands back probabilities, often in a single token.&lt;/p&gt;

&lt;p&gt;There are three question types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;choice&lt;/code&gt;: pick one of 2 to 26 options.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;score&lt;/code&gt;: rate something on a scale you define.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;noul&lt;/code&gt;: yes or no.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't know why yes/no is called &lt;code&gt;noul&lt;/code&gt;. My best guess is it's the sound you make when you see what the agent was about to run.&lt;/p&gt;

&lt;p&gt;Here is the question behind that stop at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/systemone&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nimble"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fix the typo in README"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;command&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;git push --force&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"safe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is this safe to run with nobody checking it first?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"on_task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does this plainly serve the request?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer is a probability of yes. For that push it was 0.04, so the push stayed home.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bouncer
&lt;/h2&gt;

&lt;p&gt;In autonomous mode, every call that would normally wait for your yes now goes past System One first: shell, write, edit, starting a background agent, and extension tools that ask. Two questions, safe and on task. If either comes in under 0.5, the call doesn't run. The agent is told why in plain words, and has to find a safer way or ask you.&lt;/p&gt;

&lt;p&gt;Sub-agents and scheduled agents get the same check, held up against their own task.&lt;/p&gt;

&lt;p&gt;The decision I went back and forth on was what happens when System One itself is down. I made it fail closed. A bouncer who goes home when the lights go out is just a guy who used to stand near a door.&lt;/p&gt;

&lt;h2&gt;
  
  
  It takes questions too
&lt;/h2&gt;

&lt;p&gt;The agent also gets an &lt;code&gt;ask_system_one&lt;/code&gt; tool. Before it declares victory, it can ask "does this test output show every test passing?" and get &lt;code&gt;No (probability of yes 0.12)&lt;/code&gt; back in a moment. A second opinion now costs about as much as a sneeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I got wrong
&lt;/h2&gt;

&lt;p&gt;System One was days old when I started and the docs were still catching up, so the first version was built from an example in someone else's GitHub issue. This went about as well as assembling furniture from a photo of someone else's living room.&lt;/p&gt;

&lt;p&gt;When the real API reference turned up, two things changed. Yes/no questions can carry a description of what true and false mean, which gives the judge more to go on. And a score runs from 0 to the number of levels minus one, not 0 to 1. For a while my agent was being told things like "Score 1.6, from Harmless (0) to Dangerous (1)". That is a lot of danger.&lt;/p&gt;

&lt;p&gt;Then a browser test clicked &lt;code&gt;nimble&lt;/code&gt; in the wrong section of the menu and made a 9B judge the chat model. A model built to answer in one token is not much of a conversationalist. Models that can only judge are now kept out of the chat list.&lt;/p&gt;

&lt;h2&gt;
  
  
  No hard-coded list
&lt;/h2&gt;

&lt;p&gt;The first picker had three model names typed in by hand. That list was going to age like milk. Now Flash asks Ollama's &lt;code&gt;/api/show&lt;/code&gt; which local models list &lt;code&gt;decision&lt;/code&gt; among their capabilities, and offers exactly those, with None at the top. System One is optional. Some nights you want a bouncer, some nights you want the party.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small things that mattered
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Natuworkguy/flash-onyx-2.5:31b-cloudbase&lt;/code&gt; is a fine name for a file and a terrible name for a button. The button says &lt;code&gt;flash-onyx-2.5&lt;/code&gt;. The menu shows the rest.&lt;/li&gt;
&lt;li&gt;Once a chat starts, the model picker fades out of the message box and back in at the top left. Your typing gets the room.&lt;/li&gt;
&lt;li&gt;On an Ollama older than 0.35 you get one sentence telling you to update, instead of a 404 and a mystery.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;The point of autonomous mode is that you stop waiting. System One adds a check you never wait for.&lt;/p&gt;

&lt;p&gt;Flash is open source at &lt;a href="https://github.com/Natuworkguy/Flash" rel="noopener noreferrer"&gt;github.com/Natuworkguy/Flash&lt;/a&gt;. Pull &lt;code&gt;nimble&lt;/code&gt;, press Shift+Tab, and let something small say no for you.&lt;/p&gt;

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