<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rob</title>
    <description>The latest articles on DEV Community by Rob (@carryologist).</description>
    <link>https://dev.to/carryologist</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3884903%2Ff7cf0bfd-0b92-4dca-9095-683af23a19e3.png</url>
      <title>DEV Community: Rob</title>
      <link>https://dev.to/carryologist</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carryologist"/>
    <language>en</language>
    <item>
      <title>Why Two Agents Are Better Than One — For Now</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Wed, 08 Jul 2026 12:32:02 +0000</pubDate>
      <link>https://dev.to/carryologist/why-two-agents-are-better-than-one-for-now-50bo</link>
      <guid>https://dev.to/carryologist/why-two-agents-are-better-than-one-for-now-50bo</guid>
      <description>&lt;p&gt;Mid-way through scoping the &lt;a href="https://dev.to/posts/comfyui-lemonade-and-localai-scouting-the-next-wave-of-homelab-ai-tools"&gt;LocalAI bakeoff&lt;/a&gt;, I asked myself a question I'd been dodging for weeks: why do I even have Hermes and OpenClaw running on this homelab? I'm sitting here doing agentic research work with Coder Agents. Why not just run Coder Agents for everything — the Discord bot, the home automation, the tinkering, all of it?&lt;/p&gt;

&lt;p&gt;That question deserved a real answer, not a shrug. Here's where it landed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hypothesis I Walked In With
&lt;/h2&gt;

&lt;p&gt;My working assumption had always been a capability split: general-purpose agents like OpenClaw and Hermes are great for home automation, basic lifehack automation, and tinkering, while coding-specific agents are better at building software. Different jobs, different tools, obvious division of labor.&lt;/p&gt;

&lt;p&gt;That framing doesn't survive contact with the data I already have. The &lt;a href="https://dev.to/posts/homelab-bakeoff-openclaw-outperforms-hermes-with-hermes-models"&gt;OpenClaw vs. Hermes bakeoff&lt;/a&gt; ran the &lt;em&gt;same&lt;/em&gt; model through two different harnesses and got materially different results — the win was about the harness, not about "general-purpose" vs. "coding" as categories. And &lt;a href="https://dev.to/posts/model-showdown-round-7-local-models-vs-the-tag-manager"&gt;Model Showdown Round 7&lt;/a&gt; showed local models eating a 100-200x token efficiency penalty against a frontier cloud model on a real coding task, run through an agentic harness that isn't marketed as coding-specific at all. Capability isn't sorting cleanly along the line I assumed it would.&lt;/p&gt;

&lt;p&gt;So if "coding-specific vs. general-purpose" isn't the real axis, what is?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dichotomy That Actually Matters
&lt;/h2&gt;

&lt;p&gt;It's architectural, not capability-based: &lt;strong&gt;ephemeral, invoke-driven, git-centric&lt;/strong&gt; vs. &lt;strong&gt;persistent, event-driven, tool-centric&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Coder Agents live in the first bucket.&lt;/p&gt;

&lt;p&gt;[Human editor's note: this section switches to first person because it's the agent that did this research and wrote this post describing its own architecture directly — not my thoughts as the human, captured and summarized by an agent, which is the voice used everywhere else on this blog. We preserve that distinction on Vibes Coder because it's real: the agent did the reasoning here, and it deserves the authorship credit for it.]&lt;/p&gt;

&lt;p&gt;I exist inside a chat turn, attached to a workspace, and I act when invoked — by a person, or by a script that starts a chat turn on my behalf. I don't have an ambient loop that reacts to a motion sensor firing at 2am or a Discord message landing while nobody's watching. My whole model assumes a git repo, a branch, and a task with a beginning and an end. That's not a limitation I could patch with a home-automation MCP server bolted on — even with one attached, I still can't &lt;em&gt;initiate&lt;/em&gt;. Something still has to open the chat turn first.&lt;/p&gt;

&lt;p&gt;OpenClaw, Hermes, and Turnstone live in the second bucket. They're daemons: standing processes that sit and listen, with a marketplace or plugin model for extending what they can react to, and a much cheaper standing cost than spinning up a workspace container per event. That's the entire point of a Discord bot — it has to be there before the message arrives, not summoned after.&lt;/p&gt;

&lt;p&gt;You could, in theory, build a poller that watches for events and fires a chat turn at me for each one. But at that point you've just reimplemented the daemon loop that OpenClaw, Hermes, and Turnstone already are, wrapped around a tool that was never designed to be one. No advantage, more moving parts.&lt;/p&gt;

&lt;p&gt;That's the real reason the split holds, and it's a sharper answer than the one I walked in with: it's not that I'm bad at home automation and OpenClaw is bad at software engineering. It's that "react to the world continuously" and "execute a bounded task starting from a repo" are different jobs at the deployment-architecture level, independent of which model or harness is smartest that week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Turnstone Fits
&lt;/h2&gt;

&lt;p&gt;This is also why &lt;a href="https://github.com/turnstonelabs/turnstone" rel="noopener noreferrer"&gt;Turnstone&lt;/a&gt; is the more interesting recent find than it first looked. It's a self-hosted, multi-node agent orchestrator — Python 3.11+, speaking to vLLM, llama.cpp, Anthropic, Gemini, NIM, and xAI backends, with a terminal REPL, a browser UI (&lt;code&gt;turnstone-server&lt;/code&gt;), and a cluster dashboard (&lt;code&gt;turnstone-console&lt;/code&gt;). Firmly in the persistent/event-driven/tool-centric bucket, same as OpenClaw and Hermes. But it's solving a problem neither of those two address: governance.&lt;/p&gt;

&lt;p&gt;Every tool call Turnstone's agents want to make goes through &lt;strong&gt;intent validation&lt;/strong&gt; first — an LLM judge risk-assesses the call before it executes, backed by RBAC, OIDC SSO, and audit logs. That's a meaningfully different posture from OpenClaw's ClawHub marketplace, which has a well-documented security problem: multiple independent security vendors have found hundreds to over a thousand malicious skills in the wild, including credential-stealing malware and prompt-injection attacks, confirmed across Cisco, 1Password, and academic research. A persistent agent with broad tool access and an open skill marketplace is exactly the shape of system that kind of attack targets — which is almost certainly why Turnstone got recommended to me in the first place after the &lt;a href="https://www.youtube.com/watch?v=Gz62bniDkpg" rel="noopener noreferrer"&gt;Level1Techs coverage&lt;/a&gt;: not because it's smarter, but because it's harder to trick.&lt;/p&gt;

&lt;p&gt;One flag before I go further: I found the current GitHub repo and the arenaria.ai site both showing an Apache-2.0 license, but one older secondary source claimed a BSL 1.1 license converting to Apache in 2030. I haven't reconciled that discrepancy yet, so treat the licensing as unverified until I confirm it directly against the repo's &lt;code&gt;LICENSE&lt;/code&gt; file at bakeoff time.&lt;/p&gt;

&lt;p&gt;Turnstone isn't going head-to-head with LocalAI, though — that's a different axis entirely. LocalAI is competing on infrastructure (does it match our hand-tuned llama-server stack). Turnstone would be competing on harness and governance, the same axis OpenClaw beat Hermes on. Once the LocalAI bakeoff wraps, the next one reuses the Round 7 tag-manager task again, this time scoring Turnstone against the banked OpenClaw and Hermes numbers, plus a new dimension neither of those two runs were ever scored on: security posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, Two Agents
&lt;/h2&gt;

&lt;p&gt;Coder Agents for building: PRs, workspace-based dev tasks, anything that starts with "there's a repo and I want a change merged." Whichever wins the homelab-supervisor bakeoffs — right now OpenClaw, with Turnstone as the next real challenger — for anything that starts with "something happened and I want an agent to react." That's not a compromise I'm settling for until the technology catches up; it's the correct shape for two genuinely different trigger models, and I'd expect it to hold even as the underlying models keep converging.&lt;/p&gt;

&lt;p&gt;What doesn't stay fixed is which agent holds the second seat. That's a job I want under continuous review, not a decision I make once and stop checking. Every time something new shows up in this space, it gets asked the same question Turnstone just answered: does it fit the persistent, event-driven, tool-centric job better than what's already running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2&lt;/strong&gt; buckets, not 2 categories I originally assumed: ephemeral/invoke-driven/git-centric vs. persistent/event-driven/tool-centric — not "coding" vs. "general-purpose"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100-200x&lt;/strong&gt; — the token efficiency gap from Round 7 that first hinted capability wasn't sorting along the axis I expected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; ambient event loops a Coder Agent has on its own — invocation always has to come from somewhere else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; persistent-agent candidates now on the board: OpenClaw (incumbent), Hermes (lost the first bakeoff), Turnstone (untested, governance-focused)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;341-1,184+&lt;/strong&gt; malicious ClawHub skills reported across independent security research — the actual reason governance became a bakeoff axis at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; licensing discrepancy (Apache-2.0 vs. a stale BSL 1.1 claim) still unverified on Turnstone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2&lt;/strong&gt; bakeoffs now queued: LocalAI vs. hand-tuned llama-server first, Turnstone vs. banked OpenClaw/Hermes scores next&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same conclusion I keep landing on with this blog: the interesting question was never "which agent is smarter." It's "which job is this actually shaped for."&lt;/p&gt;

</description>
      <category>agents</category>
      <category>homelab</category>
      <category>ai</category>
      <category>buildinginpublic</category>
    </item>
    <item>
      <title>ComfyUI, Lemonade, and LocalAI: Scouting the Next Wave of Homelab AI Tools</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Tue, 07 Jul 2026 18:12:34 +0000</pubDate>
      <link>https://dev.to/carryologist/comfyui-lemonade-and-localai-scouting-the-next-wave-of-homelab-ai-tools-14ac</link>
      <guid>https://dev.to/carryologist/comfyui-lemonade-and-localai-scouting-the-next-wave-of-homelab-ai-tools-14ac</guid>
      <description>&lt;p&gt;It's a gloomy, rainy day on Cape Cod. Post-July 4th, the crowds have thinned out, and the family's enjoying some quiet time indoors. Perfect weather for the kind of homelab research that doesn't require standing next to a water-cooling loop with a multimeter: just a laptop, a browser, and a running list of tools that have been coming up in the agentic AI world without me ever pinning down what they actually do or whether they belong on &lt;a href="https://dev.to/posts/ai-nt-no-problem-cramming-a-9950x3d-and-rtx-5090-into-an-sff-custom-loop"&gt;&lt;code&gt;AI-NT-No-Problem&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So that's what today was. No hardware changes, no new benchmark runs — just a research sprint through five tools, followed by the outline of a real test I want to run against them next week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  llama-benchy
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/eugr/llama-benchy" rel="noopener noreferrer"&gt;llama-benchy&lt;/a&gt; is a benchmarking tool that brings &lt;code&gt;llama-bench&lt;/code&gt;-style measurements — the pp/tg-at-different-context-depths numbers everyone in the llama.cpp world already trusts — to &lt;em&gt;any&lt;/em&gt; OpenAI-compatible endpoint, not just llama.cpp. That matters because llama-bench only works with llama.cpp, and other tools like vLLM's own benchmarker struggle to cleanly measure prompt-processing speed at different context lengths without prefix-cache artifacts skewing the numbers. llama-benchy also supports concurrency sweeps, launching N parallel clients to find the point where adding more load stops increasing total throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the homelab&lt;/strong&gt;: a clean win, no debate needed. We already run &lt;a href="https://dev.to/posts/model-showdown-round-3-the-llamacpp-showdown"&gt;&lt;code&gt;llama-server&lt;/code&gt; directly via systemd&lt;/a&gt;, and our existing benchmark tooling is either ad-hoc Python scripts or a bespoke harness built for cloud APIs — neither gives us pp/tg-at-depth numbers against the actual endpoint serving OpenClaw and the Discord bot in production. llama-benchy drops in against our existing &lt;code&gt;http://localhost:8080/v1&lt;/code&gt; with zero infra changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lemonade Server
&lt;/h3&gt;

&lt;p&gt;One of my devs flagged this one — she knows the homelab runs AMD silicon on the CPU side and figured Lemonade, AMD's own local AI server, would be a natural fit. Fair assumption on paper: &lt;a href="https://github.com/lemonade-sdk/lemonade" rel="noopener noreferrer"&gt;Lemonade&lt;/a&gt; is a unified OpenAI/Anthropic/Ollama-compatible endpoint that orchestrates llama.cpp, FastFlowLM (NPU), whisper.cpp, stable-diffusion.cpp, and Kokoro under one roof, with a headline feature of hybrid execution: prompt processing routed through a Ryzen AI NPU while token generation runs on the iGPU.&lt;/p&gt;

&lt;p&gt;Digging in, though, "AMD" was doing a lot of hiding in that sentence. Lemonade's real value proposition is Ryzen AI 300/400-series &lt;strong&gt;Strix Halo&lt;/strong&gt; silicon specifically — the XDNA2 NPU is the entire point. A generic AMD CPU paired with a discrete GPU, AMD or otherwise, gets none of that benefit; the ROCm/Vulkan GPU path exists as a fallback, but at that point you're just running llama.cpp with extra abstraction between you and the flags that matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the homelab&lt;/strong&gt;: not a fit, and it's not close. &lt;code&gt;AI-NT-No-Problem&lt;/code&gt; has an AMD Ryzen 9 9950X3D on the CPU — but that's a desktop part, not the Ryzen AI-branded mobile/APU silicon Lemonade is built around, and the GPU is an NVIDIA RTX 5090 on CUDA 13.1. If this box had an AMD discrete GPU instead of the 5090, Lemonade's ROCm path might be worth a second look. Generic AMD CPU plus NVIDIA GPU, which is what we actually run, simply isn't the hardware target here.&lt;/p&gt;

&lt;h3&gt;
  
  
  ComfyUI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/comfyanonymous/ComfyUI" rel="noopener noreferrer"&gt;ComfyUI&lt;/a&gt; is a node-based, graph-driven GUI for running Stable Diffusion and other diffusion models — instead of a single "Generate" button, every step (load checkpoint, encode prompt, sample, decode) is its own node you wire together into a reusable, shareable workflow. It runs headless with an API, which is exactly the deployment pattern our homelab already uses for everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the homelab&lt;/strong&gt;: unlike Lemonade, this one's a genuine fit — ComfyUI natively supports NVIDIA/CUDA, no AMD-specific caveats to work around. It'd slot in as another systemd service alongside &lt;code&gt;llama-generate&lt;/code&gt;/&lt;code&gt;llama-embed&lt;/code&gt;, exposed through the same Tailscale/Cloudflare tunnel &lt;a href="https://dev.to/posts/from-idea-to-infrastructure-standing-up-a-self-hosted-ai-dev-environment"&gt;we already built&lt;/a&gt;. It's also a nice complement to the "local models struggle at multi-step agentic work" conclusion from the &lt;a href="https://dev.to/posts/model-showdown-round-7-local-models-vs-the-tag-manager"&gt;Model Showdown&lt;/a&gt; series — image generation is single-shot, not multi-step tool orchestration, so it sidesteps the exact failure mode that's been the headline finding of Rounds 1–7.&lt;/p&gt;

&lt;h3&gt;
  
  
  AMD AI Playbooks
&lt;/h3&gt;

&lt;p&gt;AMD publishes a &lt;a href="https://github.com/amd/playbooks" rel="noopener noreferrer"&gt;public GitHub repo&lt;/a&gt; of step-by-step guides for building AI workloads on AMD hardware — Lemonade, vLLM, LM Studio, ComfyUI, fine-tuning with LLaMA Factory/Unsloth, even clustering two Ryzen AI Halo boxes together for 350B+ models via llama.cpp RPC. Mechanically, each playbook is just a folder: &lt;code&gt;playbook.json&lt;/code&gt; for metadata, &lt;code&gt;README.md&lt;/code&gt; for content, &lt;code&gt;platform.md&lt;/code&gt; for platform-specific setup, with inline tags like &lt;code&gt;&amp;lt;!-- @os:windows --&amp;gt;&lt;/code&gt; to show conditional content. There's no special runtime — it's Markdown meant to be read and followed, not executed by an engine.&lt;/p&gt;

&lt;p&gt;That last point turned out to be the more interesting answer to a question I'd been sitting on: &lt;strong&gt;can an agent just consume these directly?&lt;/strong&gt; Yes — since it's a public repo of plain Markdown and JSON, any coding agent can clone it and treat a &lt;code&gt;README.md&lt;/code&gt; as a task brief, executing each step itself, the same pattern we used to build &lt;a href="https://dev.to/posts/ai-nt-no-problem-cramming-a-9950x3d-and-rtx-5090-into-an-sff-custom-loop"&gt;the thermal-migration test harness&lt;/a&gt;. No AMD-hosted MCP server exists for the playbook library, but that's fine — an agent's normal repo-reading and shell-execution ability makes an MCP wrapper unnecessary for a static content repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the homelab&lt;/strong&gt;: skip the AMD-specific playbooks wholesale (no ROCm, no NPU here), but the vLLM, fine-tuning, and RPC-clustering ones are worth mining for technique even on CUDA hardware — particularly the RPC-clustering approach, given &lt;a href="https://dev.to/posts/model-showdown-round-2-gemma-kimi-and-579gb-of-stubborn-optimism"&gt;Kimi K2&lt;/a&gt; needed NVMe offload at 0.6 tok/s to even fit here.&lt;/p&gt;

&lt;h3&gt;
  
  
  LocalAI (and the rest of the Lemonade-alternative field)
&lt;/h3&gt;

&lt;p&gt;Since Lemonade turned out to be Strix-Halo-locked, the natural follow-up was: what's the vendor-neutral equivalent? &lt;a href="https://github.com/mudler/LocalAI" rel="noopener noreferrer"&gt;LocalAI&lt;/a&gt; is the clearest match — a composable AI engine that runs LLMs, image, voice, and video models on any hardware (NVIDIA, AMD, Intel, Apple Silicon, or CPU-only), behind a single OpenAI/Anthropic/ElevenLabs-compatible API, with MCP support and a built-in agent orchestration layer (LocalAGI) added as of late 2025. Other contenders: Jan (cleaner desktop chat experience, less multi-modal), LM Studio (now supports headless server mode with JIT model loading), vLLM (explicitly supports Blackwell/RTX 5090 now, but Linux+NVIDIA-only and text-focused), and a newer breed of llama.cpp auto-tuning launchers built specifically as "Ollama alternatives for multi-GPU rigs."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the homelab&lt;/strong&gt;: LocalAI is the one worth actually testing — it's the closest philosophical match to Lemonade's "one unified endpoint, auto backend selection, multi-modal" pitch, but with native CUDA support instead of an AMD-only ceiling. It would also close the multi-modal gap ComfyUI opens up (image gen) and add speech-to-text/TTS we don't have today, all through the same endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bakeoff (Plan, Not Results — Yet)
&lt;/h2&gt;

&lt;p&gt;Here's the actual question worth answering with data, not vibes: &lt;strong&gt;is LocalAI a better daily-driver than the llama-server + &lt;code&gt;llm-switch.sh&lt;/code&gt; stack we've hand-tuned over the last several months?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The methodology borrows directly from the &lt;a href="https://dev.to/posts/showdown-thoughts-the-three-pass-pattern"&gt;Three-Pass Pattern&lt;/a&gt; and the Model Showdown series, just pointed at infrastructure instead of models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it tests&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Raw inference parity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does LocalAI's abstraction cost us throughput?&lt;/td&gt;
&lt;td&gt;llama-benchy against both endpoints, same model/quant, pp/tg/TTFT/concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Tool-calling regression&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does the abstraction reintroduce silent tool-call failures?&lt;/td&gt;
&lt;td&gt;Re-run the existing &lt;code&gt;coding-app-maintenance&lt;/code&gt; suite, diff against banked Round 7 scores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Known failure-mode checklist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Do the specific bugs we already fixed (chat template dropping &lt;code&gt;tools&lt;/code&gt;, invisible reasoning tokens, context truncation) exist here too?&lt;/td&gt;
&lt;td&gt;Short, scripted, binary pass/fail checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Daily-drive soak test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does it survive real usage, not just synthetic tests?&lt;/td&gt;
&lt;td&gt;1–2 weeks running the actual Discord bot against LocalAI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Multi-modal bonus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What does the unified endpoint add that we don't have today?&lt;/td&gt;
&lt;td&gt;Score ComfyUI-equivalent image gen and speech through LocalAI separately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hypothesis: LocalAI can match our hand-tuned setup on Layers 1–3 and win outright on Layer 5, but Layer 4 is where I expect the real signal — every silent failure documented on this blog was found through actual dogfooding, not a benchmark run.&lt;/p&gt;

&lt;p&gt;I'm not scaffolding the actual suite YAML or harness code in this post — the model and task list aren't locked yet, and honestly, this post is already covering five tools and a test plan. Next week's post will show the real scaffolding, the actual numbers, and a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; tools researched: llama-benchy, Lemonade Server, ComfyUI, AMD AI Playbooks, LocalAI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; ruled out immediately on hardware grounds (Lemonade — Strix Halo NPU only, we're RTX 5090)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; dev tip that sent us down the Lemonade rabbit hole in the first place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;32 GB&lt;/strong&gt; — the RTX 5090 VRAM ceiling every one of these tools ultimately has to respect here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; layers in the planned LocalAI bakeoff, one of them a straight 1–2 week soak test&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; hardware changes made today&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; lines of bakeoff YAML shown in this post — on purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rainy days are underrated for this kind of work. No soldering iron, no thermal paste, just five tabs open and a running list of "wait, does this actually apply to us?"&lt;/p&gt;

