<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.poornerd.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.poornerd.com/" rel="alternate" type="text/html" /><updated>2026-07-20T00:55:26+00:00</updated><id>https://www.poornerd.com/feed.xml</id><title type="html">poornerd</title><subtitle>technologist, entrepreneur, vision­ary, developer
</subtitle><author><name>Brian Porter</name></author><entry><title type="html">5 Reasons Your Next Deploy Should Be 145 KB of Wasm, Not Docker</title><link href="https://www.poornerd.com/2026/07/15/wasm-not-docker.html" rel="alternate" type="text/html" title="5 Reasons Your Next Deploy Should Be 145 KB of Wasm, Not Docker" /><published>2026-07-15T08:00:00+00:00</published><updated>2026-07-15T08:00:00+00:00</updated><id>https://www.poornerd.com/2026/07/15/wasm-not-docker</id><content type="html" xml:base="https://www.poornerd.com/2026/07/15/wasm-not-docker.html"><![CDATA[<p>Yesterday I did an experiment: I took this blog — a plain Jekyll site that has
been deployed over SFTP to shared hosting for years — and made it serve through
a WebAssembly binary running server-side on Cloudflare Workers.</p>

<p>The entire “server” is about 80 lines of Rust, compiled to wasm. Gzipped, the
binary is <strong>145 KB</strong>. It handles everything my Apache <code class="language-plaintext highlighter-rouge">.htaccess</code> used to do:
the canonical-host 301 redirect, six security headers on every response,
<code class="language-plaintext highlighter-rouge">DirectoryIndex</code> resolution, trailing-slash redirects, and the 404 page. The
images and CSS never touch the wasm at all — Cloudflare’s static assets
binding serves them straight from the CDN, free and unlimited.</p>

<p>I went in expecting a fun toy. I came out convinced this is how I want to
deploy a whole class of services. Five reasons.</p>

<h2 id="1-the-artifact-is-absurdly-small">1. The artifact is absurdly small</h2>

<p>The deployable unit is 145 KB gzipped — smaller than most header images on
this blog. A Docker image shipping the same routing logic drags along a base
image, a distro, and a web server: tens to hundreds of megabytes that aren’t
your code. A wasm module is just compiled logic against a standard runtime
interface; the runtime is the platform’s problem.</p>

<h2 id="2-you-test-the-exact-artifact-you-ship">2. You test the exact artifact you ship</h2>

<p><code class="language-plaintext highlighter-rouge">wrangler dev</code> runs the same workerd runtime locally that Cloudflare runs at
the edge — same wasm binary, same bindings, up in milliseconds. My acceptance
script (twelve <code class="language-plaintext highlighter-rouge">curl</code> checks: header parity, byte-identical HTML, redirect
status codes) runs unchanged against localhost and the deployed URL. No image
build, no registry round-trip, no environment drift.</p>

<h2 id="3-the-cdn-does-the-heavy-lifting--for-free">3. The CDN does the heavy lifting — for free</h2>

<p>The 10 MB worker limit forces the right architecture anyway: <strong>logic in the
binary, bytes on the CDN</strong>. This blog’s ~74 MB of images, CSS, and fonts are
served by Cloudflare’s static assets binding — edge-cached, zero egress fees,
not counted against the worker’s request quota. The wasm only wakes up for
redirects, headers, and 404s.</p>

<h2 id="4-the-economics-are-embarrassing">4. The economics are embarrassing</h2>

<p><strong>$0 per month</strong>: 100,000 worker requests/day free, static assets unlimited,
no bandwidth charges; $5/month past that. And because wasm instantiates in
milliseconds, scale-to-zero has no cold-start penalty — the problem you
engineer around with containers doesn’t exist here.</p>

<h2 id="5-its-real-server-logic-not-a-static-hosting-hack">5. It’s real server logic, not a static-hosting hack</h2>

<p>The worker does what Apache did via <code class="language-plaintext highlighter-rouge">mod_rewrite</code> and <code class="language-plaintext highlighter-rouge">mod_headers</code> — canonical
301s, security headers, 404s — in 80 lines of typed, testable Rust instead of
<code class="language-plaintext highlighter-rouge">.htaccess</code> regex. The same shape carries a real backend: routing, auth,
database and upstream calls in one small binary. And with WASI it isn’t
vendor-locked — the same module runs on wasmtime, Fastly, or Spin.</p>

<h2 id="the-honest-caveats">The honest caveats</h2>

<p>It wasn’t friction-free, and I’d rather you hit these in this paragraph than
at midnight:</p>

<ul>
  <li><strong>Toolchain sharp edges.</strong> <code class="language-plaintext highlighter-rouge">worker-build</code> silently requires a recent
<code class="language-plaintext highlighter-rouge">worker</code> crate version; enabling LTO in the release profile broke
<code class="language-plaintext highlighter-rouge">wasm-bindgen</code> with a cryptic “externref table” error. Both fixable in
minutes once diagnosed, both annoying to diagnose.</li>
  <li><strong>Platform defaults fight parity.</strong> Cloudflare’s default URL handling
307-redirects <code class="language-plaintext highlighter-rouge">.html</code> URLs to extensionless ones — nice for a new site,
wrong when you need byte-parity with an existing Apache setup. One config
line (<code class="language-plaintext highlighter-rouge">html_handling = "none"</code>) and a few lines of Rust fixed it.</li>
  <li><strong>The 10 MB limit is real.</strong> Wasm-on-Workers is for logic, not payloads.
If your service is mostly moving large files, this isn’t your architecture.</li>
</ul>

<h2 id="takeaway">Takeaway</h2>

<p>A container ships a machine; a wasm module ships a function. For services
whose job is logic rather than bulk data — redirects today, APIs tomorrow —
the wasm version is orders of magnitude smaller, tests as the identical
artifact you deploy, starts in milliseconds, and rides a free CDN for
everything heavy.</p>

<p>The blog was the toy problem. Over the next weeks I want to push actual
backend logic through this pipeline — a real service with state and upstream
calls — and see where the model bends. If it holds, I struggle to justify a
Dockerfile for small services again.</p>]]></content><author><name>poornerd</name></author><category term="wasm" /><category term="howto" /><summary type="html"><![CDATA[Yesterday I did an experiment: I took this blog — a plain Jekyll site that has been deployed over SFTP to shared hosting for years — and made it serve through a WebAssembly binary running server-side on Cloudflare Workers.]]></summary></entry><entry><title type="html">Can a Claude Skill Out-Design You? I Handed It My Blog to Find Out</title><link href="https://www.poornerd.com/2026/07/14/can-a-claude-skill-out-design-you.html" rel="alternate" type="text/html" title="Can a Claude Skill Out-Design You? I Handed It My Blog to Find Out" /><published>2026-07-14T00:00:00+00:00</published><updated>2026-07-14T00:00:00+00:00</updated><id>https://www.poornerd.com/2026/07/14/can-a-claude-skill-out-design-you</id><content type="html" xml:base="https://www.poornerd.com/2026/07/14/can-a-claude-skill-out-design-you.html"><![CDATA[<p>I’ll admit it: I’m not a designer. I can spot good design, but producing it is another story. My last blog redesign was a two-tool grind—Google Stitch to generate layouts, then Claude Design to refine them. The result was fine. Passable. The kind of “fine” you stop noticing because you’re tired of looking at it.</p>

<p>Then I installed <a href="https://www.usehallmark.com">Hallmark</a> as a Claude Code skill and told it to redesign my blog and homepage. What came out was so much better it made the old version look embarrassing.</p>

<p>Here’s how it actually went.</p>

<h2 id="it-asked-before-it-assumed">It asked before it assumed</h2>

<p>The first thing Hallmark did wasn’t generate anything. It asked about scope: just the blog, or everything? That one question already put it ahead of the usual “here’s 12 layouts, pick one” slot-machine approach. It wanted to know what it was working on before it started working.</p>

<p>Then it went quiet and analyzed. Not for show—when it came back, it came back with <strong>four distinct design directions</strong>. Not four variations of the same idea. Four genuinely different takes, each with a rationale.</p>

