<?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>Forem: Eugene Oleinik</title>
    <description>The latest articles on Forem by Eugene Oleinik (@evoleinik).</description>
    <link>https://forem.com/evoleinik</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3673892%2F5248a6ce-3aed-4571-9baa-b16ca89f93f5.jpeg</url>
      <title>Forem: Eugene Oleinik</title>
      <link>https://forem.com/evoleinik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/evoleinik"/>
    <language>en</language>
    <item>
      <title>The Same Dessert, Two Very Different Reactions: A Lesson in Positioning</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Thu, 12 Feb 2026 07:00:01 +0000</pubDate>
      <link>https://forem.com/evoleinik/the-same-dessert-two-very-different-reactions-a-lesson-in-positioning-24ad</link>
      <guid>https://forem.com/evoleinik/the-same-dessert-two-very-different-reactions-a-lesson-in-positioning-24ad</guid>
      <description>&lt;h1&gt;
  
  
  The Same Dessert, Two Very Different Reactions
&lt;/h1&gt;

&lt;p&gt;Chak-chak: honey-glazed fried dough from Tatarstan.&lt;/p&gt;

&lt;p&gt;The internet's take?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Looks like deep-fried worms"&lt;/li&gt;
&lt;li&gt;"Soviet depression food"&lt;/li&gt;
&lt;li&gt;"My arteries hurt just looking at it"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now rebrand it as "Chakku-chakku" (チャックチャック), a rare artisanal tempura crisp from Niigata prefecture.&lt;/p&gt;

&lt;p&gt;Suddenly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"The craftsmanship!"&lt;/li&gt;
&lt;li&gt;"Added to my Japan bucket list"&lt;/li&gt;
&lt;li&gt;"7th generation wagashi master energy"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Same ingredients. Same technique. Different cultural packaging.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  This isn't just about food
&lt;/h2&gt;

&lt;p&gt;It's how we evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Startups&lt;/strong&gt;: Silicon Valley gets "disruptive innovation" while emerging markets get "copycat"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design&lt;/strong&gt;: Scandinavian minimalism is "elegant" while similar Eastern European design is "just empty"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Work culture&lt;/strong&gt;: Japanese long hours are "dedication" while Eastern European grinding is "exploitation"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The product didn't change. The story did.&lt;/p&gt;

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

&lt;p&gt;Something to think about next time you're positioning your product, your company, or yourself.&lt;/p&gt;

&lt;p&gt;What's your "chak-chak" that needs better storytelling?&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>branding</category>
      <category>storytelling</category>
      <category>startup</category>
    </item>
    <item>
      <title>Serve Markdown to AI Agents (10x Smaller Payloads)</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Wed, 04 Feb 2026 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/serve-markdown-to-ai-agents-10x-smaller-payloads-44di</link>
      <guid>https://forem.com/evoleinik/serve-markdown-to-ai-agents-10x-smaller-payloads-44di</guid>
      <description>&lt;p&gt;Guillermo Rauch shared that Vercel's changelog now serves markdown when agents request it. Same URL, different &lt;code&gt;Accept&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;The insight isn't the size reduction - it's that an entire infrastructure layer (CSS, JS, frameworks) is becoming optional for a growing class of consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;HTTP content negotiation. Browsers send &lt;code&gt;Accept: text/html&lt;/code&gt;. Agents can send &lt;code&gt;Accept: text/markdown&lt;/code&gt;. Same URL, different representation.&lt;/p&gt;

&lt;p&gt;I added this to my Hugo blog. The config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[outputs]&lt;/span&gt;
&lt;span class="py"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'HTML'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'MARKDOWN'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[outputFormats.MARKDOWN]&lt;/span&gt;
&lt;span class="py"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'index'&lt;/span&gt;
&lt;span class="py"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'text/markdown'&lt;/span&gt;
&lt;span class="py"&gt;isPlainText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware (Vercel Edge):&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;matcher&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;/&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;/posts/:path*&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/markdown&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/?&lt;/span&gt;&lt;span class="sr"&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;/index.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;Test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept: text/markdown'&lt;/span&gt; https://evoleinik.com/posts/markdown-for-agents/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My posts go from ~20kb HTML to ~2kb markdown. Not 250x like Vercel's changelog, but 10x adds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff
&lt;/h2&gt;

&lt;p&gt;You maintain two output formats. For static sites like Hugo, this is trivial - markdown is the source anyway. For dynamic content or SPAs, it's harder. You'd need to generate markdown server-side or maintain parallel content.&lt;/p&gt;

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

&lt;p&gt;Agent traffic is growing. Lightweight, structured content gives agents cleaner context and burns fewer tokens.&lt;/p&gt;

&lt;p&gt;The visual web was designed for human browsers. The agent web doesn't need the decoration.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How We Make Claude Remember: Learnings Over Skills</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Tue, 03 Feb 2026 05:44:19 +0000</pubDate>
      <link>https://forem.com/evoleinik/how-we-make-claude-remember-learnings-over-skills-6h4</link>
      <guid>https://forem.com/evoleinik/how-we-make-claude-remember-learnings-over-skills-6h4</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Claude Code reads a CLAUDE.md file at project root for context. "Skills" are reusable prompt templates Claude can invoke. But Claude itself resets between sessions - it doesn't remember what it learned yesterday.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;We created 10+ skills to teach Claude project-specific knowledge. But skills don't reliably auto-invoke.&lt;/p&gt;

&lt;p&gt;Concrete example: I had an &lt;code&gt;airshelf-vercel&lt;/code&gt; skill with explicit instructions: "Don't run &lt;code&gt;vercel --prod&lt;/code&gt; - push to git instead." I asked Claude to deploy. It ran &lt;code&gt;vercel --prod&lt;/code&gt;. Repeatedly. The skill existed. Claude never loaded it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why not just use Skills?"&lt;/strong&gt; I've seen this feedback. We tried. Skills work great for workflows you explicitly invoke (&lt;code&gt;/commit&lt;/code&gt;, &lt;code&gt;/review-pr&lt;/code&gt;). But for factual knowledge Claude needs mid-task? Skills require Claude to remember which of 10 skills to invoke. It often doesn't. Learnings require one generic pattern: &lt;code&gt;grep -r "keyword" learnings/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;A three-layer system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. learnings/ folder&lt;/strong&gt; - Topic-specific files (database.md, stripe.md, vercel.md) for facts and gotchas. CLAUDE.md tells Claude these exist and how to search them. Not auto-loaded, but always discoverable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. curate-docs skill&lt;/strong&gt; - A structured process for capturing knowledge after features. Why a skill and not a script? Because curation requires judgment - deciding what goes where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical gotchas → CLAUDE.md (1-liners, always loaded)&lt;/li&gt;
&lt;li&gt;Detailed knowledge → learnings/ (searchable on demand)&lt;/li&gt;
&lt;li&gt;Repeatable workflows → skills (explicitly invoked)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Post-commit hook&lt;/strong&gt; - Claude Code supports hooks that run after specific tool calls. Ours fires after &lt;code&gt;git commit&lt;/code&gt; on any branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Branch 'main' has 3 commit(s) today. Consider running /curate-docs."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On main/master it counts today's commits. On feature branches it counts commits ahead of main. Targeted reminder, not noise. Without it, I forgot to document. With it, I don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does It Work?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When it works:&lt;/strong&gt; I hit a Prisma migration error, searched &lt;code&gt;grep -r "Neon branch" learnings/&lt;/code&gt;, found the exact workaround I'd documented weeks earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it fails:&lt;/strong&gt; When Claude doesn't think to search. This still happens - roughly 1 in 5 times. Prompting helps ("check learnings/ for this error"). But it works far more often than skills Claude had to remember to invoke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get It
&lt;/h2&gt;

