<?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-09-01T23:42:07+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">Six Months In: Where the 2026 CTO Bets Actually Stand</title><link href="https://www.poornerd.com/2026/09/01/mid-year-check-on-the-2026-cto-bets.html" rel="alternate" type="text/html" title="Six Months In: Where the 2026 CTO Bets Actually Stand" /><published>2026-09-01T20:00:00+00:00</published><updated>2026-09-01T20:00:00+00:00</updated><id>https://www.poornerd.com/2026/09/01/mid-year-check-on-the-2026-cto-bets</id><content type="html" xml:base="https://www.poornerd.com/2026/09/01/mid-year-check-on-the-2026-cto-bets.html"><![CDATA[<p>In <a href="https://www.poornerd.com/2025/12/30/a-ctos-perspective-on-ai-in-2026.html">December I wrote down three predictions for 2026</a>: multi-agent orchestration, enabling AI company-wide, and everyone building their own agents. Half the year in, here’s where each one actually stands.</p>

<h2 id="orchestration-is-exhausting">Orchestration is exhausting</h2>

<p>The shift from single agents to multiple agents working in parallel is already here. Many engineers have figured it out and they’re more productive because of it.</p>

<p>What you only discover later is the fatigue. A senior engineer last week described it as “running four projects at the same time and context-switching between them every minute.” That’s not a tooling problem. It’s what coordinating parallel agents actually feels like in practice.</p>

<p>The next thing we’ll learn, I think, is how to tell an agent when it’s <em>done</em>. Not just “the tests pass” but “the work matches what was asked, the verification ran, the guardrails held, stop.” Right now most of us are doing that verification ourselves. The whole process becomes less stressful when the agent learns to continue until it can verify and close it too.</p>

<h2 id="enable-the-company-or-lose-the-people">Enable the company, or lose the people</h2>

<p>This is the one I was too optimistic on. I expected the bottleneck to be vendor selection. It’s not. The bottleneck is that most enterprise AI tools aren’t enterprise ready, and the gap between what your people can do with AI at home and what IT will let them do at work is widening every week.</p>

<p>That’s the talent risk nobody’s putting on slides. The best people aren’t waiting for IT to catch up. They’re updating their CVs and asking whether the next company has figured this out yet. The window for closing the gap safely is shorter than most CTOs think.</p>

<h2 id="what-is-different-in-six-months">What is different in six months</h2>

<p>The AI technology moved as fast as I expected but the enterprise tooling and the organisation moved slower, and the gap between the two is where the actual challenge is now. None of the three bets are wrong. They’re just harder, slower, and more dependent on each other than the December version of me was willing to admit.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><category term="cto" /><summary type="html"><![CDATA[In December I wrote down three predictions for 2026: multi-agent orchestration, enabling AI company-wide, and everyone building their own agents. Half the year in, here’s where each one actually stands.]]></summary></entry><entry><title type="html">Stop trying to build the perfect AI harness</title><link href="https://www.poornerd.com/2026/08/31/stop-optimising-your-ai-harness.html" rel="alternate" type="text/html" title="Stop trying to build the perfect AI harness" /><published>2026-08-31T23:00:00+00:00</published><updated>2026-08-31T23:00:00+00:00</updated><id>https://www.poornerd.com/2026/08/31/stop-optimising-your-ai-harness</id><content type="html" xml:base="https://www.poornerd.com/2026/08/31/stop-optimising-your-ai-harness.html"><![CDATA[<p>Six months ago, every thread in every AI channel or feed was about the perfect <code class="language-plaintext highlighter-rouge">CLAUDE.md</code>. Then it was <code class="language-plaintext highlighter-rouge">AGENTS.md</code>. Then it was the prompt library, the skill catalogue, the custom MCP server, the bespoke agent loop. We treated the model like a junior developer that needed detailed instructions taped to every wall.</p>

<p>I’m starting to think we got it backwards.</p>

<h2 id="the-customisation-is-encoding-yesterdays-model">The customisation is encoding yesterday’s model</h2>

<p>Every skill, every prompt, every workaround, every behavioural rule encodes an assumption about a limitation in a specific generation of AI. We find a weakness, build a process around it, and congratulate ourselves for making the agent more reliable. Then six months later the model no longer has that weakness, and our harness is still forcing yesterday’s behaviour.</p>

<p>A concrete example from my own setup: I had a custom skill that told the model to “always run <code class="language-plaintext highlighter-rouge">npm test</code> before claiming a fix worked.” It was good advice for an older model that would confidently hallucinate green checkmarks. Newer models actually run the tests themselves, read the output, and fix the real failure. The skill now just slows things down, because every prompt pays for a rule the model has internalised.</p>

<h2 id="the-effort-does-not-seem-worth-it">The effort does not seem worth it</h2>