<h2 id="i-picked-a-direction-then-it-offered-variations">I picked a direction, then it offered variations</h2>

<p>Once I chose a direction, it didn’t just run. It surfaced a second round of decisions—variations within that direction, including whether I wanted dark mode. Small thing, but it’s the difference between a tool that designs <em>for</em> you and one that designs <em>with</em> you. I got to steer twice: once on the big direction, once on the details.</p>

<h2 id="then-it-worked-for-40-minutes">Then it worked. For 40+ minutes.</h2>

<p>This is the part that sold me.</p>

<p>Hallmark didn’t spit out HTML and call it done. It drove a <strong>headless browser</strong>, took screenshots, and checked its own work—<strong>desktop and mobile</strong>, because I’d asked for both. It iterated. Rendered, looked, adjusted, rendered again. For over forty minutes it did the loop that I always skip when I’m rushing: actually looking at the thing on a real screen at real breakpoints and fixing what’s off.</p>

<p>That’s not prompt-and-pray design generation. That’s the workflow a careful front-end engineer follows—verify against the running page, not the mental model of it.</p>

<h2 id="the-result">The result</h2>

<p>Here’s the honest comparison. First, the old design—hand-wired from Google Stitch and Claude Design:</p>

<p><img src="/images/posts/can-a-claude-skill-out-design-you-before.jpg" alt="Before: the previous poornerd design, built with Google Stitch and Claude Design" /></p>

<p>And here’s what Hallmark produced:</p>

<p><img src="/images/posts/can-a-claude-skill-out-design-you-after.jpg" alt="After: the poornerd redesign produced by the Hallmark skill" /></p>

<p>Compare the two—there’s no contest. Hallmark’s version is tighter, more coherent, and it just <em>feels</em> considered in a way the old one never did.</p>

<h2 id="takeaway">Takeaway</h2>

<p>If you’re building a web page or want a redesign, go look at the <a href="https://www.usehallmark.com">Hallmark skills</a>. The magic isn’t that it generates designs—everything generates designs now. It’s that it interrogates scope first, gives you real choices instead of noise, and then verifies its own work in a real browser until it’s right.</p>

<p>For someone who isn’t a designer, that’s the whole game. It doesn’t just hand me pixels. It does the part I’d skip.</p>]]></content><author><name>poornerd</name></author><category term="claude-code" /><category term="agent-skills" /><category term="design" /><category term="ai" /><category term="howto" /><summary type="html"><![CDATA[I’ll admit it: I’m not a designer. I can spot good design, but producing it is another story. My last blog redesign was a two-tool grind—Google Stitch to generate layouts, then Claude Design to refine them. The result was fine. Passable. The kind of “fine” you stop noticing because you’re tired of looking at it.]]></summary></entry><entry><title type="html">How I Made a Minimalist Agent Harness Code Like a Senior Engineer</title><link href="https://www.poornerd.com/2026/07/12/how-i-made-minimalist-agent-harness-code-like-senior-engineer.html" rel="alternate" type="text/html" title="How I Made a Minimalist Agent Harness Code Like a Senior Engineer" /><published>2026-07-12T12:00:00+00:00</published><updated>2026-07-12T12:00:00+00:00</updated><id>https://www.poornerd.com/2026/07/12/how-i-made-minimalist-agent-harness-code-like-senior-engineer</id><content type="html" xml:base="https://www.poornerd.com/2026/07/12/how-i-made-minimalist-agent-harness-code-like-senior-engineer.html"><![CDATA[<p>Most agent tools decide for you. They ship a fixed set of commands, a fixed idea of “plan mode,” a fixed opinion about sub-agents, and you bend your workflow to fit. <a href="https://pi.dev">Pi</a> — the new minimalist agent harness behind OpenClaw — goes the other way. It’s stripped down on purpose, and the whole pitch is that you adapt it to <em>you</em>, not the reverse.</p>

<p>That sounded great. Pi isn’t a sealed product — if you need a command, a tool, a provider, a workflow, or a UI tweak, you just ask Pi to build it, and it customizes itself on the fly. It ships powerful defaults but deliberately <em>skips</em> things like sub-agents and plan mode, on the theory that you’ll add them if you actually want them. Customizations bundle as <strong>Pi packages</strong> and ship over npm or git.</p>

<p>The catch: I wasn’t sure I was ready to build up all that judgment myself, skill by skill. A blank harness is only as good as what you teach it. Then I remembered <a href="https://github.com/addyosmani/agent-skills">agent-skills</a> — the 77k-star pack of engineering process from Addy Osmani, a senior engineering leader at Google — that I’d already been testing inside Claude Code. Someone had ported it to Pi. So the test wrote itself: take the bare harness, drop in those skills, point it at a model, and see how far it gets.</p>

<h2 id="the-setup">The setup</h2>

<p>Three moving parts.</p>

<p><strong>The harness:</strong> Pi, out of the box, minimal.</p>

<p><strong>The brain:</strong> <a href="https://pi.dev/packages/@chankov/agent-skills"><code class="language-plaintext highlighter-rouge">@chankov/agent-skills</code></a>, a Pi port of Addy Osmani’s <a href="https://github.com/addyosmani/agent-skills">agent-skills</a>. The original bills itself as <em>“production-grade engineering skills for AI coding agents,”</em> and it’s cleared <strong>77k stars on GitHub</strong> — Osmani is a senior engineering leader on Google Chrome, so this isn’t hobby advice. The skills are organized by phase: idea → spec → plan → implement → test → review → ship. Each one encodes the process a good engineer actually follows instead of just “write the code.”</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>install pi.dev/packages/@chankov/agent-skills
</code></pre></div></div>

<p><strong>The model:</strong> I connected Pi to my <a href="https://poornerd.com/posts/2026/06/29/run-claude-code-on-opencode-go-subscription/">OpenCode Go</a> subscription and ran everything on <code class="language-plaintext highlighter-rouge">kimi-k2.7-code</code> at medium effort.</p>

<h2 id="the-test-spec-then-build">The test: spec, then build</h2>

<p>My prompt was specific: a playable snake game in the terminal, styled to look like <em>The Matrix</em> — green digital rain falling behind the board. Not “write snake” — I let the skills drive. It ran the spec skill first, asked me a few clarifying questions the way Claude Code does, and only then started writing code. That ordering matters. The spec-first workflow is exactly what stops a model from confidently building the wrong thing.</p>

<p>The output was clean Python: a pure game-logic core with no <code class="language-plaintext highlighter-rouge">curses</code> imports, a separate renderer for terminal I/O, an input handler, and the Matrix rain effect cascading behind the play area. The interesting part was what happened when I asked it to grade its own work.</p>

<h2 id="running-review">Running <code class="language-plaintext highlighter-rouge">/review</code></h2>

<p>I ran the installed <code class="language-plaintext highlighter-rouge">/review</code> skill on the finished game. What came back wasn’t a rubber stamp — a genuine five-axis review (correctness, readability, architecture, security, performance) that caught the things a good reviewer would: a spot where the renderer was quietly mutating game state, a bloated <code class="language-plaintext highlighter-rouge">main()</code> worth splitting, an unhandled edge case nobody asked about, and a correct <em>nothing critical</em> on security instead of invented risk.</p>

<p>That’s the tell. A weak reviewer pads the list. This one separated what’s good from what to fix, ranked it, and knew when to stop. Reading it back, I couldn’t tell it apart from a review I’d get out of Claude Code or any of the other agent harnesses I run day to day.</p>

<h2 id="one-note-it-just-runs">One note: it just runs</h2>

<p>The skills asked me clarifying questions like Claude Code — but they never stopped to ask <strong>permission</strong> before acting. Pi read files, wrote code, and ran the review on its own. Honestly, I liked it: no clicking “yes” on every step, it just moved.</p>

<p>The clean way to get that speed is to run Pi in a container. Sandbox the workspace, let it go, and autonomy stops being something to worry about — a mistake can’t reach anything outside the box.</p>