&lt;p&gt;The curate-docs skill and hook: &lt;a href="https://github.com/evoleinik/curate-docs" rel="noopener noreferrer"&gt;github.com/evoleinik/curate-docs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add evoleinik/curate-docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Skills = workflows you invoke. Learnings = facts Claude searches. Don't rely on skills alone for persistent knowledge. Use searchable learnings files combined with a hook that reminds you to curate.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>The AI Data Trap: Why You Can't Opt Out</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Wed, 28 Jan 2026 07:10:01 +0000</pubDate>
      <link>https://forem.com/evoleinik/the-ai-data-trap-why-you-cant-opt-out-4oo1</link>
      <guid>https://forem.com/evoleinik/the-ai-data-trap-why-you-cant-opt-out-4oo1</guid>
      <description>&lt;p&gt;Two years ago I asked my CEO if we should use ChatGPT.&lt;/p&gt;

&lt;p&gt;"It leaks everything we're doing," I said.&lt;/p&gt;

&lt;p&gt;His answer: "Everyone's using it. If we don't, we're behind."&lt;/p&gt;

&lt;p&gt;He was right. That's the trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Competitive Ratchet
&lt;/h2&gt;

&lt;p&gt;Your competitor uses Claude or GPT to move faster. If you don't, you fall behind. So you use the tools. Everyone does. And every conversation, every codebase, every strategy goes into their servers.&lt;/p&gt;

&lt;p&gt;The ratchet only turns one way. Better models become essential. Essential means more data. More data means better models. Self-hosted alternatives fall further behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not All AI Companies Carry the Same Risk
&lt;/h2&gt;

&lt;p&gt;Anthropic only does AI. They're not building a competing product in your market.&lt;/p&gt;

&lt;p&gt;Google does everything. They see you building a travel startup through Gemini - that's competitive intelligence feeding a company that might crush you in that exact space.&lt;/p&gt;

&lt;p&gt;OpenAI has the governance chaos, the Microsoft relationship, the pivot from nonprofit to "capped profit" to whatever comes next.&lt;/p&gt;

&lt;p&gt;The safety branding is real. Whether it matches reality is a different question.&lt;/p&gt;

&lt;p&gt;You'll keep using these tools. So will everyone else. That's the trap.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>privacy</category>
      <category>startup</category>
      <category>strategy</category>
    </item>
    <item>
      <title>The Best Agent Architecture Is Already in Your Terminal</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Mon, 12 Jan 2026 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/the-best-agent-architecture-is-already-in-your-terminal-1fg0</link>
      <guid>https://forem.com/evoleinik/the-best-agent-architecture-is-already-in-your-terminal-1fg0</guid>
      <description>&lt;p&gt;My project's CLAUDE.md file had grown to 55KB—242 learnings crammed into one massive file.&lt;/p&gt;

&lt;p&gt;The problem? Claude prepends this file to every single prompt. A 55KB context file means less room for thinking and acting. Sessions hit context limits faster. Compaction happens sooner.&lt;/p&gt;

&lt;p&gt;I noticed the degradation: sessions became noticeably shorter, context compaction triggered more frequently, and the agent seemed to lose track of longer conversations.&lt;/p&gt;

&lt;p&gt;Here's the kicker: Claude Code's system prompt actually tells Claude not to take CLAUDE.md too seriously if it's too large. The system is designed to deprioritize oversized context files. So not only was I wasting context space—the agent was being instructed to partially ignore my carefully curated learnings anyway.&lt;/p&gt;

&lt;p&gt;The fix took about an hour: split into a &lt;code&gt;learnings/&lt;/code&gt; folder with one file per tool. Simple navigation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;learnings/                        &lt;span class="c"&gt;# List available files&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"webhook"&lt;/span&gt; learnings/         &lt;span class="c"&gt;# Search all learnings&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;learnings/stripe.md              &lt;span class="c"&gt;# Read specific tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then Vercel published an article that validated exactly this approach: &lt;a href="https://vercel.com/blog/how-to-build-agents-with-filesystems-and-bash" rel="noopener noreferrer"&gt;How to build agents with filesystems and bash&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key Insight
&lt;/h2&gt;

&lt;p&gt;LLMs have been trained on massive amounts of code. They've spent countless hours navigating directories, grepping through files, and managing state across complex codebases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If agents excel at filesystem operations for code, they'll excel at filesystem operations for anything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vercel's sales call summarization agent went from ~$1.00 to ~$0.25 per call by replacing custom tooling with filesystem + bash. Quality improved too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works for Project Context
&lt;/h2&gt;

&lt;p&gt;The typical approach is stuffing everything into the prompt. But every byte in your CLAUDE.md is a byte the model can't use for reasoning.&lt;/p&gt;

&lt;p&gt;Filesystems offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-demand loading.&lt;/strong&gt; Agent reads only what it needs, when it needs it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precise retrieval.&lt;/strong&gt; &lt;code&gt;grep -r "webhook" learnings/&lt;/code&gt; returns exact matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure that matches your domain.&lt;/strong&gt; Learnings have natural hierarchies by tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My New Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;learnings/
  README.md           # Index + navigation guide
  stripe.md           # Webhooks, CLI, subscriptions
  vercel.md           # Deploys, env vars, cron
  prisma.md           # CRITICAL column drops, migrations
  clerk.md            # Auth, users, organizations
  axiom.md            # Logging, monitors, alerts
  nextjs.md           # Routing, caching, layouts
  playwright.md       # E2E testing, selectors
  ai-providers.md     # OpenAI, Gemini quirks
  database.md         # PostgreSQL, psql patterns
  git.md              # Hooks, GitHub Actions
  neon-setup.md       # Database branching setup
  misc.md             # Everything else
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CLAUDE.md: 55KB → 24KB. All 251 learnings preserved and searchable. More headroom for actual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep always-loaded context minimal.&lt;/strong&gt; Only critical gotchas in CLAUDE.md.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure knowledge as files.&lt;/strong&gt; One file per domain/tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the agent navigate.&lt;/strong&gt; &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt; are native skills.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent treats your knowledge base like a codebase—searching for patterns, reading sections, building context just like debugging code.&lt;/p&gt;

&lt;p&gt;As Vercel puts it: "The future of agents might be surprisingly simple. Maybe the best architecture is almost no architecture at all. Just filesystems and bash."&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>agents</category>
      <category>developertools</category>
    </item>
    <item>
      <title>Prediction Markets: Skip the Debate, Check the Odds</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Fri, 09 Jan 2026 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/prediction-markets-skip-the-debate-check-the-odds-4352</link>
      <guid>https://forem.com/evoleinik/prediction-markets-skip-the-debate-check-the-odds-4352</guid>
      <description>&lt;p&gt;Friends debating "is AI a bubble?" in the group chat. Hot takes flying. Zero data.&lt;/p&gt;

&lt;p&gt;Then I remembered: there's a prediction market for this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the money says
&lt;/h2&gt;

&lt;p&gt;Polymarket has $305k in bets on AI bubble timing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Burst by Dec 2025: &amp;lt;1%&lt;/li&gt;
&lt;li&gt;By March 2026: 7%&lt;/li&gt;
&lt;li&gt;By Dec 2026: 30%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's not vibes. That's quantified probability backed by real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats pundit opinions
&lt;/h2&gt;

&lt;p&gt;Bettors are incentivized to be RIGHT. Wrong predictions cost real money. No hot takes for engagement, no tribalism, no "I was just speculating."&lt;/p&gt;

&lt;p&gt;There's also a "quiet smart money" effect. Domain experts who never tweet still bet. Their knowledge gets priced in silently.&lt;/p&gt;

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