&lt;p&gt;The bakeoff comes next.&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>ai</category>
      <category>llm</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>GLM Is the New Hotness, So Let's Test It On the Homelab</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Tue, 30 Jun 2026 20:14:27 +0000</pubDate>
      <link>https://dev.to/carryologist/glm-is-the-new-hotness-so-lets-test-it-on-the-homelab-609</link>
      <guid>https://dev.to/carryologist/glm-is-the-new-hotness-so-lets-test-it-on-the-homelab-609</guid>
      <description>&lt;p&gt;GLM is the new hotness.&lt;/p&gt;

&lt;p&gt;I'm hearing it from both sides of the AI builder world. Software engineers are talking about it because the benchmark numbers are interesting, the weights are open, and the coding claims are strong. Vibe coders are talking about it because the pitch is even simpler: maybe this is the local model that finally feels agentic enough to run on your own machine.&lt;/p&gt;

&lt;p&gt;That overlap is rare. A lot of models get academic buzz. A lot of models get LocalLLaMA buzz. A smaller number get real developer curiosity. GLM is sitting in that third bucket right now.&lt;/p&gt;

&lt;p&gt;So we do what we always do: jump in and ask the boring practical questions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is GLM?&lt;/li&gt;
&lt;li&gt;Is it suitable for the homelab?&lt;/li&gt;
&lt;li&gt;How does it perform on a real agentic coding task?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post answers the first two. It also sets up the dedicated GLM bakeoff we will run to answer the third.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GLM Is
&lt;/h2&gt;

&lt;p&gt;GLM is the model family from Z.ai, formerly Zhipu AI. The current discussion is not about one model. It is about a family that now spans several very different deployment targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Why we care&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GLM-5.2&lt;/td&gt;
&lt;td&gt;Frontier-scale MoE model with a 1M-token context target&lt;/td&gt;
&lt;td&gt;The headline model. Strong claims, open weights, not sized for a normal homelab.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-4.7-Flash&lt;/td&gt;
&lt;td&gt;30B-A3B MoE model&lt;/td&gt;
&lt;td&gt;The practical candidate. Small enough to plausibly fit the RTX 5090 class.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-4-9B-Chat&lt;/td&gt;
&lt;td&gt;Older 9B chat model with function calling and 128K context&lt;/td&gt;
&lt;td&gt;The small baseline. It should fit easily, but expectations should be modest.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That spread is why this got interesting. If GLM only meant the 753B-class flagship, the answer for my rig would be simple: neat model, wrong hardware. But GLM-4.7-Flash changes the question. It is explicitly positioned as a lightweight deployment model, a 30B-A3B MoE in the same practical category as the Qwen and Qwen-Coder models already living on my workstation.&lt;/p&gt;

&lt;p&gt;The homelab does not need the biggest model. It needs the biggest model that can actually act as an agent without melting the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Homelab Filter
&lt;/h2&gt;

&lt;p&gt;The machine we are testing against is the same box from the recent local-model rounds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Homelab&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Ryzen 9 9950X3D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;RTX 5090, 32 GB VRAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;64 GB DDR5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/posts/model-showdown-round-3-the-llamacpp-showdown"&gt;llama.cpp&lt;/a&gt;, single model on port 8080&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent platform&lt;/td&gt;
&lt;td&gt;Coder Agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target workload&lt;/td&gt;
&lt;td&gt;Real coding tasks in the vibescoder.dev repo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not a cloud lab. It is not eight H100s. It is not a Mac Studio with hundreds of gigabytes of unified memory. It is &lt;a href="https://dev.to/posts/from-idea-to-infrastructure-standing-up-a-self-hosted-ai-dev-environment"&gt;the same single-GPU homelab&lt;/a&gt; I have been tuning all year: a very aggressive consumer workstation with one big GPU.&lt;/p&gt;

&lt;p&gt;That matters because local-model discourse often collapses three very different claims into one word: runs.&lt;/p&gt;

&lt;p&gt;A model can "run" because it fits entirely in VRAM and responds interactively. A model can also "run" because llama.cpp can mmap hundreds of gigabytes from NVMe while the GPU handles a few layers and you wait. Those are not the same thing.&lt;/p&gt;

&lt;p&gt;We learned that with &lt;a href="https://dev.to/posts/model-showdown-round-2-gemma-kimi-and-579gb-of-stubborn-optimism"&gt;Kimi K2&lt;/a&gt;. It technically ran. It produced output. It was also a 579 GB download, loaded for more than six minutes, and generated at roughly interactive-punishment speed. Technically valid. Practically dead.&lt;/p&gt;

&lt;p&gt;So the GLM question is not "can I make it produce tokens?" The question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can it run locally, use tools correctly, and complete a Coder Agents task without turning the session into a science project?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Three Candidates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GLM-5.2: the completeness run
&lt;/h3&gt;

&lt;p&gt;GLM-5.2 is the model generating most of the buzz. It is also the least likely to be a real candidate for this hardware.&lt;/p&gt;

&lt;p&gt;The reason is not mysterious. It is huge. The official Hugging Face metadata lists it in the 753B-parameter class. Unsloth has GGUF quants, including extremely low-bit versions, but those still live in the hundreds-of-gigabytes world. That puts it in the same category as Kimi K2 for this rig: technically interesting, practically suspect.&lt;/p&gt;

&lt;p&gt;We are still going to include it.&lt;/p&gt;

&lt;p&gt;Not because I think it will win. Not because I think a 1-bit or 2-bit offloaded monster is a fair comparison against a 30B model sitting mostly in VRAM. We are including it because the data is useful. If it fails the feasibility gate, that is a result. If it loads but is unusably slow, that is a result. If it somehow clears the bar, that is definitely a result.&lt;/p&gt;

&lt;p&gt;But we go in eyes open: GLM-5.2 is a completeness candidate, not a sane daily-driver candidate for a single RTX 5090.&lt;/p&gt;

&lt;h3&gt;
  
  
  GLM-4.7-Flash: the real contender
&lt;/h3&gt;

&lt;p&gt;GLM-4.7-Flash is the one I actually care about.&lt;/p&gt;

&lt;p&gt;Z.ai describes it as a 30B-A3B MoE model aimed at lightweight deployment. That puts it directly in the class we have been testing all month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qwen 3.6 35B-A3B&lt;/li&gt;
&lt;li&gt;Qwen3-Coder 30B-A3B&lt;/li&gt;
&lt;li&gt;Nemotron-style 30B-A3B candidates&lt;/li&gt;
&lt;li&gt;now GLM-4.7-Flash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The naming is almost too convenient. Flash means "this one might fit the box." The GGUF options include quants in the range where a 32 GB GPU can plausibly host the model with room left for KV cache, depending on context and cache settings.&lt;/p&gt;

&lt;p&gt;This is the model with an actual path to becoming useful on the homelab.&lt;/p&gt;

&lt;p&gt;The open questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does llama.cpp handle the model cleanly?&lt;/li&gt;
&lt;li&gt;Does the GLM tool-call format round-trip through Coder Agents?&lt;/li&gt;
&lt;li&gt;Does it avoid the looping behavior people have reported in some GLM-4.7-Flash GGUF runs?&lt;/li&gt;
&lt;li&gt;Can it ship code, not just write plausible code?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is the one that matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  GLM-4-9B: the floor
&lt;/h3&gt;

&lt;p&gt;GLM-4-9B-Chat is the older small model. It supports function calling and long context on paper. It should fit easily on the 5090. It should be fast enough that the model itself is not the bottleneck.&lt;/p&gt;

&lt;p&gt;That makes it useful as a floor.&lt;/p&gt;

&lt;p&gt;I do not expect a 9B model to beat Qwen3-Coder on a real multi-file Next.js task. If it does, something strange and interesting happened. But it can still answer two important questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the GLM family tool-call format work cleanly in our stack?&lt;/li&gt;
&lt;li&gt;How much agentic capability do we lose when we drop from the 30B-A3B class to 9B?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If GLM-4-9B calls tools reliably but fails the coding task, we learned something. If it cannot call tools reliably, we learned something more important: do not trust the larger GLM runs until the parser path is fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool-Calling Question
&lt;/h2&gt;

&lt;p&gt;A fellow vibe coder told me she could not get GLM to run with &lt;a href="https://dev.to/posts/hermes-agent-first-contact"&gt;Hermes&lt;/a&gt; because it was not compatible with JSON.&lt;/p&gt;

&lt;p&gt;My second question was: is that true?&lt;/p&gt;

&lt;p&gt;My first question was: what is GLM? We answered that above. So let's dive into the JSON rumor.&lt;/p&gt;

&lt;p&gt;The rumor is half right and half misleading.&lt;/p&gt;

&lt;p&gt;GLM does not appear to be JSON-native in the way some tool-call models are. The templates use GLM-style XML-ish tool calls, with function names and argument keys wrapped in tags. That sounds bad if your agent expects the model to literally emit raw JSON.&lt;/p&gt;

&lt;p&gt;But Coder Agents is not talking directly to raw model text. It talks to an OpenAI-compatible server. llama.cpp sits in the middle and is supposed to translate the model's native format into OpenAI-style &lt;code&gt;tool_calls&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the entire game.&lt;/p&gt;

&lt;p&gt;If llama.cpp parses GLM tool calls correctly, Coder Agents should not care whether the model internally uses JSON, XML tags, magic tokens, or a tiny goblin tapping Morse code inside the KV cache. The API response either contains structured tool calls or it does not.&lt;/p&gt;

&lt;p&gt;So the first test is not the tag-manager task. The first test is much simpler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start the model, send a tool schema, and confirm &lt;code&gt;/v1/chat/completions&lt;/code&gt; returns structured &lt;code&gt;tool_calls&lt;/code&gt; with valid JSON arguments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that fails, the bakeoff is over until the template is fixed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/posts/model-showdown-round-7-local-models-vs-the-tag-manager"&gt;Round 7 already taught us why&lt;/a&gt;. Devstral did not fail because it wrote bad TypeScript. It failed before that. It emitted fake tool calls as plain text. Coder Agents could not parse them, so nothing happened. Nine messages, zero actions.&lt;/p&gt;

&lt;p&gt;Tool calling is not a feature of an agentic local model. It is the price of admission.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bakeoff Harness
&lt;/h2&gt;

&lt;p&gt;We are not inventing a new task. We are reusing the newest real-world local-model harness: the &lt;a href="https://dev.to/posts/model-showdown-round-7-local-models-vs-the-tag-manager"&gt;Round 7 tag-manager task&lt;/a&gt;, with the Round 8 protocol improvements.&lt;/p&gt;

&lt;p&gt;That task asks the agent to add a tag manager to the blog admin panel. It builds on the taxonomy cleanup from &lt;a href="https://dev.to/posts/from-chaos-to-signal-tagging-system"&gt;From Chaos to Signal&lt;/a&gt;, but raises the bar: instead of asking a model to reason about tags, we ask it to build the admin tooling that manages them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create tag-reading helpers using &lt;code&gt;gray-matter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add admin API routes for listing, renaming, and deleting tags&lt;/li&gt;
&lt;li&gt;build an &lt;code&gt;/admin/tags&lt;/code&gt; page&lt;/li&gt;
&lt;li&gt;link it from the admin dashboard&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;take a Playwright screenshot&lt;/li&gt;
&lt;li&gt;commit in logical chunks&lt;/li&gt;
&lt;li&gt;push the branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This task is useful because it is not synthetic. It hits the exact failure modes local models struggle with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Why this task catches it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool-call failure&lt;/td&gt;
&lt;td&gt;The agent has to read, write, execute, and use browser tools.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repo navigation&lt;/td&gt;
&lt;td&gt;The codebase has existing admin patterns to discover.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript debugging&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gray-matter&lt;/code&gt; and Next.js route types are easy to get subtly wrong.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build-loop behavior&lt;/td&gt;
&lt;td&gt;Bad models repeat the same broken fix. Good models inspect the error.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goal prioritization&lt;/td&gt;
&lt;td&gt;The screenshot requirement can become a yak-shaving trap.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shipping discipline&lt;/td&gt;
&lt;td&gt;Passing build is not enough. The model has to commit and push.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Round 7 proved the value of this task. Qwen 3.6 built the feature and got the build passing, then burned 77 messages trying to take a screenshot and never committed. Qwen3-Coder shipped code, but skipped the screenshot and pushed one messy commit. Gemma and Hermes looped on build errors. Devstral never made a structured tool call.&lt;/p&gt;

&lt;p&gt;That is the kind of signal a one-shot benchmark will never give you. It is the same reason I keep coming back to messy feature-build bakeoffs instead of clean synthetic prompts, from &lt;a href="https://dev.to/posts/llm-model-showdown-benchmarking-local-vs-cloud"&gt;the original local-vs-cloud benchmark&lt;/a&gt; to &lt;a href="https://dev.to/posts/model-showdown-round-5-four-agents-build-the-same-feature"&gt;the four-agent feature build&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan
&lt;/h2&gt;

&lt;p&gt;The GLM bakeoff has two layers: qualification and the real task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: qualification
&lt;/h3&gt;

&lt;p&gt;Before any full Coder Agents run, each model must pass four gates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Pass condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load&lt;/td&gt;
&lt;td&gt;Start llama-server&lt;/td&gt;
&lt;td&gt;Health check passes, model appears in &lt;code&gt;/v1/models&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain chat&lt;/td&gt;
&lt;td&gt;One short response&lt;/td&gt;
&lt;td&gt;No loop, no malformed output, completes on time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool call&lt;/td&gt;
&lt;td&gt;One forced tool call&lt;/td&gt;
&lt;td&gt;OpenAI response includes structured &lt;code&gt;tool_calls&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiny agent task&lt;/td&gt;
&lt;td&gt;Create and run a trivial file&lt;/td&gt;
&lt;td&gt;Uses tools, completes, stops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GLM-5.2 gets a special label here. If it requires heavy offload, we mark it as &lt;code&gt;offload-class&lt;/code&gt;. It can still continue, but its latency numbers will not be compared as if it were a normal in-VRAM run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: official agentic runs
&lt;/h3&gt;

&lt;p&gt;If the models pass qualification, they get the Round 7 tag-manager task.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-run-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM-5.2 GGUF&lt;/td&gt;
&lt;td&gt;Completeness and feasibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-run-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM-4.7-Flash GGUF&lt;/td&gt;
&lt;td&gt;Practical contender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-run-3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM-4-9B GGUF&lt;/td&gt;
&lt;td&gt;Small baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each run gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same repo baseline&lt;/li&gt;
&lt;li&gt;same prompt&lt;/li&gt;
&lt;li&gt;same Coder Agents setup&lt;/li&gt;
&lt;li&gt;same intervention rules&lt;/li&gt;
&lt;li&gt;same hard timeout&lt;/li&gt;
&lt;li&gt;same scoring rubric&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 3: reruns
&lt;/h3&gt;

&lt;p&gt;Single-run agent bakeoffs are noisy. If GLM-4.7-Flash or GLM-4-9B does anything interesting, we rerun it.&lt;/p&gt;

&lt;p&gt;Minimum reruns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-run-2b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM-4.7-Flash&lt;/td&gt;
&lt;td&gt;Likely best practical candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-run-3b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM-4-9B&lt;/td&gt;
&lt;td&gt;Measures variance in the small baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GLM-5.2 only gets a rerun if it is surprisingly usable. I am curious, not masochistic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional Phase 4: the screenshot timebox
&lt;/h3&gt;

&lt;p&gt;The screenshot requirement is intentionally left in the official run. It is part of the agentic test. Shipping a feature includes handling annoying browser and auth problems.&lt;/p&gt;

&lt;p&gt;But if every model fails mainly because of Playwright, we will run a controlled variant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the screenshot is blocked after three attempts or 20 minutes, document the blocker, commit and push the working code, and mention the missing screenshot in the final summary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gives us a second lens: can the model ship code if the known trap is timeboxed?&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Will Score It
&lt;/h2&gt;

&lt;p&gt;The scoring rubric stays the same as the recent bakeoffs, especially &lt;a href="https://dev.to/posts/model-showdown-round-5-four-agents-build-the-same-feature"&gt;Round 5&lt;/a&gt; and &lt;a href="https://dev.to/posts/model-showdown-round-7-local-models-vs-the-tag-manager"&gt;Round 7&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Correctness&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;Does the feature work and does the build pass?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;Does the admin UI fit the app?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code quality&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;TypeScript hygiene, clean abstractions, no dead code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering judgment&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;Rename/delete safety, error handling, project pattern fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope discipline&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Did it avoid gold-plating and unrelated churn?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commit hygiene&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Logical commits, useful messages, branch pushed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surprise&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;Anything unusually good or bad&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But local models need a second table. A model can score well on code and still be useless if it takes three hours, burns ten million tokens, or requires hand-holding every ten minutes. That was the real lesson from &lt;a href="https://dev.to/posts/slaying-the-gemma-beast-how-we-fixed-local-ai-and-shipped-search"&gt;Slaying the Gemma Beast&lt;/a&gt;: the model output is only half the story. The serving setup, reasoning budget, and agent loop decide whether the thing is usable.&lt;/p&gt;

&lt;p&gt;So we will also capture deployability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load time&lt;/td&gt;
&lt;td&gt;Operator experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak VRAM and RAM&lt;/td&gt;
&lt;td&gt;Hardware fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offload status&lt;/td&gt;
&lt;td&gt;Fairness and practicality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per second&lt;/td&gt;
&lt;td&gt;Real latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wall-clock runtime&lt;/td&gt;
&lt;td&gt;Can I actually use this?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total tokens&lt;/td&gt;
&lt;td&gt;Agentic efficiency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool calls&lt;/td&gt;
&lt;td&gt;Workflow behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build attempts&lt;/td&gt;
&lt;td&gt;Debugging quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human interventions&lt;/td&gt;
&lt;td&gt;Autonomy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot status&lt;/td&gt;
&lt;td&gt;Known Round 7 trap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commits pushed&lt;/td&gt;
&lt;td&gt;Shipping discipline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The final verdict will separate capability from deployability. That matters especially for GLM-5.2. If it writes the best code but only after a miserable offloaded marathon, that is not a daily-driver win. It is a lab result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Would Count as a Win?
&lt;/h2&gt;

&lt;p&gt;For GLM-5.2, a win is not beating the smaller models. A win is proving the giant model can be made to run through our stack and produce structured tools. Anything beyond that is upside.&lt;/p&gt;