<h2 id="takeaway">Takeaway</h2>

<p>A minimalist harness plus a best-practice skills package added up to something that specced deliberately, built cleanly, and produced code and reviews I couldn’t distinguish from what Claude Code or any other harness I already use hands me. The harness stayed out of the way; the skills supplied the judgment.</p>

<p>If you’ve been curious about Pi, this is the fast way in: install <a href="https://pi.dev/packages/@chankov/agent-skills"><code class="language-plaintext highlighter-rouge">@chankov/agent-skills</code></a>, point it at a model, and ask it to review something you wrote. Then decide who the senior engineer in the room actually is.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><category term="pi" /><category term="agent-skills" /><summary type="html"><![CDATA[Most agent tools decide for you. They ship a fixed set of commands, a fixed idea of “plan mode,” a fixed opinion about sub-agents, and you bend your workflow to fit. Pi — the new minimalist agent harness behind OpenClaw — goes the other way. It’s stripped down on purpose, and the whole pitch is that you adapt it to you, not the reverse.]]></summary></entry><entry><title type="html">How I Made My Blog AI-Readable Before My Claude Bill Goes Up</title><link href="https://www.poornerd.com/2026/07/09/how-i-made-my-blog-ai-readable.html" rel="alternate" type="text/html" title="How I Made My Blog AI-Readable Before My Claude Bill Goes Up" /><published>2026-07-09T00:00:00+00:00</published><updated>2026-07-09T00:00:00+00:00</updated><id>https://www.poornerd.com/2026/07/09/how-i-made-my-blog-ai-readable</id><content type="html" xml:base="https://www.poornerd.com/2026/07/09/how-i-made-my-blog-ai-readable.html"><![CDATA[<p>I’ve been sitting on a nagging suspicion for a while: my blog was fine for humans, but probably a mess for machines. Missing meta tags. No clean structured data. Nothing that told an AI crawler what this page actually <em>is</em>.</p>

<p>So I decided to fix it in one sitting—and I used Fable 5 running inside Claude Code to do the heavy lifting, pointed at the specification checklist over at <a href="https://specification.website">https://specification.website</a>. Here’s the exact workflow, and why I did it now instead of later.</p>

<h2 id="the-setup-point-an-agent-at-a-spec">The setup: point an agent at a spec</h2>

<p>There’s a site at <a href="https://specification.website">https://specification.website</a>—”What a good website does.” It’s a checklist of what a well-built site should ship: Foundations, SEO, Accessibility, Security, Well-Known URIs, Agent Readiness, Performance, Privacy, Resilience, Internationalisation. Each topic is tagged <strong>Required</strong> or <strong>Recommended</strong>, from the boring-but-essential (<code class="language-plaintext highlighter-rouge">&lt;!doctype html&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;meta charset&gt;</code>, a real <code class="language-plaintext highlighter-rouge">&lt;title&gt;</code>) to the modern stuff (canonical URLs, theme-color, agent-readable metadata).</p>

<p>My instruction to the agent was deliberately simple:</p>

<blockquote>
  <p>Analyze my website against https://specification.website. Find everything I’m missing—required <em>and</em> recommended. Make a plan. Then fix it all.</p>
</blockquote>

<p>That’s it. No hand-holding. I wanted to see how far an agent could get when the “spec” is an external source of truth instead of a vague “make my site better.”</p>

<h2 id="it-planned-first-then-let-me-decide">It planned first, then let me decide</h2>

<p>It didn’t just start editing files. It checked my pages against the spec and came back with a plan—a grouped list of what was missing across Foundations, SEO, Agent Readiness, Performance, and the rest. Standard agent behaviour; you’ve seen it.</p>

<p>The part that mattered was what came next.</p>

<h2 id="i-made-a-few-decisions-then-let-it-run">I made a few decisions, then let it run</h2>

<p>An agent shouldn’t get to decide everything. A handful of items needed a human call—things like which canonical form to standardize on, what to put in the metadata that’s genuinely <em>mine</em>, and how far to go on the recommended-but-optional items.</p>

<p>So I answered those few questions, and then it did the work: updated the files, wired in the missing pieces, and—this is the part people skip—<strong>tested it</strong>. It didn’t just claim the fixes were done. It verified the build and checked the changes held together before calling it finished.</p>

<p>Plan → decide → fix → verify. That loop is the whole game.</p>

<h2 id="why-i-did-this-now">Why I did this <em>now</em></h2>

<p>Here’s the honest reason for the timing: this is a fantastic use of Fable 5 <em>while it’s still cheap</em>.</p>

<p>Right now, having an agent audit a whole site and ship the fixes fits inside a normal Claude subscription. That won’t last—these capabilities get more expensive as they get more valuable.</p>

<p>So do the one-off, high-leverage cleanup while it’s cheap. You only do it once per site, and the payoff lasts every time a crawler, search engine, or AI agent reads the site correctly from now on.</p>

<h2 id="takeaway">Takeaway</h2>

<p>You don’t need to be an expert on any spec to hold your app or blog to one. Point a capable agent at a good external standard, make it plan before it edits, keep the few decisions that are actually yours, and demand it verify its own work. It applies to any codebase, not just an old one.</p>

<p>I did it in one sitting. And I did it now, on purpose—because getting an agent to bring something up to a standard is cheap today, and that’s exactly when you should spend it.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><summary type="html"><![CDATA[I’ve been sitting on a nagging suspicion for a while: my blog was fine for humans, but probably a mess for machines. Missing meta tags. No clean structured data. Nothing that told an AI crawler what this page actually is.]]></summary></entry><entry><title type="html">Fast GLM 5.2 Plans, Cheap Models Code: One LiteLLM File for Claude Code</title><link href="https://www.poornerd.com/2026/06/30/fast-glm-5-2-plans-cheap-models-code-litellm.html" rel="alternate" type="text/html" title="Fast GLM 5.2 Plans, Cheap Models Code: One LiteLLM File for Claude Code" /><published>2026-06-30T12:00:00+00:00</published><updated>2026-06-30T12:00:00+00:00</updated><id>https://www.poornerd.com/2026/06/30/fast-glm-5-2-plans-cheap-models-code-litellm</id><content type="html" xml:base="https://www.poornerd.com/2026/06/30/fast-glm-5-2-plans-cheap-models-code-litellm.html"><![CDATA[<p>Claude Code doesn’t run everything on one model. It has tiers. A heavy “thinking” model carries the plan and the hard reasoning. Cheaper models do the background grunt work — summaries, quick edits, the subagents your Task tool spins up. Anthropic charges you their prices for all of it.</p>

<p>That split is the whole opportunity. The reasoning is where you want a strong model. The grunt work is where you want <em>fast and cheap</em>. There’s no rule that says both have to come from the same vendor — or from Anthropic at all.</p>

<p>So I put a <a href="https://github.com/BerriAI/litellm">LiteLLM</a> proxy in the middle. The planning tier routes to a fast <strong>GLM 5.2</strong> on <a href="https://www.baseten.com">Baseten</a>. The coding, background, and subagent tiers route to cheap budget models on <a href="https://opencode.ai/go?ref=Q6PBPEKYWW">OpenCode Go</a>. Claude Code stays completely stock — it thinks it’s talking to Anthropic, sends normal Anthropic requests, and LiteLLM translates each one to whatever backend I mapped that tier to.</p>

<blockquote>
  <p>This is a writeup of an actual setup — real config shape and ports, placeholder secrets. Substitute your own keys and model ids; provider catalogs change often, so don’t copy the model strings blind.</p>
</blockquote>

<h2 id="why-this-shape">Why this shape</h2>

<ul>
  <li><strong>One proxy, many backends.</strong> Claude Code only knows how to talk to a single Anthropic-shaped endpoint. LiteLLM fronts that endpoint and fans out per tier.</li>
  <li><strong>Cheapest capable model per tier.</strong> Heavy turns go to GLM 5.2. Background and quick turns go to a cheap, fast model. You pay for thinking where you need it and almost nothing where you don’t.</li>
  <li><strong>Claude Code stays stock.</strong> No patches, no forks, no flags. Point two env vars at the proxy and you’re done.</li>