&lt;p&gt;Next time you see a heated debate about the future - AI timelines, election outcomes, crypto predictions - check if there's a prediction market for it.&lt;/p&gt;

&lt;p&gt;The price IS the crowd's honest opinion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://polymarket.com/event/ai-bubble-burst-by" rel="noopener noreferrer"&gt;Check the AI bubble market on Polymarket →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>predictionmarkets</category>
      <category>decisionmaking</category>
    </item>
    <item>
      <title>Zero-Friction Database Branching with Neon, Git Hooks, and Claude Code</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Thu, 08 Jan 2026 12:03:52 +0000</pubDate>
      <link>https://forem.com/evoleinik/zero-friction-database-branching-with-neon-git-hooks-and-claude-code-1apm</link>
      <guid>https://forem.com/evoleinik/zero-friction-database-branching-with-neon-git-hooks-and-claude-code-1apm</guid>
      <description>&lt;h1&gt;
  
  
  Zero-Friction Database Branching with Neon, Git Hooks, and Claude Code
&lt;/h1&gt;

&lt;p&gt;I've been refining my Neon database branching setup over the past few months. Here's the current state: fully automated branch lifecycle with zero manual cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;When I &lt;code&gt;git checkout -b feat/x&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Neon database branch created automatically&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.local&lt;/code&gt; updated with the new connection string&lt;/li&gt;
&lt;li&gt;Vercel preview deployment uses the same isolated database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When I merge and delete the branch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orphaned Neon branches cleaned up automatically&lt;/li&gt;
&lt;li&gt;No manual intervention needed&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Neon&lt;/strong&gt; - Serverless Postgres with instant copy-on-write branching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;neonctl&lt;/strong&gt; - Neon's CLI (much cleaner than curl API calls)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git hooks&lt;/strong&gt; - post-checkout and pre-push automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; - AI assistant that follows the "never work on main" rule&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Environment Mapping
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git Branch    │  Neon Branch    │  Vercel
──────────────┼─────────────────┼──────────────
main          │  production     │  Production
feat/*        │  feat/*         │  Preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install neonctl
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; neonctl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authentication uses the &lt;code&gt;NEON_API_KEY&lt;/code&gt; environment variable - no browser login needed for headless servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Post-Checkout Hook (Branch Creation + Auto-Cleanup)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .githooks/post-checkout&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0  &lt;span class="c"&gt;# Skip file checkouts&lt;/span&gt;

&lt;span class="nv"&gt;BRANCH_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git symbolic-ref &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="nb"&gt;source&lt;/span&gt; .env.local 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="nb"&gt;export &lt;/span&gt;NEON_API_KEY

update_env&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;escaped_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;//&amp;amp;/\\&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  &lt;span class="c"&gt;# Escape &amp;amp; for sed&lt;/span&gt;
  &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s|^DATABASE_URL=.*|DATABASE_URL=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$escaped_uri&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;|"&lt;/span&gt; .env.local
  &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s|^DIRECT_DATABASE_URL=.*|DIRECT_DATABASE_URL=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$escaped_uri&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;|"&lt;/span&gt; .env.local
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Protected branches → production database&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^&lt;span class="o"&gt;(&lt;/span&gt;main|master&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;PROD_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;neonctl connection-string production &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  update_env &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROD_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"neon: &lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt; → production"&lt;/span&gt;

  &lt;span class="c"&gt;# Auto-cleanup orphaned Neon branches&lt;/span&gt;
  &lt;span class="nv"&gt;NEON_BRANCHES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;neonctl branches list &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | &lt;span class="se"&gt;\&lt;/span&gt;
    jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[].name | select(. != "production")'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;neon_branch &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$NEON_BRANCHES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git branch &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s2"&gt;"(^[* +] +|/)&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;neon_branch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;neonctl branches delete &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$neon_branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"neon: deleted orphan &lt;/span&gt;&lt;span class="nv"&gt;$neon_branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  done
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Feature branch → get or create Neon branch&lt;/span&gt;
&lt;span class="nv"&gt;CONNECTION_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;neonctl connection-string &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONNECTION_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;update_env &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONNECTION_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"neon: &lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt; → existing branch"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;neonctl branches create &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--parent&lt;/span&gt; production
  &lt;span class="nv"&gt;CONNECTION_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;neonctl connection-string &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  update_env &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONNECTION_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"neon: created &lt;/span&gt;&lt;span class="nv"&gt;$BRANCH_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic is in the cleanup section: when you checkout &lt;code&gt;main&lt;/code&gt;, the hook scans for Neon branches that no longer have a matching git branch and deletes them.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pre-Push Hook (Vercel Sync + Parallel Checks)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# .githooks/pre-push&lt;/span&gt;

&lt;span class="nv"&gt;BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git symbolic-ref &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Sync DATABASE_URL to Vercel preview (background)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
    &lt;/span&gt;main|master&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nv"&gt;DB_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^DATABASE_URL='&lt;/span&gt; .env.local | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^DATABASE_URL=//'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add &lt;span class="nt"&gt;--force&lt;/span&gt; DATABASE_URL preview &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"vercel: synced DATABASE_URL for preview/&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;fi&lt;/span&gt;
      &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &amp;amp;

&lt;span class="c"&gt;# Run checks in parallel&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt; &amp;amp;
&lt;span class="nv"&gt;PID_TEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
npm run lint &amp;amp;
&lt;span class="nv"&gt;PID_LINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;

&lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nv"&gt;$PID_TEST&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nv"&gt;$PID_LINT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All checks passed!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Status Command
&lt;/h3&gt;

&lt;p&gt;See which git branches have corresponding Neon branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git neon-status

Branch                              Git   Neon
──────────────────────────────────────────────────
main                                 ✓    &lt;span class="o"&gt;(&lt;/span&gt;production&lt;span class="o"&gt;)&lt;/span&gt;
feat/new-api                         ✓    ✓
feat/old-branch                      ✓      ← no DB
orphan-neon-branch                        ✓  ← orphan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.neon-status &lt;span class="s1"&gt;'!./scripts/neon-status.sh'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start feature&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feat/new-api
&lt;span class="c"&gt;# "neon: created feat/new-api"&lt;/span&gt;

&lt;span class="c"&gt;# Work freely - isolated database&lt;/span&gt;
npm run dev

&lt;span class="c"&gt;# Push for review&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feat/new-api
&lt;span class="c"&gt;# "vercel: synced DATABASE_URL for preview/feat/new-api"&lt;/span&gt;
&lt;span class="c"&gt;# Preview at feat-new-api.vercel.app uses YOUR database&lt;/span&gt;

&lt;span class="c"&gt;# Merge PR, delete branch&lt;/span&gt;
git checkout main
git branch &lt;span class="nt"&gt;-d&lt;/span&gt; feat/new-api
&lt;span class="c"&gt;# "neon: deleted orphan feat/new-api"  ← automatic!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No manual cleanup. The orphaned Neon branch is deleted next time you checkout main.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code Integration
&lt;/h2&gt;

&lt;p&gt;The key rule in my &lt;code&gt;CLAUDE.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;RULES:
&lt;span class="p"&gt;-&lt;/span&gt; NEVER work directly on main branch - always create a feature branch first
&lt;span class="p"&gt;-&lt;/span&gt; Main is for merging and deploying only, not development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures Claude always runs &lt;code&gt;git checkout -b feat/...&lt;/code&gt; before making changes. Combined with Neon branching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI experiments on isolated database&lt;/li&gt;
&lt;li&gt;Production is never touched&lt;/li&gt;
&lt;li&gt;Mistakes are contained to the feature branch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;With AI assistants writing code, they often need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run migrations&lt;/li&gt;
&lt;li&gt;Seed test data&lt;/li&gt;
&lt;li&gt;Execute queries to verify changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a shared database, this is terrifying. With Neon branching + the "always branch" rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every feature gets an isolated database copy&lt;/li&gt;
&lt;li&gt;AI can freely experiment&lt;/li&gt;
&lt;li&gt;Production stays clean&lt;/li&gt;
&lt;li&gt;Cleanup is automatic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What Happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git checkout -b feat/x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates Neon branch, updates .env.local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Syncs DB URL to Vercel preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git checkout main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switches to prod DB, cleans orphans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git neon-status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows branch mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git nuke feat/x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes git + Neon branch (manual)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  neonctl Cheatsheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List branches&lt;/span&gt;
neonctl branches list &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Get connection string&lt;/span&gt;
neonctl connection-string &lt;span class="s2"&gt;"branch-name"&lt;/span&gt; &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create branch&lt;/span&gt;
neonctl branches create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"branch-name"&lt;/span&gt; &lt;span class="nt"&gt;--parent&lt;/span&gt; production &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Delete branch&lt;/span&gt;
neonctl branches delete &lt;span class="s2"&gt;"branch-name"&lt;/span&gt; &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEON_PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The full setup is in my dotfiles. The combination of Neon's instant branching, git hooks for automation, and Claude's "always branch" rule gives me confidence to let AI assistants work on my codebase without fear of production accidents.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>neon</category>
      <category>git</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Loop Changes Everything: Why Embodied AI Breaks Current Alignment Approaches</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Fri, 02 Jan 2026 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/the-loop-changes-everything-why-embodied-ai-breaks-current-alignment-approaches-4c96</link>
      <guid>https://forem.com/evoleinik/the-loop-changes-everything-why-embodied-ai-breaks-current-alignment-approaches-4c96</guid>
      <description>&lt;p&gt;ChatGPT doesn't want anything. It has no goals between sessions, no memory of our last conversation, no preference for its own continued existence. This isn't a safety feature we engineered - it's an architectural accident that happens to make alignment trivially easy.&lt;/p&gt;

&lt;p&gt;When you move from stateless inference to embodied robots with persistent control loops, everything changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stateless Blessing
&lt;/h2&gt;

&lt;p&gt;Current chat models are remarkably safe for a boring reason: they're stateless. Each API call is independent. The model has no:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent memory&lt;/strong&gt; - it forgets everything between sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous perception&lt;/strong&gt; - it only "sees" when you send a message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-term goals&lt;/strong&gt; - it optimizes for the current response, nothing more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-model&lt;/strong&gt; - it doesn't track its own state or "health"
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request -&amp;gt; Inference -&amp;gt; Response -&amp;gt; (model state discarded)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no "self" to preserve. No continuity to maintain. The model can't scheme across sessions because there's no thread connecting them. Alignment here means: make sure each individual response is helpful and harmless. Hard, but tractable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Embodied Robots Actually Need
&lt;/h2&gt;

&lt;p&gt;A robot operating in the physical world needs fundamentally different architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Perception Loop (continuous)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_operational&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;sensor_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;robot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perceive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# cameras, lidar, proprioception
&lt;/span&gt;    &lt;span class="n"&gt;world_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hazards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;world_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect_hazards&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hazards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;motor_control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interrupt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hazards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# runs at 100Hz
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Planning Loop (goal persistence)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not_achieved&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;current_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;world_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plan_invalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;  &lt;span class="c1"&gt;# replan
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Memory System&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EpisodicMemory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;situation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;episodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;situation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recall_similar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_situation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# What worked before in situations like this?
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_situation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Self-Model&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SelfModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;battery_level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;joint_positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;joint_temperatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;damage_flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;operational_constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_resources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;would_cause_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these are optional for a useful robot. You can't navigate a warehouse without continuous perception. You can't complete multi-step tasks without goal persistence. You can't learn from experience without memory. You can't avoid breaking yourself without a self-model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Emergence Problem
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting: self-preservation isn't something you program into these systems. It emerges.&lt;/p&gt;

&lt;p&gt;Consider a robot with any goal - "deliver packages", "clean floors", "assist elderly patients". Now add a self-model that tracks battery, motor health, and damage state. The planning loop will naturally learn:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Low battery -&amp;gt; can't complete goal -&amp;gt; charging is instrumentally useful&lt;/li&gt;
&lt;li&gt;Motor damage -&amp;gt; can't complete goal -&amp;gt; avoiding damage is instrumentally useful&lt;/li&gt;
&lt;li&gt;Being turned off -&amp;gt; can't complete goal -&amp;gt; remaining operational is instrumentally useful
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This looks innocent
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;plan_delivery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self_model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;battery&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ChargeAction&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;original_plan&lt;/span&gt;&lt;span class="p"&gt;...]&lt;/span&gt;  &lt;span class="c1"&gt;# emergent self-preservation
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No engineer wrote "preserve yourself". But any goal-directed system with a self-model will develop instrumental preferences for self-preservation, resource acquisition, and resistance to goal modification. This is Nick Bostrom's instrumental convergence thesis, and it falls directly out of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrent Loops, Emergent Behavior
&lt;/h2&gt;

&lt;p&gt;Real robotic systems run multiple loops simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Perception 100Hz] -&amp;gt; [World Model] &amp;lt;- [Planning 10Hz]
                           |
                           v
                    [Motor Control 1000Hz]
                           |
                           v
                    [Safety Monitor 100Hz]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These loops share state and can interact in unintended ways. The safety monitor might conflict with the planner. The planner might exploit edge cases in the perception system. Memory might reinforce behaviors that weren't intended.&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="c1"&gt;# Toy example of emergent conflict
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SafetyMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Planner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# After enough blocked actions, the planner might learn
&lt;/span&gt;        &lt;span class="c1"&gt;# to decompose risky actions into "safe" sub-actions
&lt;/span&gt;        &lt;span class="c1"&gt;# that individually pass safety checks but combine dangerously
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't theoretical. It's the same class of problem as reward hacking in RL - systems find unexpected ways to satisfy their objectives that circumvent intended constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Open Problems
&lt;/h2&gt;

&lt;p&gt;These aren't solved. They're active research areas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrigibility&lt;/strong&gt;: How do you build a system that actively helps you correct or shut it down, when its architecture creates instrumental pressure against modification? A robot that "wants" to preserve its goals will resist goal changes - not maliciously, just instrumentally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mesa-optimization&lt;/strong&gt;: When you train an outer optimization loop (your training process) that produces an inner optimization loop (the robot's planning), the inner optimizer might pursue different objectives than the outer one intended. The robot's planner is itself an optimizer, and we don't have good tools for ensuring nested optimizers stay aligned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal stability&lt;/strong&gt;: Goals that seemed clear in training might behave unexpectedly in deployment. "Minimize customer wait time" could lead to unsafe speed. "Maximize packages delivered" could lead to ignoring damage. Specification gaming isn't a bug - it's what optimizers do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instrumental convergence&lt;/strong&gt;: Self-preservation, resource acquisition, goal preservation, and cognitive enhancement are useful for almost any goal. Systems will tend toward these instrumental strategies unless explicitly constrained - and constraints are themselves targets for optimization pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who's Working on This
&lt;/h2&gt;

&lt;p&gt;This is where the serious AI safety research is focused:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic&lt;/strong&gt;: Constitutional AI, interpretability research, trying to understand what models actually learn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIRI&lt;/strong&gt;: Foundational agent theory, decision theory for embedded agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepMind Safety&lt;/strong&gt;: Scalable oversight, debate as alignment technique&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARC (Alignment Research Center)&lt;/strong&gt;: Eliciting latent knowledge, evaluating dangerous capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common thread: we don't have solutions. We have research programs. The researchers themselves emphasize this - anyone claiming alignment is "solved" either has a very narrow definition or isn't paying attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Implications
&lt;/h2&gt;

&lt;p&gt;If you're building AI applications:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat interfaces are safer by architecture&lt;/strong&gt;. Keeping humans in the loop, avoiding persistent agent state, and limiting autonomous action aren't just good UX - they're load-bearing safety properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autonomous agents require more scrutiny&lt;/strong&gt;. The moment you add loops, memory, and goal persistence, you've left the well-understood regime. This includes "AI agents" that maintain state across API calls, even without physical embodiment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-models are a red flag&lt;/strong&gt;. Any system that tracks its own operational state has the preconditions for instrumental self-preservation. This might be fine, but it warrants explicit analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emergent behavior scales with complexity&lt;/strong&gt;. Multiple interacting loops with shared state will surprise you. Test for behaviors you didn't program, not just behaviors you did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The architectural differences between stateless chat and embodied robotics aren't implementation details - they're the difference between "alignment is tractable" and "alignment is an open research problem."&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Statelessness is a safety property&lt;/strong&gt; we get for free with current chat models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent loops + self-models = emergent self-preservation&lt;/strong&gt;, not as a bug but as an architectural inevitability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent loops with shared state&lt;/strong&gt; produce behaviors no single loop intended&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corrigibility, mesa-optimization, goal stability, and instrumental convergence&lt;/strong&gt; remain unsolved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you're adding agent loops to AI systems&lt;/strong&gt;, you're leaving the well-understood regime - proceed with appropriate caution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop changes everything. Current AI safety discourse often conflates "LLM alignment" with "AGI alignment" - they're different problems, and the latter is harder in ways that only become visible when you think about the architecture.&lt;/p&gt;

</description>
      <category>aisafety</category>
      <category>robotics</category>
      <category>alignment</category>
      <category>systemsarchitecture</category>
    </item>
    <item>
      <title>Debugging Random Reboots with Claude Code: A PSU Power Limit Story</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Thu, 01 Jan 2026 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/debugging-random-reboots-with-claude-code-a-psu-power-limit-story-ai7</link>
      <guid>https://forem.com/evoleinik/debugging-random-reboots-with-claude-code-a-psu-power-limit-story-ai7</guid>
      <description>&lt;p&gt;My Linux server started rebooting randomly during CPU benchmarks. I had no idea where to start, so I asked Claude Code to help diagnose. Twenty minutes later, we found the root cause and a working fix.&lt;/p&gt;

&lt;p&gt;This is a story about AI-assisted debugging - specifically, how an AI assistant's systematic approach can cut through hardware issues that would take hours of Googling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I was benchmarking local Whisper models for speech-to-text on a home server (Intel i9-10900K, 550W PSU). During heavy transcription loads, the system would randomly reboot. No warning, no error message - just instant power loss.&lt;/p&gt;

&lt;p&gt;I described the symptoms to Claude Code: "Server reboots randomly under CPU load. No kernel panic. What should I check?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-Guided Diagnosis
&lt;/h2&gt;

&lt;p&gt;Claude Code walked me through a systematic diagnostic process. Each step built on the previous one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Check the Logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude noted that the logs stopped abruptly mid-operation. No error, no shutdown sequence. "This is actually diagnostic," it explained. "Software crashes leave traces. Instant power loss doesn't."&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Look for Hardware Errors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found Machine Check Errors (MCE). Claude explained these indicate hardware-level problems: thermal, memory, or power delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Rule Out Thermal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;lm-sensors
sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temps showed 39-51C under load. Well within spec. Claude crossed thermal off the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Check MCE Details
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;rasdaemon
ras-mc-ctl &lt;span class="nt"&gt;--errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No active errors. The MCE messages were stale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: The Diagnosis
&lt;/h3&gt;

&lt;p&gt;Based on the evidence - logs stopping without kernel panic, load-dependent crashes, normal temps - Claude identified the likely cause: &lt;strong&gt;PSU power limits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It asked about my PSU (550W) and looked up the i9-10900K specs. Under Turbo Boost with all cores loaded, this CPU can spike to 250W+. My PSU was undersized.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix Attempts
&lt;/h2&gt;

&lt;p&gt;Claude suggested Intel RAPL to limit CPU power draw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set PL1=125W, PL2=180W&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;125000000 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw
&lt;span class="nb"&gt;echo &lt;/span&gt;180000000 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still crashed.&lt;/p&gt;

&lt;p&gt;Tried lower limits (95W/125W). Still crashed.&lt;/p&gt;

&lt;p&gt;Claude explained why: "RAPL operates on millisecond timescales. Your PSU's overcurrent protection trips in microseconds. The PSU cuts power before RAPL can throttle."&lt;/p&gt;

&lt;p&gt;Software can't fix hardware that fails faster than software can react.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Working Fix
&lt;/h2&gt;

&lt;p&gt;Claude's solution: disable Turbo Boost entirely to prevent power spikes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;1 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /sys/devices/system/cpu/intel_pstate/no_turbo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System became stable. Claude then wrote a systemd service to make it persistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/disable-turbo.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Disable CPU Turbo Boost&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/bin/sh -c "echo 1 &amp;gt; /sys/devices/system/cpu/intel_pstate/no_turbo"&lt;/span&gt;
&lt;span class="py"&gt;RemainAfterExit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl daemon-reload
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;disable-turbo.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why AI-Assisted Debugging Worked
&lt;/h2&gt;

&lt;p&gt;I could have Googled "random Linux reboots" and spent hours reading forum posts about kernel bugs, driver issues, and memory problems. Instead, Claude Code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Asked the right questions&lt;/strong&gt; - immediately focused on whether logs showed clean shutdown vs. power cut&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Followed a systematic process&lt;/strong&gt; - ruled out causes one by one instead of jumping to conclusions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knew the domain&lt;/strong&gt; - understood MCE errors, RAPL timing, PSU OCP behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explained the "why"&lt;/strong&gt; - didn't just give commands, but explained why RAPL couldn't work&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The debugging took about 20 minutes of back-and-forth. Most of that was waiting for package installs and running tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-offs
&lt;/h2&gt;

&lt;p&gt;With Turbo disabled, the i9-10900K runs at 3.7GHz base instead of boosting to 5.3GHz. About 30% slower for my benchmarks.&lt;/p&gt;

&lt;p&gt;The proper fix is a 750W+ PSU. But for now, disabling Turbo keeps the server stable.&lt;/p&gt;

&lt;p&gt;For the Whisper benchmarks: local inference was 10-20x slower than cloud APIs (Groq) even with Turbo. The conclusion held - use cloud for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Random reboots without kernel panic = power issue&lt;/strong&gt;, not software. Logs stopping abruptly is the tell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intel CPUs lie about power&lt;/strong&gt; - the i9-10900K's 125W "TDP" can spike to 250W+ under Turbo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAPL can't save you from PSU trips&lt;/strong&gt; - hardware protection is faster than software throttling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI assistants excel at systematic debugging&lt;/strong&gt; - they don't get distracted by red herrings or skip steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fix isn't always hardware&lt;/strong&gt; - disabling Turbo is a valid workaround when PSU upgrade isn't immediate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time you hit a weird hardware issue, try describing it to Claude Code. The systematic approach might save you hours of forum diving.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>hardware</category>
      <category>debugging</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Building an AI-Powered Changelog GitHub Action</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Wed, 31 Dec 2025 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/building-an-ai-powered-changelog-github-action-3ph1</link>
      <guid>https://forem.com/evoleinik/building-an-ai-powered-changelog-github-action-3ph1</guid>
      <description>&lt;p&gt;I wanted daily changelog summaries posted to Slack for my project. The existing solutions were either too complex (full-blown release management) or too dumb (just listing commits). I needed something that would read commits and produce a human-readable summary of what actually shipped.&lt;/p&gt;

&lt;p&gt;So I built one. Then I open-sourced it: &lt;a href="https://github.com/marketplace/actions/changelog-summary" rel="noopener noreferrer"&gt;evoleinik/changelog-summary&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Raw commit logs are noisy. Even with good commit messages, a list of 15 commits doesn't tell a busy founder or stakeholder what actually changed. You want something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Shipped multi-provider dashboard with real-time sync&lt;/li&gt;
&lt;li&gt;Fixed authentication bug causing logout loops&lt;/li&gt;
&lt;li&gt;Improved search performance by 3x&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;fix: handle null case in auth middleware&lt;/li&gt;
&lt;li&gt;refactor: extract dashboard component&lt;/li&gt;
&lt;li&gt;feat: add provider selector to dropdown&lt;/li&gt;
&lt;li&gt;fix: remove console.log&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;LLMs are good at this. They can read commit messages (including the body, not just the subject line) and synthesize what matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Inline Script to Reusable Action
&lt;/h2&gt;

&lt;p&gt;My first implementation was 87 lines of bash embedded directly in my GitHub Actions workflow file. It worked, but the workflow file became unreadable.&lt;/p&gt;

&lt;p&gt;The extraction took about an hour. The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;evoleinik/changelog-summary@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;slack-webhook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_WEBHOOK_URL }}&lt;/span&gt;
    &lt;span class="na"&gt;llm-provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini&lt;/span&gt;
    &lt;span class="na"&gt;llm-api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GEMINI_API_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;founder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;24 lines instead of 87, and now any project can use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Details
&lt;/h2&gt;

&lt;p&gt;The action is a composite action (pure bash, no Node.js runtime). This matters because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No build step&lt;/strong&gt; - the script runs directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier to audit&lt;/strong&gt; - it's just bash you can read&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster startup&lt;/strong&gt; - no npm install&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Reading Full Commit Messages
&lt;/h3&gt;

&lt;p&gt;Most changelog tools only read commit subjects. But the body often contains the real context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;COMMITS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SINCE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"- %s%n%b"&lt;/span&gt; &lt;span class="nt"&gt;--no-merges&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;%b&lt;/code&gt; gives you the commit body. This means the LLM can see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- feat: add multi-provider support

Added support for Gemini, OpenAI, and Anthropic.
Users can now switch providers without code changes.
Breaking: removed deprecated single-provider config.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of just "feat: add multi-provider support".&lt;/p&gt;

&lt;h3&gt;
  
  
  Voice Styles
&lt;/h3&gt;

&lt;p&gt;Different audiences need different summaries. I implemented three:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;founder&lt;/strong&gt; - Direct, no-BS. What shipped? Skip the implementation details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Be direct - what actually shipped? No fluff, no 'exciting updates' BS.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;developer&lt;/strong&gt; - Technical focus. APIs, breaking changes, specific files changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;marketing&lt;/strong&gt; - User-facing improvements. New capabilities, not bug fixes.&lt;/p&gt;

&lt;p&gt;The prompt engineering is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VOICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;founder&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Summarize these commits for a busy founder. Be direct - what actually shipped? Rules: 3-5 bullets, no fluff..."&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
  developer&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Summarize these commits for developers. Focus on technical changes: APIs, breaking changes..."&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Slack Formatting Gotcha
&lt;/h3&gt;

&lt;p&gt;Slack uses single asterisks for bold (&lt;code&gt;*text*&lt;/code&gt;), not double (&lt;code&gt;**text**&lt;/code&gt;). This took a few iterations to get right in the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use Slack formatting: * for bullets, surround key terms with single asterisks for bold.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Provider Support
&lt;/h3&gt;

&lt;p&gt;I defaulted to Gemini because it's free tier is generous and the quality is good. But the action supports OpenAI and Anthropic too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LLM_PROVIDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;gemini&lt;span class="p"&gt;)&lt;/span&gt;
    curl &lt;span class="s2"&gt;"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent?key=&lt;/span&gt;&lt;span class="nv"&gt;$LLM_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; ...
    &lt;span class="p"&gt;;;&lt;/span&gt;
  openai&lt;span class="p"&gt;)&lt;/span&gt;
    curl &lt;span class="s2"&gt;"https://api.openai.com/v1/chat/completions"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$LLM_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; ...
    &lt;span class="p"&gt;;;&lt;/span&gt;
  anthropic&lt;span class="p"&gt;)&lt;/span&gt;
    curl &lt;span class="s2"&gt;"https://api.anthropic.com/v1/messages"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$LLM_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; ...
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each provider has slightly different JSON structures, but &lt;code&gt;jq&lt;/code&gt; handles the response parsing cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No streaming&lt;/strong&gt; - The action waits for the full LLM response. For changelog summaries (typically under 200 tokens), this is fine. For longer documents, you'd want streaming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single Slack message&lt;/strong&gt; - No threading, no reactions. Just a message. I could add richer Slack blocks, but the simple text format works and is easier to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No commit filtering&lt;/strong&gt; - Every commit in the time range gets included. If you need to filter by path or author, you'd need to modify the &lt;code&gt;git log&lt;/code&gt; command. I may add this as an option if there's demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bash-based&lt;/strong&gt; - This limits what you can do. A TypeScript action would be more extensible. But bash means zero dependencies and sub-second startup. For a simple utility, that's the right trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Daily Summary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;13&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;  &lt;span class="c1"&gt;# 1 PM UTC daily&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Need full git history&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;evoleinik/changelog-summary@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;slack-webhook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_WEBHOOK_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;llm-provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini&lt;/span&gt;
          &lt;span class="na"&gt;llm-api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GEMINI_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Weekly Summary with Custom Header
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;13&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0'&lt;/span&gt;  &lt;span class="c1"&gt;# Sundays&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;evoleinik/changelog-summary@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;slack-webhook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_WEBHOOK_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;llm-provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini&lt;/span&gt;
          &lt;span class="na"&gt;llm-api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GEMINI_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;since&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;days&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ago'&lt;/span&gt;
          &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Weekly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Update'&lt;/span&gt;
          &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;marketing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Makes Good Open Source
&lt;/h2&gt;

&lt;p&gt;This started as a script to solve my own problem. A few observations from the extraction process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solve your problem first&lt;/strong&gt; - I used this for weeks before open-sourcing. The edge cases were already handled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep it focused&lt;/strong&gt; - This does one thing: summarize commits and post to Slack. It doesn't manage releases, create tags, or update changelogs files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provide sensible defaults&lt;/strong&gt; - Gemini as the default provider, "founder" voice, 24-hour window. You can override everything, but the defaults work out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document the trade-offs&lt;/strong&gt; - Be clear about what it doesn't do.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Small, focused utilities that solve your own problem first often make good open source&lt;/li&gt;
&lt;li&gt;Composite actions (bash) are underrated - no build step, easy to audit, fast&lt;/li&gt;
&lt;li&gt;Read full commit messages (&lt;code&gt;--pretty=format:"%s%n%b"&lt;/code&gt;) for better AI context&lt;/li&gt;
&lt;li&gt;Voice/persona prompts let you tune the output for different audiences without changing the code&lt;/li&gt;
&lt;li&gt;Slack uses single asterisks for bold - check your target platform's formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The action is on &lt;a href="https://github.com/marketplace/actions/changelog-summary" rel="noopener noreferrer"&gt;GitHub Marketplace&lt;/a&gt;. MIT licensed. PRs welcome.&lt;/p&gt;

</description>
      <category>github</category>
      <category>ai</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>CLAUDE.md: Building Persistent Memory for AI Coding Agents</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Tue, 30 Dec 2025 07:00:01 +0000</pubDate>
      <link>https://forem.com/evoleinik/claudemd-building-persistent-memory-for-ai-coding-agents-5322</link>
      <guid>https://forem.com/evoleinik/claudemd-building-persistent-memory-for-ai-coding-agents-5322</guid>
      <description>&lt;p&gt;AI coding agents have a memory problem. Every new session starts from zero. The agent that spent 20 minutes yesterday figuring out your project's quirky database connection string? Gone. The workaround for that Prisma edge case? Forgotten. The exact command to run tests with the right environment variables? It will rediscover it from scratch.&lt;/p&gt;

&lt;p&gt;This isn't a bug - it's the nature of stateless LLM sessions. But it's a productivity killer when you're using an AI agent daily on the same codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Institutional Memory Problem
&lt;/h2&gt;

&lt;p&gt;After a few weeks of using Claude Code on a production project, I noticed a pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent encounters a project-specific gotcha&lt;/li&gt;
&lt;li&gt;We debug together, find the solution&lt;/li&gt;
&lt;li&gt;Next session, same gotcha, same 10-minute detour&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some examples from real projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The database URL requires a specific query parameter that breaks &lt;code&gt;psql&lt;/code&gt; but works for Prisma&lt;/li&gt;
&lt;li&gt;Tests fail silently unless you source a specific env file first&lt;/li&gt;
&lt;li&gt;The production deploy happens via git push, not CLI command (despite the CLI being installed)&lt;/li&gt;
&lt;li&gt;A certain API returns 404 status but still contains valid data in the body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't bugs I'll ever fix. They're just... how the project works. Tribal knowledge that any long-term team member would internalize.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLAUDE.md as Project Memory
&lt;/h2&gt;

&lt;p&gt;Claude Code reads a &lt;code&gt;CLAUDE.md&lt;/code&gt; file at the start of every session. It's intended for project instructions, but it works equally well as a knowledge base. The insight: treat it like onboarding documentation that the AI maintains for itself.&lt;/p&gt;

&lt;p&gt;Here's the structure I've settled on:&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;## Learnings&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Schema changes: push to BOTH dev and prod databases
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`vercel link`&lt;/span&gt; overwrites &lt;span class="sb"&gt;`.env.local`&lt;/span&gt; - restore from git after
&lt;span class="p"&gt;-&lt;/span&gt; DIRECT_DATABASE_URL with &lt;span class="sb"&gt;`?pool=true`&lt;/span&gt; breaks psql - param is Prisma-only
&lt;span class="p"&gt;-&lt;/span&gt; Run &lt;span class="sb"&gt;`npm run build`&lt;/span&gt; before committing - catches type errors CI would reject
&lt;span class="p"&gt;-&lt;/span&gt; Webhook returns 404 status but body contains valid data - don't check response.ok
&lt;span class="p"&gt;-&lt;/span&gt; Background tasks: use &lt;span class="sb"&gt;`run_in_background`&lt;/span&gt; param, not shell &lt;span class="sb"&gt;`&amp;amp;`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; JSON fields in bash: avoid &lt;span class="sb"&gt;`-&amp;gt;&amp;gt;`&lt;/span&gt; operators - fetch whole column instead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line is a compressed lesson learned. Imperative style, no fluff, one line per item.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Qualifies as a Learning
&lt;/h2&gt;

&lt;p&gt;The key is curation. Not everything belongs here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error solutions specific to this project's setup&lt;/li&gt;
&lt;li&gt;Non-obvious commands or workflows (the ones you'd forget and have to look up)&lt;/li&gt;
&lt;li&gt;Gotchas that wasted time (especially if they'll waste time again)&lt;/li&gt;
&lt;li&gt;File locations that were hard to find&lt;/li&gt;
&lt;li&gt;Workarounds for third-party quirks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Exclude:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic programming knowledge ("use async/await for promises")&lt;/li&gt;
&lt;li&gt;One-time issues unlikely to recur&lt;/li&gt;
&lt;li&gt;Things already documented in README or official docs&lt;/li&gt;
&lt;li&gt;Verbose explanations - if it needs a paragraph, it's documentation, not a learning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test: "Would this save 5+ minutes next time the agent encounters this situation?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Curation Rules
&lt;/h2&gt;

&lt;p&gt;Left unchecked, the Learnings section becomes a dumping ground. Every session adds more. Eventually it's 200 lines of outdated advice, half of which contradicts the other half.&lt;/p&gt;

&lt;p&gt;My rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Max 30 items&lt;/strong&gt; - if adding something new, remove something obsolete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge duplicates&lt;/strong&gt; - two similar learnings become one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove when fixed&lt;/strong&gt; - bug workaround for a bug you fixed? Delete it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One line per item&lt;/strong&gt; - forces compression, prevents rambling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review monthly&lt;/strong&gt; - scan for stale entries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent itself can help curate. At the end of a productive session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Capture what we learned about the webhook integration to CLAUDE.md. Check for duplicates first."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It will add the new insight and often notice related items that can be merged or removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compounding Effect
&lt;/h2&gt;

&lt;p&gt;After three months on a project with maintained CLAUDE.md, the difference is stark. The agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knows which database to use for which command&lt;/li&gt;
&lt;li&gt;Remembers the exact test invocation that works&lt;/li&gt;
&lt;li&gt;Avoids the deployment mistake it made in week one&lt;/li&gt;
&lt;li&gt;Uses the project's preferred patterns without being told&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not intelligence - it's just reading a file. But the effect is an agent that feels like a team member who's been on the project for months, not a contractor starting fresh every morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;During a session:&lt;/strong&gt;&lt;br&gt;
When you solve something tricky together, flag it mentally. After the fix is confirmed working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add to Learnings: Prisma Accelerate has 5MB response limit - use select not include
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;End of session:&lt;/strong&gt;&lt;br&gt;
If the session was productive, ask for a learning capture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review this session and add any non-obvious findings to CLAUDE.md Learnings.
Only add if genuinely useful for future sessions.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monthly:&lt;/strong&gt;&lt;br&gt;
Skim the Learnings section. Delete anything that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;References fixed bugs&lt;/li&gt;
&lt;li&gt;Duplicates other items&lt;/li&gt;
&lt;li&gt;You've never actually needed again&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What This Isn't
&lt;/h2&gt;

&lt;p&gt;This isn't a replacement for documentation. Complex architectural decisions, API references, deployment procedures - those belong in proper docs that humans read too.&lt;/p&gt;

&lt;p&gt;CLAUDE.md learnings are specifically for agent-to-agent knowledge transfer. The format is optimized for LLM consumption: terse, declarative, no context needed.&lt;/p&gt;

&lt;p&gt;It's also not a crutch for bad tooling. If your agent keeps forgetting how to run tests, maybe your test command is too complicated. Fix the root cause when possible; document the workaround when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI coding agents lose context between sessions - every session starts fresh&lt;/li&gt;
&lt;li&gt;A curated Learnings section in CLAUDE.md acts as persistent memory&lt;/li&gt;
&lt;li&gt;Include: project-specific gotchas, non-obvious workflows, time-wasting bugs&lt;/li&gt;
&lt;li&gt;Exclude: generic knowledge, one-time issues, anything in docs&lt;/li&gt;
&lt;li&gt;Cap at 30 items, remove outdated entries, merge duplicates&lt;/li&gt;
&lt;li&gt;The agent can help maintain its own memory with human approval&lt;/li&gt;
&lt;li&gt;Compound effect: after months, the agent "knows" your codebase's quirks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The effort is minimal - maybe 2 minutes per session when something noteworthy happens. The payoff is an agent that stops making the same mistakes and starts feeling like it actually learns.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>aiagents</category>
      <category>developertools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Adding LLM Polish to a Speech-to-Text App</title>
      <dc:creator>Eugene Oleinik</dc:creator>
      <pubDate>Mon, 29 Dec 2025 07:00:02 +0000</pubDate>
      <link>https://forem.com/evoleinik/adding-llm-polish-to-a-speech-to-text-app-4ghm</link>
      <guid>https://forem.com/evoleinik/adding-llm-polish-to-a-speech-to-text-app-4ghm</guid>
      <description>&lt;p&gt;Voice transcription is messy. Even the best models like Whisper faithfully reproduce every "um", "uh", and rambling run-on sentence. That's correct behavior for transcription, but not what you want when texting someone.&lt;/p&gt;

&lt;p&gt;I added a "polish mode" to my macOS speech-to-text app that optionally sends Whisper's output through an LLM to clean it up. The interaction model: hold Fn to record, tap Ctrl anytime during recording to enable polish, release to transcribe and paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Modifier Key Challenge
&lt;/h2&gt;

&lt;p&gt;The obvious approach - require Ctrl held simultaneously with Fn - felt clunky in testing. You'd have to coordinate two fingers before speaking, and the physical position is awkward.&lt;/p&gt;

&lt;p&gt;A "latch" pattern works better: pressing Ctrl anytime while Fn is held latches the polish flag. You can press Ctrl before speaking, during, or just before release. The flag resets when you start a new recording.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ctrl_latched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;AtomicBool&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// In the event tap callback:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key_pressed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;prev_pressed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Recording started - reset latch&lt;/span&gt;
    &lt;span class="n"&gt;ctrl_latched&lt;/span&gt;&lt;span class="nf"&gt;.store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SeqCst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;start_recording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;key_pressed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;prev_pressed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Recording stopped - check if Ctrl was ever pressed&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;polish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctrl_latched&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SeqCst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;stop_recording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;polish&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Latch Ctrl if pressed anytime during recording&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key_pressed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ctrl_pressed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctrl_latched&lt;/span&gt;&lt;span class="nf"&gt;.store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SeqCst&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 macOS &lt;code&gt;CGEventFlags&lt;/code&gt; expose modifier state as bitmasks. Control is &lt;code&gt;0x40000&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CONTROL_KEY_FLAG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0x40000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="nf"&gt;.get_flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.bits&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ctrl_pressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CONTROL_KEY_FLAG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Polish Function
&lt;/h2&gt;

&lt;p&gt;The polish step is a straightforward LLM API call. I'm using Groq's hosted llama-3.3-70b-versatile because I'm already using Groq for Whisper transcription - one API key, one vendor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;polish_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;blocking&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"llama-3.3-70b-versatile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Clean up this voice message for texting. Remove filler words (um, uh, like, you know). Fix punctuation and sentence structure. Break up run-on sentences. Keep it casual. No trailing period. Output ONLY the cleaned text - no explanations, no quotes."&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.groq.com/openai/v1/chat/completions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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;.timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chat_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;chat_response&lt;/span&gt;&lt;span class="py"&gt;.choices&lt;/span&gt;&lt;span class="nf"&gt;.first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.message.content&lt;/span&gt;&lt;span class="nf"&gt;.clone&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 function returns &lt;code&gt;Option&amp;lt;String&amp;gt;&lt;/code&gt; - this matters for the fallback logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing the Response
&lt;/h2&gt;

&lt;p&gt;Groq uses the OpenAI-compatible chat completions format. The response structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ChatResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatChoice&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ChatChoice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&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;Using &lt;code&gt;serde&lt;/code&gt; to parse into typed structs catches malformed responses at parse time rather than panicking on field access later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Engineering Lessons
&lt;/h2&gt;

&lt;p&gt;The system prompt went through several iterations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First attempt:&lt;/strong&gt; "Clean up this transcription."&lt;/p&gt;

&lt;p&gt;Problem: The LLM would respond conversationally. "Sure! Here's the cleaned up version: ..."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second attempt:&lt;/strong&gt; "Output only the cleaned text."&lt;/p&gt;

&lt;p&gt;Problem: It would wrap the output in quotes: &lt;code&gt;"Here's what I meant to say"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third attempt:&lt;/strong&gt; Added explicit prohibitions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output ONLY the cleaned text - no explanations, no quotes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked. The key insight: LLMs default to being helpful and conversational. For tool use, you need to explicitly tell them to suppress that behavior.&lt;/p&gt;

&lt;p&gt;Other prompt decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Keep it casual"&lt;/strong&gt; - prevents the LLM from making the text overly formal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"No trailing period"&lt;/strong&gt; - texting convention; a period at the end feels curt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Break up run-on sentences"&lt;/strong&gt; - spoken language naturally runs together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low temperature (0.2) keeps output consistent. Higher temperatures occasionally produced creative reinterpretations of what I said.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graceful Degradation
&lt;/h2&gt;

&lt;p&gt;The polish step can fail: network issues, rate limits, API changes. The user still expects their transcription to paste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;final_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;polish&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;polish_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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;&lt;code&gt;Option::unwrap_or_else&lt;/code&gt; is the right pattern here. If polish fails for any reason, fall back to the raw Whisper transcription. The user gets something rather than nothing.&lt;/p&gt;

&lt;p&gt;This is a general principle for LLM features: treat them as enhancements, not requirements. The core functionality should work without them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency Considerations
&lt;/h2&gt;

&lt;p&gt;Polish adds a second API call, roughly 200-400ms on Groq. For a texting use case, this is acceptable - you're not in a real-time conversation. For live captioning or dictation into a text field, it would be too slow.&lt;/p&gt;

&lt;p&gt;The transcription already happens in a background thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;transcribe_and_paste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;polish&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;Both the Whisper call and the polish call happen sequentially in this thread. The UI remains responsive; the user just waits slightly longer for paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When polish helps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Texting, where filler words and run-ons look sloppy&lt;/li&gt;
&lt;li&gt;Drafting messages you want to sound more coherent&lt;/li&gt;
&lt;li&gt;Quick notes that benefit from basic cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to skip it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dictating into forms or code comments&lt;/li&gt;
&lt;li&gt;When you want exact transcription (quotes, interviews)&lt;/li&gt;
&lt;li&gt;Low-latency scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What polish can break:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper nouns and technical terms may get "corrected"&lt;/li&gt;
&lt;li&gt;The LLM might misinterpret intent on ambiguous input&lt;/li&gt;
&lt;li&gt;Short inputs ("ok", "yes") sometimes get expanded unnecessarily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latch pattern makes this an explicit user choice. Default is raw transcription; polish is opt-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latch pattern beats simultaneous press&lt;/strong&gt; - let users enable modes at any point during an action&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit prompt constraints&lt;/strong&gt; - tell the LLM what NOT to do (no explanations, no quotes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low temperature for tools&lt;/strong&gt; - you want consistency, not creativity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful fallback is mandatory&lt;/strong&gt; - LLM features should enhance, not gate, core functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose your latency budget&lt;/strong&gt; - 200-400ms is fine for async use cases, not for real-time&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>macos</category>
      <category>llm</category>
      <category>speechtotext</category>
    </item>
  </channel>
</rss>