<p>The uncomfortable fact is that a lot of carefully crafted harnesses are turning into a new form of technical debt. Not because they’re wrong, but because the model improves faster than the configuration does.</p>

<p>Every layer of instruction the model has to read is tokens paid before it does any real work. A prompt that says “be careful, double-check, never do X” is teaching the model the wrong thing if X stopped being a problem six months ago — you’ve capped its ceiling. And you’re now maintaining a system whose capabilities double every few months, which no software framework has ever evolved at this pace.</p>

<p>CTOs and engineering leads have never had to manage assets that age out faster than they can be documented.</p>

<h2 id="treat-the-harness-like-code-not-furniture">Treat the harness like code, not furniture</h2>

<p>The shape of the fix is the same shape as the fix for any kind of accumulation:</p>

<ul>
  <li><strong>Delete by default.</strong> If a skill, prompt, or rule can’t justify itself in the current model, it goes. No “but it was useful in March.”</li>
  <li><strong>Retest on every model upgrade.</strong> When you swap models, you re-evaluate the whole configuration, not just the bits that broke.</li>
  <li><strong>Treat every customisation as having an expiration date.</strong> When you add something, write down what it’s compensating for. When the compensation is no longer needed, the customisation follows.</li>
</ul>

<p>The emerging question is whether the optimal harness is the most sophisticated one, or the thinnest one that still does the job.</p>

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

<p>The biggest risk in this space right now might be believing that your six-month-old AI setup is still helping. It might be the thing holding you back — a set of guardrails for a model that’s already grown past them, paid for in tokens and capped behaviour on every run.</p>

<p>The fix isn’t more cleverness in the harness. It’s treating customisations like code with an expiration date: delete what the model no longer needs, retest what you keep, and assume the rest will follow.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><category term="agent-skills" /><summary type="html"><![CDATA[Six months ago, every thread in every AI channel or feed was about the perfect CLAUDE.md. Then it was AGENTS.md. Then it was the prompt library, the skill catalogue, the custom MCP server, the bespoke agent loop. We treated the model like a junior developer that needed detailed instructions taped to every wall.]]></summary></entry><entry><title type="html">1,227 Requests in 191 Days: What Lighthouse’s Agentic Score Actually Measures</title><link href="https://www.poornerd.com/2026/08/23/1227-requests-191-days-lighthouse-agentic-score.html" rel="alternate" type="text/html" title="1,227 Requests in 191 Days: What Lighthouse’s Agentic Score Actually Measures" /><published>2026-08-23T01:00:00+00:00</published><updated>2026-08-23T01:00:00+00:00</updated><id>https://www.poornerd.com/2026/08/23/1227-requests-191-days-lighthouse-agentic-score</id><content type="html" xml:base="https://www.poornerd.com/2026/08/23/1227-requests-191-days-lighthouse-agentic-score.html"><![CDATA[<p>Chrome shipped a Lighthouse audit in May that checks whether you publish an <code class="language-plaintext highlighter-rouge">llms.txt</code>. Google Search’s own <a href="https://developers.google.com/search/docs/appearance/ai-features">AI features guide</a> tells you Search ignores it. Both pages are still up.</p>

<p>That’s the post. The rest is how I got there.</p>

<h2 id="what-landed">What landed</h2>

<p>Chrome 13.2 put the code in on 1 May. 13.3 turned it on by default six days later. PageSpeed Insights now shows a fifth card next to Performance, Accessibility, Best Practices and SEO. Six audits in the new category: WebMCP tool registration, agent-centric accessibility (axe rules in a fresh hat), CLS, and <code class="language-plaintext highlighter-rouge">llms.txt</code>. Two of those audits — CLS and the accessibility rules — already existed under different names. WebMCP and <code class="language-plaintext highlighter-rouge">llms.txt</code> are new.</p>

<p>The scoring is a fraction, not a 0–100 number. You see something like <code class="language-plaintext highlighter-rouge">3/4</code>, which looks meaningful until you read the code that produces it.</p>

<h2 id="how-the-score-is-actually-built">How the score is actually built</h2>

<p><code class="language-plaintext highlighter-rouge">calculateCategoryFraction()</code> skips anything marked <code class="language-plaintext highlighter-rouge">notApplicable</code>, <code class="language-plaintext highlighter-rouge">manual</code> or <code class="language-plaintext highlighter-rouge">informative</code> before it counts. Not enrolled in the WebMCP origin trial? Those three audits get skipped, and you’re scored out of 3, not out of 6. The number changes based on which experimental flags are flipped on. That’s not a property of the category — it’s a property of how the audit was bolted together.</p>