</ul>

<h2 id="1-install-litellm">1. Install LiteLLM</h2>

<p>Use a venv so the proxy is isolated. <a href="https://github.com/astral-sh/uv"><code class="language-plaintext highlighter-rouge">uv</code></a> is fast; plain <code class="language-plaintext highlighter-rouge">python -m venv</code> works too.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uv venv .venv
uv pip <span class="nb">install</span> <span class="nt">--python</span> .venv <span class="s1">'litellm[proxy]'</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">[proxy]</code> extra pulls in the proxy server (the <code class="language-plaintext highlighter-rouge">litellm --config …</code> mode). Without it you only get the Python library.</p>

<blockquote>
  <p><strong>Security — pin your version.</strong> LiteLLM’s PyPI releases <strong>1.82.7</strong> and <strong>1.82.8</strong> shipped credential-stealing malware. Pin away from those two. This setup uses <strong>1.90.0</strong>. Do not downgrade into the bad pair.</p>
</blockquote>

<h2 id="2-drop-your-keys-in-env">2. Drop your keys in <code class="language-plaintext highlighter-rouge">.env</code></h2>

<p>The launcher sources <code class="language-plaintext highlighter-rouge">.env</code>, so provider keys live there:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">BASETEN_API_KEY</span><span class="o">=</span>baseten-XXXXXXXX
<span class="nv">OPENCODE_API_KEY</span><span class="o">=</span>opencode-XXXXXXXX      <span class="c"># works for both Zen and Go plans</span>
<span class="nv">LITELLM_MASTER_KEY</span><span class="o">=</span>sk-litellm-local     <span class="c"># the proxy's OWN auth (not a provider key)</span>

<span class="c"># Critical for OpenCode Go — see the gotchas section.</span>
<span class="nv">LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES</span><span class="o">=</span>True
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">LITELLM_MASTER_KEY</code> is how the proxy authenticates <em>you</em>. Claude Code sends it as <code class="language-plaintext highlighter-rouge">ANTHROPIC_AUTH_TOKEN</code>, and the two values have to match (default <code class="language-plaintext highlighter-rouge">sk-litellm-local</code>). That last env var isn’t optional if you use OpenCode Go — skip ahead to the gotchas if you want to know why before you trust it.</p>

<h2 id="3-write-the-routing-config--this-is-the-whole-idea">3. Write the routing config — this is the whole idea</h2>

<p><code class="language-plaintext highlighter-rouge">litellm.config.yaml</code> is a list of <code class="language-plaintext highlighter-rouge">model_name</code> → backend mappings. The <code class="language-plaintext highlighter-rouge">model_name</code> is the tier Claude Code asks for; <code class="language-plaintext highlighter-rouge">litellm_params.model</code> is the real backend, prefixed with the provider so LiteLLM knows how to talk to it (<code class="language-plaintext highlighter-rouge">baseten/…</code> for Baseten, <code class="language-plaintext highlighter-rouge">openai/…</code> for any OpenAI-compatible endpoint).</p>

<p>This is where the brain/hands split lives:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">model_list</span><span class="pi">:</span>
  <span class="c1"># THINKING TIER → fast GLM 5.2 on Baseten. This is the model that plans.</span>
  <span class="pi">-</span> <span class="na">model_name</span><span class="pi">:</span> <span class="s">claude-opus-4-8</span>
    <span class="na">litellm_params</span><span class="pi">:</span>
      <span class="na">model</span><span class="pi">:</span> <span class="s">baseten/zai-org/GLM-5.2</span>
      <span class="na">api_key</span><span class="pi">:</span> <span class="s">os.environ/BASETEN_API_KEY</span>

  <span class="c1"># MID TIER → OpenCode Zen (OpenAI-compatible)</span>
  <span class="pi">-</span> <span class="na">model_name</span><span class="pi">:</span> <span class="s">claude-sonnet-4-6</span>
    <span class="na">litellm_params</span><span class="pi">:</span>
      <span class="na">model</span><span class="pi">:</span> <span class="s">openai/deepseek-v4-flash</span>
      <span class="na">api_base</span><span class="pi">:</span> <span class="s">https://opencode.ai/zen/v1</span>
      <span class="na">api_key</span><span class="pi">:</span> <span class="s">os.environ/OPENCODE_API_KEY</span>

  <span class="c1"># FAST / BACKGROUND TIER → OpenCode Go (OpenAI-compatible, cheap)</span>
  <span class="pi">-</span> <span class="na">model_name</span><span class="pi">:</span> <span class="s">claude-haiku-4-5</span>
    <span class="na">litellm_params</span><span class="pi">:</span>
      <span class="na">model</span><span class="pi">:</span> <span class="s">openai/kimi-k2.7-code</span>
      <span class="na">api_base</span><span class="pi">:</span> <span class="s">https://opencode.ai/zen/go/v1</span>
      <span class="na">api_key</span><span class="pi">:</span> <span class="s">os.environ/OPENCODE_API_KEY</span>

  <span class="c1"># SUBAGENTS (Claude Code's Task tool) → cheap model on Go.</span>
  <span class="c1"># Claude Code sends CLAUDE_CODE_SUBAGENT_MODEL's value as `model`; map it here.</span>
  <span class="pi">-</span> <span class="na">model_name</span><span class="pi">:</span> <span class="s">subagent</span>
    <span class="na">litellm_params</span><span class="pi">:</span>
      <span class="na">model</span><span class="pi">:</span> <span class="s">openai/deepseek-v4-flash</span>
      <span class="na">api_base</span><span class="pi">:</span> <span class="s">https://opencode.ai/zen/go/v1</span>
      <span class="na">api_key</span><span class="pi">:</span> <span class="s">os.environ/OPENCODE_API_KEY</span>

<span class="na">litellm_settings</span><span class="pi">:</span>
  <span class="na">drop_params</span><span class="pi">:</span> <span class="kc">true</span>   <span class="c1"># silently drop Anthropic params the OpenAI backends reject</span>

<span class="na">general_settings</span><span class="pi">:</span>
  <span class="na">master_key</span><span class="pi">:</span> <span class="s">os.environ/LITELLM_MASTER_KEY</span>
</code></pre></div></div>

<p>A few things that bite people:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">drop_params: true</code></strong> matters because Claude Code sends Anthropic-only params the OpenAI backends will reject. This strips them silently.</li>
  <li><strong>The <code class="language-plaintext highlighter-rouge">openai/</code> / <code class="language-plaintext highlighter-rouge">baseten/</code> prefix on <code class="language-plaintext highlighter-rouge">model:</code> is not optional.</strong> LiteLLM reads the provider from that prefix. Leave it off and you get <code class="language-plaintext highlighter-rouge">LLM Provider NOT provided</code> at startup — and the deployment is <em>silently</em> skipped, so every later request for that tier 404s with “no healthy deployments.”</li>
  <li><strong>The separate <code class="language-plaintext highlighter-rouge">subagent</code> entry</strong> exists because Claude Code’s Task tool sends <code class="language-plaintext highlighter-rouge">CLAUDE_CODE_SUBAGENT_MODEL</code>’s value verbatim as the <code class="language-plaintext highlighter-rouge">model</code> field. Point that env var at the literal string <code class="language-plaintext highlighter-rouge">subagent</code> (step 5) and the proxy routes every subagent wherever you want — here, a cheap model on Go.</li>
</ul>

<blockquote>
  <p><strong>Want it even cheaper?</strong> Make a second config — <code class="language-plaintext highlighter-rouge">litellm.config.go.yaml</code> — where <em>every</em> tier goes through OpenCode Go (no Baseten, no Zen), using Go-catalog model ids. Run it on a different port (e.g. <code class="language-plaintext highlighter-rouge">30181</code>) so it sits alongside the main proxy. Now you can flip between “GLM 5.2 brain” and “all-cheap” with a single alias.</p>