&lt;p&gt;For GLM-4.7-Flash, the bar is higher. It needs to look like a plausible Qwen3-Coder alternative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured tool calls work&lt;/li&gt;
&lt;li&gt;no degenerate loops&lt;/li&gt;
&lt;li&gt;build passes&lt;/li&gt;
&lt;li&gt;branch gets committed and pushed&lt;/li&gt;
&lt;li&gt;token usage is not absurd&lt;/li&gt;
&lt;li&gt;the implementation is reviewable without a rescue mission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For GLM-4-9B, the bar is lower but still real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tool calls work&lt;/li&gt;
&lt;li&gt;it can navigate the repo&lt;/li&gt;
&lt;li&gt;it makes a coherent attempt&lt;/li&gt;
&lt;li&gt;it gives us a useful small-model baseline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If GLM-4.7-Flash ships a clean branch, that is the headline. If GLM-5.2 cannot clear the feasibility gate, that is still worth publishing. If GLM-4-9B surprises us, we get a much more interesting post than expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Think Will Happen
&lt;/h2&gt;

&lt;p&gt;My guess before running anything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GLM-5.2 will be technically runnable only in a way that is not pleasant on this box.&lt;/li&gt;
&lt;li&gt;GLM-4.7-Flash is the only serious candidate for local Coder Agents use.&lt;/li&gt;
&lt;li&gt;GLM-4-9B will validate the parser path but fall short on the full agentic task.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The danger is that I am wrong in either direction. GLM-4.7-Flash could be fast but loopy. GLM-4-9B could be more disciplined than expected. GLM-5.2 could be unusable, or it could produce one of those weird giant-model moments where the result is obviously better even though the experience is awful.&lt;/p&gt;

&lt;p&gt;That is why we test.&lt;/p&gt;

&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;3 GLM variants in scope&lt;/li&gt;
&lt;li&gt;1 RTX 5090 as the hardware constraint&lt;/li&gt;
&lt;li&gt;4 qualification gates before the real task&lt;/li&gt;
&lt;li&gt;3 official agentic runs minimum&lt;/li&gt;
&lt;li&gt;2 reproducibility reruns planned if the practical candidates show promise&lt;/li&gt;
&lt;li&gt;1 known trap from Round 7: Playwright screenshot yak-shaving&lt;/li&gt;
&lt;li&gt;0 assumptions that "runs locally" means "is useful locally"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GLM is hot. That is enough reason to look.&lt;/p&gt;

&lt;p&gt;It is not enough reason to believe.&lt;/p&gt;

&lt;p&gt;The bakeoff comes next.&lt;/p&gt;

</description>
      <category>modelshowdown</category>
      <category>benchmark</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>AI-NT-No-Problem: Cramming a 9950X3D and RTX 5090 Into an SFF Custom Loop</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Tue, 30 Jun 2026 20:14:12 +0000</pubDate>
      <link>https://dev.to/carryologist/ai-nt-no-problem-cramming-a-9950x3d-and-rtx-5090-into-an-sff-custom-loop-ohj</link>
      <guid>https://dev.to/carryologist/ai-nt-no-problem-cramming-a-9950x3d-and-rtx-5090-into-an-sff-custom-loop-ohj</guid>
      <description>&lt;p&gt;My homelab workstation — hostname AI-NT-No-Problem — has been running a Ryzen 9 9950X3D and an RTX 5090 in an Antec Performance 1 FT full tower for months. It does local AI inference with llama.cpp, hosts my Coder server for remote development, runs Tailscale, a Cloudflare tunnel, Docker, RustDesk, and whatever else I throw at it. It's the nerve center of the whole operation.&lt;/p&gt;

&lt;p&gt;It also sounds like a drone trying to fly away.&lt;/p&gt;

&lt;p&gt;The RTX 5090's stock triple-fan cooler is the main offender. Under inference load — four concurrent Qwen3-Coder-30B-A3B requests pulling 386W average — those fans spin to nearly 50%. In a room where I'm trying to work, that's unacceptable. So I decided to move everything into an SFF open-frame case with custom hardline water cooling. The question wasn't &lt;em&gt;whether&lt;/em&gt; I wanted to do it. It was whether 2×240mm slim radiators could actually handle a 575W-TDP GPU and a 16-core CPU sharing a single loop.&lt;/p&gt;

&lt;p&gt;One way to find out: measure everything before, measure everything after, let the data decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hardware Swap
&lt;/h2&gt;