<p>Then there’s the <code class="language-plaintext highlighter-rouge">llms.txt</code> audit itself. No file at <code class="language-plaintext highlighter-rouge">/llms.txt</code> returns 404, which Lighthouse marks as Not Applicable, and your score doesn’t move. A malformed file fails. <a href="https://github.com/GoogleChrome/lighthouse/blob/main/core/audits/agentic/llms-txt.js"><code class="language-plaintext highlighter-rouge">llms-txt.js</code></a> wants an H1, at least one real Markdown link, and 50 characters of content. A tidy plain-text list of URLs fails. Twenty guides out there explain how to hit 3/3. The denominator isn’t 3, and anyone who wrote those guides didn’t open the source.</p>

<h2 id="what-crawlers-are-actually-doing">What crawlers are actually doing</h2>

<p>Agents are showing up. <a href="https://www.humansecurity.com/">HUMAN</a> put browser-based agents at roughly 71% of agent traffic in April, with SaaS growing 41.5% month over month. Real number, real direction.</p>

<p>Not for this stuff. <a href="https://seekio.com/">Seekio</a> watched ~900 domains for 191 days and logged 1,227 requests for <code class="language-plaintext highlighter-rouge">llms.txt</code> against 44,996,657 AI-bot requests in the same window. None came from GPTBot, ClaudeBot, PerplexityBot or Google-Extended. <a href="https://ahrefs.com/">Ahrefs</a> found 97% of <code class="language-plaintext highlighter-rouge">llms.txt</code> files across 137,210 domains were never fetched at all. <a href="https://seranking.com/">SE Ranking</a> modeled 300,000 domains and their AI-citation predictions got better when they deleted the feature.</p>

<p>WebMCP is starker. A scan of 111,076 of the top 200,000 sites found zero implementations. No mainstream agent calls <code class="language-plaintext highlighter-rouge">modelContext</code> yet. They all still parse the DOM.</p>

<p>Two of the new audits (CLS, accessibility) are things you should be doing anyway. The other two — WebMCP and <code class="language-plaintext highlighter-rouge">llms.txt</code> — audit for the file nobody requests and the API nobody calls.</p>

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

<p>This can flip. Google says Gemini in Chrome will be the first consumer of WebMCP tools, and the origin trial runs through Chrome 156, so registering tools today is a cheap forward bet. Buys you nothing now, isn’t stupid.</p>

<p>The category is experimental, doesn’t touch your other scores, isn’t a ranking factor. <a href="https://github.com/GoogleChrome/lighthouse/issues/17082">Issue #17082</a> has PageSpeed Insights running Chrome 146 against docs that require 150, open since June. Take the number accordingly.</p>

<h2 id="what-to-do">What to do</h2>

<p>Fix the CLS. Fix the accessibility tree. If you publish an <code class="language-plaintext highlighter-rouge">llms.txt</code>, write it with real Markdown links — because a broken one costs you a point an absent one doesn’t. Then move on to the parts of your site that real agents are actually hitting today.</p>

<hr />

<p><strong>Links:</strong></p>

<ul>
  <li><a href="https://github.com/GoogleChrome/lighthouse/tree/main/core/audits/agentic">Lighthouse agentic browsing audits (source)</a></li>
  <li><a href="https://github.com/GoogleChrome/lighthouse/blob/main/core/audits/agentic/llms-txt.js"><code class="language-plaintext highlighter-rouge">core/audits/agentic/llms-txt.js</code></a></li>
  <li><a href="https://developers.google.com/search/docs/appearance/ai-features">Google’s AI features guide</a></li>
  <li><a href="https://seekio.com/">AI bots ignore llms.txt — Seekio</a></li>
  <li><a href="https://ahrefs.com/">llms.txt study — Ahrefs</a></li>
  <li><a href="https://seranking.com/">llms.txt and AI citations — SE Ranking</a></li>
  <li><a href="https://www.freecodecamp.org/">A Developer’s Guide to WebMCP — freeCodeCamp</a></li>
  <li><a href="https://www.humansecurity.com/">State of Agentic Traffic, April 2026 — HUMAN</a></li>
  <li><a href="https://www.debugbear.com/">Google Lighthouse Has A New Agentic Browsing Category — DebugBear</a></li>