</blockquote>

<h2 id="4-start-the-proxy">4. Start the proxy</h2>

<p>A tiny launcher that loads keys and runs the proxy:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail
<span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">dirname</span> <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span>
<span class="nb">set</span> <span class="nt">-a</span><span class="p">;</span> <span class="nb">source</span> .env<span class="p">;</span> <span class="nb">set</span> +a
<span class="nb">exec</span> .venv/bin/litellm <span class="nt">--config</span> litellm.config.yaml <span class="nt">--port</span> 30180
</code></pre></div></div>

<p>Save it as <code class="language-plaintext highlighter-rouge">start-proxy.sh</code>, <code class="language-plaintext highlighter-rouge">chmod +x</code>, run it, leave it running:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./start-proxy.sh   <span class="c"># listens on http://localhost:30180</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">set -a; source .env; set +a</code> dance exports every var from <code class="language-plaintext highlighter-rouge">.env</code> so the proxy actually sees the keys, then flips export back off to keep your shell clean.</p>

<p>Startup prints <code class="language-plaintext highlighter-rouge">register_model: … not in built-in cost map</code> warnings for your custom model strings. Harmless — LiteLLM just has no pricing table for them, so its cost tracking shows 0.</p>

<h2 id="5-point-claude-code-at-the-proxy">5. Point Claude Code at the proxy</h2>

<p>Two ways in. The first is file-driven and tied to the repo dir; the second works from anywhere.</p>

<p><strong>Settings file</strong> — <code class="language-plaintext highlighter-rouge">.claude/settings.json</code>, read once at startup:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"env"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"ANTHROPIC_BASE_URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:30180"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ANTHROPIC_AUTH_TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sk-litellm-local"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ANTHROPIC_MODEL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"claude-opus-4-8"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ANTHROPIC_DEFAULT_SONNET_MODEL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"claude-sonnet-4-6"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ANTHROPIC_DEFAULT_HAIKU_MODEL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"claude-haiku-4-5"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"CLAUDE_CODE_SUBAGENT_MODEL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"subagent"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Then <code class="language-plaintext highlighter-rouge">cd</code> into the repo and run <code class="language-plaintext highlighter-rouge">claude</code>. Restart Claude after editing this file. Those <code class="language-plaintext highlighter-rouge">ANTHROPIC_DEFAULT_*</code> vars pin each Claude Code tier to a <code class="language-plaintext highlighter-rouge">model_name</code> the proxy actually knows, so the <code class="language-plaintext highlighter-rouge">/model</code> picker always lands on something routable. Skip them and you risk sending an id the proxy can’t map.</p>

<p><strong>Inline env</strong> — works from any directory, overrides <code class="language-plaintext highlighter-rouge">settings.json</code> for that one run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ANTHROPIC_BASE_URL</span><span class="o">=</span>http://localhost:30180 <span class="se">\</span>
<span class="nv">ANTHROPIC_AUTH_TOKEN</span><span class="o">=</span>sk-litellm-local <span class="se">\</span>
<span class="nv">ANTHROPIC_MODEL</span><span class="o">=</span>claude-opus-4-8 <span class="se">\</span>
<span class="nv">ANTHROPIC_DEFAULT_SONNET_MODEL</span><span class="o">=</span>claude-sonnet-4-6 <span class="se">\</span>
<span class="nv">ANTHROPIC_DEFAULT_HAIKU_MODEL</span><span class="o">=</span>claude-haiku-4-5 <span class="se">\</span>
<span class="nv">CLAUDE_CODE_SUBAGENT_MODEL</span><span class="o">=</span>subagent <span class="se">\</span>
claude
</code></pre></div></div>

<p>For something repeatable, alias it in <code class="language-plaintext highlighter-rouge">~/.zshrc</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">alias </span><span class="nv">ccgo</span><span class="o">=</span><span class="s1">'ANTHROPIC_BASE_URL=http://localhost:30181 ANTHROPIC_AUTH_TOKEN=sk-litellm-local ANTHROPIC_MODEL=claude-opus-4-8 ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-6 ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-haiku-4-5 CLAUDE_CODE_SUBAGENT_MODEL=subagent claude'</span>
</code></pre></div></div>

<p>(Swap the port to <code class="language-plaintext highlighter-rouge">30180</code> for the Zen/Baseten proxy, <code class="language-plaintext highlighter-rouge">30181</code> for the all-Go one.)</p>

<h2 id="6-verify-it-works">6. Verify it works</h2>

<p>With the proxy running, hit it directly with an Anthropic-shaped request:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.venv/bin/python - <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">PY</span><span class="sh">'
import json, urllib.request
body = json.dumps({"model": "claude-opus-4-8", "max_tokens": 16,
    "messages": [{"role": "user", "content": "reply with OK"}]}).encode()
req = urllib.request.Request("http://localhost:30180/v1/messages", data=body,
    headers={"x-api-key": "sk-litellm-local", "anthropic-version": "2023-06-01",
             "content-type": "application/json"})
print(urllib.request.urlopen(req, timeout=30).read().decode())
</span><span class="no">PY
</span></code></pre></div></div>

<p>Expect a 200 with an Anthropic-shaped <code class="language-plaintext highlighter-rouge">content</code> block. Swap the model for <code class="language-plaintext highlighter-rouge">claude-sonnet-4-6</code>, <code class="language-plaintext highlighter-rouge">claude-haiku-4-5</code>, or <code class="language-plaintext highlighter-rouge">subagent</code> to prove each tier independently.</p>

<h2 id="using-the-split-in-practice">Using the split in practice</h2>

<p>Once it’s wired up, the brain/hands split isn’t just a config diagram — it changes how a session feels.</p>

<p>The heavy reasoning rides on GLM 5.2. That’s the Opus tier, which is what Claude Code leans on when it’s actually <em>thinking</em> — working through Plan mode, holding the shape of a change in its head, deciding what to do. The cheap models carry the volume: the Haiku tier’s background chores and every subagent your Task tool fans out. You’re paying for a strong reasoner on the turns that need one and pennies on the turns that don’t.</p>

<p>If you want to be deliberate about it, lean into the tiers: draft the approach in <strong>Plan mode on the Opus/GLM tier</strong>, then <code class="language-plaintext highlighter-rouge">/model</code> down to a cheaper tier to grind out the execution. Plan with the smart model, code with the cheap one. That’s the whole pitch, and now it’s a config file instead of a vendor lock-in.</p>

<h2 id="gotchas-the-painful-ones">Gotchas (the painful ones)</h2>

<p><strong>OpenCode Go 404s on <code class="language-plaintext highlighter-rouge">/v1/responses</code>.</strong> Every request to a Go-backed tier logs <code class="language-plaintext highlighter-rouge">Client error '404 Not Found' for url 'https://opencode.ai/zen/go/v1/responses'</code>, then cascades into <code class="language-plaintext highlighter-rouge">429 No deployments available</code> as the failing deployment goes into cooldown. The cause: LiteLLM’s Anthropic pass-through converts <code class="language-plaintext highlighter-rouge">/v1/messages</code> into an OpenAI <em>Responses API</em> call (<code class="language-plaintext highlighter-rouge">/v1/responses</code>). Zen implements that endpoint; Go does not — only <code class="language-plaintext highlighter-rouge">chat/completions</code> and <code class="language-plaintext highlighter-rouge">messages</code>. The fix is the env var from step 2:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES</span><span class="o">=</span>True
</code></pre></div></div>

<p>It’s global but only touches the Anthropic→OpenAI path, so Zen and Baseten keep working. The tell: when you see a <code class="language-plaintext highlighter-rouge">404 … /v1/&lt;endpoint&gt;</code> against a provider, you’re calling an API surface that provider doesn’t implement — reach for the chat-completions toggle before anything else.</p>