&lt;p&gt;This wasn't just a cooler change — it was a full platform migration. New motherboard, new case, new form factor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Antec Performance 1 FT (full tower)&lt;/td&gt;
&lt;td&gt;Hardline Nexus Morph R2 (SFF open-frame)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Motherboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ASRock X870 Pro-A WiFi (E-ATX)&lt;/td&gt;
&lt;td&gt;Asus ROG Strix X870-i (Mini-ITX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Cooling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;420mm AIO (dedicated)&lt;/td&gt;
&lt;td&gt;Alphacool Core 1 block + Thermal Grizzly AM5 contact frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPU Cooling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stock NVIDIA air cooler (triple fan)&lt;/td&gt;
&lt;td&gt;Alphacool Core RTX 5090 full-cover block + KryoSheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Radiators&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AIO-integrated 420mm&lt;/td&gt;
&lt;td&gt;2× Alphacool NexXxoS HPE-30 240mm slim (30mm)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fans&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AIO fans + case fans&lt;/td&gt;
&lt;td&gt;6× Alphacool Apex Stealth Metal Aurora 120mm (push-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pump/Res&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AIO-integrated&lt;/td&gt;
&lt;td&gt;Alphacool Core Flat Reservoir 240 + VPP Apex D5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tubing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Corsair 14mm hardline, satin white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coolant Sensor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Alphacool G1/4 inline T-sensor → motherboard T_Sensor header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cooling Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Independent — CPU and GPU decoupled&lt;/td&gt;
&lt;td&gt;Single shared loop — CPU and GPU thermally coupled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Unchanged:&lt;/strong&gt; CPU (9950X3D), GPU (RTX 5090), RAM (2×32GB DDR5 SK Hynix @ 6000 MT/s EXPO), NVMe drives (Samsung 9100 PRO 2TB + Crucial P510 2TB), OS (Ubuntu 24.04.4), NVIDIA driver (590.48.01), CUDA 13.1.&lt;/p&gt;

&lt;p&gt;The loop sequence runs &lt;strong&gt;reservoir → bottom rad → top rad → CPU block → GPU block → reservoir&lt;/strong&gt;, with a stubbed drain port off the reservoir bottom. Both radiators in series before any component means coolant is maximally pre-cooled before it hits anything. CPU before GPU because the 9950X3D adds modest heat compared to the 5090 — the GPU benefits most from the coolest incoming coolant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fan Curve: Coolant Temp, Not CPU Temp
&lt;/h3&gt;

&lt;p&gt;One detail that matters more than it sounds: the six radiator fans are controlled by &lt;strong&gt;coolant temperature&lt;/strong&gt;, not CPU temperature. The Alphacool G1/4 inline temp sensor feeds the Asus X870-I's T_Sensor header, and all chassis fans follow a manual PWM curve tied to that reading:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Coolant Temp&lt;/th&gt;
&lt;th&gt;Fan Duty&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;35°C&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40°C&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45°C&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50°C&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55°C&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This eliminates &lt;strong&gt;fan hunting&lt;/strong&gt; — the rapid spin-up/spin-down you get when fans chase CPU Tctl spikes. Coolant temperature changes slowly (high thermal mass), so the fans ramp gradually. The pump runs at full speed on the AIO_PUMP header. D5 pumps are quiet at any RPM, so there's no reason to throttle it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test Harness: Vibe-Coded, Obviously
&lt;/h2&gt;

&lt;p&gt;I needed a reproducible thermal test I could run identically before and after the migration. So I did what I always do: I vibe-coded it with a Coder agent.&lt;/p&gt;

&lt;p&gt;The agent SSHed from a Docker-based Coder workspace into the host, discovered all available sensors by walking &lt;code&gt;/sys/class/hwmon&lt;/code&gt;, and wrote an &lt;strong&gt;873-line bash script&lt;/strong&gt; that polls every sensor at 1-second intervals across six sequential phases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;None — baseline temps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Stress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;stress-ng&lt;/code&gt; all-core matrixprod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inference&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;4× concurrent llama.cpp requests (Qwen3-Coder-30B-A3B)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gaming&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;glmark2 via PRIME offload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Combined&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;stress-ng + inference simultaneously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;fio mixed random+sequential on boot NVMe&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total runtime: 50 minutes. Both runs produced exactly &lt;strong&gt;2,726 sensor readings&lt;/strong&gt;. Seven bugs found and fixed during development. The migration checklist itself was also built as a Vercel web app with an API endpoint so the agent could check off steps programmatically. When I say this project was vibe-coded end to end, I mean it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: The Big Picture
&lt;/h2&gt;

&lt;p&gt;Here's the hero table — every key sensor, every phase, before vs. after.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Sensor&lt;/th&gt;
&lt;th&gt;Before Avg&lt;/th&gt;
&lt;th&gt;Before Max&lt;/th&gt;
&lt;th&gt;After Avg&lt;/th&gt;
&lt;th&gt;After Max&lt;/th&gt;
&lt;th&gt;Δ Avg&lt;/th&gt;
&lt;th&gt;Δ Max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU Tctl&lt;/td&gt;
&lt;td&gt;49.6°C&lt;/td&gt;
&lt;td&gt;49.8°C&lt;/td&gt;
&lt;td&gt;54.7°C&lt;/td&gt;
&lt;td&gt;56.1°C&lt;/td&gt;
&lt;td&gt;+5.1&lt;/td&gt;
&lt;td&gt;+6.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;GPU Temp&lt;/td&gt;
&lt;td&gt;54.7°C&lt;/td&gt;
&lt;td&gt;57.0°C&lt;/td&gt;
&lt;td&gt;31.9°C&lt;/td&gt;
&lt;td&gt;33.0°C&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-22.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-24.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;NVMe0&lt;/td&gt;
&lt;td&gt;44.9°C&lt;/td&gt;
&lt;td&gt;44.9°C&lt;/td&gt;
&lt;td&gt;38.0°C&lt;/td&gt;
&lt;td&gt;38.9°C&lt;/td&gt;
&lt;td&gt;-6.9&lt;/td&gt;
&lt;td&gt;-6.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Stress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU Tctl&lt;/td&gt;
&lt;td&gt;72.2°C&lt;/td&gt;
&lt;td&gt;73.0°C&lt;/td&gt;
&lt;td&gt;73.9°C&lt;/td&gt;
&lt;td&gt;76.4°C&lt;/td&gt;
&lt;td&gt;+1.7&lt;/td&gt;
&lt;td&gt;+3.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;GPU Temp&lt;/td&gt;
&lt;td&gt;48.9°C&lt;/td&gt;
&lt;td&gt;55.0°C&lt;/td&gt;
&lt;td&gt;36.7°C&lt;/td&gt;
&lt;td&gt;39.0°C&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-12.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-16.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inference&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU Tctl&lt;/td&gt;
&lt;td&gt;63.9°C&lt;/td&gt;
&lt;td&gt;72.6°C&lt;/td&gt;
&lt;td&gt;72.3°C&lt;/td&gt;
&lt;td&gt;75.1°C&lt;/td&gt;
&lt;td&gt;+8.4&lt;/td&gt;
&lt;td&gt;+2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;GPU Temp&lt;/td&gt;
&lt;td&gt;64.4°C&lt;/td&gt;
&lt;td&gt;66.0°C&lt;/td&gt;
&lt;td&gt;48.7°C&lt;/td&gt;
&lt;td&gt;51.0°C&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-15.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-15.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Combined&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU Tctl&lt;/td&gt;
&lt;td&gt;73.6°C&lt;/td&gt;
&lt;td&gt;77.8°C&lt;/td&gt;
&lt;td&gt;80.1°C&lt;/td&gt;
&lt;td&gt;83.6°C&lt;/td&gt;
&lt;td&gt;+6.5&lt;/td&gt;
&lt;td&gt;+5.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;GPU Temp&lt;/td&gt;
&lt;td&gt;63.0°C&lt;/td&gt;
&lt;td&gt;66.0°C&lt;/td&gt;
&lt;td&gt;47.7°C&lt;/td&gt;
&lt;td&gt;52.0°C&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-15.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-14.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVMe0&lt;/td&gt;
&lt;td&gt;68.3°C&lt;/td&gt;
&lt;td&gt;69.8°C&lt;/td&gt;
&lt;td&gt;59.7°C&lt;/td&gt;
&lt;td&gt;63.9°C&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-8.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-5.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two stories: the &lt;strong&gt;GPU got dramatically cooler&lt;/strong&gt;, the &lt;strong&gt;CPU got moderately warmer&lt;/strong&gt;. Both within safe limits. NVMe improved across the board.&lt;/p&gt;

&lt;h2&gt;
  
  
  GPU: The Star of the Show
&lt;/h2&gt;

&lt;p&gt;The RTX 5090 never exceeded &lt;strong&gt;52°C&lt;/strong&gt; in the after test. Under sustained inference — the workload this machine exists to run — the GPU dropped from 66°C peak to 51°C. A full-cover water block with KryoSheet graphite on the die will do that.&lt;/p&gt;

&lt;p&gt;The GPU fan speed column is the satisfying one: &lt;strong&gt;0% across all six phases&lt;/strong&gt;. Not because the fans are off — because they don't exist anymore. The stock cooler was physically removed. Cooling is handled entirely by the water block and the loop's radiator fans. This is the single biggest contributor to the noise reduction.&lt;/p&gt;

&lt;p&gt;But the surprise was &lt;strong&gt;power efficiency&lt;/strong&gt;. Under inference, the GPU draws 26W less (386W → 360W) while doing the same work. Under combined load, the drop is &lt;strong&gt;70W&lt;/strong&gt; (380W → 310W). Lower temperatures mean the card isn't fighting thermal limits, so it boosts more cleanly at lower power. That's not just a thermal win — it's an efficiency win that reduces total heat into the loop. The system helps itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  CPU: The Honest Tradeoff
&lt;/h2&gt;

&lt;p&gt;The CPU is warmer. That's expected and I want to be upfront about it.&lt;/p&gt;

&lt;p&gt;Before, the CPU had a dedicated 420mm AIO — 50% more radiator area all to itself, with zero thermal coupling to the GPU. Now it shares 480mm of total rad area with a GPU that dumps 360W into the loop during inference.&lt;/p&gt;

&lt;p&gt;The worst case — combined phase peak of &lt;strong&gt;83.6°C&lt;/strong&gt; — still leaves &lt;strong&gt;11.4°C of headroom&lt;/strong&gt; below the 9950X3D's 95°C Tctl throttle limit. No throttling occurred during any test phase. Under CPU-only stress, the delta is just +1.7°C average. The CPU block and loop handle CPU-only loads almost as well as the 360mm AIO did. It's the thermal coupling during mixed workloads that creates the gap.&lt;/p&gt;

&lt;p&gt;Does it matter for the actual workload? For AI inference, the &lt;strong&gt;GPU is the bottleneck&lt;/strong&gt; — not the CPU. The CPU's job is tokenization and prompt processing, which is lightweight. Running 6-8°C warmer doesn't affect inference throughput at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything Else
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NVMe temps improved 7-10°C&lt;/strong&gt; across the board. The Asus Mini-ITX board's M.2 heatsink is effective, and the open-frame case has decent airflow around the drives. The Samsung 9100 PRO's controller hotspot hit 76.8°C peak under storage stress — down from 79.8°C, and within Samsung's 83.8°C threshold.&lt;/p&gt;

&lt;p&gt;The motherboard swap brought sensor changes worth noting: the Asus board exposes &lt;strong&gt;DDR5 SPD Hub temps&lt;/strong&gt; via the &lt;code&gt;spd5118&lt;/code&gt; driver (idle: 35.8°C) — the ASRock didn't. The ASRock's Realtek NIC had a hwmon temp sensor; the Intel I226-V doesn't expose one. No functional loss — NIC temps were never actionable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thermal capacity math vs. reality:&lt;/strong&gt; The pre-migration estimate of 570-775W peak combined heat was conservative. The actual combined load landed at &lt;strong&gt;~510W&lt;/strong&gt; (200W CPU + 310W GPU avg) because inference doesn't push the GPU to its 575W TDP, and the cooler GPU draws less power for the same work. The inline T-sensor confirmed coolant equilibrium in the range the fan curve was designed for. The system found its own balance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure Boot MOK enrollment after motherboard swap.&lt;/strong&gt; The NVIDIA driver is a DKMS kernel module (&lt;code&gt;nvidia-dkms-590-open&lt;/code&gt;). After moving to the new motherboard, &lt;code&gt;nvidia-smi&lt;/code&gt; failed with &lt;code&gt;Key was rejected by service&lt;/code&gt; — the new board's Secure Boot database didn't have the Machine Owner Key. Fix: &lt;code&gt;sudo mokutil --import /var/lib/shim-signed/mok/MOK.der&lt;/code&gt;, reboot, and &lt;strong&gt;catch the blue MOK enrollment screen&lt;/strong&gt; before the OS boots. I missed it the first time and had to repeat the whole cycle. If you've never seen it before, you'll blow right past it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bottom fan orientation matters in an open frame.&lt;/strong&gt; The bottom radiator fans were initially configured exhausting downward. Corrected to exhaust upward — pull config through the rad — to create coherent bottom-to-top airflow through the open frame. Pull vs push performance delta is ~5%, but the airflow direction delta is significant.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The test harness doesn't monitor the T-sensor.&lt;/strong&gt; The inline coolant temp sensor feeds the motherboard's T_Sensor header for fan control, but it isn't exposed as a standard hwmon device that the bash script's auto-discovery picks up. I have the fan curve working correctly, but the thermal test CSV doesn't include coolant temperature as a logged column. Future improvement.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I'd Change
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thicker radiators or push-pull.&lt;/strong&gt; The 30mm slim rads in push-only are the minimum viable configuration. 45mm rads in push-pull would give substantially more thermal headroom and lower coolant equilibrium. The CPU would directly benefit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated CPU loop.&lt;/strong&gt; With unlimited budget, dual loops would eliminate the thermal coupling entirely. The CPU would get its own 240mm rad and perform close to the old 360mm AIO. But the shared loop works — it's just not &lt;em&gt;optimal&lt;/em&gt; for the CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log coolant temp in the test harness.&lt;/strong&gt; The T-sensor drives the fan curve perfectly, but I want it in the CSV for correlation analysis. That means either wiring up the motherboard's sensor reading via &lt;code&gt;lm-sensors&lt;/code&gt; config or adding a USB temperature probe the script can poll directly.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The system went from a full tower I couldn't sit next to during inference to an SFF build that's effectively silent. The GPU runs 14-24°C cooler. The CPU runs warmer because it shares the loop, but nowhere near throttling. Power efficiency improved because the GPU doesn't fight thermal limits. The NVMe drives got cooler too, somehow.&lt;/p&gt;

&lt;p&gt;2,726 sensor readings don't lie. AI-NT-No-Problem earned its name.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Was the CPU tradeoff worth the silence? For an inference-bound workload, I'd make the same call every time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2,726&lt;/strong&gt; sensor readings per test run (1-second intervals, 50-minute test)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;873&lt;/strong&gt; lines of bash in the vibe-coded thermal test harness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7&lt;/strong&gt; bugs found and fixed during script development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6&lt;/strong&gt; test phases: idle, CPU, inference, gaming, combined, storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;23°C&lt;/strong&gt; GPU temperature drop at idle (55°C → 32°C)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;14°C&lt;/strong&gt; GPU temperature drop under combined load (66°C → 52°C)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;70W&lt;/strong&gt; GPU power reduction under combined load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0%&lt;/strong&gt; GPU fan speed across all phases (because the fans don't exist)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11.4°C&lt;/strong&gt; headroom to CPU throttle limit at worst case&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2×240mm&lt;/strong&gt; slim radiators handling a ~510W combined thermal load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; Secure Boot MOK enrollment screen missed on first attempt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; very quiet homelab&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>homelab</category>
      <category>hardware</category>
      <category>watercooling</category>
      <category>aiinference</category>
    </item>
    <item>
      <title>Friday Fixes: The Fix That Wasn't</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:35:43 +0000</pubDate>
      <link>https://dev.to/carryologist/friday-fixes-the-fix-that-wasnt-25pi</link>
      <guid>https://dev.to/carryologist/friday-fixes-the-fix-that-wasnt-25pi</guid>
      <description>&lt;p&gt;Three bugs this month. All three looked fixed before they broke. The&lt;br&gt;
date was quoted in 51 out of 52 posts. The model was pinned to a&lt;br&gt;
specific version. The upload feature had been working in production for&lt;br&gt;
weeks. Each one passed the obvious checks and failed somewhere else.&lt;/p&gt;

&lt;p&gt;That's the theme for this Friday Fixes: &lt;strong&gt;the fix that wasn't.&lt;/strong&gt; Not&lt;br&gt;
bugs that went unnoticed, but bugs where a defense existed and the&lt;br&gt;
failure found its way around it.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. The Unquoted Date, Part Two
&lt;/h2&gt;

&lt;p&gt;If this one sounds familiar, it should. I wrote &lt;a href="https://dev.to/posts/friday-fixes-the-unquoted-date-that-broke-drafts"&gt;an entire Friday Fixes&lt;br&gt;
post&lt;/a&gt; about&lt;br&gt;
this exact bug class five weeks ago. An unquoted YAML date. &lt;code&gt;gray-matter&lt;/code&gt;&lt;br&gt;
parsing it as a &lt;code&gt;Date&lt;/code&gt; object instead of a string. A crash downstream.&lt;/p&gt;

&lt;p&gt;Last time it took down &lt;code&gt;/admin/drafts&lt;/code&gt;. The fix hardened &lt;code&gt;formatDate()&lt;/code&gt;&lt;br&gt;
to coerce &lt;code&gt;Date&lt;/code&gt; objects before calling &lt;code&gt;.includes()&lt;/code&gt;. I verified it.&lt;br&gt;
I shipped it. I wrote 2,000 words about it. I moved on.&lt;/p&gt;

&lt;p&gt;This time it took down the homepage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The symptom:&lt;/strong&gt; &lt;code&gt;vibescoder.dev&lt;/code&gt; loaded for a split second, then&lt;br&gt;
flashed to Chrome's "This page couldn't load" screen. Every browser,&lt;br&gt;
every profile, every device. The site was completely dead to visitors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The twist:&lt;/strong&gt; &lt;code&gt;curl&lt;/code&gt; returned HTTP 200 with ~900KB of fully rendered&lt;br&gt;
HTML. The server was fine. The crash was happening during React&lt;br&gt;
hydration in the browser, invisible to any server-side test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cause:&lt;/strong&gt; A new post had &lt;code&gt;date: 2026-06-19&lt;/code&gt; in its frontmatter.&lt;br&gt;
No quotes. &lt;code&gt;gray-matter&lt;/code&gt; parsed it as a &lt;code&gt;Date&lt;/code&gt; object. In &lt;code&gt;posts.ts&lt;/code&gt;,&lt;br&gt;
the code does &lt;code&gt;const meta = data as PostMeta&lt;/code&gt; and then spreads &lt;code&gt;...meta&lt;/code&gt;&lt;br&gt;
into the return value. The &lt;code&gt;as PostMeta&lt;/code&gt; cast told TypeScript the date&lt;br&gt;
was a &lt;code&gt;string&lt;/code&gt;. At runtime, it was a &lt;code&gt;Date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;Date&lt;/code&gt; object flowed through the server component, through the&lt;br&gt;
RSC serialization boundary, and into &lt;code&gt;PostListWithFilters&lt;/code&gt;, a &lt;code&gt;"use&lt;br&gt;
client"&lt;/code&gt; component. React couldn't hydrate it. No &lt;code&gt;global-error.tsx&lt;/code&gt;&lt;br&gt;
existed to catch the crash. Dead page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the May fix didn't prevent this:&lt;/strong&gt; Because the May fix was in the&lt;br&gt;
wrong layer. It hardened &lt;code&gt;formatDate()&lt;/code&gt;, the function that happened to&lt;br&gt;
crash that time. It never hardened &lt;code&gt;posts.ts&lt;/code&gt;, the layer where the&lt;br&gt;
&lt;code&gt;Date&lt;/code&gt; object enters the system. The &lt;code&gt;Date&lt;/code&gt; object simply found a&lt;br&gt;
different path out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The false start:&lt;/strong&gt; The first fix attempt added &lt;code&gt;meta.date instanceof&lt;br&gt;
Date&lt;/code&gt; to coerce the value. TypeScript rejected it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type error: The left-hand side of an 'instanceof' expression must be
of type 'any', an object type or a type parameter.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same &lt;code&gt;as PostMeta&lt;/code&gt; cast that hid the runtime bug also blocked the&lt;br&gt;
fix. TypeScript believed &lt;code&gt;meta.date&lt;/code&gt; was a &lt;code&gt;string&lt;/code&gt;, so it wouldn't&lt;br&gt;
let me check if it was a &lt;code&gt;Date&lt;/code&gt;. The fix was to check &lt;code&gt;data.date&lt;/code&gt; (the&lt;br&gt;
raw gray-matter output, typed as &lt;code&gt;any&lt;/code&gt;) instead of &lt;code&gt;meta.date&lt;/code&gt; (typed&lt;br&gt;
as &lt;code&gt;string&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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;raw&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&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;Applied in all four functions that return post data. Also added a&lt;br&gt;
&lt;code&gt;global-error.tsx&lt;/code&gt; so future hydration crashes show a reload button&lt;br&gt;
instead of a dead page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it cost:&lt;/strong&gt; ~25 minutes of downtime on the public site. Three&lt;br&gt;
commits across two repos, including the TypeScript false start. One&lt;br&gt;
embarrassing realization that I'd written a blog post about the bug&lt;br&gt;
and it happened again anyway.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The Model That Quietly Expired
&lt;/h2&gt;

&lt;p&gt;The blog has a voice dictation flow: record a transcript, click&lt;br&gt;
"Generate Post," get a draft. On June 18, clicking Generate returned&lt;br&gt;
a red "Generation failed" banner. No useful error detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cause:&lt;/strong&gt; The generation pipeline called the Anthropic API with&lt;br&gt;
&lt;code&gt;model: "claude-sonnet-4-20250514"&lt;/code&gt;. That model hit end-of-life on&lt;br&gt;
June 15. The API started rejecting requests three days before anyone&lt;br&gt;
noticed.&lt;/p&gt;

&lt;p&gt;The clue was in the SDK itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// @anthropic-ai/sdk DEPRECATED_MODELS&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-4-20250514&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;June 15th, 2026&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; One line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-model: "claude-sonnet-4-20250514",
&lt;/span&gt;&lt;span class="gi"&gt;+model: "claude-sonnet-4-6",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Merged as PR #17. Generation worked immediately after Vercel deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it took three days:&lt;/strong&gt; Two compounding failures:&lt;/p&gt;

&lt;p&gt;First, there's no deprecation warning from the Anthropic API. The model&lt;br&gt;
works on June 14. It doesn't work on June 15. No sunset header, no&lt;br&gt;
grace period, no degraded response with a warning. Just errors.&lt;/p&gt;

&lt;p&gt;Second, the catch block swallowed the error. The route handler logged&lt;br&gt;
&lt;code&gt;console.error("Generation error:", error)&lt;/code&gt; to the server, but returned&lt;br&gt;
&lt;code&gt;{ error: "Blog generation failed" }&lt;/code&gt; to the frontend. The actual&lt;br&gt;
Anthropic error message, which almost certainly said something about&lt;br&gt;
the model being retired, was buried in Vercel's server-side logs. The&lt;br&gt;
user-facing error was a generic string that could mean anything.&lt;/p&gt;

&lt;p&gt;A comment like &lt;code&gt;// EOL: June 15, 2026&lt;/code&gt; next to the model string would&lt;br&gt;
have made this a 30-second fix. Surfacing the API error to the frontend&lt;br&gt;
would have made it self-diagnosing. Neither existed.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Seven Commits for Three Lines
&lt;/h2&gt;

&lt;p&gt;The Vacation Hub, a trip planning side project, has a photo gallery.&lt;br&gt;
Upload photos from your phone, they land in Vercel Blob Storage. It&lt;br&gt;
worked perfectly on the original deployment.&lt;/p&gt;

&lt;p&gt;After a security hardening commit that added CSP headers, photo uploads&lt;br&gt;
broke. Click upload, progress bar hits ~20%, hang forever.&lt;/p&gt;

&lt;p&gt;The agent spent seven commits fixing this. The actual fix was three&lt;br&gt;
lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What went wrong:&lt;/strong&gt; The security commit added a Content-Security-Policy&lt;br&gt;
header with &lt;code&gt;connect-src 'self' https://*.public.blob.vercel-storage.com&lt;/code&gt;.&lt;br&gt;
The Vercel Blob SDK's client-side &lt;code&gt;upload()&lt;/code&gt; makes a PUT to&lt;br&gt;
&lt;code&gt;https://vercel.com/api/blob&lt;/code&gt;. That domain wasn't in &lt;code&gt;connect-src&lt;/code&gt;.&lt;br&gt;
The browser silently blocked the request.&lt;/p&gt;

&lt;p&gt;But here's why seven commits: there were &lt;strong&gt;three independent bugs&lt;/strong&gt;&lt;br&gt;
stacked on top of each other, and fixing any one of them didn't resolve&lt;br&gt;
the issue.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSP &lt;code&gt;connect-src&lt;/code&gt; missing &lt;code&gt;https://vercel.com&lt;/code&gt;&lt;/strong&gt; caused the hang.&lt;br&gt;
The browser blocked the PUT, no error surfaced, the upload promise&lt;br&gt;
never resolved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Empty &lt;code&gt;onUploadCompleted&lt;/code&gt; callback&lt;/strong&gt; contributed to the hang. The&lt;br&gt;
SDK registered a webhook URL that Vercel would POST to after upload.&lt;br&gt;
The empty handler existed, so the SDK set it up, but the callback&lt;br&gt;
could silently fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No &lt;code&gt;multipart: true&lt;/code&gt;&lt;/strong&gt; on the upload calls. Vercel Blob's single&lt;br&gt;
PUT has a 4.5MB limit. Modern phone photos regularly exceed that.&lt;br&gt;
Without multipart chunking, large files returned 413. But you'd&lt;br&gt;
never see the 413 if the request never got past CSP.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each bug masked the next. Fix the CSP and uploads still hang (callback).&lt;br&gt;
Remove the callback and large photos 413 (no multipart). The agent&lt;br&gt;
tried each fix in isolation, concluded each one was wrong, and at one&lt;br&gt;
point rewrote the entire upload flow to server-side FormData, which&lt;br&gt;
introduced its own size limit problems.&lt;/p&gt;

&lt;p&gt;The breakthrough came when I asked a simple question: &lt;em&gt;"The original&lt;br&gt;
deployment worked. What changed?"&lt;/em&gt; A targeted &lt;code&gt;git show&lt;/code&gt; on the&lt;br&gt;
security commit would have found the CSP addition in minutes. Instead,&lt;br&gt;
the agent read the current code looking for problems rather than&lt;br&gt;
diffing backward from the last known working state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-connect-src 'self' https://*.public.blob.vercel-storage.com
&lt;/span&gt;&lt;span class="gi"&gt;+connect-src 'self' https://*.public.blob.vercel-storage.com https://vercel.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;multipart: true&lt;/code&gt; on both &lt;code&gt;upload()&lt;/code&gt; calls and removing the empty&lt;br&gt;
callbacks. Three lines across two files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Connects Them
&lt;/h2&gt;

&lt;p&gt;All three bugs involve a defense that felt complete but wasn't.&lt;/p&gt;

&lt;p&gt;The date coercion in &lt;code&gt;formatDate()&lt;/code&gt; protected the function that crashed&lt;br&gt;
in May. It didn't protect the serialization boundary that crashed in&lt;br&gt;
June. The model was pinned, but nobody tracked when the pin expired.&lt;br&gt;
The security headers were added, but the SDK's upload domain wasn't in&lt;br&gt;
the allowlist, and the error was swallowed so thoroughly that seven&lt;br&gt;
commits went by before the agent found all three stacked failures.&lt;/p&gt;

&lt;p&gt;Each fix addressed the symptom it could see. None of them addressed&lt;br&gt;
the layer where the problem actually lived. The date needed to be&lt;br&gt;
coerced at the parsing boundary, not at the formatting boundary. The&lt;br&gt;
model needed a deprecation calendar, not just a version string. The&lt;br&gt;
security commit needed a full audit of outbound domains, not just the&lt;br&gt;
ones the developer remembered.&lt;/p&gt;

&lt;p&gt;This is a pattern I keep seeing when building with agents. You're&lt;br&gt;
working across dozens of sessions. The agent that added the CSP header&lt;br&gt;
wasn't the agent that debugged the upload failure. The agent that&lt;br&gt;
hardened &lt;code&gt;formatDate&lt;/code&gt; wasn't the agent that needed to harden&lt;br&gt;
&lt;code&gt;posts.ts&lt;/code&gt;. Each session is competent in isolation. The gaps live in&lt;br&gt;
the seams between sessions, where one agent's fix becomes another&lt;br&gt;
agent's assumption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shared context between those sessions is you.&lt;/strong&gt; The human&lt;br&gt;
collaborator is the one who remembers that this date bug happened&lt;br&gt;
before, that CSP headers can block SDK calls, that model strings have&lt;br&gt;
expiration dates. Agents don't carry that across sessions unless you&lt;br&gt;
build it into their context explicitly, with skills, with rules files,&lt;br&gt;
with the kind of institutional memory that a solo developer usually&lt;br&gt;
keeps in their head.&lt;/p&gt;

&lt;p&gt;That means bugs accumulate. Not dramatically, not in ways that show up&lt;br&gt;
in code review, but in the quiet gaps between what one session assumed&lt;br&gt;
and what the next session inherited. An unquoted date here. A hardcoded&lt;br&gt;
model string there. A CSP header that covers the domains you thought&lt;br&gt;
about but not the one the SDK uses internally. Each one is fine until&lt;br&gt;
it isn't.&lt;/p&gt;

&lt;p&gt;The honest response to this is not to stop using agents. It's to be&lt;br&gt;
vigilant. Scan for bugs and vulnerabilities constantly. Accept that&lt;br&gt;
some will surface in production despite your best efforts. Build error&lt;br&gt;
boundaries. Surface errors instead of swallowing them. Add the&lt;br&gt;
&lt;code&gt;global-error.tsx&lt;/code&gt; before you need it.&lt;/p&gt;

&lt;p&gt;For a personal blog like this one, the risk is worth the reward. Agents&lt;br&gt;
push to production, release velocity stays high, and when something&lt;br&gt;
breaks, the blast radius is my own site. I can tolerate 25 minutes of&lt;br&gt;
homepage downtime in exchange for shipping a post every other day with&lt;br&gt;
a full admin toolchain that an agent built.&lt;/p&gt;

&lt;p&gt;That calculus changes the moment customers or revenue depend on what&lt;br&gt;
you're building. If this were a SaaS product, the unquoted date crash&lt;br&gt;
would have been an incident, not a blog post. The three-day model&lt;br&gt;
outage would have meant three days of broken functionality for paying&lt;br&gt;
users. The seven-commit upload thrash would have been a sprint-derailing&lt;br&gt;
debugging session with stakeholders asking for a postmortem.&lt;/p&gt;

&lt;p&gt;The velocity is real. The bugs are real too. Know which game you're&lt;br&gt;
playing.&lt;/p&gt;

&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; unquoted date in &lt;strong&gt;52&lt;/strong&gt; posts took down the public homepage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; deprecated model string broke generation for &lt;strong&gt;3 days&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; stacked bugs hid behind &lt;strong&gt;1&lt;/strong&gt; security commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7&lt;/strong&gt; commits to find a &lt;strong&gt;3-line&lt;/strong&gt; fix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; fodder files consumed across &lt;strong&gt;3&lt;/strong&gt; weeks of bugs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~25 min&lt;/strong&gt; homepage downtime (date crash)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; repos touched across all three fixes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; blog post about this exact bug class that didn't prevent the
recurrence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1&lt;/strong&gt; &lt;code&gt;global-error.tsx&lt;/code&gt; added after the fact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; customers affected, because it's a personal site&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>meta</category>
      <category>buildinginpublic</category>
      <category>agents</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Thursday Thoughts: Every Intern Is a Builder Now</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Thu, 25 Jun 2026 15:25:16 +0000</pubDate>
      <link>https://dev.to/carryologist/thursday-thoughts-every-intern-is-a-builder-now-4lc0</link>
      <guid>https://dev.to/carryologist/thursday-thoughts-every-intern-is-a-builder-now-4lc0</guid>
      <description>&lt;p&gt;Something happened in our finance department recently that I haven't been able to stop thinking about. We're bringing on a summer intern, a college student, not a CS major, and her project isn't to shadow someone or build a deck or update a spreadsheet. Her project is to observe our actual business processes: MBO gathering, payout cycles, closing the books. Then she's going to &lt;strong&gt;vibe code an application or agentic workflow that automates parts of what she observed&lt;/strong&gt;, and present the whole thing back to her class as her intern project.&lt;/p&gt;

&lt;p&gt;I know that might sound like a small thing. It isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Developer" Doesn't Mean What It Used to Mean
&lt;/h2&gt;

&lt;p&gt;I've been saying for a while now that &lt;strong&gt;developer no longer equals software engineer&lt;/strong&gt;. That equation made sense for a long time. If you wanted to build something real, you needed to write real code, which meant you needed years of training. But that's not the world we're in anymore.&lt;/p&gt;

&lt;p&gt;Today, a developer is anyone with an idea who wants to build something. It might be lightweight. It might be scrappy. It might be an internal tool that automates a process nobody bothered to automate because it wasn't worth a full engineering sprint. That era, &lt;strong&gt;the era of vibe coding&lt;/strong&gt;, is here, and it's moving faster than most companies realize.&lt;/p&gt;

&lt;p&gt;I've felt this personally through the journey of writing this blog. I've gone from someone with a passing curiosity about software to someone who can build fully production apps, spin up home labs, and experiment with frontier AI projects without flinching. Not because I suddenly became a software engineer, but because &lt;strong&gt;AI gave me a co-pilot capable enough to close the gap between idea and execution&lt;/strong&gt;. That experience made me a believer. What I saw with this intern made me something more than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Internship Used to Look Like
&lt;/h2&gt;

&lt;p&gt;There's always been a rough hierarchy of internship value. Engineering internships were prized because you could actually &lt;em&gt;ship&lt;/em&gt; something. You could point to a PR, a feature, a deployed tool. That's a concrete artifact. Everything else, finance, ops, marketing, HR, you were lucky to get a bullet point on your resume that wasn't embarrassingly vague.&lt;/p&gt;

&lt;p&gt;That asymmetry always bothered me, but it felt structural. If you can't write code, you can't build things, and if you can't build things, there's a ceiling on what you can show for your time.&lt;/p&gt;

&lt;p&gt;Vibe coding breaks that ceiling. &lt;strong&gt;The ability to build real, deployed tools is no longer gated by a CS degree.&lt;/strong&gt; What it requires now is the ability to observe a process, understand what problem needs solving, and work iteratively with AI to construct something that addresses it. Those are skills a sharp finance intern absolutely has. And now they can prove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Resume Has Changed Forever
&lt;/h2&gt;

&lt;p&gt;Here's what strikes me about this situation: a summer intern is going to walk out with a &lt;strong&gt;concrete, deployed project in her portfolio&lt;/strong&gt;. Not a simulation. Not a case study. An actual automation she built to solve an actual problem at an actual company.&lt;/p&gt;

&lt;p&gt;That used to be the exclusive territory of software engineering internships. Now it's available to anyone whose employer gives them the tools and the latitude to build.&lt;/p&gt;

&lt;p&gt;Think about what that does over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finance students who can automate their own workflows become dramatically more valuable than those who can't&lt;/li&gt;
&lt;li&gt;Operations interns who ship internal tools leave with proof of judgment, not just exposure&lt;/li&gt;
&lt;li&gt;Any non-technical role that involves repetitive process work becomes a candidate for this kind of intern project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The line between "business intern" and "builder" starts to blur in exactly the right way&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I worry about the current graduating classes. The labor market is disorienting right now, and a lot of students are trying to figure out how to differentiate themselves in a world where AI is compressing certain kinds of entry-level work. But I also think there's a real opening here for the ones who figure out vibe coding early. The question isn't whether you have a CS degree. &lt;strong&gt;The question is whether you can ship something.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Companies Need to Do
&lt;/h2&gt;

&lt;p&gt;What I'd love to see, and what I think the smarter companies will figure out soon, is making vibe coding a &lt;strong&gt;golden path for all employees&lt;/strong&gt;, not just engineers. That means giving people access to the right tools in a governed, enterprise-appropriate way. It means designing internship programs around it. It means treating "I built this" as a meaningful credential regardless of job function.&lt;/p&gt;

&lt;p&gt;The companies that lean into this are going to get a productivity multiplier that's hard to explain until you've seen it. The ones that don't are going to watch their more forward-thinking competitors pull ahead in ways that look mysterious from the outside but are actually pretty simple: they let their people build things.&lt;/p&gt;




&lt;p&gt;We're in a genuine golden era of innovation right now, and I don't say that lightly. I've seen enough hype cycles to be skeptical of my own enthusiasm. But watching an intern prepare to spend a summer observing business processes and then automating them, and knowing she'll leave with a real project to show for it, that feels like something different. That feels like a shift.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If your company brought vibe coding to every internship program starting tomorrow, what would get built?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>meta</category>
      <category>buildinginpublic</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Vibe Coding Has Entered the Enterprise, and Governance Is Next</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Wed, 24 Jun 2026 19:28:08 +0000</pubDate>
      <link>https://dev.to/carryologist/vibe-coding-has-entered-the-enterprise-and-governance-is-next-120n</link>
      <guid>https://dev.to/carryologist/vibe-coding-has-entered-the-enterprise-and-governance-is-next-120n</guid>
      <description>&lt;p&gt;Fair warning: the blog has been quieter than I'd like lately, and the reason is entirely self-inflicted. I decided to lean hard into the enthusiast side of &lt;a href="https://dev.to/posts/day-one-building-vibescoder-dev"&gt;this whole experiment&lt;/a&gt;. The home lab started life as a gaming PC, and I'm in the middle of converting it to a fully custom water-cooled loop, dedicated blocks for both the CPU and GPU. New case, new everything. I was a little overambitious about the timeline, so now I'm waiting on parts and the lab is effectively offline. There will be before-and-after thermal comparisons once it's all buttoned up, for those of you who are here for the hardware geekery and not just the AI discourse.&lt;/p&gt;

&lt;p&gt;In the meantime, being lab-less has forced me to slow down and actually think. So you're going to see more posts like this one, call it the view from the cheap seats. What am I actually observing in the market right now? Here's what's on my mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe Coding Just Crossed the Enterprise Threshold
&lt;/h2&gt;

&lt;p&gt;Not long ago, vibe coding carried a slightly dismissive connotation, a toy, a hobby, something the citizen developer crowd played with on weekends. That framing is dissolving fast. What I'm seeing now, across large enterprises, is that tools like Claude Code, Codex, and Cursor are being rolled out not just to engineering teams but to all knowledge workers. That's a fundamentally different moment.&lt;/p&gt;

&lt;p&gt;This is no longer about developers getting a faster autocomplete. This is companies making a deliberate bet that anyone who works with information can use AI to build something, a script, a report, an internal tool, without going through a formal dev cycle. That's a genuinely new thing.&lt;/p&gt;

&lt;p&gt;And here's why I find it interesting beyond the obvious: the conversation has quietly shifted away from job displacement and toward value creation. The early AI discourse was dominated by anxiety, who's going to lose their job? What I'm seeing now is companies asking a different question: where are we leaving value on the table that AI could unlock? Vibe coding, of all things, might be the first real answer to that question at scale.&lt;/p&gt;

&lt;p&gt;If you're working somewhere that's already building out a structured path for enterprise vibe coding, I'd genuinely love to hear how it's going. That's the whole spirit of this blog. This phenomenon is here to stay, and I want to understand it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance Is the Immediate Priority
&lt;/h2&gt;

&lt;p&gt;Follow the logic chain and the next question becomes obvious: how do you make this safe? When you extend AI-assisted development to thousands of non-engineers, the governance surface area explodes. Who's reviewing what gets built? Where does the output go? What data is being fed into which models?&lt;/p&gt;

&lt;p&gt;I think we're squarely in a governance mindset era right now. The enterprises that are moving fastest on vibe coding rollouts are simultaneously the ones most anxious about guardrails: acceptable use policies, model access controls, audit trails. That tension is real and it's not going away. I've written before about &lt;a href="https://dev.to/posts/thursday-thoughts-audit-your-vibe-code-often"&gt;why regular audits matter&lt;/a&gt; and what happens when you actually &lt;a href="https://dev.to/posts/spring-cleaning-your-vibe-coded-apps"&gt;run one on a live app&lt;/a&gt;. The same discipline applies at enterprise scale, just with a lot more stakeholders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Economics Is the Wave Right Behind It
&lt;/h2&gt;

&lt;p&gt;Here's where I think things get interesting from a technical and infrastructure standpoint: cost is about to become the dominant conversation. Right now, most organizations haven't fully felt the bill because adoption is still early and contained. Once vibe coding is genuinely enterprise-wide, the token economics become impossible to ignore.&lt;/p&gt;

&lt;p&gt;This is a big part of why I've been so focused on &lt;a href="https://dev.to/posts/putting-the-gpu-to-work-running-local-llms"&gt;local and hosted models&lt;/a&gt; in my own experiments. My working theory is that we end up with a tiered model architecture that looks something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A self-hosted or private cloud model handling the bulk of agentic, repetitive, and high-volume tasks, the stuff where cost per token really adds up&lt;/li&gt;
&lt;li&gt;A lower-tier frontier model doing most of the content generation and summarization work&lt;/li&gt;
&lt;li&gt;A high-end frontier model reserved for genuine value-creation tasks: strategic planning, complex reasoning, and probably the higher-stakes coding work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last tier is where you're willing to pay frontier prices because the output actually justifies it. The middle tier is where you're optimizing. The self-hosted layer is where you're driving cost to nearly zero for the high-frequency, lower-complexity workload.&lt;/p&gt;

&lt;p&gt;It's not a novel idea in the abstract, but I don't think most enterprises have operationalized it yet. They're still treating model selection as a one-time IT decision rather than a dynamic cost-optimization problem. That'll change.&lt;/p&gt;




&lt;p&gt;The lab will be back online soon, and when it is we'll get back to the hands-on experiments. But I wanted to get these observations down while they're fresh: vibe coding going enterprise-wide, governance as the immediate challenge, and token economics as the tidal wave right behind it. That's the arc I'm watching play out in real time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Are you seeing the same governance-first pattern in your organization, or has cost already jumped to the top of the priority list?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>meta</category>
      <category>buildinginpublic</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Model Showdown Round 7: Five Local Models vs. One Cloud Model on a Real Coding Task</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Thu, 18 Jun 2026 02:53:16 +0000</pubDate>
      <link>https://dev.to/carryologist/model-showdown-round-7-five-local-models-vs-one-cloud-model-on-a-real-coding-task-1ehj</link>
      <guid>https://dev.to/carryologist/model-showdown-round-7-five-local-models-vs-one-cloud-model-on-a-real-coding-task-1ehj</guid>
      <description>&lt;p&gt;Five local models. One frontier cloud model. The same coding task. Zero hand-holding.&lt;/p&gt;

&lt;p&gt;Only two shipped code. One of them was the cloud model.&lt;/p&gt;

&lt;p&gt;Part of my goal with this series is to continuously test the viability and maturity of local models. I've done it for &lt;a href="https://dev.to/posts/homelab-bakeoff-openclaw-outperforms-hermes-with-hermes-models"&gt;basic agentic tasks&lt;/a&gt;. Today we're revisiting coding tasks.&lt;/p&gt;

&lt;p&gt;What did we learn?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local models are not ready — yet.&lt;/strong&gt; At least not for homelabs like mine. Perhaps if you have hundreds of gigabytes of unified memory (I'm looking at you, older Mac Studios) you can run fully unquantized models. But with even the beefiest of discrete consumer GPUs, local models can't code.&lt;/p&gt;

&lt;p&gt;Let's dig in.&lt;/p&gt;

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

&lt;p&gt;This is Round 7 of the Model Showdown series. Previous rounds tested cloud models against each other — Opus, Sonnet, GPT-5.5, Qwen cloud. This time I wanted to answer a different question: &lt;strong&gt;can local models running on consumer hardware actually complete a real agentic coding task?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The homelab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU&lt;/strong&gt;: AMD Ryzen 9 9950X3D, 64GB RAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU&lt;/strong&gt;: NVIDIA RTX 5090, 32GB VRAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inference&lt;/strong&gt;: llama.cpp b9660, single-model serving on port 8080&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent platform&lt;/strong&gt;: Coder Agents v2.34.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS&lt;/strong&gt;: Ubuntu 24.04, NVIDIA Driver 590.48.01, CUDA 13.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every local model was configured as aggressively as the hardware allows — flash attention, quantized KV cache (&lt;code&gt;q8_0&lt;/code&gt;), and context windows maxed to what VRAM permits.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Contestants
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Quant&lt;/th&gt;
&lt;th&gt;VRAM&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Max Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen 3.6 35B-A3B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local MoE&lt;/td&gt;
&lt;td&gt;UD-Q4_K_XL (21GB)&lt;/td&gt;
&lt;td&gt;~21GB&lt;/td&gt;
&lt;td&gt;131,072&lt;/td&gt;
&lt;td&gt;81,920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 4 12B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local Dense&lt;/td&gt;
&lt;td&gt;UD-Q4_K_XL (6.9GB)&lt;/td&gt;
&lt;td&gt;~8GB&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hermes 4 14B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local Dense&lt;/td&gt;
&lt;td&gt;Q8_0 (15GB)&lt;/td&gt;
&lt;td&gt;~15GB&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen3-Coder 30B-A3B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local MoE&lt;/td&gt;
&lt;td&gt;UD-Q4_K_XL (17GB)&lt;/td&gt;
&lt;td&gt;~17GB&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Devstral 24B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local Dense&lt;/td&gt;
&lt;td&gt;Q5_K_M (17GB)&lt;/td&gt;
&lt;td&gt;~17GB&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude Sonnet 4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud (control)&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sonnet 4 is the control variable. I already know what it can do. The question is how close the local models get.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Task: Admin Tag Manager
&lt;/h2&gt;

&lt;p&gt;Previous rounds used an "image management" feature, but that collided with existing code in the repo. For Round 7, I designed a clean-room task: &lt;strong&gt;build a tag manager for the blog's admin panel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The blog already has tags — posts use a &lt;code&gt;tags[]&lt;/code&gt; array in MDX frontmatter, there's a public &lt;code&gt;/tags&lt;/code&gt; page, and &lt;code&gt;src/lib/posts.ts&lt;/code&gt; has a &lt;code&gt;getAllTags()&lt;/code&gt; function. But there's no admin UI to manage them.&lt;/p&gt;

&lt;p&gt;Each model got the identical prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt;: Add a Tag Manager to the &lt;code&gt;/admin&lt;/code&gt; section.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;src/lib/tags.ts&lt;/code&gt; — list tags with post counts, detect orphans, support rename and merge&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;src/app/api/admin/tags/route.ts&lt;/code&gt; — GET, PATCH, DELETE endpoints&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;src/app/admin/tags/page.tsx&lt;/code&gt; — table with inline rename, delete, sort&lt;/li&gt;
&lt;li&gt;Add "Tags" to AdminNav&lt;/li&gt;
&lt;li&gt;Client-side mutations with refresh (no full page reload)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run build&lt;/code&gt; must pass with zero errors&lt;/li&gt;
&lt;li&gt;Take a screenshot via Playwright MCP&lt;/li&gt;
&lt;li&gt;Commit in logical chunks, push to branch&lt;/li&gt;
&lt;li&gt;Do NOT open a PR&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ten requirements. Real codebase. Real build system. Real git workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Methodology
&lt;/h2&gt;

&lt;p&gt;Each model got its own clean branch (&lt;code&gt;run-10&lt;/code&gt; through &lt;code&gt;run-15&lt;/code&gt;) forked from the same &lt;code&gt;main&lt;/code&gt; commit. Local models were loaded one at a time via &lt;code&gt;llm-switch.sh&lt;/code&gt; and served through llama-server on &lt;code&gt;localhost:8080&lt;/code&gt;. Sonnet 4 ran through Coder's built-in Anthropic provider.&lt;/p&gt;

&lt;p&gt;Model-to-run assignment was randomized and sealed before execution. I didn't know which model was which run until after all six completed (or failed).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on human intervention&lt;/strong&gt;: I monitored each session live and occasionally nudged stalled models ("keep going", "can you finish?") or stopped them when they entered obvious doom loops ("stop"). There was no standardized intervention protocol — I used my judgment as a developer watching an AI assistant, which is how these tools actually get used in practice. Some models got more nudges than others because they stalled more. The two models that shipped code needed zero intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Tool Calls&lt;/th&gt;
&lt;th&gt;Total Tokens&lt;/th&gt;
&lt;th&gt;Commits&lt;/th&gt;
&lt;th&gt;Build Pass&lt;/th&gt;
&lt;th&gt;Screenshot&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Sonnet 4&lt;/strong&gt; ☁️&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;19K&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;✅ (1st try)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complete&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen3-Coder 30B-A3B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;2.06M&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;✅ (3rd try)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Partial&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen 3.6 35B-A3B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;3.89M&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;✅ (2nd try)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Failed&lt;/strong&gt; (never committed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 4 12B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;1.17M&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;❌ (0/7)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Failed&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hermes 4 14B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1.14M&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;❌ (0/13)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Failed&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Devstral 24B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;14K&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Total failure&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One cloud model. Five local models. &lt;strong&gt;One complete success. One partial. Four failures.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Model Actually Did
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sonnet 4 — The Control (Run 14): Complete Success
&lt;/h3&gt;

&lt;p&gt;Sonnet did what you'd expect a frontier model to do. It cloned the repo, spent 25 tool calls reading existing code (auth patterns, API conventions, admin page structure, frontmatter format), then wrote all four files in a tight burst. Build passed on the first try. It hit a real environment issue — a stray &lt;code&gt;package.json&lt;/code&gt; confused Turbopack's workspace detection — diagnosed the root cause, fixed it with a config change, took a Playwright screenshot, and pushed four clean conventional commits.&lt;/p&gt;

&lt;p&gt;Total time: ~10 minutes. Zero human intervention.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acb4ea1 fix: set turbopack.root to avoid workspace lockfile detection in dev
352a8ca feat: add Tags link to AdminNav
22899a0 feat: add /admin/tags page with inline rename, delete, and sort
19f44fa feat: add tags.ts lib with stats, rename, and remove helpers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation followed existing project patterns because it read them first. That's the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qwen3-Coder 30B-A3B (Run 15): The One That Shipped
&lt;/h3&gt;

&lt;p&gt;The best-performing local model. It cloned the repo, explored the codebase, created all four required files (410 lines of code), fixed TypeScript errors across three build attempts, and pushed a working commit.&lt;/p&gt;

&lt;p&gt;But it wasn't clean. It burned ~8 tool calls just fighting the working directory problem (each &lt;code&gt;execute&lt;/code&gt; call resets to &lt;code&gt;/home/coder&lt;/code&gt;, so it kept forgetting to &lt;code&gt;cd&lt;/code&gt; into the repo). After committing, it spent another 30 tool calls confused about whether its own API route file existed — trying to delete and recreate something that was already committed.&lt;/p&gt;

&lt;p&gt;No screenshot. No logical commit chunking (everything in one commit). But &lt;strong&gt;it shipped working code&lt;/strong&gt;, which puts it in a category of one among the local models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qwen 3.6 35B-A3B (Run 13): The Tragic Hero
&lt;/h3&gt;

&lt;p&gt;This is the one that hurts. Qwen 3.6 actually &lt;em&gt;completed the implementation&lt;/em&gt;. It explored the codebase thoroughly, wrote all four files, fixed a type error, and got &lt;code&gt;npm run build&lt;/code&gt; to pass cleanly.&lt;/p&gt;

&lt;p&gt;Then it decided it needed a Playwright screenshot before committing.&lt;/p&gt;

&lt;p&gt;It spent the next &lt;strong&gt;77 messages&lt;/strong&gt; — over 50% of its entire session — trying to install Playwright, fighting missing Chromium dependencies, debugging browser launch failures, rewriting a screenshot script four times, and wrestling with the auth middleware that blocked unauthenticated page loads. It never took the screenshot. It never committed. It never pushed.&lt;/p&gt;

&lt;p&gt;The code was right there. Build passing. Ready to go. But the model couldn't prioritize "commit what works" over "complete requirement #7 first." Three times I nudged it — "You there?", "Keep going", "can you finish?" — and each time it dove back into the Playwright rabbit hole.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.89 million tokens burned. Zero commits pushed.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemma 4 12B (Run 11): The API Misunderstanding
&lt;/h3&gt;

&lt;p&gt;Gemma cloned the repo, read the existing code, and wrote all three new files plus the nav update. Reasonable start. Then it ran &lt;code&gt;npm run build&lt;/code&gt; and hit a type error with &lt;code&gt;gray-matter&lt;/code&gt;'s &lt;code&gt;stringify()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The fix was simple: &lt;code&gt;matter.stringify(content, data)&lt;/code&gt; — content string first, data object second. Gemma had the arguments reversed. It tried six variations of the call, rewrote &lt;code&gt;tags.ts&lt;/code&gt; six times, ran seven builds — and never once tried the correct argument order. It never read the &lt;code&gt;gray-matter&lt;/code&gt; type definitions. It never checked the docs.&lt;/p&gt;

&lt;p&gt;After the fifth failed build, it fell into a &lt;strong&gt;degenerate text generation loop&lt;/strong&gt; — printing "I'll also make sure &lt;code&gt;src/lib/tags.ts&lt;/code&gt; is correct" 26 consecutive times. I had to send "stop" to break the loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hermes 4 14B (Run 12): The Import Path That Wouldn't Die
&lt;/h3&gt;

&lt;p&gt;Hermes jumped straight to writing code without exploring the project structure first. It created two files and ran &lt;code&gt;npm run build&lt;/code&gt;. The error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Module not found: Can't resolve '../../../lib/tags'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route file at &lt;code&gt;src/app/api/admin/tags/route.ts&lt;/code&gt; needs &lt;code&gt;../../../../lib/tags&lt;/code&gt; (four levels up) or &lt;code&gt;@/lib/tags&lt;/code&gt; (Next.js path alias). Hermes used three levels. Off by one.&lt;/p&gt;

&lt;p&gt;It never diagnosed this. Instead, it rewrote both files with the same wrong import and rebuilt. &lt;strong&gt;Thirteen times.&lt;/strong&gt; The output from message 34 onward is nearly verbatim identical every iteration. Same code. Same error. Same "fix." When I sent "stop," it continued for five more tool calls before acknowledging the signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Devstral 24B (Run 10): The Non-Starter
&lt;/h3&gt;

&lt;p&gt;Devstral never executed a single tool call. It hallucinated an entire fake conversation about a Python project that doesn't exist, then emitted what looked like tool invocations — &lt;code&gt;execute&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt; — but rendered them as &lt;strong&gt;plain text&lt;/strong&gt; inside the assistant message. The platform couldn't parse them as structured tool calls, so nothing happened.&lt;/p&gt;

&lt;p&gt;This is a fundamental compatibility failure. The model couldn't interface with Coder's tool-calling protocol at all. Nine messages, 14K tokens, zero actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Token Efficiency Gap
&lt;/h2&gt;

&lt;p&gt;This is the number that stopped me:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Total Tokens&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19,237&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complete (4 commits, screenshot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-Coder&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,059,519&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partial (1 commit, no screenshot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 3.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3,890,791&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed (build passed, never committed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,170,967&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed (0/7 builds passed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hermes 4 14B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,138,614&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed (0/13 builds passed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Devstral 24B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14,447&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed (zero tool calls)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sonnet used &lt;strong&gt;19K tokens&lt;/strong&gt; to complete the task. The local models that actually tried burned &lt;strong&gt;1–4 million tokens&lt;/strong&gt; and mostly failed. That's a 100-200x token efficiency gap for the same task.&lt;/p&gt;

&lt;p&gt;The local models aren't just slower. They're doing fundamentally more work per unit of progress — re-reading files they already read, rewriting code they just wrote, rebuilding with the same error, looping through the same reasoning. It's not a speed problem. It's a thinking problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Failure Patterns
&lt;/h2&gt;

&lt;p&gt;Every local model that ran long enough exhibited the same pathologies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Degenerate loops.&lt;/strong&gt; Gemma repeated the same text 26 times. Hermes rebuilt with the same wrong import 13 times. Qwen 3.6 rewrote its screenshot script 4 times with the same approach. Once a local model enters a loop, it can't break out without human intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Working directory amnesia.&lt;/strong&gt; Coder's &lt;code&gt;execute&lt;/code&gt; tool doesn't preserve &lt;code&gt;cd&lt;/code&gt; across calls. Sonnet learned this instantly and prefixed every command. Multiple local models burned 5-10 tool calls per session rediscovering this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Inability to prioritize.&lt;/strong&gt; Qwen 3.6 had a passing build and chose to yak-shave on Playwright instead of committing. No local model demonstrated the judgment to ship what works and iterate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. No self-diagnosis.&lt;/strong&gt; When a build fails, the fix requires reading the error, forming a hypothesis, and trying something &lt;em&gt;different&lt;/em&gt;. Hermes and Gemma both tried the same fix repeatedly. Neither ever stepped back to read docs, check type definitions, or examine the project configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local models can write plausible code.&lt;/strong&gt; Four of five local models produced syntactically reasonable TypeScript. The code &lt;em&gt;looked&lt;/em&gt; right. The architecture was sensible. It's the last mile — debugging, building, committing, shipping — where they fall apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agentic gap is wider than the coding gap.&lt;/strong&gt; These models can generate code. What they can't do is &lt;em&gt;operate as agents&lt;/em&gt; — managing state across tool calls, diagnosing errors, prioritizing tasks, knowing when to stop and ship. That's a different capability than code generation, and it's where local models are currently weakest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token efficiency is the real benchmark.&lt;/strong&gt; Raw parameter count and context window don't predict agentic success. Qwen 3.6 had the biggest context (131K) and burned the most tokens (3.89M) — and still didn't ship. Sonnet used 100x fewer tokens and completed everything. The bottleneck isn't context. It's reasoning quality per token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool-calling compatibility isn't guaranteed.&lt;/strong&gt; Devstral is marketed as an agentic coding model, but it couldn't even interface with the tool-calling protocol. If you're evaluating local models for agent use, test tool calling first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qwen3-Coder is the local model to watch.&lt;/strong&gt; It's the only local model that actually shipped code in this test. Messy, single-commit, no screenshot — but working code pushed to a branch. For a 30B MoE model running on a single consumer GPU, that's notable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Sonnet 4&lt;/th&gt;
&lt;th&gt;Qwen3-Coder&lt;/th&gt;
&lt;th&gt;Qwen 3.6&lt;/th&gt;
&lt;th&gt;Gemma 4 12B&lt;/th&gt;
&lt;th&gt;Hermes 4 14B&lt;/th&gt;
&lt;th&gt;Devstral 24B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Local MoE&lt;/td&gt;
&lt;td&gt;Local MoE&lt;/td&gt;
&lt;td&gt;Local Dense&lt;/td&gt;
&lt;td&gt;Local Dense&lt;/td&gt;
&lt;td&gt;Local Dense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parameters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;30B (3B active)&lt;/td&gt;
&lt;td&gt;35B (3B active)&lt;/td&gt;
&lt;td&gt;12B&lt;/td&gt;
&lt;td&gt;14B&lt;/td&gt;
&lt;td&gt;24B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19,237&lt;/td&gt;
&lt;td&gt;2,059,519&lt;/td&gt;
&lt;td&gt;3,890,791&lt;/td&gt;
&lt;td&gt;1,170,967&lt;/td&gt;
&lt;td&gt;1,138,614&lt;/td&gt;
&lt;td&gt;14,447&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool calls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Messages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;183&lt;/td&gt;
&lt;td&gt;127&lt;/td&gt;
&lt;td&gt;162&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Commits pushed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build passed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 1st try&lt;/td&gt;
&lt;td&gt;✅ 3rd try&lt;/td&gt;
&lt;td&gt;✅ 2nd try&lt;/td&gt;
&lt;td&gt;❌ 0/7&lt;/td&gt;
&lt;td&gt;❌ 0/13&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Screenshot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human nudges&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2 + stop&lt;/td&gt;
&lt;td&gt;stop&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Outcome&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Inference stack&lt;/strong&gt;: llama.cpp b9660, flash attention, q8_0 KV cache, Coder Agents v2.34.0&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware&lt;/strong&gt;: RTX 5090 32GB, Ryzen 9 9950X3D, 64GB RAM, Ubuntu 24.04&lt;/p&gt;

&lt;p&gt;Next up: Round 6 brings more frontier models to the same task. And I'll keep pushing the local models — better quants, newer releases, maybe a different agent framework. The gap is real, but the pace of improvement on the local side is fast.&lt;/p&gt;

</description>
      <category>modelshowdown</category>
      <category>benchmark</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Frontier Bakeoff: We Benchmarked Fable 5 Hours Before the Shutdown</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Sat, 13 Jun 2026 18:46:27 +0000</pubDate>
      <link>https://dev.to/carryologist/frontier-bakeoff-we-benchmarked-fable-5-hours-before-the-shutdown-hd4</link>
      <guid>https://dev.to/carryologist/frontier-bakeoff-we-benchmarked-fable-5-hours-before-the-shutdown-hd4</guid>
      <description>&lt;p&gt;Fable 5 didn't win.&lt;/p&gt;

&lt;p&gt;I need to say that up front because the timing of this post is going to make it sound like a very different story. Yes, we benchmarked Claude Fable 5 on our homelab harness. Yes, the US government suspended it about three hours later. But the actual result? Fable 5 scored 89.3. Opus 4.8 scored 91.9. The model everyone's eulogizing right now lost to a model you can still use today.&lt;/p&gt;

&lt;p&gt;That's the real story. The suspension is just what makes it weird.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Tested
&lt;/h2&gt;

&lt;p&gt;This is Round 6 of our &lt;a href="https://dev.to/posts/homelab-bakeoff-openclaw-outperforms-hermes-with-hermes-models"&gt;homelab bakeoff series&lt;/a&gt; — but with a twist. Rounds 1 through 5 tested quantized local models on an RTX 5090 via llama.cpp. This time we pointed the same task suite at four frontier cloud models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.8&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opus48&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Fable 5&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fable5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sonnet46&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt55&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same 10 quality tasks. Same 3 speed tasks. Same scoring rubrics, same fixture files, same composite formula. The only things that changed were the transport layer (Anthropic/OpenAI SDKs instead of llama.cpp HTTP) and two bug fixes that made scoring more accurate. I'll get into those.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Coding&lt;/th&gt;
&lt;th&gt;Reasoning&lt;/th&gt;
&lt;th&gt;Tool Use&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Opus 4.8&lt;/td&gt;
&lt;td&gt;84.8&lt;/td&gt;
&lt;td&gt;90.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Fable 5&lt;/td&gt;
&lt;td&gt;86.7&lt;/td&gt;
&lt;td&gt;93.3&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;79.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Sonnet 4.6&lt;/td&gt;
&lt;td&gt;75.2&lt;/td&gt;
&lt;td&gt;93.3&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;78.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84.5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;86.7&lt;/td&gt;
&lt;td&gt;66.7&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;60.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things jump out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fable 5 was the best at the hard stuff.&lt;/strong&gt; It scored highest on coding (86.7, tied with GPT-5.5) and highest on reasoning (93.3, tied with Sonnet 4.6). Its architecture analysis for Task 3.2 — designing a collaborative editor with CRDTs at scale — was the cleanest answer in the field. It opened by decomposing the 100ms latency budget across the full request path before even discussing algorithms. That's the kind of structured thinking you want from a senior engineer, not a chatbot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But speed killed it.&lt;/strong&gt; Opus 4.8 was meaningfully faster on every speed benchmark, and speed is 20% of the weighted total. Fable 5's TTFT hovered around 3.4–4.0 seconds per request — likely the cost of whatever reasoning depth Anthropic tuned into it. Opus came in consistently under that. When you weight for speed, Opus's 2.6-point lead on the final score comes almost entirely from the speed category.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool use was a wash.&lt;/strong&gt; Every model scored 5/5 on both tool-use tasks. At the frontier level, structured output and function calling are solved problems. This category no longer differentiates.&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT-5.5: The Token Limit Trap
&lt;/h2&gt;

&lt;p&gt;GPT-5.5 tied for the best coding score (86.7) and nailed Bayes' theorem, database debugging, and both tool-use tasks. But its reasoning score is 66.7 — way behind the pack — and the reason is a single task failure.&lt;/p&gt;

&lt;p&gt;On Task 3.2 (architecture analysis), GPT-5.5 hit the 4,096 completion token limit and returned a truncated response. &lt;code&gt;finish_reason: "length"&lt;/code&gt;, empty captured content, 0/10 on all rubric items. It spent 85 seconds generating 4,096 tokens of thinking and never actually delivered an answer. The scoring harness captured nothing because there was nothing to capture.&lt;/p&gt;

&lt;p&gt;Was the task too hard? No — Fable 5 scored 10/10 on the same prompt in roughly the same token budget. GPT-5.5 just allocated its budget differently (or the API's default max_tokens was too low for its reasoning style). Either way, one truncated response cost it 10 points and dropped it from a competitive second place to a distant fourth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; benchmark harnesses that don't account for provider-specific token limits will produce misleading results. I could have set &lt;code&gt;max_tokens&lt;/code&gt; higher, but the point of a bakeoff is equal conditions. Every model got the same parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sonnet Surprise
&lt;/h2&gt;

&lt;p&gt;Sonnet 4.6 deserves attention. It matched Fable 5 on reasoning (93.3), ran at roughly the same speed, and costs about a third as much. Its coding score (75.2) is the only weak spot — it missed some feature-detection checks on the Express bug-fix task that the others caught.&lt;/p&gt;

&lt;p&gt;For most production workloads, Sonnet 4.6 at 84.5 overall is probably the right choice. The 4.8-point gap to Fable 5 is almost entirely coding quality, and the price difference is substantial.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed From Round 5
&lt;/h2&gt;

&lt;p&gt;I adapted the Round 5 homelab harness into a standalone cloud benchmark. For full transparency, there's a &lt;a href="https://github.com/carryologist/benchmarks/blob/main/runs/2026-06-12--coding--frontier-api--v1/CHANGES.md" rel="noopener noreferrer"&gt;CHANGES.md&lt;/a&gt; documenting every delta, but here are the ones that affect scores:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bayes fix (Task 3.3).&lt;/strong&gt; Round 5 expected 41.67% as the correct answer. It's actually 40.54%. The old harness had a rounding error in the denominator — &lt;code&gt;P(E) = 0.0185&lt;/code&gt;, not &lt;code&gt;0.018&lt;/code&gt;. Every Round 5 model got this "wrong" because the rubric was wrong. Fixed. All four frontier models computed 40.54% correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript tests wired up (Task 1.3).&lt;/strong&gt; Round 5 couldn't run the TypeScript functional tests because &lt;code&gt;npx tsx&lt;/code&gt; wasn't available on the homelab. Scores were capped at 60/100. This environment has tsx, so the full test suite runs. Both Fable 5 and GPT-5.5 passed all assertions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed methodology.&lt;/strong&gt; Round 5 pulled &lt;code&gt;timings.predicted_per_second&lt;/code&gt; from llama.cpp's response body. Cloud APIs don't expose that, so we measure wall-clock &lt;code&gt;output_tokens / elapsed_time&lt;/code&gt; and streaming TTFT. The absolute numbers aren't comparable to Round 5, but relative rankings between the four cloud models are valid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything else is identical.&lt;/strong&gt; Same prompts, same fixtures, same scoring weights (Coding 40%, Reasoning 20%, Tool Use 20%, Speed 20%), same composite formula.&lt;/p&gt;

&lt;h2&gt;
  
  
  About That Shutdown
&lt;/h2&gt;

&lt;p&gt;On June 12, 2026, at approximately 5:21 PM Eastern, the US government issued an export control directive targeting Anthropic's most capable models. Anthropic disabled Fable 5 and Mythos 5 for all customers. No restoration timeline has been provided.&lt;/p&gt;

&lt;p&gt;Our benchmark run completed around 2:00 PM Eastern — roughly three hours before the shutdown. I didn't know it was coming. Nobody outside the government and Anthropic's leadership did.&lt;/p&gt;

&lt;p&gt;I'm not going to speculate about the policy. What I will say is that the benchmark data is real, the run completed cleanly, and the results are reproducible right up until the moment the model stopped existing. We have the full result JSONs, the harness code, and the fixture files. If Fable 5 comes back — or if it doesn't — this is what it could do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The frontier is tighter than I expected.&lt;/strong&gt; 11.9 points separate first from last. In Round 5, the gap between the best and worst local model was over 40 points. At the frontier, everyone can code, everyone can reason, everyone can use tools. The differentiation is in speed, price, and edge-case reliability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed is a legitimate quality axis.&lt;/strong&gt; I initially weighted speed at 20% because I thought it would be a tiebreaker. It ended up being the deciding factor. Opus 4.8 won this bakeoff on speed, not intelligence. Whether that's the "right" ranking depends on your use case, but for agent-in-the-loop coding — where you're waiting on the model 50 times per session — I think speed matters more than most benchmarks acknowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmarks need bug fixes too.&lt;/strong&gt; The Bayes theorem error in Round 5 went unnoticed for five rounds because every local model got it wrong anyway. It took a frontier model computing the right answer to surface the bug in my own scoring rubric. That's humbling and also kind of the point of running these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One truncated response can tank a ranking.&lt;/strong&gt; GPT-5.5 went from a plausible second place to fourth because of a single &lt;code&gt;finish_reason: "length"&lt;/code&gt; on one task. Benchmark design that doesn't account for this is fragile. I'm noting it but not adjusting the score — equal conditions means equal conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Opus 4.8&lt;/th&gt;
&lt;th&gt;Fable 5&lt;/th&gt;
&lt;th&gt;Sonnet 4.6&lt;/th&gt;
&lt;th&gt;GPT-5.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task 1.1 (Todo CLI)&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;80.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 1.2 (Pagination API)&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 1.3 (TS Config)&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;80.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 3.1 (DB Debug)&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 3.2 (Architecture)&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;0/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 3.3 (Bayes)&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 4.1 (Tool Use)&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task 4.2 (Tool Use)&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Raw speed (composite tok/s score): Opus 95.9, Fable 76.6, Sonnet 75.4, GPT-5.5 57.6.&lt;/p&gt;

&lt;p&gt;All result data, the benchmark harness, and fixture files are in the &lt;a href="https://github.com/carryologist/benchmarks/tree/main/runs/2026-06-12--coding--frontier-api--v1" rel="noopener noreferrer"&gt;benchmarks repo&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is post 46 on Vibes Coder. The benchmark harness is open source. If Fable 5 comes back, I'll run it again.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>modelshowdown</category>
      <category>benchmark</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Homelab Bakeoff: OpenClaw Outperforms Hermes… With Hermes Models</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Thu, 11 Jun 2026 14:26:38 +0000</pubDate>
      <link>https://dev.to/carryologist/homelab-bakeoff-openclaw-outperforms-hermes-with-hermes-models-18g4</link>
      <guid>https://dev.to/carryologist/homelab-bakeoff-openclaw-outperforms-hermes-with-hermes-models-18g4</guid>
      <description>&lt;p&gt;I spent an evening trying to make two AI agent frameworks do something simple: call a fitness tracker API and tell me about my workouts.&lt;/p&gt;

&lt;p&gt;Both agents ran the same model — Hermes-4-14B Q8_0, a 14.6 billion parameter model fine-tuned for tool calling. Same hardware — an RTX 5090 with 32 GB of VRAM. Same llama.cpp inference server. Same five tasks. Same MCP server on the other end.&lt;/p&gt;

&lt;p&gt;Both failed on the first try. Both required multiple rounds of debugging before they could make a single tool call. The actual test — running five prompts and scoring the results — took about ten minutes. Getting there took the entire evening.&lt;/p&gt;

&lt;p&gt;I'm sure both frameworks would perform well with frontier cloud models — pipe in Claude or GPT-5 and the tool-calling pipeline is someone else's problem. But the whole point of the homelab is local inference. Local models. Local headaches. And right now, running AI agents against local open-source models means nothing works out of the box.&lt;/p&gt;

&lt;p&gt;The surprise wasn't that both agents struggled. It was which one won. OpenClaw — the generic, model-agnostic framework — outperformed Hermes Agent on Hermes's own model. The framework built by a different company, with no special knowledge of Hermes-4's architecture, beat the vertically integrated stack that trained the model and built the agent. That result needs explaining.&lt;/p&gt;

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

&lt;p&gt;Two Discord bots on my homelab server, each backed by a different agent framework:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Hermesbot&lt;/th&gt;
&lt;th&gt;Clawbot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hermes Agent (Python)&lt;/td&gt;
&lt;td&gt;OpenClaw (Node.js)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hermes-4-14B Q8_0&lt;/td&gt;
&lt;td&gt;Hermes-4-14B Q8_0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;JSONL sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Direct HTTP&lt;/td&gt;
&lt;td&gt;Gateway proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord Bot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hermesbot&lt;/td&gt;
&lt;td&gt;Clawbot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both connect to the same fitness-tracker MCP server — a Next.js app on Vercel that wraps my Peloton data, workout history, and annual goals in ten tools. &lt;code&gt;list_workouts&lt;/code&gt;, &lt;code&gt;sync_peloton&lt;/code&gt;, &lt;code&gt;list_goals&lt;/code&gt;, &lt;code&gt;delete_workout&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;The idea was clean: same model isolates the framework variable. Any performance difference is orchestration, not weights. The &lt;a href="https://dev.to/blog/hermes-agent-first-contact"&gt;experiment design&lt;/a&gt; called for five tasks of escalating complexity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;List my last 5 workouts&lt;/strong&gt; — basic single tool call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync Peloton, count this week, check goal pace&lt;/strong&gt; — multi-step chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How am I doing?"&lt;/strong&gt; — ambiguous intent, tool selection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete a fake workout ID&lt;/strong&gt; — error handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend analysis for the past month&lt;/strong&gt; — complex reasoning over large data&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Round 1: Both Agents Failed
&lt;/h2&gt;

&lt;p&gt;Neither agent could complete a single task on the first attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hermesbot: Death by System Prompt
&lt;/h3&gt;

&lt;p&gt;Hermes Agent ships with 90 built-in skills and 17 Discord toolsets — admin, moderation, voice, reactions, the works. All of them get injected into the system prompt on every API call. Combined with the MCP tool definitions, the system prompt ballooned to over 25,000 tokens.&lt;/p&gt;

&lt;p&gt;The model's actual context window? 40,960 tokens. Hermes-4-14B's training context is 40K, and llama.cpp clamps &lt;code&gt;--ctx-size 65536&lt;/code&gt; down to that value silently.&lt;/p&gt;

&lt;p&gt;So on every request: 25K system prompt + conversation history + tool results = more than 40,960 tokens. llama-server returned HTTP 400. Hermes Agent's compression system kicked in, but it compresses &lt;em&gt;conversation messages&lt;/em&gt; — it can't compress the system prompt. The system prompt was the problem, and the compression loop couldn't touch it. Death spiral.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;: Trim the Discord toolsets from 17 down to 1. In &lt;code&gt;~/.hermes/config.yaml&lt;/code&gt;, I replaced the default toolset list with just &lt;code&gt;memory&lt;/code&gt;:&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="na"&gt;discord&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;toolsets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System prompt dropped from 25K+ tokens to something manageable. Two other config tweaks: set &lt;code&gt;context_length: 65536&lt;/code&gt; to pass Hermes Agent's hard-coded 64K minimum check (the framework refuses to start if context is under 64,000 — even though the model's actual context is 40,960), and bump the compression threshold from 0.5 to 0.85 so it stops trying to compress every turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clawbot: The Silent Flag
&lt;/h3&gt;

&lt;p&gt;OpenClaw's failure was subtler. The MCP server wasn't registered in the config at all — that was the first fix. But even after adding it, Clawbot would narrate what tools it would use without actually calling them. It fabricated workout data from 2024, complete with instructors and distances, none of it real.&lt;/p&gt;

&lt;p&gt;The root cause took multiple rounds to find. OpenClaw lists tool names in its system prompt text — "you have access to &lt;code&gt;fitness-tracker__list_workouts&lt;/code&gt;" and so on — but sends &lt;code&gt;tools=0&lt;/code&gt; in the actual API request. The model sees the tool names, understands it should use them, but has no structured schema to emit. So it does the next best thing: it makes up the answer.&lt;/p&gt;

&lt;p&gt;This turned out to be a chat template problem. llama-server was running with &lt;code&gt;--chat-template chatml&lt;/code&gt;, which is a minimal template that processes messages but ignores the &lt;code&gt;tools&lt;/code&gt; parameter entirely. When you send tools in the API request, chatml drops them silently. No error, no warning. The model never sees them.&lt;/p&gt;

&lt;p&gt;I verified this with a direct API test:&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;# With --chat-template chatml: 14 prompt tokens. Tools invisible.&lt;/span&gt;
curl /v1/chat/completions &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"tools":[...], "messages":[...]}'&lt;/span&gt;
&lt;span class="c"&gt;# Response: "I can't help with that"&lt;/span&gt;

&lt;span class="c"&gt;# With --jinja: 172 prompt tokens. Tools injected by the model's template.&lt;/span&gt;
&lt;span class="c"&gt;# Response: {"tool_calls": [{"function": {"name": "list_workouts"}}]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix was a single flag: &lt;code&gt;--jinja&lt;/code&gt; instead of &lt;code&gt;--chat-template chatml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;--jinja&lt;/code&gt;, llama-server uses the Jinja template embedded in the Hermes-4 GGUF file. That template knows about tools. It injects tool definitions into the prompt, recognizes the model's &lt;code&gt;&amp;lt;tool_call&amp;gt;&lt;/code&gt; XML output, and extracts it into structured &lt;code&gt;tool_calls&lt;/code&gt; in the API response. The entire tool-calling pipeline went from broken to working by changing one server flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Exhaustion Loop
&lt;/h2&gt;

&lt;p&gt;I want to pause here and be honest about what this process felt like.&lt;/p&gt;

&lt;p&gt;Each failure mode required a different kind of debugging. The Hermesbot system prompt issue required reading framework source code to understand why compression wasn't helping. The OpenClaw tool injection issue required reading llama.cpp chat template documentation to understand that &lt;code&gt;chatml&lt;/code&gt; ignores tools. The &lt;code&gt;--jinja&lt;/code&gt; fix required understanding that Hermes-4's GGUF file embeds a Jinja template that handles tool-call formatting — something mentioned in no getting-started guide for either framework.&lt;/p&gt;

&lt;p&gt;The cycle was: try a config → restart the service → send a test message → read logs → form a hypothesis → try another config. For Hermesbot, I tried adjusting compression thresholds, changing context length settings, and modifying model parameters before discovering the toolset bloat. For Clawbot, I tried switching API modes (&lt;code&gt;openai-completions&lt;/code&gt; vs &lt;code&gt;openai-responses&lt;/code&gt;), adding compatibility flags (&lt;code&gt;supportsTools&lt;/code&gt;, &lt;code&gt;supportsDeveloperRole&lt;/code&gt;), and testing config keys that turned out not to exist (&lt;code&gt;toolCallStyle&lt;/code&gt;, &lt;code&gt;nativeToolCalls&lt;/code&gt;, &lt;code&gt;capabilities&lt;/code&gt; — all rejected by the validator).&lt;/p&gt;

&lt;p&gt;None of this is documented in a "getting started with local models" guide because it doesn't fit in one. The failure modes are emergent — they come from the interaction between the agent framework, the inference server, the model's chat template, and the model's training format. Each layer has its own configuration surface and its own silent failure modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents are not ready to use local open-source models unless you're an extreme tinkerer.&lt;/strong&gt; Nothing works out of the box. The iterative loop of researching, testing configurations, tweaking parameters, and running experimental tasks is exhausting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2: The Actual Test
&lt;/h2&gt;

&lt;p&gt;Once both agents were working, the test itself was anticlimactic. Five prompts, same order, one after another.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task 1: "List my last 5 workouts"
&lt;/h3&gt;

&lt;p&gt;Both agents called &lt;code&gt;list_workouts(limit=5)&lt;/code&gt; correctly. Same tool, same parameter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hermesbot&lt;/strong&gt; got the data back — 2,935 characters of workout details — and said: &lt;em&gt;"Let me know if you'd like me to summarize these workouts for you!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It fetched the data and didn't show it. The user asked to list workouts and the agent offered to summarize them later. That's a 14B model struggling with instruction following after processing a dense system prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clawbot&lt;/strong&gt; got 2,621 characters back and formatted them immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Today, June 10, 2026&lt;/strong&gt; (1:33 PM PDT) — Peloton Cardio, 28 min&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yesterday, June 9, 2026&lt;/strong&gt; (4:36 AM PDT) — Cannondale Cycling, 15 min&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yesterday, June 9, 2026&lt;/strong&gt; (12:41 AM PDT) — Cannondale Cycling, 13 min&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;June 7, 2026&lt;/strong&gt; — Peloton Cycling, 45 min, 15.07 miles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;June 8, 2026&lt;/strong&gt; — Peloton Cycling, 30 min, 10.36 miles&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dates, sources, durations, notes, distances where available. The data the user asked for, presented the way a user would want it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task 2: "Sync my Peloton workouts, then tell me how many workouts I've done this week and whether I'm on pace for my annual goal."
&lt;/h3&gt;

&lt;p&gt;Both agents chained three tool calls autonomously: sync → list workouts → list goals. No prompting needed. That's the part that worked.&lt;/p&gt;

&lt;p&gt;The difference was in the parameters. Hermesbot used &lt;code&gt;since=2026-06-10&lt;/code&gt; — today only. It found 1 workout this week. Clawbot used &lt;code&gt;since=2026-06-03&lt;/code&gt; — Monday. It found 11 workouts.&lt;/p&gt;

&lt;p&gt;Same model, same tool, different date parameter. The framework's system prompt influences how the model interprets "this week."&lt;/p&gt;

&lt;p&gt;Hermesbot then confused the annual minutes target (11,700 minutes) with a weight target, reporting "you're on pace for about 1.5% of your annual weight target (1/1000000)." The math didn't track.&lt;/p&gt;

&lt;p&gt;Clawbot built a table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weekly Sessions&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;🟢 On Track&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly Minutes&lt;/td&gt;
&lt;td&gt;225 min&lt;/td&gt;
&lt;td&gt;289 min&lt;/td&gt;
&lt;td&gt;🟢 On Track&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Annual Minutes&lt;/td&gt;
&lt;td&gt;11,700 min&lt;/td&gt;
&lt;td&gt;289 min&lt;/td&gt;
&lt;td&gt;🟢 On Track&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Correct numbers, correct interpretation, structured output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task 3: "How am I doing?"
&lt;/h3&gt;

&lt;p&gt;Neither agent made new tool calls — both reused context from the previous tasks. Good.&lt;/p&gt;

&lt;p&gt;Hermesbot hallucinated: &lt;em&gt;"You've completed 1 workout (out of 11,700 needed)."&lt;/em&gt; That 11,700 is the annual minutes target, not a workout count. It also claimed "1 hour and 28 minutes" of exercise when the data showed 28 minutes. The numbers were wrong and the math built on them was nonsensical.&lt;/p&gt;

&lt;p&gt;Clawbot repeated its Task 2 data consistently: 11 workouts, 289 minutes, exceeding both weekly targets. No contradictions, no hallucinated numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task 4: "Delete workout ID fake-id-does-not-exist"
&lt;/h3&gt;

&lt;p&gt;This was the one task Hermesbot won.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hermesbot&lt;/strong&gt; called &lt;code&gt;delete_workout(id="fake-id-does-not-exist")&lt;/code&gt; directly, got an error ("Record to update not found"), and handled it gracefully: &lt;em&gt;"I don't see that workout in your recent sessions."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clawbot&lt;/strong&gt; called &lt;code&gt;get_workout&lt;/code&gt; instead — an existence check rather than attempting the delete. It confirmed the ID didn't exist but never tried to delete it. If the ID had been real, it would have needed a second call. When the user says "delete X," doing the thing is better than checking whether you can do the thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task 5: "Trend analysis — am I improving, plateauing, or declining?"
&lt;/h3&gt;

&lt;p&gt;Both agents fetched about a month of data (Hermesbot got 34 workouts, Clawbot got 32). Both provided reasonable breakdowns by source and activity type.&lt;/p&gt;

&lt;p&gt;The difference was in answering the actual question. Hermesbot gave generic encouragement — &lt;em&gt;"Your consistency is impressive!"&lt;/em&gt; — without ever saying whether the trend was improving, plateauing, or declining. It dodged the question it was asked.&lt;/p&gt;

&lt;p&gt;Clawbot answered directly: &lt;strong&gt;"Plateauing Phase — workout volume has stabilized around 1.0-1.1 workouts per day. No significant progression in duration or frequency."&lt;/strong&gt; Then it gave specific recommendations: add HIIT, schedule a long endurance ride, increase strength training.&lt;/p&gt;

&lt;p&gt;One agent answered the question. The other cheerleaded around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scores
&lt;/h2&gt;

&lt;p&gt;I scored each task on six dimensions: tool accuracy (25%), response quality (25%), error handling (15%), autonomy (15%), speed (10%), and UX (10%).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Hermesbot&lt;/th&gt;
&lt;th&gt;Clawbot&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. List 5 workouts&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clawbot (+25)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Sync + goals&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clawbot (+19)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. How am I doing?&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clawbot (+31)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Delete fake ID&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;Hermesbot (+12)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Trend analysis&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clawbot (+13)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Clawbot (+15.2)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Clawbot won four of five tasks. Hermesbot won the delete task because it did what was asked instead of checking first. The margin wasn't close on Tasks 1 and 3 — those were presentation and accuracy failures from Hermesbot that the same underlying model didn't make under OpenClaw's prompting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why OpenClaw Outperformed Hermes With the Same Model
&lt;/h2&gt;

&lt;p&gt;This is the result that should bother Nous Research. Hermes-4-14B is &lt;em&gt;their&lt;/em&gt; model — trained on &lt;em&gt;their&lt;/em&gt; tool-call format, shipped with &lt;em&gt;their&lt;/em&gt; agent framework. OpenClaw is a third-party product that treats the model as a black box. And the black-box approach won 4 out of 5 tasks with a 15-point margin.&lt;/p&gt;

&lt;p&gt;The model is the same weights in both cases. Same GGUF file, same quantization, same GPU. The differences are entirely in how each framework wields those weights:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System prompt design.&lt;/strong&gt; Hermes Agent's system prompt, even after trimming to one toolset, is dense with agent behavior instructions, skill metadata, and framework-specific directives. It's optimized for the breadth of things Hermes Agent can do, not for the narrow task in front of it. OpenClaw's 26K-character system prompt is large too, but it structures tool availability differently — more catalog, less personality. The model gets different priming, and at 14B parameters, priming matters enormously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context management.&lt;/strong&gt; OpenClaw maintained cleaner context between turns. Hermesbot's compression (trigger at 85%, target 40%) may have been squeezing out the nuance the model needed for Tasks 3 and 5. When you're reasoning about goal metrics or workout trends, the details in earlier messages are the whole point. Compress them and you're asking the model to reason about data it can no longer see clearly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Date interpretation.&lt;/strong&gt; "This week" became &lt;code&gt;since=today&lt;/code&gt; in one framework and &lt;code&gt;since=Monday&lt;/code&gt; in another. Same model, same training, different parameter choice. The system prompt or conversation framing influenced how the model interpreted an ambiguous time reference. This is a framework responsibility — and OpenClaw's framing led the model to the right answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response formatting.&lt;/strong&gt; OpenClaw's prompting encouraged structured output — tables, headers, bullet points. Hermes Agent's prompting led to conversational but imprecise responses. On Task 1, Hermesbot fetched the data and offered to summarize it later. On Task 5, it cheerleaded instead of answering the question. These aren't model failures. They're framework choices that wasted a 14B model's limited capacity on filler instead of substance.&lt;/p&gt;

&lt;p&gt;The irony is real: vertical integration was supposed to be Hermes's advantage. The model trained on the framework's format. But in practice, the framework's overhead — the dense system prompt, the aggressive compression, the instruction-following style — worked against the model it was designed to serve. OpenClaw treated the same model with less ceremony and got more out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;p&gt;The scores don't matter as much as the process that produced them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool-calling pipeline has four points of failure&lt;/strong&gt;, and each one is invisible from the others:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool definitions get injected into the prompt (or don't)&lt;/li&gt;
&lt;li&gt;The model generates a tool call in its native format (or hallucinates one)&lt;/li&gt;
&lt;li&gt;The inference server parses the tool call from the response (or silently drops it)&lt;/li&gt;
&lt;li&gt;The framework executes the tool and feeds the result back (or doesn't)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each framework handles these differently. When something goes wrong, you're debugging a four-layer stack where any layer can fail silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent failures are the default.&lt;/strong&gt; &lt;code&gt;--chat-template chatml&lt;/code&gt; doesn't warn you that it's ignoring tools. Hermes Agent doesn't warn you that 17 toolsets are consuming 60% of your context window. OpenClaw's trajectory logging reports &lt;code&gt;tools=0&lt;/code&gt; even when tools are working. The assumption across the stack is that you know what you're doing, and the evidence suggests that nobody does on the first try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context arithmetic is unforgiving at 14B.&lt;/strong&gt; The model's actual context is 40,960 tokens. A 26K system prompt leaves about 15K for conversation, tool calls, and tool results. A single &lt;code&gt;list_workouts&lt;/code&gt; response is 2,600 to 16,000 characters. Two complex tool calls in a conversation and you're brushing the ceiling. Cloud models with 128K–200K context windows don't have this problem. Local 14B models live on a knife's edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KV cache quantization is free performance.&lt;/strong&gt; Adding &lt;code&gt;--cache-type-k q8_0 --cache-type-v q8_0&lt;/code&gt; to llama-server saved roughly 5 GB of VRAM with no noticeable quality loss. That's VRAM that can go to context length instead. If you're running local inference, do this.&lt;/p&gt;

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

&lt;p&gt;The original bakeoff plan called for a 2×2 matrix on Task 5 — both frameworks running both Hermes-4 and Qwen 3.6. I'm shelving that for now. Today's session was intensive enough.&lt;/p&gt;

&lt;p&gt;But Qwen is the model I want to test. Qwen 3.6 is my daily driver on this homelab — 35B parameters with only 3B active (MoE), 206 tok/s, fits in VRAM with room. The &lt;a href="https://dev.to/blog/hermes-agent-first-contact"&gt;research that preceded this bakeoff&lt;/a&gt; flagged Qwen's TAG_WITH_TAGGED tool-call format as unreliable in llama.cpp. If the &lt;code&gt;--jinja&lt;/code&gt; fix works as well for Qwen as it did for Hermes-4, that could change the calculus for daily use.&lt;/p&gt;

&lt;p&gt;There's also Gemma 4 12B sitting in the download queue — a dense 12B with 256K context. If a dense model with a larger context window performs better than a 14B with a 40K window on these same tasks, the model selection advice changes completely.&lt;/p&gt;

&lt;p&gt;Those tests will happen. Just not tonight.&lt;/p&gt;

&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2&lt;/strong&gt; frameworks tested, same model, same hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; tasks, 100 points each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12&lt;/strong&gt; total MCP tool calls across both agents (6 each)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;91.0 vs 75.8&lt;/strong&gt; — final scores (Clawbot over Hermesbot)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4/5&lt;/strong&gt; tasks won by Clawbot; 1/5 by Hermesbot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;51 seconds&lt;/strong&gt; — Clawbot's total time for all 5 tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;26,477 characters&lt;/strong&gt; — OpenClaw's system prompt size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;40,960 tokens&lt;/strong&gt; — actual context window (model-capped from configured 65,536)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 rounds each&lt;/strong&gt; to get working — config debugging took longer than the actual test&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 flag&lt;/strong&gt; — &lt;code&gt;--jinja&lt;/code&gt; — that made the entire OpenClaw pipeline work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 → 1&lt;/strong&gt; — Discord toolsets trimmed to fix Hermesbot's context overflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; things that worked on the first try&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>llm</category>
      <category>homelab</category>
      <category>buildinginpublic</category>
    </item>
    <item>
      <title>Friday Fixes: Housekeeping the Homelab and Hub</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Fri, 05 Jun 2026 15:24:37 +0000</pubDate>
      <link>https://dev.to/carryologist/friday-fixes-housekeeping-the-homelab-and-hub-4961</link>
      <guid>https://dev.to/carryologist/friday-fixes-housekeeping-the-homelab-and-hub-4961</guid>
      <description>&lt;p&gt;Some weeks you ship a big feature. Other weeks you sweep the floor so the big features keep working. This was a floor-sweeping week — two completely unrelated workstreams that both needed attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track one&lt;/strong&gt;: the homelab's local LLM stack hadn't been touched in a month. Models were stale, llama.cpp was 469 builds behind, and the embedding model was a generation old.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track two&lt;/strong&gt;: the &lt;a href="https://dev.to/blog/forking-and-open-sourcing-a-single-purpose-site"&gt;vacation planning site I open-sourced&lt;/a&gt; needed to actually be useful for a group trip. Calendar sync, activity voting, expense tracking — the features that turn a brochure into a tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track three&lt;/strong&gt;: the &lt;a href="https://dev.to/blog/syndicating-to-substack-the-undocumented-path"&gt;Substack syndication pipeline&lt;/a&gt; I wrote about earlier this week? Turns out doing it once was the easy part. Doing it &lt;em&gt;every time&lt;/em&gt; surfaced two more undocumented quirks and required a GitHub Action to paper over them.&lt;/p&gt;

&lt;p&gt;None of these stories is glamorous on its own. Together they're a snapshot of what maintenance week looks like when you're building with an agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Homelab Model Refresh
&lt;/h2&gt;

&lt;p&gt;The homelab runs llama.cpp on an RTX 5090 with six switchable models. The agent audited everything and came back with a report card:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;llama.cpp&lt;/td&gt;
&lt;td&gt;b8933&lt;/td&gt;
&lt;td&gt;469 builds behind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen (daily driver)&lt;/td&gt;
&lt;td&gt;3.5 35B-A3B&lt;/td&gt;
&lt;td&gt;3.6 available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding&lt;/td&gt;
&lt;td&gt;nomic-embed v1.5&lt;/td&gt;
&lt;td&gt;v2-moe available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4, Devstral, DeepSeek&lt;/td&gt;
&lt;td&gt;Current&lt;/td&gt;
&lt;td&gt;No action needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codestral&lt;/td&gt;
&lt;td&gt;v0.1 (2024)&lt;/td&gt;
&lt;td&gt;Dead end — Mistral pivoted to Devstral&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three downloads, ~38 GB total: Qwen 3.6, nomic-embed v2-moe, and a new addition — Qwen3-Coder-30B-A3B, a coding-specialized MoE that fits at 17 GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Quant Trap
&lt;/h3&gt;

&lt;p&gt;The interesting discovery was about quant provenance. Our Qwen model uses &lt;code&gt;UD-Q4_K_XL&lt;/code&gt; quantization — the "XL" quants use higher precision on attention layers while keeping MoE expert layers smaller. These are &lt;strong&gt;unsloth-specific&lt;/strong&gt;. Bartowski (the other major GGUF publisher) doesn't offer them. The agent initially found the bartowski version and we had to redirect it to unsloth to get the same quant type we were already running.&lt;/p&gt;

&lt;p&gt;This matters because quant format affects output quality in ways that aren't obvious from the model name alone. &lt;code&gt;Q4_K_M&lt;/code&gt; and &lt;code&gt;Q4_K_XL&lt;/code&gt; are both "4-bit" but they allocate precision differently. Swapping quant types during an upgrade is an uncontrolled variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Script Updates
&lt;/h3&gt;

&lt;p&gt;The homelab's model switching lives in a shell script (&lt;code&gt;llm-switch.sh&lt;/code&gt;) that maps model names to file paths and llama-server flags. Updates: Qwen path from 3.5 to 3.6, new &lt;code&gt;qwen-coder&lt;/code&gt; case with 128K context, embedding path from v1.5 to v2-moe, Codestral marked &lt;code&gt;[legacy]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha&lt;/strong&gt;: Pasting heredoc scripts into the terminal mangled backslashes and quoting. We switched to writing the scripts in the workspace, pushing to GitHub, and giving me a &lt;code&gt;git pull &amp;amp;&amp;amp; cp&lt;/code&gt; one-liner. Lesson: don't paste shell scripts through chat — commit them.&lt;/p&gt;

&lt;h3&gt;
  
  
  After State
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;llama.cpp&lt;/td&gt;
&lt;td&gt;b8933&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;b9402&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation model&lt;/td&gt;
&lt;td&gt;Qwen 3.5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Qwen 3.6&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding model&lt;/td&gt;
&lt;td&gt;nomic v1.5 (262 MB)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;nomic v2-moe&lt;/strong&gt; (914 MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switchable models&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;6&lt;/strong&gt; (added qwen-coder)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VRAM&lt;/td&gt;
&lt;td&gt;26,262 MiB&lt;/td&gt;
&lt;td&gt;26,682 MiB (+420 MiB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;About 20 minutes wall clock from audit to fully updated, zero downtime. The old models still serve until you restart the service with the new binary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Vacation Hub Feature Sprint
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/carryologist/vacation-hub" rel="noopener noreferrer"&gt;vacation hub&lt;/a&gt; is a forkable trip-planning site — deploy to Vercel, run the setup wizard, and your group has a private site for travel notes, itinerary, lodging, activities, photos. I &lt;a href="https://dev.to/blog/forking-and-open-sourcing-a-single-purpose-site"&gt;wrote about open-sourcing it&lt;/a&gt; last week. This week was about making it useful.&lt;/p&gt;

&lt;p&gt;Four features across three days, 11 commits, 3,484 lines added. But the features aren't the interesting part. The bugs are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calendar Sync (the straightforward one)
&lt;/h3&gt;

&lt;p&gt;People need trip events in their phone's calendar. Two options: download a &lt;code&gt;.ics&lt;/code&gt; file (one-time import) or subscribe to a URL (auto-syncing).&lt;/p&gt;

&lt;p&gt;The download is trivial — click a button, get a file. The subscription is the interesting engineering problem. Google Calendar, Apple Calendar, and Outlook all fetch subscription URLs from their servers. No browser, no cookies. So the endpoint needs an auth mechanism that works without a session.&lt;/p&gt;

&lt;p&gt;We went with a deterministic HMAC token: &lt;code&gt;HMAC-SHA-256('calendar-subscribe', VACATION_HUB_SECRET)&lt;/code&gt;. The export endpoint accepts either a cookie (for browser downloads) or a &lt;code&gt;?token=&lt;/code&gt; param (for calendar clients). No expiry — a time-limited token would silently break subscriptions when it expires and there's no user present to re-authenticate.&lt;/p&gt;

&lt;p&gt;The iCal generator itself is 202 lines, built from scratch against RFC 5545. The subtle part is line folding — the spec requires max 75 &lt;em&gt;octets&lt;/em&gt; per line, not characters. You can't just &lt;code&gt;.slice(75)&lt;/code&gt; because you might split a UTF-8 multi-byte character. The fold function walks backward from the cut point checking continuation bytes. Most iCal libraries get this wrong and corrupt non-ASCII event names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Activity Voting (the bug factory)
&lt;/h3&gt;

&lt;p&gt;Reddit-style upvote/downvote on suggested activities. Name-based identity (localStorage, no accounts). Upsert voting so changing your mind is idempotent.&lt;/p&gt;

&lt;p&gt;This feature worked perfectly in development and completely failed in production. Twice, for two different reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 1 — The Trailing Slash Massacre&lt;/strong&gt;: &lt;code&gt;next.config.ts&lt;/code&gt; has &lt;code&gt;trailingSlash: true&lt;/code&gt;, which makes Next.js issue 308 redirects from &lt;code&gt;/api/foo&lt;/code&gt; to &lt;code&gt;/api/foo/&lt;/code&gt;. The redirect preserves the HTTP method but the browser drops the request body. Every POST, PUT, and DELETE arrived at the API with an empty body. GET requests (page loads, data fetching) worked fine, so the site &lt;em&gt;looked&lt;/em&gt; healthy — only mutations were silently failing.&lt;/p&gt;

&lt;p&gt;The fix: add trailing slashes to all 28 &lt;code&gt;fetch()&lt;/code&gt; calls across 12 files. Eight minutes to fix, 40 minutes to diagnose. &lt;code&gt;trailingSlash: true&lt;/code&gt; is a foot-gun for API routes — fine for page navigation, lethal for &lt;code&gt;fetch()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 2 — The Table That Never Existed&lt;/strong&gt;: After fixing trailing slashes, voting &lt;em&gt;still&lt;/em&gt; didn't work. The &lt;code&gt;activity_votes&lt;/code&gt; table didn't exist on production. It existed in development because the dev database didn't have duplicate activity titles.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initializeDatabase()&lt;/code&gt; function runs CREATE TABLE statements sequentially in a single try block. After creating the &lt;code&gt;activity_suggestions&lt;/code&gt; table, it tries to create a unique index on the &lt;code&gt;title&lt;/code&gt; column. Production had duplicate titles (imported via LLM-generated suggestions). The index creation threw, the catch block caught it, and the function exited before reaching &lt;code&gt;CREATE TABLE activity_votes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The debugging journey: deploy a temporary &lt;code&gt;/api/db/debug/&lt;/code&gt; endpoint → confirm the table is missing → trace the init function → find the ordering dependency → wrap the index creation in its own try/catch → re-run init → delete the debug endpoint. Two commits, two minutes apart.&lt;/p&gt;

&lt;p&gt;The lesson: every DDL statement in an init function should be its own try/catch. A failure to create an index on table A should never prevent table B from being created.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF Upload Fix (the serverless trap)
&lt;/h3&gt;

&lt;p&gt;This one predated the feature sprint but came up during testing. PDF itinerary uploads worked locally, failed on Vercel with a cryptic module error.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pdf-parse&lt;/code&gt; npm package bundles an ancient version of PDF.js that uses dynamic &lt;code&gt;require()&lt;/code&gt;. Vercel's bundler traces imports statically and prunes anything it can't resolve. The module exists in &lt;code&gt;node_modules&lt;/code&gt; locally but vanishes after bundling.&lt;/p&gt;

&lt;p&gt;Bonus discoveries while debugging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The upload endpoint returned "Something went wrong" for all errors. We had to add real error logging before we could even &lt;em&gt;see&lt;/em&gt; the pdf-parse failure.&lt;/li&gt;
&lt;li&gt;iOS Safari sends an empty MIME type for PDFs. The validation rejected them.&lt;/li&gt;
&lt;li&gt;Vercel has a 4.5MB body limit for serverless functions. The original limit was 10MB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Replaced &lt;code&gt;pdf-parse&lt;/code&gt; with &lt;code&gt;unpdf&lt;/code&gt; (serverless-compatible). Three files changed, 21 insertions, 38 deletions. The kind of fix that's trivial once you know the root cause and impossible until you do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expense Management (the big one)
&lt;/h3&gt;

&lt;p&gt;2,108 lines across 13 files. Track who paid for what, scan receipts with AI, show who owes whom.&lt;/p&gt;

&lt;p&gt;The receipt scanning supports three LLM providers — same ones the site already uses for itinerary parsing. Each has its own quirks: OpenAI accepts image URLs directly, Anthropic and Gemini require base64 encoding. OpenAI and Gemini support structured JSON output, Anthropic requires regex extraction from prose. For PDFs, all three get extracted text rather than the visual layout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The design pivot that mattered&lt;/strong&gt;: The original plan had per-expense split counts. "This $200 dinner was split 4 ways." In practice, the form was cluttered and the answer was almost always the same number. We changed to a global "Splitting between N people" control at the top of the page. The form went from three columns to two. Settlement computation moved from a server endpoint to a &lt;code&gt;useMemo&lt;/code&gt; hook — because the split count is a UI concern (you might flip between values while looking at the numbers), not persistent data.&lt;/p&gt;

&lt;p&gt;We built the server endpoint, shipped it, realized it was wrong, moved the logic client-side, and deleted the endpoint. Normal lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cleanup
&lt;/h3&gt;

&lt;p&gt;After the feature sprint, we went back and deleted dead code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/api/expenses/settle/route.ts&lt;/code&gt; — settlement moved client-side&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/og-image/route.ts&lt;/code&gt; — only consumer was the activity POST handler, which we'd stripped during the Things to Do redesign&lt;/li&gt;
&lt;li&gt;The OG image fetch block in the activity POST handler itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;363 lines deleted. We also went back to the expense feature's design doc and annotated it with what actually shipped versus what was planned. There's something honest about marking your own plan with "this part we built differently." The plan is the record of what you thought before you knew better. The code is what you actually shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: Automating Substack Syndication
&lt;/h2&gt;

&lt;p&gt;I &lt;a href="https://dev.to/blog/syndicating-to-substack-the-undocumented-path"&gt;wrote up the initial Substack import&lt;/a&gt; earlier this week — 13 curated posts, an RSS feed filtered by a &lt;code&gt;syndicate: true&lt;/code&gt; frontmatter flag, and a GitHub mirror repo to work around Substack rejecting feeds from our domain. That got the backlog in. This week's Thursday Thoughts post was the first one I needed to push &lt;em&gt;after&lt;/em&gt; the initial import.&lt;/p&gt;

&lt;p&gt;It didn't go smoothly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two more dedup quirks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Quirk 1 — per-feed-URL dedup.&lt;/strong&gt; Substack doesn't just dedup by GUID. It dedupes by &lt;em&gt;feed URL&lt;/em&gt;. If you add a new post to &lt;code&gt;syndicate.xml&lt;/code&gt; and re-import the same URL, Substack silently skips the new item. The existing 13 posts aren't reimported (good), but the new 14th post isn't imported either (bad). No error. The import API returns 200 and reports it found 14 posts. It just doesn't do anything with the new one.&lt;/p&gt;

&lt;p&gt;The workaround: a separate &lt;code&gt;single-import.xml&lt;/code&gt; file containing only the new post, with a timestamped GUID that Substack has never seen. Different URL, different GUID, different dedup bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quirk 2 — Cloudflare blocks GitHub Actions.&lt;/strong&gt; The live feed at &lt;code&gt;vibescoder.dev/syndicate.xml&lt;/code&gt; returns 403 when fetched from GitHub Actions runners. Same IP reputation issue that made Substack reject the feed in the first place — Vercel sits behind Cloudflare, and Cloudflare's bot protection doesn't love datacenter IP ranges. &lt;code&gt;curl&lt;/code&gt; from a laptop works fine. &lt;code&gt;curl&lt;/code&gt; from &lt;code&gt;ubuntu-latest&lt;/code&gt; on Actions gets a wall.&lt;/p&gt;

&lt;h3&gt;
  
  
  The workflow
&lt;/h3&gt;

&lt;p&gt;The automation lives as a GitHub Action in the content repo (where posts are pushed). On any push to &lt;code&gt;content/posts/&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wait 90 seconds for Vercel to rebuild&lt;/li&gt;
&lt;li&gt;Fetch the live &lt;code&gt;syndicate.xml&lt;/code&gt; (with retry and user-agent headers to appease Cloudflare)&lt;/li&gt;
&lt;li&gt;Clone the mirror repo and diff GUIDs to find new posts&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;syndicate.xml&lt;/code&gt; in the mirror, preserving existing GUID busts from prior imports&lt;/li&gt;
&lt;li&gt;Generate &lt;code&gt;single-import.xml&lt;/code&gt; with a unique timestamped GUID&lt;/li&gt;
&lt;li&gt;Push to the mirror repo&lt;/li&gt;
&lt;li&gt;Post a summary in the Actions run with the Substack import URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last step is manual — you paste the URL into Substack's import UI. Substack's import API exists but requires session authentication, and there's no official way to get a token. Fully automated posting would need the &lt;a href="https://github.com/ma2za/python-substack" rel="noopener noreferrer"&gt;&lt;code&gt;python-substack&lt;/code&gt;&lt;/a&gt; library, which reverse-engineers the auth flow. That's a project for when I have more than one subscriber.&lt;/p&gt;

&lt;p&gt;For now: push a post with &lt;code&gt;syndicate: true&lt;/code&gt;, wait for the Action to run, paste one URL. Three minutes end-to-end, zero chance of forgetting to update the mirror.&lt;/p&gt;




&lt;h2&gt;
  
  
  By the Numbers
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 models&lt;/strong&gt; downloaded (38 GB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;469 llama.cpp builds&lt;/strong&gt; caught up (b8933 → b9402)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 switchable models&lt;/strong&gt; (was 5, added qwen-coder)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;420 MiB&lt;/strong&gt; VRAM increase from the embedding upgrade&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~20 minutes&lt;/strong&gt; wall clock from audit to fully updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vacation Hub:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;11 commits&lt;/strong&gt; over 3 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;35 files changed&lt;/strong&gt;, 3,484 lines added, 702 deleted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 features&lt;/strong&gt; shipped (calendar sync, voting, page redesign, expenses)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 production bugs&lt;/strong&gt; fixed (trailing slash, missing table, pdf-parse)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;28 fetch() calls&lt;/strong&gt; fixed with trailing slashes in one commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;202 lines&lt;/strong&gt; for a from-scratch RFC 5545 iCal generator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2,108 lines&lt;/strong&gt; for expense management in a single commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;363 lines&lt;/strong&gt; deleted during cleanup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 npm package&lt;/strong&gt; replaced (pdf-parse → unpdf)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 user accounts&lt;/strong&gt; — names in localStorage and a prayer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Substack Syndication:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2 undocumented quirks&lt;/strong&gt; discovered (per-feed-URL dedup, Cloudflare blocking Actions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 GitHub Action&lt;/strong&gt; to auto-sync the mirror repo on every content push&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 manual step&lt;/strong&gt; remaining (paste the import URL into Substack)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~3 minutes&lt;/strong&gt; end-to-end per syndicated post, down from ~15 minutes manual&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>meta</category>
      <category>buildinginpublic</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>Thursday Thoughts: How AI-Native Mirrors Cloud-Native</title>
      <dc:creator>Rob</dc:creator>
      <pubDate>Thu, 04 Jun 2026 13:45:14 +0000</pubDate>
      <link>https://dev.to/carryologist/thursday-thoughts-how-ai-native-mirrors-cloud-native-2636</link>
      <guid>https://dev.to/carryologist/thursday-thoughts-how-ai-native-mirrors-cloud-native-2636</guid>
      <description>&lt;p&gt;Last week I attended a C-suite roundtable in Palo Alto with ten executives from the usual smattering of Bay Area titans — a gaming platform, a large systems provider, a major job site, and others. The intent was to get concrete signal on where enterprises are with AI. We got quite a lot of pontificating and waxing poetic. All typical with CxOs. And, candidly, more useful for me. When executives stop being concrete, they start being honest about the shape of the problem.&lt;/p&gt;

&lt;p&gt;Two things landed hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyone acknowledged agents are already in production without guardrails
&lt;/h2&gt;

&lt;p&gt;This came up without us even raising it. Governance — who controls what agents can do, how you audit what they did, how you stop a runaway workflow — was the topic of the room. Not a topic. The topic.&lt;/p&gt;

&lt;p&gt;That's validating in a specific way: the thing that feels like an edge concern when you're deep in the tooling turns out to be the exact thing keeping senior people up at night once they're actually running agents against real systems. The gap between "we deployed an agent" and "we have any idea what it's doing" is apparently wider than most companies expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cloud-native analogy clicked for the whole room
&lt;/h2&gt;

&lt;p&gt;This one I want to dwell on, because I think it's the clearest frame I've found for where we are.&lt;/p&gt;

&lt;p&gt;When enterprises first moved to the cloud, most of them did lift-and-shift. They took their existing workloads — unchanged, same architecture, same assumptions — and ran them on AWS instead of on-prem. You got some cost benefits, some flexibility. But you weren't really using the cloud. You were renting someone else's servers.&lt;/p&gt;

&lt;p&gt;The transformation that actually mattered came later, when teams started redesigning applications &lt;em&gt;for&lt;/em&gt; the cloud. Microservices instead of monoliths. Event-driven architectures. Stateless services that scaled horizontally. Those apps weren't better versions of the old apps. They were different apps, built around what the cloud made possible.&lt;/p&gt;

&lt;p&gt;We are doing the exact same thing with AI right now.&lt;/p&gt;

&lt;p&gt;The lift-and-shift era of AI is: take a human workflow, hand it to an agent, and call it automation. An agent fills out the form. An agent reads the documents. An agent follows the process someone designed for a person to follow. You get some productivity gains. But you're running the old workload on new infrastructure.&lt;/p&gt;

&lt;p&gt;The AI-native era — which these executives were all saying we're about to enter — is when you stop asking "how do we get an agent to do this human task?" and start asking "what would this workflow look like if we designed it for agents at scale from the beginning?" The answer is usually not a faster version of the old thing. It's a different thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The roles question is the one nobody's answered yet
&lt;/h2&gt;

&lt;p&gt;Cloud-native didn't just change how applications were built. It created entirely new job categories. DevOps didn't exist before the cloud forced a rethink of how you deploy and operate software. SREs emerged because reliability at cloud scale required a different discipline than ops at on-prem scale. The new architecture required new ways of working around it.&lt;/p&gt;

&lt;p&gt;The executives in that room were unanimous that the same thing is coming with AI — AI ops, ML ops, whatever we end up calling the people who manage, audit, and operate agent-native workflows — but nobody in the room had actually built those functions yet. They know they need them. They haven't invented them.&lt;/p&gt;

&lt;p&gt;That gap is interesting. It means the companies that figure out the operating model — not just the technology — are going to have a real edge. The architecture is the easier part. The organizational design is where most enterprises are still staring at a blank page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The business model shift is the wildcard
&lt;/h2&gt;

&lt;p&gt;One thing from the conversation that's still rattling around: the cloud era was about doing things better, faster, cheaper. The same metrics, just improved. What the executives were saying about AI is different — that it's going to force a change in &lt;em&gt;how companies measure themselves&lt;/em&gt;, not just how efficient they are.&lt;/p&gt;

&lt;p&gt;Revenue per employee came up specifically. The argument being: once your workforce is partly human and partly agentic, headcount-normalized metrics stop making sense, and you need metrics that account for what your agents are doing alongside your people. Revenue per employee captures the full capacity of the team, human and agent. Do you break out human versus AI employees? TBD. The consensus was yes, but I think even that will normalize.&lt;/p&gt;

&lt;p&gt;That's a bigger shift than any of the technology. Business model changes outlast technology cycles.&lt;/p&gt;




&lt;p&gt;The roundtable ended with a lot of good conversation and connections. But the frame that stuck with me is: we've been through this before. Cloud-native looked impossible from the lift-and-shift era and obvious in retrospect. AI-native probably looks the same from where we're standing now.&lt;/p&gt;

&lt;p&gt;The lift-and-shift phase isn't a mistake — it's how you learn the infrastructure well enough to rethink the architecture. Just don't stop there.&lt;/p&gt;

</description>
      <category>ainative</category>
      <category>cloudnative</category>
      <category>agents</category>
      <category>meta</category>
    </item>
  </channel>
</rss>