</ul>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><category term="seo" /><summary type="html"><![CDATA[Chrome shipped a Lighthouse audit in May that checks whether you publish an llms.txt. Google Search’s own AI features guide tells you Search ignores it. Both pages are still up.]]></summary></entry><entry><title type="html">TIME Built an AI-Only Website. My Blog Already Had One — Minus the Ads</title><link href="https://www.poornerd.com/2026/08/06/time-built-ai-only-website-my-blog-already-had-one.html" rel="alternate" type="text/html" title="TIME Built an AI-Only Website. My Blog Already Had One — Minus the Ads" /><published>2026-08-06T21:30:00+00:00</published><updated>2026-08-06T21:30:00+00:00</updated><id>https://www.poornerd.com/2026/08/06/time-built-ai-only-website-my-blog-already-had-one</id><content type="html" xml:base="https://www.poornerd.com/2026/08/06/time-built-ai-only-website-my-blog-already-had-one.html"><![CDATA[<p>Vincent Schmalbach published a great piece of detective work yesterday: <a href="https://www.vincentschmalbach.com/time-serves-ai-bots-a-different-website/">TIME Is Serving AI Bots a Different Website, With Ads Built In</a>. Fetch a TIME article as Chrome and you get 303 KB of HTML. Fetch it as ClaudeBot or PerplexityBot and you get 13 KB of clean markdown at the same URL — with sponsored FAQ blocks inside that no human reader will ever see, and a fresh ad-impression UUID on every request. TIME says bot traffic already outnumbers human traffic on most days.</p>

<p>My first reaction wasn’t shock. It was: <em>hang on, I already built most of that.</em></p>

<h2 id="the-non-evil-version-was-already-running-here">The non-evil version was already running here</h2>

<p>Every post here has a raw-markdown twin published next to the HTML — the header image of this post is the twin of <a href="/2026/07/15/wasm-not-docker.html">my wasm-not-docker post</a>, exactly as ClaudeBot receives it. Add <code class="language-plaintext highlighter-rouge">llms.txt</code>, a <code class="language-plaintext highlighter-rouge">&lt;link rel="alternate" type="text/markdown"&gt;</code> on every post, and a <code class="language-plaintext highlighter-rouge">robots.txt</code> that welcomes the AI crawlers instead of fighting them. A Jekyll plugin generates it all at build time.</p>

<p>But TIME’s experiment exposed my one gap: <strong>discovery</strong>. My twins lived at <code class="language-plaintext highlighter-rouge">.md</code> URLs a bot had to know about. Most crawlers just fetch the canonical URL and take what comes back. The negotiation has to happen at the canonical URL. Fifteen lines of Apache fix it:</p>

<div class="language-apache highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">RewriteCond</span> %{HTTP_USER_AGENT} (GPTBot|OAI-SearchBot|ClaudeBot|PerplexityBot|CCBot|Amazonbot|...) [NC]
<span class="nc">RewriteCond</span> %{DOCUMENT_ROOT}/$1.md -f
<span class="nc">RewriteRule</span> ^(.+)\.html$ /$1.md [L]
</code></pre></div></div>

<p>Known AI crawlers now get the twin at the article’s normal URL, as <code class="language-plaintext highlighter-rouge">text/markdown</code>, at about a fifth of the size. Humans, Googlebot, and Bingbot get the identical HTML they always got — same split TIME uses, so it’s not cloaking — plus a <code class="language-plaintext highlighter-rouge">Link: rel="alternate"</code> header, with <code class="language-plaintext highlighter-rouge">Vary: User-Agent</code> so caches key correctly.</p>

<h2 id="why-bother">Why bother?</h2>

<p>Because AI answers are becoming a distribution channel, and this is how you optimize for it. A model reading my page now spends its context window on my actual words instead of burning four-fifths of it on navigation and markup — which means more of the article survives into whatever ChatGPT, Claude, or Perplexity tells the person who asked. Cleaner input, better odds of being quoted correctly and cited as the source. That’s SEO for the agent era, and it costs me a fifth of the bandwidth per crawl.</p>

<h2 id="what-im-not-copying">What I’m not copying</h2>

<p>The ad half. <code class="language-plaintext highlighter-rouge">no-store</code> plus per-request impression UUIDs exists only to make every bot fetch a billable event, and sponsor copy phrased as chatbot answers — injected where no human editor will ever see it — is a trust problem wearing a disclosure label. TIME is probably right that when most of your readers are models, someone will sell that audience. I’d rather my machine readers get the same deal my human readers get: the actual content, nothing else.</p>

<p>The forked web is coming either way. The only decision left is whether the version you serve the machines is your content — or somebody’s ad inventory.</p>]]></content><author><name>poornerd</name></author><category term="ai" /><category term="agents" /><summary type="html"><![CDATA[Vincent Schmalbach published a great piece of detective work yesterday: TIME Is Serving AI Bots a Different Website, With Ads Built In. Fetch a TIME article as Chrome and you get 303 KB of HTML. Fetch it as ClaudeBot or PerplexityBot and you get 13 KB of clean markdown at the same URL — with sponsored FAQ blocks inside that no human reader will ever see, and a fresh ad-impression UUID on every request. TIME says bot traffic already outnumbers human traffic on most days.]]></summary></entry><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></feed>