<p><strong>Catalog drift.</strong> You’ll hit <code class="language-plaintext highlighter-rouge">ModelError: Model &lt;x&gt; is not supported</code> or <code class="language-plaintext highlighter-rouge">There are no healthy deployments for this model</code> because provider catalogs change often and they are <em>not</em> symmetric between plans. OpenCode Zen’s model list is different from Go’s, and an id that looks plausibly newer can still be the wrong plan. Check the live catalog and copy the exact <code class="language-plaintext highlighter-rouge">id</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Zen</span>
curl <span class="nt">-s</span> https://opencode.ai/zen/v1/models <span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$OPENCODE_API_KEY</span><span class="s2">"</span>
<span class="c"># Go</span>
curl <span class="nt">-s</span> https://opencode.ai/zen/go/v1/models <span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$OPENCODE_API_KEY</span><span class="s2">"</span>
</code></pre></div></div>

<p>Match the <code class="language-plaintext highlighter-rouge">id</code> field exactly in your <code class="language-plaintext highlighter-rouge">model:</code> line (after the <code class="language-plaintext highlighter-rouge">openai/</code> prefix, or <code class="language-plaintext highlighter-rouge">baseten/…</code> for Baseten). A 403 instead of a model error means a backend key is wrong, not the model string.</p>

<h2 id="tldr">TL;DR</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uv venv .venv <span class="o">&amp;&amp;</span> uv pip <span class="nb">install</span> <span class="nt">--python</span> .venv <span class="s1">'litellm[proxy]'</span>   <span class="c"># 1. install (avoid 1.82.7/1.82.8)</span>
<span class="c"># 2. put BASETEN_API_KEY / OPENCODE_API_KEY / LITELLM_MASTER_KEY +</span>
<span class="c">#    LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES=True in .env</span>
<span class="c"># 3. write litellm.config.yaml: thinking tier → baseten/zai-org/GLM-5.2, rest → openai/… on OpenCode</span>
./start-proxy.sh                                                   <span class="c"># 4. start it (port 30180)</span>
<span class="nv">ANTHROPIC_BASE_URL</span><span class="o">=</span>http://localhost:30180 <span class="nv">ANTHROPIC_AUTH_TOKEN</span><span class="o">=</span>sk-litellm-local claude   <span class="c"># 5. use it</span>
</code></pre></div></div>

<p>Claude Code never knows the difference. You just stopped paying Opus prices for grunt work.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="claude-code" /><category term="howto" /><summary type="html"><![CDATA[Claude Code doesn’t run everything on one model. It has tiers. A heavy “thinking” model carries the plan and the hard reasoning. Cheaper models do the background grunt work — summaries, quick edits, the subagents your Task tool spins up. Anthropic charges you their prices for all of it.]]></summary></entry><entry><title type="html">Run Claude Code on a $10/Month OpenCode Go Subscription</title><link href="https://www.poornerd.com/2026/06/29/run-claude-code-on-opencode-go-subscription.html" rel="alternate" type="text/html" title="Run Claude Code on a $10/Month OpenCode Go Subscription" /><published>2026-06-29T12:00:00+00:00</published><updated>2026-06-29T12:00:00+00:00</updated><id>https://www.poornerd.com/2026/06/29/run-claude-code-on-opencode-go-subscription</id><content type="html" xml:base="https://www.poornerd.com/2026/06/29/run-claude-code-on-opencode-go-subscription.html"><![CDATA[<p>OpenCode Go is a roughly $10/month subscription that hands you a whole catalog of cheap coding models — Kimi, GLM, DeepSeek, MiniMax, Qwen, and more — behind a single API key. The catch most people assume is that you have to use OpenCode’s own client to get at them. You don’t. The Claude Code harness you already know how to drive can talk to that same subscription. You just have to repoint it.</p>

<p>There are two ways to do that. The first takes thirty seconds and works with a subset of the models. The second installs a small proxy and unlocks the whole catalog with stable per-model routing. Here’s both.</p>

<h2 id="way-1-environment-variables-only">Way 1: Environment variables only</h2>

<p>Claude Code speaks the Anthropic Messages API. OpenCode Go exposes some of its models through an Anthropic-compatible endpoint, so for those models you don’t need anything in the middle — you just tell Claude Code’s Anthropic client to call OpenCode Go instead of Anthropic. That’s a handful of environment variables:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">ANTHROPIC_DEFAULT_OPUS_MODEL</span><span class="o">=</span>minimax-m3
<span class="nb">export </span><span class="nv">ANTHROPIC_DEFAULT_SONNET_MODEL</span><span class="o">=</span>minimax-m3
<span class="nb">export </span><span class="nv">ANTHROPIC_DEFAULT_HAIKU_MODEL</span><span class="o">=</span>minimax-m3
<span class="nb">export </span><span class="nv">CLAUDE_CODE_SUBAGENT_MODEL</span><span class="o">=</span>minimax-m3
<span class="nb">export </span><span class="nv">ANTHROPIC_BASE_URL</span><span class="o">=</span><span class="s2">"https://opencode.ai/zen/go/"</span>
<span class="nb">export </span><span class="nv">ANTHROPIC_AUTH_TOKEN</span><span class="o">=</span><span class="s2">""</span>
<span class="nb">export </span><span class="nv">ANTHROPIC_API_KEY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$OPENCODE_API_KEY</span><span class="s2">"</span>
</code></pre></div></div>

<p>Set those, start <code class="language-plaintext highlighter-rouge">claude</code>, and every Opus/Sonnet/Haiku slot — plus subagents — resolves to the OpenCode Go model you named.</p>

<p>The one limitation: this only works with models that ship an Anthropic-compatible interface. On OpenCode Go’s model list those are the ones with <code class="language-plaintext highlighter-rouge">@ai-sdk/anthropic</code> in the AI SDK column — at the moment, the MiniMax and Qwen families (<code class="language-plaintext highlighter-rouge">minimax-m3</code>, <code class="language-plaintext highlighter-rouge">qwen-3.7-plus</code>, <code class="language-plaintext highlighter-rouge">qwen-3.7-max</code>). Anything else needs a translation layer, which is exactly what the second method gives you. (Credit to <a href="https://kkovacs.eu/opencode-go-with-claude-code/">kkovacs.eu</a> for documenting this approach.)</p>

<h2 id="way-2-the-ocgo-proxy-all-models-with-mapping">Way 2: The ocgo proxy (all models, with mapping)</h2>

<p><a href="https://github.com/emanuelcasco/ocgo"><code class="language-plaintext highlighter-rouge">ocgo</code></a> is a small Go CLI that runs a local compatibility proxy. It translates Claude Code’s Anthropic Messages API requests into OpenAI-style chat completions on the way out, so <em>any</em> OpenCode Go model works — not just the Anthropic-compatible ones — and it remembers a per-model mapping so you don’t reconfigure every session.</p>

<p>Install it and save your key:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>emanuelcasco/tap/ocgo
ocgo setup   <span class="c"># paste your OpenCode Go API key when prompted</span>
</code></pre></div></div>

<p>Now map each Claude model name to the OpenCode Go model you actually want behind it. Three commands — one per tier:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ocgo mapping claude <span class="nb">set </span>claude-opus-4-8 glm-5.2
ocgo mapping claude <span class="nb">set </span>claude-sonnet-4-6 deepseek-v4-flash
ocgo mapping claude <span class="nb">set </span>claude-haiku-4-5 kimi-k2.7-code
</code></pre></div></div>

<p>Then launch Claude Code through the proxy:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ocgo launch claude
</code></pre></div></div>

<p>Under the hood, <code class="language-plaintext highlighter-rouge">ocgo launch claude</code> exports <code class="language-plaintext highlighter-rouge">ANTHROPIC_BASE_URL=http://127.0.0.1:3456</code> and <code class="language-plaintext highlighter-rouge">ANTHROPIC_AUTH_TOKEN=unused</code>, then routes each Claude model name to its mapped OpenCode Go model — Opus requests go to GLM, Sonnet to DeepSeek, Haiku to Kimi — while the proxy injects your real OpenCode Go key upstream. Unmapped names pass through unchanged.</p>

<p>The proof is in the model picker. Ask the running session what it is and it tells you straight:</p>

<blockquote>
  <p>Model: <code class="language-plaintext highlighter-rouge">kimi-k2.7-code</code>. Claude Code session, but not running a Claude model.</p>
</blockquote>

<p>That’s the Claude Code harness, your $10 subscription, and a Kimi model answering the prompt.</p>

<h2 id="which-one-to-use">Which one to use</h2>

<p>Way 1 is zero-install but limited to the Anthropic-compatible models. Way 2 is one <code class="language-plaintext highlighter-rouge">brew install</code> and a proxy process, but it opens the entire OpenCode Go catalog and lets you pin a different model to each tier and keep it there. Either way, you keep the harness you like and swap out the expensive backend for a ten-dollar one.</p>

<p>And honestly — if you can get good work done on the cheap models, you’ll have no trouble at all when you do reach for the expensive ones.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="claude-code" /><category term="opencode" /><category term="howto" /><summary type="html"><![CDATA[OpenCode Go is a roughly $10/month subscription that hands you a whole catalog of cheap coding models — Kimi, GLM, DeepSeek, MiniMax, Qwen, and more — behind a single API key. The catch most people assume is that you have to use OpenCode’s own client to get at them. You don’t. The Claude Code harness you already know how to drive can talk to that same subscription. You just have to repoint it.]]></summary></entry><entry><title type="html">How to Open the Hermes Dashboard Locally Against a Remote Server</title><link href="https://www.poornerd.com/2026/06/04/hermes-dashboard-remote-server.html" rel="alternate" type="text/html" title="How to Open the Hermes Dashboard Locally Against a Remote Server" /><published>2026-06-04T09:00:00+00:00</published><updated>2026-06-04T09:00:00+00:00</updated><id>https://www.poornerd.com/2026/06/04/hermes-dashboard-remote-server</id><content type="html" xml:base="https://www.poornerd.com/2026/06/04/hermes-dashboard-remote-server.html"><![CDATA[<p>Most of my Hermes Agent work happens on a remote server, not my laptop. That’s where the agents run, where the gateway lives, and where things keep humming when my Mac is asleep. But the Hermes dashboard — the nice Kanban view of boards, channels, and sessions — wants to open in a browser. On a headless server, there is no browser.</p>

<p>The obvious fix is to bind the dashboard to <code class="language-plaintext highlighter-rouge">0.0.0.0</code> and open the port. Don’t. That puts your agent control panel on the public internet. The better move is to keep the dashboard bound to <code class="language-plaintext highlighter-rouge">127.0.0.1</code> on the server and tunnel that port to your laptop over SSH. Your browser hits <code class="language-plaintext highlighter-rouge">localhost</code>, SSH carries the traffic, and nothing is ever exposed.</p>

<p>Three steps.</p>

<h2 id="1-open-an-ssh-tunnel">1. Open an SSH tunnel</h2>

<p>From your local machine, connect to the server with a local port forward:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh -L 9119:127.0.0.1:9119 root@your-server.example.com
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">-L 9119:127.0.0.1:9119</code> part is the whole trick. It says: take local port <code class="language-plaintext highlighter-rouge">9119</code> on my laptop and forward it to <code class="language-plaintext highlighter-rouge">127.0.0.1:9119</code> on the remote side. Anything I send to <code class="language-plaintext highlighter-rouge">localhost:9119</code> here comes out as a connection to <code class="language-plaintext highlighter-rouge">127.0.0.1:9119</code> there.</p>

<p>Leave this SSH session open. The tunnel only lives as long as the connection does.</p>

<h2 id="2-start-the-dashboard-on-the-remote">2. Start the dashboard on the remote</h2>

<p>Inside that same SSH session, on the server, run:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hermes dashboard --host 127.0.0.1 --port 9119 --no-open
</code></pre></div></div>

<p>Two flags matter here:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">--host 127.0.0.1</code> keeps the dashboard private to the box. It’s not listening on any public interface — only the loopback address that our tunnel targets.</li>
  <li><code class="language-plaintext highlighter-rouge">--no-open</code> stops Hermes from trying to launch a browser on the server. There isn’t one, and without this flag it’ll complain.</li>
</ul>

<h2 id="3-open-it-in-your-local-browser">3. Open it in your local browser</h2>

<p>Back on your laptop, go to:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost:9119
</code></pre></div></div>

<p>The request travels down the tunnel and lands on the dashboard running remotely. You get the full Kanban view — boards, channels, gateway status, sessions — exactly as if it were running locally.</p>

<h2 id="takeaway">Takeaway</h2>

<p>Keep the dashboard bound to <code class="language-plaintext highlighter-rouge">127.0.0.1</code> on the server and let SSH do the reaching. One <code class="language-plaintext highlighter-rouge">-L</code> flag, no open ports, no reverse proxy, no auth layer to bolt on. Close the SSH session when you’re done and the tunnel disappears with it.</p>]]></content><author><name>poornerd</name></author><category term="hermes-agent" /><category term="howto" /><summary type="html"><![CDATA[Most of my Hermes Agent work happens on a remote server, not my laptop. That’s where the agents run, where the gateway lives, and where things keep humming when my Mac is asleep. But the Hermes dashboard — the nice Kanban view of boards, channels, and sessions — wants to open in a browser. On a headless server, there is no browser.]]></summary></entry><entry><title type="html">Why I Switched From OpenClaw to Hermes Agent</title><link href="https://www.poornerd.com/2026/05/21/why-i-switched-from-openclaw-to-hermes-agent.html" rel="alternate" type="text/html" title="Why I Switched From OpenClaw to Hermes Agent" /><published>2026-05-21T20:00:00+00:00</published><updated>2026-05-21T20:00:00+00:00</updated><id>https://www.poornerd.com/2026/05/21/why-i-switched-from-openclaw-to-hermes-agent</id><content type="html" xml:base="https://www.poornerd.com/2026/05/21/why-i-switched-from-openclaw-to-hermes-agent.html"><![CDATA[<p>I liked the idea behind OpenClaw. I got tired of babysitting it.</p>

<p>Hermes Agent is what I switched to when I wanted the same general promise, but less setup drama and fewer updates that turned into an afternoon.</p>

<p><img src="/images/posts/why-i-switched-from-openclaw-to-hermes-agent.jpg" alt="Hermes Agent header" /></p>

<h2 id="setup-should-not-be-a-side-quest">Setup should not be a side quest</h2>

<p>This was the first difference. Hermes was easier to get running and easier to keep running.</p>

<p>That sounds like a small thing until you use these tools every day. Then it is the thing.</p>

<h2 id="hermes-feels-agent-first">Hermes feels agent-first</h2>

<p>A lot of tools bolt agent behavior onto a chat wrapper. Hermes feels built around the agent loop itself: tools, memory, skills, cron jobs, delegation, real workflows.</p>

<p>That matters. I do not want a demo. I want a system.</p>

<h2 id="the-learning-loop-is-the-real-feature">The learning loop is the real feature</h2>

<p>The killer feature for me is that Hermes can turn solved work into reusable skills.</p>

<p>Do something once, save the workflow, load it next time. The system gets better because I used it, not because I waited for a product update and hoped nothing else broke. Strange concept, I know.</p>

<h2 id="memory-that-does-useful-work">Memory that does useful work</h2>

<p>Another thing Hermes gets right is memory.</p>

<p>It automatically keeps track of useful context, organizes it, and makes it available later when I need it. That sounds obvious until you use tools that forget everything the moment the session ends.</p>

<p>It means less re-explaining, less copy-pasting, and a better shot at building on previous work instead of recreating it from scratch.</p>

<h2 id="stability-wins">Stability wins</h2>

<p>OpenClaw updates kept breaking enough things that I stopped trusting the ground under my feet.</p>

<p>Hermes has felt more stable, more composable, and generally better thought through from a security and operations point of view. Not perfect. Just less brittle, which is the adult version of exciting.</p>

<h2 id="why-i-stayed">Why I stayed</h2>

<p>I switched for easier setup.</p>

<p>I stayed because Hermes helps me build a repeatable system around AI work instead of starting fresh every session.</p>

<p>That is the difference between a fun tool and infrastructure.</p>

<p><strong>Links:</strong></p>
<ul>
  <li>Hermes Agent: <a href="https://hermes-agent.nousresearch.com/">hermes-agent.nousresearch.com</a></li>
</ul>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><category term="howto" /><category term="hermes-agent" /><summary type="html"><![CDATA[I liked the idea behind OpenClaw. I got tired of babysitting it.]]></summary></entry><entry><title type="html">Can You Actually See What Your AI Coding Agents Are Doing?</title><link href="https://www.poornerd.com/2026/05/21/can-you-actually-see-what-your-ai-coding-agents-are-doing.html" rel="alternate" type="text/html" title="Can You Actually See What Your AI Coding Agents Are Doing?" /><published>2026-05-21T12:00:00+00:00</published><updated>2026-05-21T12:00:00+00:00</updated><id>https://www.poornerd.com/2026/05/21/can-you-actually-see-what-your-ai-coding-agents-are-doing</id><content type="html" xml:base="https://www.poornerd.com/2026/05/21/can-you-actually-see-what-your-ai-coding-agents-are-doing.html"><![CDATA[<p>If you’ve ever had three Claude Code sessions open across three projects and lost track of which one was about to hit a context limit, this post is for you. The tool is called <strong>abtop</strong>, and it’s exactly what the name suggests — <code class="language-plaintext highlighter-rouge">htop</code>, but for AI coding agents.</p>

<h2 id="what-it-shows">What it shows</h2>

<p>One terminal window, every running agent at a glance. For each Claude Code and Codex CLI session it tracks:</p>

<ol>
  <li><strong>Tokens used and context window fill %</strong> — per-session bars with warnings when one is getting close to full. This is the headline feature for me.</li>
  <li><strong>Rate limit status</strong> — your quota in real-time, so you stop guessing whether you’ve been throttled.</li>
  <li><strong>Child processes and open ports</strong> — if an agent spawned a dev server and forgot to kill it, you’ll see the orphan port and can clean it up with one key.</li>
</ol>

<p>It also catches subagents, git status, and the current task each session is on. All read-only — no API keys, no auth.</p>

<h2 id="why-this-matters">Why this matters</h2>

<p>The dumb thing about running multiple agents is that you can’t actually see them. Each one lives in its own terminal pane, you have to switch around to check status, and there’s no system-level view of who’s burning tokens or about to compact. abtop is the missing system view. One screen, every session, live.</p>

<h2 id="installing-it">Installing it</h2>

<p>macOS or Linux, one line:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl --proto '=https' --tlsv1.2 -LsSf https://github.com/graykode/abtop/releases/latest/download/abtop-installer.sh | sh
</code></pre></div></div>

<p>Or via Cargo if you have Rust set up:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo install abtop
</code></pre></div></div>

<p>Then just run <code class="language-plaintext highlighter-rouge">abtop</code>. Recommended terminal size is 120x40 or larger.</p>

<p>Windows users — it needs Unix tools (<code class="language-plaintext highlighter-rouge">ps</code>, <code class="language-plaintext highlighter-rouge">lsof</code>), so run it inside WSL.</p>

<h2 id="bonus-tmux-integration">Bonus: tmux integration</h2>

<p>If you already work in tmux, this gets better. abtop discovers sessions across panes, and pressing <code class="language-plaintext highlighter-rouge">Enter</code> on a row jumps you straight to the terminal running that agent. So the flow is: scan abtop → see the one at 87% context → Enter → you’re there, ready to wrap up or compact.</p>

<p>That’s it. Tiny tool, dead-simple install, and once it’s in your stack you’ll wonder how you managed running parallel agents without it.</p>

<p><strong>Links:</strong></p>
<ul>
  <li>Repo: <a href="https://github.com/graykode/abtop">github.com/graykode/abtop</a></li>
</ul>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="claude-code" /><category term="agents" /><category term="developer" /><category term="howto" /><summary type="html"><![CDATA[If you’ve ever had three Claude Code sessions open across three projects and lost track of which one was about to hit a context limit, this post is for you. The tool is called abtop, and it’s exactly what the name suggests — htop, but for AI coding agents.]]></summary></entry><entry><title type="html">Conductor: How I Run a Whole Team of Claude Agents From One Mac</title><link href="https://www.poornerd.com/2026/05/19/conductor-how-i-run-whole-team-claude-agents-from-one-mac.html" rel="alternate" type="text/html" title="Conductor: How I Run a Whole Team of Claude Agents From One Mac" /><published>2026-05-19T09:00:00+00:00</published><updated>2026-05-19T09:00:00+00:00</updated><id>https://www.poornerd.com/2026/05/19/conductor-how-i-run-whole-team-claude-agents-from-one-mac</id><content type="html" xml:base="https://www.poornerd.com/2026/05/19/conductor-how-i-run-whole-team-claude-agents-from-one-mac.html"><![CDATA[<p>The bottleneck moved. A single Claude Code agent is already faster than me. The limit is how many I can supervise without losing the plot. For a long time that was one.</p>

<p>With <a href="https://www.conductor.build">Conductor</a>, it’s closer to ten.</p>

<h2 id="what-it-is">What it is</h2>

<p>A Mac app that runs Claude Code agents in parallel. Not a new model, not a new IDE — a harness. It reuses my existing <code class="language-plaintext highlighter-rouge">~/.claude</code> setup verbatim: skills, hooks, MCP servers, RTK, CLAUDE.md. No re-onboarding.</p>

<h2 id="one-overview-many-agents-many-projects">One overview, many agents, many projects</h2>

<p>Every workspace across every project, one window, status at a glance.</p>

<p><img src="/images/posts/conductor-how-i-run-whole-team-claude-agents-from-one-mac.jpg" alt="Conductor workspaces overview" /></p>

<p>Right now: one agent writing this post, one on a Jekyll deploy script, one on a flaky test in another repo, one drafting a PR. Three projects. I know who’s blocked on me without clicking in.</p>

<h2 id="the-loop-plan--implement--review--pr--merge--archive">The loop: plan → implement → review → PR → merge → archive</h2>

<p>Every task moves through the same six steps. My job is two of them — approving the plan, reviewing the diff. The rest is the agent.</p>

<h2 id="worktrees--setuprun-scripts">Worktrees + setup/run scripts</h2>

<p>Every task gets its own git worktree. Own branch, own checkout. Three versions of the same site can run side by side with no collisions. Each workspace runs my project’s setup script (deps, env) and run script (dev server) on demand. Testing a branch is one click.</p>

<h2 id="staying-out-of-the-ide">Staying out of the IDE</h2>

<p>The unexpected win. When the editor is in front of me I twitch — rename things, fix a semicolon, lose an hour. Conductor keeps me one step back, on intent and review.</p>

<p>When I actually need code, the diff viewer is right there. “Open in IDE” and “open terminal in this worktree” are one click each. No access lost — just no default pull.</p>

<h2 id="github-issues-in-prs-out">GitHub issues in, PRs out</h2>

<p>Point Conductor at an issue. It opens a workspace with that context, plans, executes, opens a linked PR. Backlog turns into throughput.</p>

<h2 id="so-is-it-smarter-than-claude">So is it smarter than Claude?</h2>

<p>No. That’s the point. Conductor isn’t a better model — it’s the safety rails (worktrees, PRs, structured review, archive) that let me run a lot of Claude at once without it turning into chaos.</p>

<p>If your bottleneck moved from “the AI is too slow” to “I can only supervise one of these at a time,” try it.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><category term="claude-code" /><category term="developer" /><category term="howto" /><summary type="html"><![CDATA[The bottleneck moved. A single Claude Code agent is already faster than me. The limit is how many I can supervise without losing the plot. For a long time that was one.]]></summary></entry></feed>