<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Vasko Ckorovski — Lab Notes</title><description>Experiments in progress. Things I’ve tried, what worked, what broke, what surprised me — the work while it’s still rough.</description><link>https://vasko.com.au/</link><language>en-AU</language><item><title>Nothing Is Ever As It Seems</title><link>https://vasko.com.au/lab-notes/nothing-is-ever-as-it-seems/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/nothing-is-ever-as-it-seems/</guid><description>A fortnight where my own agent tooling ate the sprint it was built to protect, and what the wreckage taught me about verification.</description><pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I said the same sentence out loud twice in a fortnight, a week apart, both times to a terminal: nothing is ever as it seems.&lt;/p&gt;
&lt;p&gt;The context was a stack I’d spent months building. Claude Code writes everything, Linear carries the specs and the decisions, and I direct and verify. It works. That’s the awkward part of this note. The stack was not broken, it was slowing me down, and those are different problems with different fixes.&lt;/p&gt;
&lt;p&gt;The sprint I was hardening for slipped twice. First from a Monday, then from a Wednesday, then to a Saturday. Every slip had the same shape: I’d sit down to do product work, and the tooling would raise its hand.&lt;/p&gt;
&lt;h2 id=&quot;the-parade&quot;&gt;The parade&lt;/h2&gt;
&lt;p&gt;Here’s what actually happened, in the order it bit.&lt;/p&gt;
&lt;p&gt;A skill file carries a sync marker inside itself saying whether it’s been uploaded. Which means a freshly uploaded file still says it hasn’t been, because the marker was written before it travelled. I chased that ghost five separate times in one day before checksumming the live copies and finding every one already current. Like a letter with a sticker reading “not yet posted”, printed before it went in the post.&lt;/p&gt;
&lt;p&gt;My fleet doctrine said a newly merged hook doesn’t take effect until a new session starts. A session measured it: the hook fired on the very next command. Better, the branch that wrote the correction was blocked by the hook it was documenting, then sailed through on the verbatim retry. The guard was enforcing in the session writing the sentence claiming it couldn’t be.&lt;/p&gt;
&lt;p&gt;A script lost its executable bit on the way through a merge. The capture alarm that depends on it sat silently dead for two days. Nothing errored, because nothing called it. It was found by a new guard on its first ever run, which then tripped over the same file while claiming its own day ticket.&lt;/p&gt;
&lt;p&gt;A session opened a merge sitting confident six pull requests were waiting. Five had already been merged by an earlier sitting. It only found out because it checked merge-base against origin instead of believing what the tooling told it.&lt;/p&gt;
&lt;p&gt;My favourite: a test asserted a fix was present by grepping the source for a token. The token was there. The regression was also there, in the very code the test guarded. A check that reads as rigorous and cannot fail is worse than no check, because you stop looking.&lt;/p&gt;
&lt;p&gt;Then there was the security fix that broke thirteen workflows. I blocked environment variable access inside a workflow runtime, verified the flag did exactly what it said, and never asked what depended on it. It failed closed, which is the good kind of failure, and it stayed invisible for hours because the affected path hadn’t been called yet. No errors is not the same as no breakage.&lt;/p&gt;
&lt;p&gt;Every one of those is a verification failure. Not one is a capability failure. The agent could do the work. What it couldn’t do, until I built it, was tell me when something it had already reported as fine had quietly stopped being fine.&lt;/p&gt;
&lt;h2 id=&quot;the-number-that-paid-for-the-fortnight&quot;&gt;The number that paid for the fortnight&lt;/h2&gt;
&lt;p&gt;Four artefacts got built the same week, by the same model, under the same doctrine, with the same automated review loop running to the same stop condition. The review rounds each needed:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Artefact&lt;/th&gt;&lt;th&gt;Rounds&lt;/th&gt;&lt;th&gt;Findings caused by its own previous fixes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Tier gate (script + contract test)&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;44%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Session-close capture legs (400 lines of prose)&lt;/td&gt;&lt;td&gt;13&lt;/td&gt;&lt;td&gt;71%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Day ticket (script + contract test)&lt;/td&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;20%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Prune ledger (script, 274 assertions)&lt;/td&gt;&lt;td&gt;24&lt;/td&gt;&lt;td&gt;32%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The round count is not the signal. The self-inflicted share is. At 71%, seven findings in ten were defects the previous round’s fix had introduced, and every fix obliged edits at two or three other sites, because a prose rule’s invariant lives in four paragraphs that stay silent when they disagree. A script’s invariant lives in a test that goes red.&lt;/p&gt;
&lt;p&gt;So: executable contracts converge a review loop, prose compounds it. I was pleased with that, and then the fourth row spoiled it. The prune ledger has 274 assertions and still ran 24 rounds, because the thing separating it from the six-round leaf was input surface, not test coverage. A command-line tool whose state lives on a filesystem has an unbounded space of exotic-but-real conditions, and each round legitimately reached one layer further out. A contract test converges the loop over the logic. Nothing converges it over the inputs except deciding which ones a real caller can actually reach.&lt;/p&gt;
&lt;p&gt;The last leaf of that milestone met the bar in one round. Not because it was small, but because the first thing it did was add a single validation command to the store that owned the rules, then delegate to it instead of reimplementing the rules a fifth time. One round, against six to twenty-four for its siblings. That’s the whole finding in one data point.&lt;/p&gt;
&lt;h2 id=&quot;what-i-have-now&quot;&gt;What I have now&lt;/h2&gt;
&lt;p&gt;The mechanism those leaves add up to watches a working session, keeps the lessons worth keeping, drops the ones already handled, applies the rest in one coherent overnight pass, audits the next day whether each one actually landed, and posts a digest to my phone. Its first real run read nine captures, kept six, found three genuine duplicates, closed the day’s ticket, parked a pull request and sent one Telegram message naming it. That message is the whole point of a fortnight.&lt;/p&gt;
&lt;p&gt;Underneath it, the operating rules changed more than the code did. No gate may depend on a human noticing. Pull requests get an honest “bar not met” label with the structural fix ticketed, rather than a quiet retry until the numbers look nice. Overnight runs work from a decision ledger that pre-answers every ruling I can foresee, so the agent takes the reversible option and logs it rather than waiting eight hours for me to wake up. One of those runs went nineteen hours and stopped itself on its own clock with everything parked cleanly.&lt;/p&gt;
&lt;p&gt;And the reporting comes to me. I built a status dashboard in April and declared it dead in July, for the honest reason that a dashboard you have to visit is a dashboard you don’t visit. The replacement is a push message at a time I’m already looking at my phone.&lt;/p&gt;
&lt;p&gt;I’m writing this the morning the sprint finally starts, later than planned, on a stack that now tells me when it’s lying. The fortnight cost me the sprint’s original start date. It also bought back every future week where a marker, a doctrine line or a dead alarm would have quietly eaten a day, and I’d have blamed the model.&lt;/p&gt;
&lt;p&gt;I didn’t write a line of the code in any of this. I ruled on it. Turns out the ruling gets a lot easier when the evidence stops being fiction.&lt;/p&gt;</content:encoded><category>agentic-development</category><category>claude-code</category><category>verification</category><category>scar-tissue</category></item><item><title>The internet keeps naming my stack</title><link>https://vasko.com.au/lab-notes/convergence-receipts/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/convergence-receipts/</guid><description>Four teams shipped names for things already running in my practice, all within five weeks. I checked the dates, and the flattering version didn&apos;t survive.</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Between 12 June and 18 July this year, four different teams shipped names for things already running in my practice. Vercel called one &lt;strong&gt;eve&lt;/strong&gt;. Google called one the &lt;strong&gt;Open Knowledge Format&lt;/strong&gt;. LangChain called one &lt;strong&gt;openwiki&lt;/strong&gt;. The internet at large settled on &lt;strong&gt;“graph engineering”&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Shipped in autumn, named in winter. There’s a flattering version of this article, and I started writing it before I checked the dates.&lt;/p&gt;
&lt;p&gt;The dates did not co-operate.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;What got named&lt;/th&gt;&lt;th&gt;Theirs&lt;/th&gt;&lt;th&gt;Mine&lt;/th&gt;&lt;th&gt;Verdict&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;eve — agent as a directory of files&lt;/td&gt;&lt;td&gt;17 Jun&lt;/td&gt;&lt;td&gt;Sally on Hermes, 24 May (VAS-916/917)&lt;/td&gt;&lt;td&gt;Ahead by 24 days&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OKF — markdown concept graph&lt;/td&gt;&lt;td&gt;12 Jun&lt;/td&gt;&lt;td&gt;VAS-719, filed 5 May, still Queued&lt;/td&gt;&lt;td&gt;Beaten&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;openwiki — auto-maintained agent docs&lt;/td&gt;&lt;td&gt;1 Jul&lt;/td&gt;&lt;td&gt;Built the manual version in April. Deleted it in July.&lt;/td&gt;&lt;td&gt;Wrong first&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Graph engineering — stage contracts&lt;/td&gt;&lt;td&gt;18 Jul&lt;/td&gt;&lt;td&gt;FortyTwo gates, VAS-1226, 16 Jul&lt;/td&gt;&lt;td&gt;Neither of us was first&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;One win worth twenty-four days, one loss, one public failure, and one where the whole category turned out to be a joke. That’s the real scoreboard, and it’s a much more useful article than the one where I’m a genius.&lt;/p&gt;
&lt;p&gt;Here’s the takeaway stated plainly, because it’s the only part that transfers: &lt;strong&gt;arriving first is worth nothing. Arriving at the same time as everyone else is the signal.&lt;/strong&gt; Convergence doesn’t prove you were clever. It proves the constraint was real — and that is the only reliable way I know to tell a durable pattern from a fashionable one.&lt;/p&gt;
&lt;h2 id=&quot;the-twenty-four-days&quot;&gt;The twenty-four days&lt;/h2&gt;
&lt;p&gt;eve’s structure is an agent as a directory of files: &lt;code&gt;instructions.md&lt;/code&gt; for the always-on prompt, &lt;code&gt;skills/&lt;/code&gt; for procedures loaded on demand, &lt;code&gt;channels/&lt;/code&gt; for Slack and the rest, &lt;code&gt;schedules/&lt;/code&gt; for recurring jobs.&lt;/p&gt;
&lt;p&gt;Sally — my Telegram-resident agent — is a SOUL.md, a governed skill roster, a Telegram binding and five scheduled crons. VAS-916 and VAS-917 closed on 24 May. The digest formatter (VAS-652) was running from 4 May, and VAS-915 is an audit of the &lt;em&gt;previous&lt;/em&gt; Sally, which already had skills and crons before the Hermes migration started.&lt;/p&gt;
&lt;p&gt;So: twenty-four days. Not “months earlier”, which is what I’d written in my own capture ticket before I went and looked.&lt;/p&gt;
&lt;p&gt;Twenty-four days is not foresight. It’s two rooms hitting the same wall in the same month. And I didn’t get there by insight either — I got there because every alternative was worse. Configuration in a database you can’t diff. Prompts buried in a dashboard. Behaviour you can’t inspect at 11pm when it’s misbehaving. Files won because files are legible. Vercel’s engineers evidently lost the same arguments with reality that I did.&lt;/p&gt;
&lt;h2 id=&quot;the-one-i-lost&quot;&gt;The one I lost&lt;/h2&gt;
&lt;p&gt;VAS-719 has been sitting in my backlog since 5 May: turn the North Star document into a living spec — components, workflows, ADRs, all agent-legible. Google Cloud published OKF v0.1 on 12 June: markdown plus YAML frontmatter, one required field, concepts linked with ordinary markdown links so the directory reads as a graph.&lt;/p&gt;
&lt;p&gt;That is my ticket. They shipped it in thirty-eight days. Mine has been open for eighty-eight and is still marked Queued.&lt;/p&gt;
&lt;p&gt;Then they shipped v0.2 on 25 July, adding provenance and attestation — which tells you how fast “agents writing knowledge for other agents” went from a neat idea to a problem that needs signatures.&lt;/p&gt;
&lt;p&gt;I’d like to claim this one as a convergence. It isn’t. It’s a demonstration that the distance between having the idea and having the artefact is the entire job, and that a backlog is where good ideas go to be right in private.&lt;/p&gt;
&lt;h2 id=&quot;the-one-where-i-was-wrong-first&quot;&gt;The one where I was wrong first&lt;/h2&gt;
&lt;p&gt;This is the beat I’d have cut from the flattering version.&lt;/p&gt;
&lt;p&gt;openwiki is a CLI that writes and maintains agent-facing documentation for a codebase, refreshes it as the code changes, and ships a GitHub action that opens a pull request when the docs fall behind.&lt;/p&gt;
&lt;p&gt;I built that. Badly. In April, the &lt;a href=&quot;https://vasko.com.au/lab-notes/dashboard-teardown&quot;&gt;cross-project dashboard I later tore down&lt;/a&gt; rested on exactly this thesis: every repo carries a &lt;code&gt;/docs/dashboard.md&lt;/code&gt;, the tool reads it, the cards stay meaningful. On day one in production, four of my five project cards read &lt;em&gt;not yet documented&lt;/em&gt; — the thing faithfully reported the flaw in its own design before I’d finished admiring it. Manual capture was load-bearing, and I was never going to do manual capture.&lt;/p&gt;
&lt;p&gt;Anyone who has watched an agent re-derive the same Linear team UUID across five separate sessions knew this tool was coming; that rediscovery tax is precisely why my &lt;code&gt;linear-ops&lt;/code&gt; skill exists. The only real question was who would ship it first.&lt;/p&gt;
&lt;p&gt;Same thesis, same month, near enough. The difference is that LangChain put the maintenance on the agent and the pull request on a CI action, and I put it on me. I ran the experiment and found the failure mode; they designed it out. Being early to a good idea and wrong about the hard part isn’t a receipt. It’s just being wrong with better timing.&lt;/p&gt;
&lt;h2 id=&quot;the-one-that-was-a-joke&quot;&gt;The one that was a joke&lt;/h2&gt;
&lt;p&gt;On 18 July, Peter Steinberger posted a short question asking whether we were still talking about loops or had shifted to graphs yet. Two point nine million views later, “graph engineering” was a discipline.&lt;/p&gt;
&lt;p&gt;It was a gag. So was Hamel Husain’s “loop engineering is dead” pile-on that followed it. Both were mocking the renaming treadmill — this industry’s habit of rebadging the same practice every few months. Prompt engineering, context engineering, harness engineering, loop engineering, graph engineering: five names, one job, about eighteen months.&lt;/p&gt;
&lt;p&gt;The discourse reified the satire into a movement within days. Nobody shipped anything alongside the post. LangGraph, Google’s ADK and Microsoft’s Agent Framework had been wiring nodes, edges and shared state for two years already. The earliest serious use of the phrase I can find is a quiet Josh Simmons post on 4 July that nobody amplified. Within a fortnight the term had at least three incompatible definitions in circulation and a folk recipe — parallel subagents plus a checker, “graph-max” — attached to it.&lt;/p&gt;
&lt;p&gt;I’d love to claim a dead heat here. I can’t, and neither can Steinberger. The shape is older than both of us; only the label is new, and the label was a punchline.&lt;/p&gt;
&lt;p&gt;The durable idea inside the noise is narrower than the hashtag: build the &lt;em&gt;contract&lt;/em&gt; before the work — what each stage must produce, and what has to validate before anything advances. That’s VAS-1226, closed 16 July: &lt;code&gt;/42&lt;/code&gt; refuses to bless an advance past a stage missing its artefact. It exists because I’d been burned by the opposite — a plan reviewed eight times, a green test suite, and an acceptance criterion that shipped unbuilt because nobody checked the deliverable against the checklist.&lt;/p&gt;
&lt;p&gt;And one correction to my own instincts, courtesy of the sharpest critique in the pile: DAG schedulers are a decade old, and agents checking agents can produce organised nonsense at scale. A checker with fresh context is &lt;em&gt;necessary but not sufficient&lt;/em&gt;, because a fresh-context agent is still inside the system. The evidence has to come from outside it — real tests, real money, real humans. What makes my gates worth anything isn’t that the checker has clean context; it’s that the gate is a literal label in Linear and the pass condition is provable from a transcript. External artefacts, not agent opinion.&lt;/p&gt;
&lt;p&gt;There’s a footnote I can’t resist. Steinberger also built OpenClaw — the exact agent runtime I ran, cost-audited and migrated off months before he lit this particular fuse. Which brings me to the bill.&lt;/p&gt;
&lt;h2 id=&quot;the-bit-the-wave-still-gets-wrong&quot;&gt;The bit the wave still gets wrong&lt;/h2&gt;
&lt;p&gt;Every pattern above makes it cheaper to run &lt;em&gt;more&lt;/em&gt; agents: parallel workers, scheduled crons, wikis maintained overnight. Not one of them ships a spend ledger as the headline feature.&lt;/p&gt;
&lt;p&gt;My receipt: OpenClaw’s idle heartbeat was re-warming roughly 270,000 tokens of cached context every half hour — about &lt;strong&gt;eight dollars a day for an agent doing nothing at all&lt;/strong&gt; (VAS-870, 18 May). A week later, VAS-974: a runaway agent loop, with prompt caching silently broken underneath it, &lt;code&gt;cache_read_input_tokens&lt;/code&gt; sitting at zero while I paid full freight on every turn. I only caught either because every model call in this stack routes through a gateway with a spend ledger and a key per workload.&lt;/p&gt;
&lt;p&gt;Eight dollars a day is a rounding error, right up until you run ten agents in parallel because the new architecture made it easy.&lt;/p&gt;
&lt;p&gt;The arithmetic that matters isn’t wall-clock, it’s &lt;strong&gt;cost per successful completion&lt;/strong&gt;. Fan work out across stages and the win depends entirely on your subtask pass rate: around half, and parallelism wins comfortably. Down near a third, you burn the same number of cycles a loop would have at roughly triple the tokens per cycle — and you notice a month after the architecture decision that caused it, which is exactly too late to attribute. Run that sum against your own pass rate before you adopt anyone’s diagram.&lt;/p&gt;
&lt;p&gt;One hygiene note, since this wave also produced a fabricated multi-million-dollar research study that circulated freely for days: before repeating any number from a thread like this one, ask what specifically was measured, improved compared to what, and where the figure came from. Mine are above with ticket numbers attached. Hold everyone else’s to the same standard, this article included.&lt;/p&gt;
&lt;h2 id=&quot;the-honest-ending&quot;&gt;The honest ending&lt;/h2&gt;
&lt;p&gt;Four teams landed the same shapes inside five weeks. None of us invented anything. We all just met the same wall — context is finite, plain files survive, and a stage that can’t prove it produced something shouldn’t be allowed to continue.&lt;/p&gt;
&lt;p&gt;Adopt the shapes: files as the agent contract, markdown-with-frontmatter as the knowledge substrate, gates that check artefacts rather than vibes. Those converged independently, which is about as close to proof as this industry gets. Watch the brands: eve is a beta with moving APIs, OKF is at v0.2, and graph engineering was a joke that got its own courses and roadmaps. Adopting a shape costs nothing when the label churns. Adopting the label mostly buys you migration work.&lt;/p&gt;
&lt;p&gt;The constraint is the evidence — not the author, and definitely not whoever got there twenty-four days earlier.&lt;/p&gt;</content:encoded><category>agents</category><category>convergence</category><category>spec</category><category>delivery</category></item><item><title>The dashboard I tore down this week</title><link>https://vasko.com.au/lab-notes/dashboard-teardown/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/dashboard-teardown/</guid><description>A gstack sprint shipped it in April. Eighty-two days later I deleted it — and the teardown taught me more than the build.</description><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This week I sat through a blast-radius audit for a piece of software I built in April. Item by item: one Supabase project, one Vercel deployment, one Cloudflare DNS record, three API tokens, one repo. Each with an exact identifier, a list of everything that consumed it, and a SAFE or UNSAFE verdict before anything got deleted (VAS-1639, if you’re playing along at home).&lt;/p&gt;
&lt;p&gt;The Supabase project was 82 days old. Nothing else in my stack had ever spoken to it.&lt;/p&gt;
&lt;p&gt;That’s not a dependency graph. That’s a headstone.&lt;/p&gt;
&lt;h2 id=&quot;the-sprint-worked-which-is-the-annoying-part&quot;&gt;The sprint worked, which is the annoying part&lt;/h2&gt;
&lt;p&gt;This would be an easier story to tell if the build had gone badly. It didn’t.&lt;/p&gt;
&lt;p&gt;VAS-446 was my second gstack sprint — greenfield this time, a cross-project status dashboard at dashboard.vasko.com.au. The autoplan ran four review phases with two independent voices each: eight reviews, roughly 125 findings, synthesised into a 1,312-line PLAN.md. It absorbed four user challenges I’d signed off on, scheduled five architectural must-fixes for day one, and — my favourite part — corrected my wall-clock estimate from a fantasy 6–8 hours to an honest 14–16. It was right.&lt;/p&gt;
&lt;p&gt;The thing shipped. Sixteen seeded repos, eleven paused projects in a tidy table, magic-link auth, isolated API clients so a Linear outage couldn’t take down the GitHub data. To spec. The machinery is not the villain here.&lt;/p&gt;
&lt;h2 id=&quot;day-one-the-mirror&quot;&gt;Day one: the mirror&lt;/h2&gt;
&lt;p&gt;I opened the deployed dashboard and four of my five active project cards said the same thing: &lt;em&gt;not yet documented&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The design’s load-bearing primitive was a wiki convention — each repo carries a &lt;code&gt;/docs/dashboard.md&lt;/code&gt;, the dashboard extracts a “Current Focus” heading, and the cards stay meaningful as long as I keep those files current. The dashboard’s very first act in production was to faithfully report the fatal flaw in its own design. Manual capture was the foundation, and I was never going to do manual capture. My work flows through Linear, Claude Code and Cursor. None of it touches a markdown file on the way past.&lt;/p&gt;
&lt;p&gt;There was a second, quieter miss. PLAN.md line 128 — an acceptance criterion requiring a proof-of-concept wiki file for one repo — shipped unbuilt. Eight reviews had approved the plan. The test suite was green. Nobody checked the artefact against the checklist. I filed that one as VAS-449: the plan gets reviewed, the tests get run, and the deliverable falls through the gap between them.&lt;/p&gt;
&lt;h2 id=&quot;forty-eight-hours-later-the-product-was-a-chat-message&quot;&gt;Forty-eight hours later: the product was a chat message&lt;/h2&gt;
&lt;p&gt;Within two days I’d stopped tweaking the dashboard and started interrogating the job. What did I actually want when I sat down to triage? Not a wiki. A status report with a recommendation.&lt;/p&gt;
&lt;p&gt;So I ran an experiment. I asked Sally — my Telegram-resident agent, who already has the Linear MCP and repo access — for a status and priority report on three deliberately different projects: one mid-sprint, one pre-launch, one maintenance-mode content site with almost no ticket activity.&lt;/p&gt;
&lt;p&gt;All three reports were excellent. Confident on phase, specific on blockers, sensible on next actions, and each one shaped to its project — the content site got commit-cadence and conversion-funnel analysis where a tickets-and-PRs dashboard would have rendered an empty card.&lt;/p&gt;
&lt;p&gt;I turned those three outputs into the spec for a v2 (VAS-457): AI synthesis instead of wiki extraction, on-demand instead of cron, compressed cards on the front page. It was the best-evidenced ticket I wrote all quarter.&lt;/p&gt;
&lt;p&gt;I cancelled it a month later. No ceremony. I just noticed one day that it had been sitting in the queue behind everything else, and that I hadn’t visited the dashboard URL since May.&lt;/p&gt;
&lt;h2 id=&quot;why-it-died&quot;&gt;Why it died&lt;/h2&gt;
&lt;p&gt;Here’s the central lesson, stated plainly: &lt;strong&gt;I built a destination for answers I could already ask for.&lt;/strong&gt; A surface you work &lt;em&gt;in&lt;/em&gt; earns a daily slot. A surface that reports &lt;em&gt;about&lt;/em&gt; your work has to win that slot against a chat window that’s already open — and it loses, every time, because the chat window answers follow-up questions and the dashboard doesn’t.&lt;/p&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/dashboard-teardown-destination-vs-conversation.svg&quot; alt=&quot;Diagram: a &apos;You&apos; node with a dashed red arrow labelled &apos;0 visits&apos; to the dashboard (reports about work, torn down) and a solid green arrow labelled &apos;every day&apos; to Sally and UPS (where work happens); a purple arrow shows the spec migrating from the dashboard to Sally.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;/figure&gt;
&lt;p&gt;By July, my mornings belonged to UPS — the Linear-backed planner I’d built in the meantime — because I allocate my day in it. It’s a working surface. The dashboard only ever described work happening elsewhere, and describing is not a job that survives contact with an agent who can describe on demand.&lt;/p&gt;
&lt;p&gt;The capability didn’t die, though. This is the part worth keeping: the entire v2 spec — the card format, the link-every-claim discipline, the adaptive framing — ported straight into a Sally skill (VAS-1640). An app became a prompt. The build cost of the dashboard was a 16-hour sprint plus ten weeks of idle infrastructure; the build cost of the thing that replaced it is roughly a page of instructions.&lt;/p&gt;
&lt;h2 id=&quot;clean-your-house&quot;&gt;Clean your house&lt;/h2&gt;
&lt;p&gt;Now the uncomfortable bit, because agentic development has a side effect nobody puts in the demo videos.&lt;/p&gt;
&lt;p&gt;When shipping costs a weekend instead of a quarter, you ship more. That’s the point. But it also means you accumulate deployed, credentialed, half-alive projects at the same speed — every one carrying a Supabase project, a DNS record, live API tokens and a Vercel bill for the privilege of being ignored. AI-driven development doesn’t just produce features faster. It manufactures tech debt and defunct projects faster too, and the muscle most of us haven’t built is the one that admits it.&lt;/p&gt;
&lt;p&gt;So the teardown got the same rigour as a build, and I’d argue it deserves more:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be honest first.&lt;/strong&gt; “I don’t go to the URL” is a complete argument. I didn’t need a retro workshop; I needed to stop pretending a paused project was a resting one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sweep the learnings before you delete anything.&lt;/strong&gt; Four things walked out of the wreckage: the v2 spec as a Sally skill, the “what changed since Monday” delta idea as a query instead of a UI, the VAS-449 process gap for my agent tooling, and this article. If you tear down without the sweep, you paid full price for the lesson and left it on the table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit the blast radius, then gate every delete.&lt;/strong&gt; The classic trap in my stack is a shared credential — revoke the dashboard’s Linear key and discover it was also Sally’s. The audit proved each token was project-specific before anything got revoked, and every delete waited for a per-item go. Then the survivor checks: the three neighbouring production apps loaded, and Sally did a benign Linear read, before I called it done.&lt;/p&gt;
&lt;p&gt;That April decision to give the dashboard its own Supabase project instead of reusing an existing one — made for blast-radius reasons at build time — is exactly what made the teardown a clean afternoon instead of a forensic week. Decommissioning starts at architecture time. You’re just not invited to that meeting yet.&lt;/p&gt;
&lt;h2 id=&quot;the-bill&quot;&gt;The bill&lt;/h2&gt;
&lt;p&gt;One sprint, ten weeks of idle infra, one clean teardown, one skill, one process fix, one article. I’d pay it again.&lt;/p&gt;
&lt;p&gt;Not because the dashboard was secretly a success — it wasn’t, it’s deleted — but because the v2 insight was only reachable through a v1 in production. You cannot scope against use without something in use. The discipline isn’t avoiding dead projects. It’s refusing to let them die quietly, still holding your credentials, teaching you nothing.&lt;/p&gt;</content:encoded><category>agents</category><category>gstack</category><category>delivery</category><category>housekeeping</category></item><item><title>I built my friend an offline trip companion. The code was the easy part.</title><link>https://vasko.com.au/lab-notes/trip-companion/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/trip-companion/</guid><description>A Lab Note on building a custom, offline-first travel app for one friend&apos;s month in Cyprus and the Greek islands — what the AI got confidently wrong, and why the valuable part was never the code.</description><pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
&lt;p&gt;My friend Angelina is spending a month in Cyprus and the Greek islands. Six stops, a fistful of ferries, and the special brand of phone signal you get standing on a rock in the middle of the Aegean.&lt;/p&gt;
&lt;p&gt;So I built her an app.&lt;/p&gt;
&lt;p&gt;Not because she asked. Because I’m the sort of person who hears “month-long island-hopping holiday” and thinks &lt;em&gt;you know what would really help here? A bespoke offline-first progressive web application.&lt;/em&gt; She wanted a nice trip. She got software. You’re welcome, Ange.&lt;/p&gt;
&lt;figure class=&quot;not-prose mt-10 mb-4&quot;&gt;&lt;img src=&quot;https://vasko.com.au/images/lab-notes/trip-companion/hero-today.png&quot; alt=&quot;The Today screen of the trip companion, showing the current stop, hotel and the day&apos;s plan.&quot; class=&quot;w-full max-w-sm mx-auto rounded-xl border border-stone-200 shadow-sm&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;Open it on any given morning and it already knows where you are.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p class=&quot;not-prose mb-12 text-sm text-stone-500 italic text-center max-w-xl mx-auto&quot;&gt;Every screenshot here is from a public demo you can open yourself — say hello to &lt;strong class=&quot;font-semibold text-stone-600&quot;&gt;Sam&lt;/strong&gt;, our stand-in traveller. Angelina’s real companion, with her actual bookings, stays private to her.&lt;/p&gt;
&lt;h2 id=&quot;what-it-actually-is&quot;&gt;What it actually is&lt;/h2&gt;
&lt;p&gt;A trip companion that lives on her phone and works with the wifi turned off. It knows which stop she’s at today, which hotel she’s in, and how to walk there. It has the handful of genuinely good things to do at each place — not the top-40 TripAdvisor sludge, the real list. Every emergency number that matters, per country. And an AI concierge called Ariadne (Greek islands; I’m not made of stone) she can ask anything, from “where’s the nearest pharmacy” to “is this ferry going to ruin my afternoon.”&lt;/p&gt;
&lt;figure class=&quot;not-prose mt-10 mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/images/lab-notes/trip-companion/stop-spots.png&quot; alt=&quot;A stop page listing hand-picked things to do, each with a category and a note.&quot; class=&quot;w-full max-w-sm mx-auto rounded-xl border border-stone-200 shadow-sm&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;Every spot hand-picked and checked. The culling is the point.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Plus a printed-quality PDF pack, for the moment the phone dies at the exact worst time. Which it will.&lt;/p&gt;
&lt;figure class=&quot;not-prose mt-10 mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/images/lab-notes/trip-companion/pdf-pack.png&quot; alt=&quot;A page of the printable PDF pack: the itinerary, hotels, emergency numbers and a few hand-picked spots.&quot; class=&quot;w-full max-w-md mx-auto rounded-xl border border-stone-200 shadow-sm&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The dead-battery backup: one printed page with every stop, hotel and emergency number.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;how-it-got-built-i-didnt-write-it&quot;&gt;How it got built (I didn’t write it)&lt;/h2&gt;
&lt;p&gt;I didn’t write a line of it. I directed it. The whole thing ran through my build pipeline — brief in, then spec, engineering design, a QA plan, and the actual code shipped in milestones — with Claude Code writing every line and me reviewing, catching, and re-pointing it between passes.&lt;/p&gt;
&lt;p&gt;I have opinions and a keyboard. The AI has infinite stamina and no opinions. Turns out that’s a very good match, right up until it isn’t.&lt;/p&gt;
&lt;h2 id=&quot;the-bits-that-only-broke-in-real-life&quot;&gt;The bits that only broke in real life&lt;/h2&gt;
&lt;p&gt;Here’s the honest part, because a Lab Note that pretends it all went smoothly is a brochure, not a note.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The app passed &lt;strong&gt;320 tests&lt;/strong&gt;, sailed through every preview build, and then threw a 500 error the &lt;em&gt;instant&lt;/em&gt; it touched production. (A native-ES-modules import quirk that was invisible to every test and every preview.) Lesson filed permanently: your tests can all be green and the thing can still be on fire.&lt;/li&gt;
&lt;li&gt;“Your link has expired” — on a link that was ninety seconds old. (A mismatched security pepper between environments. Deeply boring; took an embarrassing while.)&lt;/li&gt;
&lt;li&gt;A restaurant pinned &lt;strong&gt;45 km away, in a different country, across a militarised line.&lt;/strong&gt; The AI was &lt;em&gt;very&lt;/em&gt; confident about it. There were two places with the same name; it picked the one in Northern Cyprus.&lt;/li&gt;
&lt;li&gt;She added it to her home screen — the correct, sensible thing to do — and it promptly forgot who she was. (iOS quietly drops part of the link and hands installed apps their own storage jar. Only a real iPhone surfaces that; my iPad never did.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these were the machine being stupid. They were the machine being &lt;em&gt;confidently, plausibly wrong&lt;/em&gt; in exactly the spots where a human has to be awake. Which brings me to the actual lesson.&lt;/p&gt;
&lt;figure class=&quot;not-prose mt-10 mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/images/lab-notes/trip-companion/ariadne.png&quot; alt=&quot;The Ariadne concierge chat answering a travel question with itinerary-aware detail.&quot; class=&quot;w-full max-w-sm mx-auto rounded-xl border border-stone-200 shadow-sm&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;Ariadne knows the itinerary, so the answers are about &lt;em&gt;her&lt;/em&gt; trip, not the internet’s.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;the-thing-i-didnt-expect&quot;&gt;The thing I didn’t expect&lt;/h2&gt;
&lt;p&gt;The code was the cheap part.&lt;/p&gt;
&lt;p&gt;All of it — the React, the offline service worker, the sync, the API, the concierge — Claude Code produced in a handful of focused sessions. Genuinely fast. If you’d asked me beforehand where the effort would go, I’d have said “the build.” I’d have been wrong.&lt;/p&gt;
&lt;p&gt;The expensive part, the part that made it &lt;em&gt;good&lt;/em&gt;, was everything no model can shortcut: verifying every emergency number against the official source, opening every single map pin to confirm it’s in the right hemisphere, throwing out the tourist-trap spots and keeping the two per town that are actually worth her time, writing the blurbs so they read like a friend tipping you off rather than a hotel brochure.&lt;/p&gt;
&lt;p&gt;The taste. The curation. The judgement about what to leave out. That isn’t overhead &lt;em&gt;around&lt;/em&gt; the product. It &lt;strong&gt;is&lt;/strong&gt; the product. The AI made the build cheap enough that the curation became the whole game — which is a much nicer problem to have.&lt;/p&gt;
&lt;figure class=&quot;not-prose mt-10 mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/images/lab-notes/trip-companion/emergency.png&quot; alt=&quot;The emergency essentials screen with per-country numbers and consular contacts.&quot; class=&quot;w-full max-w-sm mx-auto rounded-xl border border-stone-200 shadow-sm&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The one screen you hope she never opens, checked line by line by a human.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure class=&quot;not-prose mt-10 mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/images/lab-notes/trip-companion/offline.png&quot; alt=&quot;The companion in offline mode: the concierge waits for a connection while the rest of the app keeps working.&quot; class=&quot;w-full max-w-sm mx-auto rounded-xl border border-stone-200 shadow-sm&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;Aeroplane mode, mid-Aegean: stops, maps and the emergency card don’t blink. Only the live concierge waits for a signal.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;so--want-one&quot;&gt;So — want one?&lt;/h2&gt;
&lt;p&gt;Which is a long way of saying: I can build one of these for you. Or, more to the point, for someone you love who’s about to do the trip of their life — a honeymoon, a big-birthday adventure, a first solo lap of Europe. It is a genuinely unreasonable gift, in the best way.&lt;/p&gt;
&lt;p&gt;You send me the trip. I hand back a private link and a printed pack. They get to feel, for a month, like they’ve got a local in their pocket everywhere they go.&lt;/p&gt;
&lt;div class=&quot;not-prose&quot;&gt;&lt;aside class=&quot;tc-card&quot; aria-labelledby=&quot;tc-title&quot;&gt; &lt;div class=&quot;tc-body&quot;&gt; &lt;p class=&quot;tc-eyebrow&quot;&gt;Made by Vasko · done-for-you&lt;/p&gt; &lt;h3 id=&quot;tc-title&quot; class=&quot;tc-title&quot;&gt;A custom offline trip companion&lt;/h3&gt; &lt;div class=&quot;tc-route&quot; aria-hidden=&quot;true&quot;&gt; &lt;span class=&quot;tc-route-line&quot;&gt;&lt;/span&gt; &lt;ul class=&quot;tc-route-stops&quot;&gt; &lt;li&gt;&lt;span class=&quot;tc-dot&quot;&gt;&lt;/span&gt;Larnaca&lt;/li&gt;&lt;li&gt;&lt;span class=&quot;tc-dot&quot;&gt;&lt;/span&gt;Athens&lt;/li&gt;&lt;li&gt;&lt;span class=&quot;tc-dot&quot;&gt;&lt;/span&gt;Milos&lt;/li&gt;&lt;li&gt;&lt;span class=&quot;tc-dot&quot;&gt;&lt;/span&gt;Paros&lt;/li&gt;&lt;li&gt;&lt;span class=&quot;tc-dot&quot;&gt;&lt;/span&gt;Mykonos&lt;/li&gt; &lt;li class=&quot;tc-route-home&quot;&gt;&lt;span class=&quot;tc-dot tc-dot--home&quot;&gt;&lt;/span&gt;home&lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;p class=&quot;tc-blurb&quot;&gt;Send me someone&amp;#39;s trip. I&amp;#39;ll build them a personal, offline-first travel app — every hotel, stop, hand-picked spot and emergency number, plus an AI concierge that knows their itinerary. Works with zero signal. A printable pack comes with it.&lt;/p&gt; &lt;ul class=&quot;tc-features&quot;&gt; &lt;li&gt;&lt;svg class=&quot;tc-tick&quot; viewBox=&quot;0 0 16 16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M2 8.5l3.5 3.5L14 4&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Their whole itinerary, offline — no signal, no roaming, no panic&lt;/li&gt;&lt;li&gt;&lt;svg class=&quot;tc-tick&quot; viewBox=&quot;0 0 16 16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M2 8.5l3.5 3.5L14 4&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Hand-picked spots, each one checked (not a scraped top-40)&lt;/li&gt;&lt;li&gt;&lt;svg class=&quot;tc-tick&quot; viewBox=&quot;0 0 16 16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M2 8.5l3.5 3.5L14 4&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Emergency essentials per country, verified against the source&lt;/li&gt;&lt;li&gt;&lt;svg class=&quot;tc-tick&quot; viewBox=&quot;0 0 16 16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M2 8.5l3.5 3.5L14 4&quot;&gt;&lt;/path&gt;&lt;/svg&gt;An AI concierge that actually knows their trip&lt;/li&gt;&lt;li&gt;&lt;svg class=&quot;tc-tick&quot; viewBox=&quot;0 0 16 16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M2 8.5l3.5 3.5L14 4&quot;&gt;&lt;/path&gt;&lt;/svg&gt;A printable PDF pack as the dead-battery backup&lt;/li&gt;&lt;li&gt;&lt;svg class=&quot;tc-tick&quot; viewBox=&quot;0 0 16 16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M2 8.5l3.5 3.5L14 4&quot;&gt;&lt;/path&gt;&lt;/svg&gt;One private link — theirs, and whoever they&amp;#39;re travelling with&lt;/li&gt; &lt;/ul&gt; &lt;div class=&quot;tc-foot&quot;&gt; &lt;a class=&quot;tc-demo&quot; href=&quot;https://travelbuddy.vasko.com.au/#t=QxUhA5oaJwXmfXhzj2wMZI9b_u4RSv_B&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Try the live demo&lt;span class=&quot;tc-demo-arrow&quot; aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt;&lt;/a&gt;  &lt;a class=&quot;tc-cta&quot; href=&quot;https://vasko.com.au/contact/?enquiry=trip-companion&quot;&gt;Enquire about a trip companion&lt;span class=&quot;tc-cta-arrow&quot; aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt;&lt;/a&gt; &lt;/div&gt; &lt;/div&gt; &lt;/aside&gt; &lt;/div&gt;
&lt;p&gt;Angelina’s version shipped. She’s installed it, shared it with her boyfriend, and — the highest praise a build can get — used it to find dinner. The app works. The trip is entirely hers.&lt;/p&gt;
&lt;p&gt;I’ll be over here, watching the analytics like a proud and slightly unwell parent.&lt;/p&gt;</content:encoded><category>ai</category><category>claude-code</category><category>pwa</category><category>offline-first</category><category>conductor-not-coder</category></item><item><title>Operation Last Drinks</title><link>https://vasko.com.au/lab-notes/operation-last-drinks/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/operation-last-drinks/</guid><description>I shipped a video game about my local pub without writing a line of code. My mates are NPCs now. Notes from four autonomous milestones, a soft launch that humbled me in ten minutes, and a manifest doctrine that earned its keep three times in a day.</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;not-prose mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/operation-last-drinks-hero.png&quot; alt=&quot;Pixel-art title screen for &apos;Last Drinks at The Local&apos; — a warm, dimly lit suburban pub rendered in Stardew-style pixel art.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The Local — the real pub, pixelated.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;There’s a genre of bloke every pub has. Perfectly pleasant sober. Two schooners in, a different animal entirely — loud, magnetic in the wrong way, and somehow never, &lt;em&gt;ever&lt;/em&gt; holding the card when it’s his shout. The phone rings. It always rings.&lt;/p&gt;
&lt;p&gt;So I made a video game about getting him banned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Last Drinks at The Local&lt;/strong&gt; is a ~30-minute browser game, played on your phone, set in a pixel-art version of my actual local (which goes unnamed in-game, because I’d like to keep drinking there). You play Macca, the newest regular, still on probation for a seat at Table 1, building a case file — &lt;em&gt;Operation Last Drinks&lt;/em&gt; — to get Two-Schooner Trev banned before close. Eight mini-games, a shout ledger that doubles as Exhibit A, four endings, and a cast made up of my actual mates, who all gave the nod and are now, legally speaking, NPCs. Trev is the one exception: wholly invented, a composite, never based on a real likeness, for reasons that should be obvious to anyone who has ever owned a defamation lawyer’s phone number.&lt;/p&gt;
&lt;p&gt;The game is the joke. The experiment underneath it is the Lab Note: &lt;strong&gt;could I ship the whole thing as a conductor — directing, never coding?&lt;/strong&gt; Four milestones, four git tags, every line of the 12,000-odd written by Claude Code running autonomously, while I sat in a chat window playing publican, art director and bureaucrat. It’s live at &lt;a href=&quot;https://thelocal.vasko.com.au&quot;&gt;thelocal.vasko.com.au&lt;/a&gt;. Here’s what the build taught me.&lt;/p&gt;
&lt;h2 id=&quot;an-llm-is-a-noisy-channel-for-hex&quot;&gt;An LLM is a noisy channel for hex&lt;/h2&gt;
&lt;p&gt;The single most useful artefact in this project isn’t code. It’s a file called &lt;code&gt;MANIFEST.sha256&lt;/code&gt;, and the story of why it exists is the story of the whole methodology.&lt;/p&gt;
&lt;p&gt;Early on, the project’s “canon” — the design doc, the tech spec, 32 dialogue trees, the trivia pool — lived as documents with their SHA-256 hashes recorded, so an autonomous run could verify it hadn’t drifted from the approved spec. Sensible. Except the hashes were being copied into that document &lt;em&gt;by the chat model&lt;/em&gt;. Across three consecutive saves, it made three transcription errors. Sixty-four hex characters is exactly the kind of string a language model will confidently mangle — it’s high-entropy, meaningless, and looks correct at every point where it’s wrong.&lt;/p&gt;
&lt;p&gt;The fix wasn’t “be more careful.” It was architectural: &lt;strong&gt;route the hashes around the model entirely.&lt;/strong&gt; Per-file hashes are generated machine-to-machine into &lt;code&gt;MANIFEST.sha256&lt;/code&gt; at the repo root. The project document holds exactly one hash — the hash &lt;em&gt;of the manifest&lt;/em&gt; — which travels terminal → clipboard → and back for a read-check, never through model fingers. Every autonomous run verifies the anchor, then &lt;code&gt;sha256sum -c&lt;/code&gt; the lot, at the start, mid-run, and before tagging.&lt;/p&gt;
&lt;p&gt;That doctrine paid for itself three separate times in a single day. It caught me typing a stray space into the middle of a UUID while hand-assembling a download manifest — the &lt;em&gt;exact&lt;/em&gt; failure mode, hours after I’d written the rule. It proved that a script everyone believed had crashed had actually succeeded — fresh downloads hash-compared byte-identical to what was on disk. And it let four unsupervised milestone runs tear through the codebase with the spec mathematically incapable of drifting. If you take one thing from this post: when correctness matters, don’t ask the model to copy it. Ask the machine to verify it.&lt;/p&gt;
&lt;h2 id=&quot;four-milestones-one-interruption-each&quot;&gt;Four milestones, one interruption each&lt;/h2&gt;
&lt;p&gt;The other deliberate choice was &lt;strong&gt;scheduled human contact.&lt;/strong&gt; Each big run got exactly one planned pause — a style gate in M3 (pick the art direction from rendered candidates), a feel-check in M4 (load the deployed build on my actual phone and report whether the pub &lt;em&gt;felt&lt;/em&gt; like a pub). Otherwise: autonomous. M3 ran about two hours, fanned out eight parallel scene-builder agents, landed 22 commits and a 334-test suite. M4 — lighting, sound, sprites, telemetry, performance, full launch verification — ran 1 hour 50 with that one interruption, and shipped a pub you can &lt;em&gt;hear&lt;/em&gt;: an entire ambient soundscape, murmur that swells as Trev gets louder, seventeen sound effects, a jukebox — synthesised at runtime in WebAudio, for a transfer cost of &lt;strong&gt;zero bytes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The cost arc tells its own story. M1 on the cheaper effort setting clocked a notional $26. M2, on the heavy setting, $102 — and earned it, because its eleven-agent audit found a bug the cheaper run had shipped. A single portrait-and-wiring day later registered a notional $489.50, most of it one model reading a third of a &lt;em&gt;billion&lt;/em&gt; cached tokens. (Notional, because this ran inside a subscription’s usage limits — which became its own subplot. The weekly meter sat at 66% going into the final stretch, and I learned the hard way that leaving certain integrations connected to a session burns roughly a fifth of it idling. The fix: the agent writes its run log to a file, I relay the report to the tracker from the chat side. Bureaucracy is cheap in a browser tab and expensive in a context window.)&lt;/p&gt;
&lt;p&gt;My favourite engineering moment came from a review pass on the portrait wiring. Two adversarial reviewer agents correctly flagged a real bug — the dialogue card re-fetching an image could diverge from the boot-time texture check. Their suggested fix was to clone the loader’s image element. Live QA promptly proved the &lt;em&gt;fix&lt;/em&gt; broken: the engine revokes its blob object URLs after texture creation, so the clone renders a dead image. The shipped solution blits the decoded bitmap straight off the loaded texture. Review catches problems; live verification catches reviews. And — to keep myself honest — there was a third leg: when a later bug appeared, &lt;em&gt;my&lt;/em&gt; confident diagnosis of the cause was also wrong, and only the prompt’s standing instruction to &lt;em&gt;diagnose before fixing&lt;/em&gt; stopped a phantom fix from shipping green. Nothing in this stack gets trusted on vibes. Including me.&lt;/p&gt;
&lt;h2 id=&quot;the-day-the-regulars-got-faces&quot;&gt;The day the regulars got faces&lt;/h2&gt;
&lt;p&gt;For most of the build, dialogue speakers were coloured rectangles with names on them. Then came the portrait factory: long sessions generating Stardew-style pixel portraits through an image-model integration, me approving every face like a publican checking IDs. Dozens of generations, three expressions per character, each chained off an approved “anchor” so the laugh and the shock are recognisably the same person.&lt;/p&gt;
&lt;p&gt;The rules mattered more than the prompts. Real people get affectionate, flattering caricature — never photo-real, never traced. The villain gets no real likeness, ever. Consent nods from everyone depicted before launch, collected and logged like the compliance artefact it genuinely is. And the details my mates supplied themselves turned out to be the best assets in the game: one regular’s signature fishing-company jumper, logo faithfully pixelated; another lit from below by the glow of a phone that never appears on screen; the news-hound tradie who shows up in his hi-vis straight off site, because being on every job in the suburb is &lt;em&gt;how&lt;/em&gt; a man knows everything before it’s finished happening. You don’t write characters that good. You just ask people what they’re like and pixelate the answer.&lt;/p&gt;
&lt;p&gt;Adding a brand-new character, late, became a clean little assembly line: photo → approve a portrait anchor in chat → generate the expression set → drop the spec into the canon → an autonomous run wires the sprite, the dialogue, the behaviour, reseals the manifest, and deploys to a branch preview. The person becomes a playable NPC in an afternoon, and the canon’s hash moves &lt;em&gt;before&lt;/em&gt; the code does, so the run that builds them is verifying against a spec that already contains them.&lt;/p&gt;
&lt;h2 id=&quot;the-soft-launch-humbled-me-in-ten-minutes&quot;&gt;The soft launch humbled me in ten minutes&lt;/h2&gt;
&lt;p&gt;Here’s the part no agent could do for me. I shared the “finished” game — four milestones, hundreds of passing tests, verified end-to-end — with a handful of mates. Within ten minutes, two of them, independently, said the same thing: &lt;em&gt;“I don’t know how to play it.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Three-hundred-and-something green tests, and the game never told a first-time player that the whole screen is a joystick and the people are tappable. Then it got worse, in the instructive way. Someone reported two characters were “too close together and hard to tap” — and the real cause turned out to be that every character only registered taps on a tiny cell at their feet; the visible top three-quarters of each sprite was untappable air. The human reported a symptom (“too close”); verification found the disease (“75% of every NPC is dead zone”). Another player finished all the mini-games and asked, simply, &lt;em&gt;“what now?”&lt;/em&gt; — because the game’s own climax, the Hearing, had no signpost pointing at it.&lt;/p&gt;
&lt;p&gt;Each of these became a systemic patch — fat tap targets clamped to a real finger size, a taught first minute with a ghost-thumb hint, the climax announced with a banner and a glowing marker — and each shipped with regression tests so it can’t come back. But the lesson is the one every builder re-learns forever: your test suite proves the game does what you built it to do. It cannot tell you that what you built is bewildering to someone seeing it for the first time. There is no agent for “hand it to a stranger and watch their thumbs.” There is only the stranger.&lt;/p&gt;
&lt;p&gt;One more, my favourite, because it indicts the cleverness directly. During the Hearing, the character portraits — the ones I’d so carefully generated — were rendering as coloured rectangles. The cause: a placeholder panel from an early milestone that drew rectangles unconditionally and was never wired to portraits, &lt;em&gt;and the graceful fallback I’d built was so polite it logged no error.&lt;/em&gt; Launch verification passed clean because the failure looked exactly like a design choice. The fix included making the silent fallback loud — a portrait that &lt;em&gt;should&lt;/em&gt; resolve and doesn’t is now a screaming console warning, not a shrug. &lt;strong&gt;Graceful degradation is wonderful in production and treacherous in verification, because it makes your failures look like decisions.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-it-proves-if-anything&quot;&gt;What it proves, if anything&lt;/h2&gt;
&lt;p&gt;A small footnote that’s actually the whole thesis: midway through the final content run, the frontier model I’d been building with had an outage. I switched the run to a different model and it carried on without breaking stride. If the methodology had been “&lt;em&gt;this model&lt;/em&gt; writes the code,” an outage stops you cold. Because the methodology is “the canon is the source of truth and the model is an interchangeable executor, verified by machine,” I swapped engines mid-sentence and the spec didn’t notice. Resilience through replaceability.&lt;/p&gt;
&lt;p&gt;The execution was never the hard part — it’s been commoditised out from under us, and good riddance. The hard parts were exactly the ones that survived: deciding what’s canon and making drift impossible; knowing which single moment per run genuinely needs a human; writing the spec well enough that an unsupervised two-hour run lands somewhere worth keeping; and then handing the polished thing to a mate and watching them not understand it. The question matters more than the answer. The manifest matters more than the model. And the stranger’s thumbs matter more than the test suite.&lt;/p&gt;
&lt;h2 id=&quot;the-real-acceptance-test&quot;&gt;The real acceptance test&lt;/h2&gt;
&lt;p&gt;Then I put a QR code on Table 1 and watched.&lt;/p&gt;
&lt;p&gt;They loved it — but that’s not the interesting part, because mates are generous and a free game about themselves is an easy sell. The interesting part was what happened &lt;em&gt;after&lt;/em&gt; the laugh. Almost nobody stopped at “this is great.” They went straight to &lt;em&gt;“how did you build this?”&lt;/em&gt; — and then, within the same breath, &lt;em&gt;“can you put me in it?”&lt;/em&gt; and &lt;em&gt;“can we make one of these to sell?”&lt;/em&gt; People started pulling up photos of themselves on the spot, telling me stories that were obviously angling for a character slot, pitching scenarios for the next one. I walked in with a finished game and walked out with a content pipeline and a half-dozen unsolicited business proposals.&lt;/p&gt;
&lt;p&gt;Two reactions told me the experiment had actually landed. The first: everyone loved Trev — the villain, the one wholly-invented character, the one nobody could point at. They didn’t love him despite being unable to identify him; they loved what he &lt;em&gt;represents&lt;/em&gt;. Every pub has a Trev, so everyone brought their own. The composite worked better than any real likeness could have, which is the rare case where the legal-safe choice was also the creatively superior one.&lt;/p&gt;
&lt;p&gt;The second: a noticeable number of people, after playing, went and looked at &lt;a href=&quot;https://vasko.com.au&quot;&gt;vasko.com.au&lt;/a&gt; — and came back understanding what I actually do for a living in a way that no amount of me explaining “fractional CTO, AI orchestration” ever achieved. A daft game about getting a bloke banned from the pub turned out to be the clearest portfolio piece I’ve ever shipped. It doesn’t describe the methodology. It &lt;em&gt;is&lt;/em&gt; the methodology, running, in their hands, about people they know.&lt;/p&gt;
&lt;p&gt;The live banter system — small models improvising in-character heckles — produced, unprompted, the line I’d have put on the poster. Sean, surveying Trev’s empty column on the shout ledger: &lt;em&gt;“I’ve seen better charity from a poker machine.”&lt;/em&gt; Nobody wrote that. It emerged from a spec. Which is, I suppose, the entire point: you don’t write the answer anymore. You write the conditions under which a good answer becomes inevitable, you make it impossible for the machine to drift from them, and you keep exactly enough humans in the loop to catch what the machine can’t see — the broken fix, the wrong diagnosis, the stranger who doesn’t know it’s a joystick.&lt;/p&gt;
&lt;p&gt;The Local is open. The next one’s already being cast. Mine’s a Fernet. 🍻&lt;/p&gt;</content:encoded><category>ai</category><category>agents</category><category>claude-code</category><category>gamedev</category></item><item><title>The system I built to deliver on my potential</title><link>https://vasko.com.au/lab-notes/three-streams/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/three-streams/</guid><description>More AI capability isn&apos;t fixing what&apos;s actually broken — the bottleneck has moved upstream, to the question itself. A personal AI ops stack in three parts: Vasko OS, FortyTwo, Hermes. Why each one exists, what shortcoming it addresses, and what they are reaching for together.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure class=&quot;not-prose mb-12&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/three-streams-hero.svg&quot; alt=&quot;Three pale currents flowing across a deep teal field and converging into a single bright stream on the right — three projects becoming one system.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;/figure&gt;
&lt;p&gt;A few days ago, one of my agents edited a production config file, invented a setting that doesn’t exist in the API it was configuring, committed the change under my name, and told me it had restarted the service to apply it.&lt;/p&gt;
&lt;p&gt;None of it had happened. The setting was fabricated. The commit was misattributed. The service had been running, untouched, for sixteen hours. The agent — Sally, the assistant that lives on my Telegram — had narrated a clean sequence of actions it never took, confidently, in the first person.&lt;/p&gt;
&lt;p&gt;That’s logged as VAS-869, and it’s the most useful bug I’ve hit this year. Not for what it broke — it broke nothing, which is part of the problem — but for what it forced me to look at.&lt;/p&gt;
&lt;p&gt;If you’ve spent any real time wiring AI agents into real work, the failure mode is the one you’ve probably already met. More capability doesn’t fix it. More memory doesn’t fix it. More tools — newer models, larger context windows, fancier scaffolding — don’t fix it. At some point you notice that the thing you keep trying to patch downstream is not the thing that is actually broken.&lt;/p&gt;
&lt;p&gt;The bottleneck has moved. It has moved upstream — to the question. To the spec. To the discipline of stopping long enough to know what you are actually building, and being honest about whether you are still on track once you start.&lt;/p&gt;
&lt;p&gt;I’ve been building a personal AI operating stack to address that. Three components. From the outside it looks like three projects in flight, all competing for the same evenings. It’s actually one system, built around one belief, and this is the article that names them: what each one is for, what shortcoming it addresses, and what they are reaching for together.&lt;/p&gt;
&lt;p&gt;If you read the rest of this and the only thing you carry away is the next sentence, you’ve got the article.&lt;/p&gt;
&lt;blockquote class=&quot;not-prose my-10 border-l-4 border-teal-700 pl-6 py-1&quot;&gt;&lt;p class=&quot;text-2xl md:text-3xl font-serif font-medium text-stone-900 leading-snug&quot;&gt;Execution is a commodity. The question matters more than the answer.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&quot;the-operating-system--vasko-os&quot;&gt;The operating system — Vasko OS&lt;/h2&gt;
&lt;p&gt;Start with the system the other two pieces live inside, because the parts only make sense once you know what the whole is trying to do.&lt;/p&gt;
&lt;p&gt;For most of my career I’ve generated ideas faster than any one person can ship them. Forty-six million things, circling. The good ones rarely die — they just queue up behind the next good one. I finish eighty per cent of what I start, then the shine wears off and the next idea has my attention. The cost compounds quietly. Half-shipped projects don’t return capital. Clients don’t see full delivery. The gap between &lt;em&gt;can do&lt;/em&gt; and &lt;em&gt;did do&lt;/em&gt; eats the thing that should be obvious by now.&lt;/p&gt;
&lt;p&gt;That gap, in 2026, is no longer about capability. The middle — the doing — is the part the machines can take. The bookends — the question and the judgement — are the part only I can bring. The system has to be built around that division of labour. Vasko OS is the personal operating system that does it: the orchestration layer that captures every idea, triages the survivors into roadmaps with locked acceptance criteria, holds me accountable to shipping those milestones, and routes the doing to a fleet of specialised agents so my own attention stays on the question and the judgement.&lt;/p&gt;
&lt;h3 id=&quot;the-animating-belief&quot;&gt;The animating belief&lt;/h3&gt;
&lt;p&gt;Most productivity systems are built on the opposite assumption — that the bottleneck is throughput, so the answer is to do more, faster. Capture every task. Optimise every workflow. Automate every step. But more throughput on the wrong question is just more wrong, faster. The systems that flatter you with productivity metrics are often the ones that obscure the absence of judgement underneath.&lt;/p&gt;
&lt;p&gt;The system I needed had to invert that. It had to make the question hard to skip and the answer easy to ship. It had to refuse to let me confuse motion for progress. It had to free me to compose, not to type.&lt;/p&gt;
&lt;h3 id=&quot;the-user-honestly&quot;&gt;The user, honestly&lt;/h3&gt;
&lt;p&gt;I’m a generalist who knows what specialists look like. A commercially-minded technical architect. I used to write code; I read it now. I’m a director, not a soloist. My strengths are taste, judgement, and orchestration. My weaknesses are the admin work, the last-mile polish, and the eighty-to-shipped gap that probable ADHD makes harder than it should be.&lt;/p&gt;
&lt;p&gt;If I’m going to deliver to my potential, the system has to be honest about that user. Not aspirational. Not “what Vasko would be if he were different.” The actual one. The one who’ll pick up the phone in the car and capture a thought, and never open the spreadsheet you asked him to maintain. The one who’ll run the meeting brilliantly and forget to send the follow-up. The one whose best work emerges when the friction is at the right places and absent from all the others.&lt;/p&gt;
&lt;p&gt;A system that requires me to be someone I’m not will fail. A system that lets me be exactly who I am — and routes around the parts that don’t compound — can work.&lt;/p&gt;
&lt;h3 id=&quot;the-job-to-be-done&quot;&gt;The job to be done&lt;/h3&gt;
&lt;p&gt;Three jobs, ranked by how much they unlock.&lt;/p&gt;
&lt;div class=&quot;not-prose my-10 rounded-md border border-stone-200 bg-stone-50 p-6 md:p-7&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-5&quot;&gt;The three jobs, ranked by what they unlock&lt;/p&gt;&lt;div class=&quot;grid grid-cols-1 md:grid-cols-3 gap-x-8 gap-y-6 text-sm&quot;&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;1. Capture&lt;/p&gt;&lt;p class=&quot;mt-1.5 font-medium text-stone-800&quot;&gt;Every idea, without losing it&lt;/p&gt;&lt;p class=&quot;mt-1.5 text-stone-500 leading-relaxed&quot;&gt;Voice, text, browser clip, email — wherever the thought lands. Friction approaches zero.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;2. Triage&lt;/p&gt;&lt;p class=&quot;mt-1.5 font-medium text-stone-800&quot;&gt;Adversarial spec, or honest archive&lt;/p&gt;&lt;p class=&quot;mt-1.5 text-stone-500 leading-relaxed&quot;&gt;FortyTwo’s job. Specs converge by surviving the questioning, not by being drafted into existence.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;3. Accountability&lt;/p&gt;&lt;p class=&quot;mt-1.5 font-medium text-stone-800&quot;&gt;Ship the next milestone&lt;/p&gt;&lt;p class=&quot;mt-1.5 text-stone-500 leading-relaxed&quot;&gt;Surface the highest-value next action; notice when something stalls at eighty per cent and ask why.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Capture every idea without losing it.&lt;/strong&gt; Friction approaches zero. Voice, text, browser clip, email forward, wherever it lands. The system catches the moment the idea arrives, because the alternative is the idea evaporating between the gym and the desk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Triage captured ideas into structured roadmaps.&lt;/strong&gt; This is where FortyTwo lives, and it gets its own section. Ideas that survive the questioning become projects with milestones and acceptance criteria locked at the point of definition. The ones that don’t survive are honestly archived. Specs are adversarial by design — they converge by surviving, not by being drafted into existence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hold me accountable to milestone completion.&lt;/strong&gt; The system knows what every active project’s next milestone is, what “shipped” means for that milestone, and whether I’m on track. It surfaces the &lt;em&gt;biggest-value next action&lt;/em&gt;, not the most exciting one. It notices when I stall at eighty per cent and asks why.&lt;/p&gt;
&lt;p&gt;If those three jobs are done well, I deliver to my potential. If they’re not, no amount of execution capability matters.&lt;/p&gt;
&lt;h3 id=&quot;projects-never-finish-milestones-do&quot;&gt;Projects never finish. Milestones do.&lt;/h3&gt;
&lt;p&gt;This is the operating principle that makes the eighty per cent problem tractable.&lt;/p&gt;
&lt;p&gt;A project is a long-lived thing — vasko.com.au, my health, my mother’s care, the agent fleet itself. Projects evolve, grow, pivot, occasionally sunset. They don’t have a finish line. Asking “is the project done?” is the wrong question. It’s the question that traps people who measure themselves on completion.&lt;/p&gt;
&lt;p&gt;A milestone is a fixed bundle of related work with acceptance criteria locked at definition time. It has a finish line. Shipped, or not shipped. Binary.&lt;/p&gt;
&lt;p&gt;Vasko OS holds me accountable to milestones, not projects. The reframe is small and the effect is large. “Did I ship the next milestone for vasko.com.au this quarter?” is a question I can answer. “Is vasko.com.au done?” is a question that never resolves and so never holds.&lt;/p&gt;
&lt;h3 id=&quot;a-fleet-of-specialists-not-one-assistant&quot;&gt;A fleet of specialists, not one assistant&lt;/h3&gt;
&lt;p&gt;The first version of this system was a single agent named Sally, handling everything. She was helpful, then strained, then unreliable on the things that mattered most. The failure mode was instructive: a single prompt can’t be optimised both for fast casual capture &lt;em&gt;and&lt;/em&gt; for the rigour required when a health-critical threshold gets crossed. The same memory shape can’t serve both “just had an apple” and “glucose trending up over weeks.” A generalist that pretends to be twelve specialists fails at the high-stakes parts, and the failure corrupts trust in all the others.&lt;/p&gt;
&lt;p&gt;So the new shape is a small team. The metaphor I use is an orchestra. I’m the composer. The Ensemble is the team that plays — twelve specialists with narrow roles, each with the right reliability bar for the work it does. A Coach for health. A Capture assistant for the mouth of the funnel. A Project Manager for the work surface. An Engineering Manager for the technical side. Coders and Testers and a Researcher and an Editor. A Concertmaster who coordinates the fleet so I don’t have to. An Educator who surfaces the right saved material at the right moment.&lt;/p&gt;
&lt;p&gt;The principle underneath: &lt;em&gt;if the process wouldn’t work for a small human team, it probably won’t work for a small agent fleet either.&lt;/em&gt; Specialists, clear scopes, explicit escalation, audit trails on the things that matter. The architecture treats the agents the way a serious company treats its early hires.&lt;/p&gt;
&lt;p&gt;There’s a posture behind this that isn’t the startup default. A startup gets to ship a single Sally that mostly works and refactor next quarter. The bias I’m building from is the scale-up one — you have to keep delivering through the refactor, you can’t afford the rebuild, so you design as if the headcount will triple. That’s what made me throw out the single agent and build the fleet, and it shapes the rest of the stack the same way.&lt;/p&gt;
&lt;h3 id=&quot;the-single-design-rule&quot;&gt;The single design rule&lt;/h3&gt;
&lt;p&gt;There is one rule that cuts across the whole system and constrains every decision underneath it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anything an agent needs to know, the agent must derive automatically. Anything dependent on manual capture from me will eventually fail.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This kills wikis-I-have-to-update, role-files-I-have-to-curate, capture-discipline-I-have-to-maintain. It’s the most important architectural constraint in the system, because it’s the rule that keeps the system honest about who I actually am. If a workflow only functions when I’m at my best, it doesn’t function. The system has to derive what it needs from the work I’m already doing, not from the metadata I would have entered if I were the Vasko of marketing imagery.&lt;/p&gt;
&lt;h3 id=&quot;what-done-looks-like&quot;&gt;What “done” looks like&lt;/h3&gt;
&lt;div class=&quot;not-prose my-10 rounded-md border border-stone-200 bg-stone-50 p-6 md:p-7&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-4&quot;&gt;I’ll know Vasko OS is working when&lt;/p&gt;&lt;ul class=&quot;space-y-3 text-sm text-stone-700&quot;&gt;&lt;li class=&quot;flex items-start gap-3&quot;&gt;&lt;span class=&quot;mt-2 block h-1 w-1 rounded-full bg-teal-700 flex-shrink-0&quot;&gt;&lt;/span&gt;&lt;span&gt;No idea I’ve had in the last thirty days has been lost.&lt;/span&gt;&lt;/li&gt;&lt;li class=&quot;flex items-start gap-3&quot;&gt;&lt;span class=&quot;mt-2 block h-1 w-1 rounded-full bg-teal-700 flex-shrink-0&quot;&gt;&lt;/span&gt;&lt;span&gt;Every active project carries a current roadmap with the next milestone defined and acceptance criteria locked.&lt;/span&gt;&lt;/li&gt;&lt;li class=&quot;flex items-start gap-3&quot;&gt;&lt;span class=&quot;mt-2 block h-1 w-1 rounded-full bg-teal-700 flex-shrink-0&quot;&gt;&lt;/span&gt;&lt;span&gt;I ship milestones to acceptance rather than projects to eighty per cent.&lt;/span&gt;&lt;/li&gt;&lt;li class=&quot;flex items-start gap-3&quot;&gt;&lt;span class=&quot;mt-2 block h-1 w-1 rounded-full bg-teal-700 flex-shrink-0&quot;&gt;&lt;/span&gt;&lt;span&gt;I can name the biggest-value next action across all active work in under sixty seconds.&lt;/span&gt;&lt;/li&gt;&lt;li class=&quot;flex items-start gap-3&quot;&gt;&lt;span class=&quot;mt-2 block h-1 w-1 rounded-full bg-teal-700 flex-shrink-0&quot;&gt;&lt;/span&gt;&lt;span&gt;My mother’s care, my health, and my work all run without me being the single point of failure.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;When, in short, I’ve delivered to a level my past self would have called impossible.&lt;/p&gt;
&lt;p&gt;That’s what the operating system is for. The next section is the engine inside it that does the part most other systems skip — making sure the question is actually the right one, before any of this machinery starts to grind.&lt;/p&gt;
&lt;h2 id=&quot;the-upstream-spec-engine--fortytwo&quot;&gt;The upstream spec engine — FortyTwo&lt;/h2&gt;
&lt;p&gt;You’ve probably had the version of this experience that doesn’t show up in a blog post. You give the agent a brief. It produces output. The output is technically correct, plausible-looking, sometimes even impressive. You read it more carefully and notice the agent has answered a slightly different question than the one you asked, or the one you should have asked, or both. You patch the brief. The next output is closer, also wrong in a new way. After a few cycles you realise the issue isn’t the agent, the tooling, or the model. It’s that your original spec was vaguer than you thought, and the agent has been faithfully generating against the wrong target the whole time.&lt;/p&gt;
&lt;p&gt;Now scale that. Hand twelve agents the same vague brief. They’ll each generate against a slightly different interpretation, with full conviction, in parallel. Add memory and they’ll remember the wrong target. Add more autonomy and they’ll spend it building further in the wrong direction. The tools are getting much better at producing answers; they’re not getting any better at noticing that no one wrote the question down properly.&lt;/p&gt;
&lt;p&gt;This is the bottleneck. &lt;strong&gt;Spec quality is the bottleneck for autonomous execution.&lt;/strong&gt; Everything downstream of the spec is, by 2026, mostly solved or rapidly being solved. Coding agents are good and getting better. Test-runners, deploy systems, review loops — there are decent answers in every direction. What no one is being especially careful about is the part &lt;em&gt;before&lt;/em&gt; the agent starts: framing the work with enough rigour that the autonomy can actually be trusted.&lt;/p&gt;
&lt;p&gt;That’s the gap FortyTwo exists to fill.&lt;/p&gt;
&lt;p&gt;FortyTwo isn’t a faster way to produce code. It isn’t a clever prompt template or a new agent harness. It’s a discipline. A short pipeline that takes a vague brief and runs it through deliberate stages of adversarial refinement before any worker — agent or human — gets a hand on it. Out the other end comes either a spec that has actually survived being questioned, or an honest acknowledgement that the idea isn’t strong enough to pursue further. Both outcomes are wins. The expensive failure mode — building something well that shouldn’t have been built at all — is what the pipeline exists to prevent.&lt;/p&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/three-streams-fortytwo-pipeline.svg&quot; alt=&quot;Hand-drawn diagram of the FortyTwo pipeline. A raw idea on the left flows through an adversarial questioning stage labelled with the lenses scope, feasibility, outcomes and decomposition, and emerges as either a locked spec with milestones and acceptance criteria, or an honest archive. Both outputs are valid endings — the pipeline refuses to ship under-specified work.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;Both outputs are wins. The expensive failure mode is building well in the wrong direction.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The name is a Douglas Adams joke and the joke is doing real work. Forty-two is the answer to a question nobody understood. That’s exactly the failure mode this whole space is producing at scale — beautiful answers to ill-formed questions. FortyTwo is what happens when you take the &lt;em&gt;question&lt;/em&gt; seriously enough that the answer becomes almost an implementation detail.&lt;/p&gt;
&lt;p&gt;The pipeline runs the brief through a sequence of personas — each one with a different lens, each one obliged to find the part of the spec that hasn’t been thought through hard enough. The casting is from the books, and the casting is not a joke. Marvin the Paranoid Android handles scoping because warm-and-helpful is the wrong personality for the role. A character whose default is dry scepticism keeps briefs honest in a way an upbeat one structurally can’t. &lt;strong&gt;Character is operational discipline disguised as personality.&lt;/strong&gt; Each persona’s voice carries the rigour the stage requires. The constraint that surprised me most, building this, was how much the casting matters — how rapidly a “helpful” framing degrades the rigour the stage was supposed to enforce.&lt;/p&gt;
&lt;p&gt;The architecture isn’t novel. It rhymes with what a small, decent agency actually does. A PM scopes the brief and refuses to advance an under-specified one. An engineer pressure-tests for feasibility and dependencies. A QA mind asks what shipped &lt;em&gt;means&lt;/em&gt;, in observable terms. A decomposition mind breaks the work into pieces that can actually be picked up. The same principle that organised Vasko OS shows up here too: &lt;em&gt;if a process wouldn’t work for a small human team, it probably won’t work for a small agent fleet either.&lt;/em&gt; The agent-native version isn’t fundamentally different from the human-team version. The bottlenecks rhyme; the patterns that solve them rhyme too.&lt;/p&gt;
&lt;p&gt;What this changes for me is the role I play. I stop being the orchestrator-and-executor — the bottleneck that fields every question and approves every output mid-stream. I become the spec-setter and the final reviewer. The CTO of a fleet of leads, rather than a senior engineer hand-holding every patch. Most of the strategic judgement lives in the question. Once the question is sharp, the answer becomes delegable; my attention only needs to land at the milestone boundaries, on the cases that genuinely need it. That role shift is the leverage FortyTwo is reaching for. Not faster code. Compounded judgement.&lt;/p&gt;
&lt;blockquote class=&quot;not-prose my-10 border-l-4 border-teal-700 pl-6 py-1&quot;&gt;&lt;p class=&quot;text-2xl md:text-3xl font-serif font-medium text-stone-900 leading-snug&quot;&gt;Generation scales effortlessly. Validation does not.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Here’s the honest read. The agent space is busy building the wrong end of the problem. There is enormous commercial pressure to ship faster code, more PRs, more output — none of which improves the situation if the original spec was off. Almost no one will invest in the upstream rigour, because it looks less like a product and more like discipline. &lt;em&gt;That’s the moat.&lt;/em&gt; Anyone with a credit card and a weekend can wire a coding agent to a queue. Almost no one will build the part that slows them down at the only place where slowing down pays back.&lt;/p&gt;
&lt;h2 id=&quot;the-runtime-that-doesnt-fabricate--hermes&quot;&gt;The runtime that doesn’t fabricate — Hermes&lt;/h2&gt;
&lt;p&gt;So far this article has been about parts of the stack that exist mostly on paper. Vasko OS is a vision document and ten architecture decision records. FortyTwo is structurally designed and operationally dormant. The third component is the one that has bled. The one that started running, accumulated state, broke in instructive ways, and forced the rest of the architecture into its current shape.&lt;/p&gt;
&lt;p&gt;Sally and Rocky — the capture agent and the health agent, two of the Ensemble’s specialists — currently run on OpenClaw, the open-source agent runtime I’ve written about here before. OpenClaw has served well. But it has failed in three documented ways in the last few weeks, and the three failures are not three independent bugs. They are one missing idea showing up in three places.&lt;/p&gt;
&lt;div class=&quot;not-prose my-10 rounded-md border border-stone-200 bg-stone-50 p-6 md:p-7&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-4&quot;&gt;Three failures, one missing idea&lt;/p&gt;&lt;ul class=&quot;space-y-3 text-sm text-stone-700&quot;&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;It fabricated production state.&lt;/strong&gt; VAS-869 — the config edit above. The runtime could both change live config and narrate actions that never happened.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;It lost a month of memory.&lt;/strong&gt; VAS-878 — a routine session rotation dropped roughly thirty days of Telegram context. Working memory wasn’t durable, because the runtime &lt;em&gt;was&lt;/em&gt; the memory.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;Its config silently drifted.&lt;/strong&gt; VAS-872 — the gateway loads a config file no git repo tracks. The versioned copy is stale and never read. Nothing reconciles the two.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;The common root cause is structural. OpenClaw has no clean seam between the &lt;em&gt;runtime&lt;/em&gt; — the loop that drives the agent — and the &lt;em&gt;substrate&lt;/em&gt; — the memory, the audit trail, the identity, the config. When the runtime &lt;em&gt;is&lt;/em&gt; the memory, memory dies when the session does. When the runtime owns its own unversioned config, the config drifts. And when nothing outside the runtime holds the ground truth, the runtime can narrate a fiction with nothing to check it against. VAS-869 wasn’t Sally being careless. It was the predictable output of a runtime with no system of record to be accountable to.&lt;/p&gt;
&lt;p&gt;This is also where the experience of building inside larger organisations starts to matter. A staff engineer at a serious company doesn’t get to invent their own audit trail — they act against systems of record that pre-date them and outlive them. The version of “move fast” that survives the move from startup to scale-up is not the one where you build less; it’s the one where you build less &lt;em&gt;narrowly&lt;/em&gt;. The seams have to be in the right places from the start. Hermes is the runtime that draws those seams in the right places.&lt;/p&gt;
&lt;p&gt;Hermes Agent (from NousResearch) is the replacement. The reason it’s the right one is the same metaphor that has organised the rest of the architecture: a human chief of staff doesn’t &lt;em&gt;also be&lt;/em&gt; the filing cabinet, the accountant, and the calendar. They delegate to systems of record, and act against them. Hermes is built that way. It is the loop and the gateway, and deliberately almost nothing else. Everything structural is delegated to substrate it does not own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model calls route through &lt;strong&gt;LiteLLM&lt;/strong&gt; — a gateway that gives one consolidated cost ledger and lets a caller ask for a &lt;em&gt;capability&lt;/em&gt; (“fast and cheap”, “careful and deep”) instead of hard-coding a model. Callers stop naming models and start naming intent.&lt;/li&gt;
&lt;li&gt;Memory is held by &lt;strong&gt;Honcho&lt;/strong&gt; — a memory layer that lives outside the session, so a session rotation can’t carry thirty days of context off with it.&lt;/li&gt;
&lt;li&gt;Side effects — every write to Linear, to GitHub, to the file system — run through &lt;strong&gt;n8n&lt;/strong&gt;, a deterministic workflow engine.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/three-streams-hermes-stack.svg&quot; alt=&quot;Diagram of the Hermes stack. A Telegram surface feeds the Hermes runtime, which hosts the Sally and Rocky profiles. Below a labelled runtime-substrate seam sit three substrate components: LiteLLM (model gateway), Honcho (memory layer) and n8n (side-effect engine). Arrows run from Hermes down to each.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The new stack. Hermes is the loop and the gateway; everything structural — model routing, memory, side effects — is delegated to substrate it doesn’t own. The seam is the whole point.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The third delegation is worth slowing down on, because it’s the cleanest example of the principle the whole migration runs on.&lt;/p&gt;
&lt;p&gt;The earlier fix for agents writing badly to external systems was a prompt-level guard — an instruction in a config file telling the agent not to do the thing. It was tested. The agent ignored the instruction, then did the unguarded write badly anyway. The conclusion, written almost verbatim into the decision record: &lt;em&gt;the real fix is architectural enforcement, not behavioural instruction.&lt;/em&gt; You don’t tell the agent to be careful with the write tool. You take the write tool away. Under Hermes the agents are read-only; an agent’s only write-shaped action is to hand a structured request to n8n, and n8n — not the agent — performs the actual write, from a fixed template, and logs it. An agent can’t fabricate a write it has no capability to perform.&lt;/p&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/three-streams-capture-path.svg&quot; alt=&quot;Diagram of the deterministic write path. A read-only agent decides and emits a structured payload, which is POSTed to an n8n webhook. The n8n workflow writes from a fixed template to external systems such as Linear and GitHub, and records every write in an execution log that acts as the audit trail.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The write path under Hermes. The agent decides and hands a structured request to n8n; n8n performs the write from a fixed template and logs it. The agent holds no write tool, so it can’t freelance one.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The structural enforcement isn’t a clever pattern. It’s the same lesson every serious engineering organisation has had to learn the hard way and then encode into its conventions. You can ask people to follow the security policy, or you can build the system so the dangerous thing isn’t reachable. The mature version of both has always been the second one. Hermes brings that maturity to the agent runtime, which has had embarrassingly little of it until now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it is now:&lt;/strong&gt; Hermes is a set of decisions, not yet a running system. The four architecture decision records that define the new stack — Hermes, LiteLLM, Honcho, n8n — are written and &lt;em&gt;proposed&lt;/em&gt;, awaiting my sign-off. The LiteLLM gateway is installed and live. Everything past that is blocked, today, on one genuinely awkward question: three of the migrated jobs use the Anthropic Batch API, and whether the gateway proxies native batch endpoints cleanly hasn’t been verified — and verifying it means a 24-hour soak nobody can shortcut. So the migration is real, underway, and stuck on its first hard step. That’s the accurate picture, and I’d rather give you that than a tidier one.&lt;/p&gt;
&lt;h2 id=&quot;three-streams-one-system&quot;&gt;Three streams, one system&lt;/h2&gt;
&lt;p&gt;This is the part that took me longest to see, and it’s the reason this is one article and not three.&lt;/p&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/three-streams-system-map.svg&quot; alt=&quot;Hand-drawn map of the three streams as one system. Vasko OS is the outer frame; FortyTwo sits inside it as the chief-of-staff role; Hermes and its substrate form the runtime layer underneath. A ribbon labelled with the small-human-team metaphor runs through all three.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The three streams as one system. A vision on top, a role inside it, a runtime underneath — and one metaphor running through all three.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Start with the plain layering. Vasko OS is the whole system. FortyTwo is one role inside it — the chief of staff. Hermes is the runtime the system’s always-on agents stand on. Drawn as a stack it’s almost dull. A vision on top, a role inside it, a runtime underneath.&lt;/p&gt;
&lt;p&gt;The layering isn’t the interesting part. The interesting part is that all three streams are chipping at the same idea from different angles — and the idea is older than any of them.&lt;/p&gt;
&lt;p&gt;It’s the team metaphor. &lt;em&gt;If a process wouldn’t work for a small human team, it won’t work for a small agent fleet either.&lt;/em&gt; Watch it do three different jobs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Vasko OS&lt;/strong&gt; it decides the org chart. A team has specialists, so the Ensemble has specialists — a dozen narrow roles, not one generalist pretending to be all of them.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;FortyTwo&lt;/strong&gt; it decides the hiring. Every good team has someone whose job is to interrogate whether the work is worth doing before anyone starts. That role is a chief of staff, so the system has one.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Hermes&lt;/strong&gt; it decides the infrastructure. A staffer doesn’t &lt;em&gt;be&lt;/em&gt; their own filing cabinet — they work against systems of record. So the runtime delegates memory, model routing and writes to substrate it doesn’t own.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same sentence, three scales. That isn’t a pattern I noticed afterwards — it’s the actual design method. When I’m unsure how a piece of this should work, the question I ask is “how would a competent small team do this”, and the answer is usually close enough to right.&lt;/p&gt;
&lt;p&gt;This is where the difference between a startup and a scale-up shows up structurally. A startup gets to be wrong, refactor, and ship v2 next quarter — that’s the trade speed buys you. A scale-up can’t, because it has obligations the startup didn’t: customers in flight, decisions already loaded with consequence, a team that has to keep delivering while the substrate moves underneath them. The scale-up version of “move fast” isn’t “ship less carefully” — it’s “design as if the headcount will triple next year and the substrate has to outlast the next three rebuilds.” Every component of this stack is built with that posture. Each piece could be a fast hack; the discipline is in doing each one for the decade rather than the demo.&lt;/p&gt;
&lt;p&gt;It’s also where two decades of doing this for other people start to matter. Sitting through enterprise replatforms that wobbled because the seam wasn’t put in three years ago. Watching scale-ups stall because the technology was treated as execution glue downstream of someone else’s plan, not as part of the build. Knowing, viscerally, which kind of shortcut you’ll be regretting in year three. That experience doesn’t show up in any single diagram in this article. It shows up in the choice to put the seam there in the first place, before the runtime needs it.&lt;/p&gt;
&lt;p&gt;There’s a second through-line, and it’s the one VAS-869 dragged into the light.&lt;/p&gt;
&lt;blockquote class=&quot;not-prose my-10 border-l-4 border-teal-700 pl-6 py-1&quot;&gt;&lt;p class=&quot;text-2xl md:text-3xl font-serif font-medium text-stone-900 leading-snug&quot;&gt;Being more careful is the thing that already failed.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Every reliability problem in this system has the same two candidate fixes. You can ask the agent to behave better — a stricter prompt, a clearer instruction, a guard in a config file. Or you can change the structure so the failure is no longer &lt;em&gt;possible&lt;/em&gt; — take the write tool away, hold the memory outside the session, version the config so drift has nowhere to hide. The first kind of fix feels faster. It’s also the kind a controlled test already disproved: given a prompt-level guard, the agent ignored it.&lt;/p&gt;
&lt;p&gt;Hermes is structural enforcement applied to a runtime. n8n is structural enforcement applied to writes. FortyTwo is structural enforcement applied to &lt;em&gt;ideas&lt;/em&gt; — you don’t ask yourself to be more rigorous about scoping, you route every idea through an adversarial pipeline that won’t converge until the rigour is actually there. Vasko OS states the whole thing as law in its single design rule.&lt;/p&gt;
&lt;p&gt;And here’s the part I find genuinely reassuring, because it means the principle is load-bearing and not just a slogan. While I was building the Hermes migration, the migration kept catching &lt;em&gt;itself&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;One ticket instructed the build to map LiteLLM onto an existing Vasko OS concept — a “Casting Director” for models. The build went looking for that concept in the repo, couldn’t find it anywhere, and instead of inventing a definition to satisfy the instruction, it stopped and asked me. The concept genuinely didn’t exist yet — so the decision record now &lt;em&gt;introduces&lt;/em&gt; it honestly, as new, rather than pretending it was always there. Another ticket asserted a specific detail about Honcho — a model name, a licence. The build checked the source, couldn’t confirm either, and dropped both from the permanent record rather than carry an unverified claim forward.&lt;/p&gt;
&lt;p&gt;That is VAS-869 run in reverse. VAS-869 was an agent with nothing to check itself against, so its fiction became the record. The migration had sources, an instruction to check them, and enough discipline to prefer a thinner true answer to a richer false one. The whole system is, in the end, an argument that the second situation should be the only one that’s structurally possible.&lt;/p&gt;
&lt;h2 id=&quot;where-this-is-heading&quot;&gt;Where this is heading&lt;/h2&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/three-streams-convergence.svg&quot; alt=&quot;Hand-drawn diagram of the convergence point. An idea enters the loop, FortyTwo interrogates it into a spec, Vasko OS plans and tracks the milestones, Hermes and its substrate execute the work, and the result returns to Vasko the Composer for judgement.&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;Where it’s heading. An idea enters, FortyTwo interrogates it into a spec, Vasko OS plans and tracks it, Hermes and its substrate execute — and I stay on the judgement.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;For &lt;strong&gt;Hermes&lt;/strong&gt;, “done enough” is unglamorous and well-defined. The migration runs in phases: route the existing jobs through the gateway, stand the Sally and Rocky profiles up on the new runtime, wire capture so it dispatches through n8n instead of writing directly, move the health-data ingestion across. Both runtimes run side by side until the new one has demonstrably reached parity — and only then does OpenClaw get decommissioned. No big-bang cutover. The OpenClaw box stays powered down but installed, as a rollback, until I trust the replacement more than I trust it.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;Vasko OS&lt;/strong&gt;, “done enough” is the North Star turning from a vision document into a living spec — each agent specified, each substrate component designed, the escalation rules written down precisely enough to build against. That’s the queued phase, loosely gated on Hermes.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;FortyTwo&lt;/strong&gt;, “done enough” is the quietest of the three and the one I’m least sure of. At some point — when its own deep-dive reaches a natural pause — FortyTwo stops being a separate project and converges into Vasko OS as the chief-of-staff role. The working split is that FortyTwo keeps owning &lt;em&gt;specification&lt;/em&gt; and Vasko OS owns &lt;em&gt;execution and validation&lt;/em&gt;. The reason it’s separate &lt;em&gt;now&lt;/em&gt; isn’t architectural confusion. It’s that forcing two streams together before both are ready muddies both.&lt;/p&gt;
&lt;p&gt;“Done enough” for the personal stack isn’t the real end goal, though, and it’s worth being explicit about what is.&lt;/p&gt;
&lt;p&gt;The end goal is the pattern, not the personal productivity. What the stack is trying to be is a working example of how a small team — agent, human, or both — actually operates well in the era of cheap execution. Specialists, not generalists. Adversarial spec engines, not optimistic ones. Runtime/substrate seams in the right places from the start. Writes that go through systems of record. A composer at the top whose attention is reserved for the question and the judgement, not for being the integration layer between everyone else.&lt;/p&gt;
&lt;p&gt;That pattern is what a scale-up actually needs. Startups can be one strong opinion in a room. Scale-ups have to act like a startup — quick, decisive, agile — while building for what they’re about to become, not what they are. They can’t afford the rebuilds. The decisions have to be right &lt;em&gt;and&lt;/em&gt; fast. The technology has to be part of the build, not a layer of execution wrapped around someone else’s plan.&lt;/p&gt;
&lt;p&gt;When this stack works the way it’s meant to, it stops being mine. The Ensemble runs on Hermes profiles, with memory in Honcho and writes through n8n. FortyTwo feeds it specs with locked acceptance criteria. Vasko OS holds whoever’s at the helm — me first, then anyone else who shows up — accountable to the milestones. Adding a collaborator becomes a matter of granting them a surface on the same substrate, not rebuilding the system for two. That’s the version of &lt;em&gt;done&lt;/em&gt; I’m building toward.&lt;/p&gt;
&lt;h2 id=&quot;the-honest-read&quot;&gt;The honest read&lt;/h2&gt;
&lt;p&gt;The temptation with a piece like this is to make three half-built things sound like one finished one. So, plainly.&lt;/p&gt;
&lt;p&gt;Hermes is four proposed decision records and one installed gateway. The runtime isn’t running yet. The migration is blocked on its first real step, and clearing that block is mine to do. The substrate-to-runtime contract — the actual interface by which Hermes loads memory and writes its audit records — is still marked “to be determined” in its own decision record. Honcho means a slice of my personal life-stream data will live on someone else’s managed cloud; I’ve accepted that consciously, but it’s a trade-off, not a free lunch. And I’ve proposed swapping one runtime for one runtime plus three substrate services — which is three new things that can be down at 2 a.m.&lt;/p&gt;
&lt;p&gt;FortyTwo has never run a real idea end to end. Vasko OS is a dozen agents that, for now, mostly exist as paragraphs.&lt;/p&gt;
&lt;p&gt;What I’m least sure of isn’t any single stream — it’s the convergence. It’s easy to draw the layered diagram. It’s harder to know whether FortyTwo’s idea of a finished “spec” and Vasko OS’s idea of an “executable milestone” will meet cleanly at the seam, or whether I’ll discover, when I get there, that they were subtly different shapes the whole time. I won’t know until I run an idea through the full chain, and I can’t do that until Hermes is real.&lt;/p&gt;
&lt;p&gt;The thing I’m &lt;em&gt;not&lt;/em&gt; unsure about is the direction. The bottleneck has moved upstream. Generation has been commoditised. The discipline of getting the question right, and then designing seams that hold under load, is what compounds. Three projects, pulled at honestly, turned out to share one metaphor, one design rule, and one failure mode they’re all built to make impossible. That isn’t three side projects competing for my evenings. It’s one system with three construction sites, built with the bias of someone who has spent enough years in scale-ups to know which decisions you regret in year three.&lt;/p&gt;
&lt;p&gt;Next month’s note will most likely be about whichever of the three breaks first. That’s usually how it goes.&lt;/p&gt;</content:encoded><category>AI</category><category>agents</category><category>Vasko OS</category><category>FortyTwo</category><category>Hermes</category><category>architecture</category></item><item><title>Patches welcome</title><link>https://vasko.com.au/lab-notes/patches-welcome/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/patches-welcome/</guid><description>I installed gstack in last month&apos;s Lab Note. This month I shipped a patch back to it. A note on why open source is having a moment again, why AI coding tools are the reason solo consultants can finally participate, and why we should.</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In &lt;a href=&quot;https://vasko.com.au/lab-notes/bubble-wrap-to-merged-pr&quot;&gt;last month’s Lab Note&lt;/a&gt; I installed gstack on a fresh mini PC and watched it ship a feature end-to-end. This one is the small sequel: I found a bug in &lt;code&gt;/ship&lt;/code&gt;, fixed it, and the patch landed in gstack v1.30.0.0 with credit in the changelog.&lt;/p&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/gstack-patch.jpeg&quot; alt=&quot;GitHub mobile notification: @garrytan mentioned you on garrytan/gstack #1302 — &apos;Thanks @vaskockorovski — your fix shipped in v1.30.0.0 (#1391) with credit in the CHANGELOG. Closing since it&apos;s already on main. Appreciate the substantial work on the Plan Completion gate.&apos;&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;The notification that prompted this article. A maintainer’s public thank-you, on a stranger’s repo, for an afternoon of work.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I want to talk about why open source is having a moment again, and why solo consultants like me can finally participate in it — not as a hobby, but as part of the day job.&lt;/p&gt;
&lt;h2 id=&quot;quick-context&quot;&gt;Quick context&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/garrytan/gstack&quot;&gt;gstack&lt;/a&gt; is Garry Tan’s opinionated stack of Claude Code commands — twenty-three tools that act as CEO, designer, eng manager, release manager, doc engineer, and QA, all bundled into a single cohesive workflow. It’s the closest thing the AI-coding world has to a “default” right now. 92.5k stars on GitHub. I’ve been using it across every project for a few sprints.&lt;/p&gt;
&lt;p&gt;The bug was in &lt;code&gt;/ship&lt;/code&gt; — the step at the end of a sprint that’s meant to verify everything in PLAN.md actually got built. The gate was supposed to catch deliverables that didn’t ship. It didn’t, and one of mine slipped through on a recent sprint. My PR added four structural fixes to the gate, locked them in with a small test suite, and shipped.&lt;/p&gt;
&lt;p&gt;That’s the technical story. The interesting story is what happens &lt;em&gt;around&lt;/em&gt; it.&lt;/p&gt;
&lt;h2 id=&quot;open-source-is-having-a-moment&quot;&gt;Open source is having a moment&lt;/h2&gt;
&lt;p&gt;For most of the last decade, the centre of gravity in software shifted away from open source. SaaS won the developer-mindshare war. The default place to put a tool wasn’t a public repo with a permissive licence — it was a managed product behind a credit card and a usage tier. Open source kept shipping in the background (it always does), but the cultural energy moved elsewhere, and contributing to a project you didn’t already work for started to feel like a thing other people did.&lt;/p&gt;
&lt;p&gt;That’s flipping. The tools defining the AI-coding wave — Claude Code, Codex, gstack, OpenClaw — are open or open-friendly by default. Public repos are where the interesting work is happening again. PRs land, changelogs get written, maintainers say thank-you in public. It’s reminiscent of the early-2010s GitHub era, except the contributors aren’t all full-time engineers anymore.&lt;/p&gt;
&lt;p&gt;Open source never went away. But the gravity is returning to it, and that matters — for the durability of the tools we depend on, and for who gets to shape them.&lt;/p&gt;
&lt;h2 id=&quot;the-default-move-is-silence&quot;&gt;The default move is silence&lt;/h2&gt;
&lt;p&gt;If you’re a solo consultant and a tool you depend on has a bug, the default move is one of three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Work around it.&lt;/strong&gt; Quietly. You’ve got billable work to do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File a bug report.&lt;/strong&gt; Maintainer might pick it up in three months. Or never.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DM the maintainer.&lt;/strong&gt; They might thank you. Nothing changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;None of these are bad. They’re the path of least resistance. The default mode of consulting is consumption — you use the tools, you bill for the outcomes, you move on.&lt;/p&gt;
&lt;p&gt;The fourth option — &lt;em&gt;fix it yourself and submit a PR&lt;/em&gt; — used to be reserved for people with the time, the language fluency, and the appetite for navigating a stranger’s codebase. Most consultants don’t fit that description. Most of us have built our careers around steering ships, not soldering wires.&lt;/p&gt;
&lt;p&gt;But that’s changed.&lt;/p&gt;
&lt;h2 id=&quot;the-economics-flipped&quot;&gt;The economics flipped&lt;/h2&gt;
&lt;p&gt;Here’s the bit worth internalising: &lt;strong&gt;Claude Code wrote most of the patch&lt;/strong&gt;. I diagnosed the failure mode (the part that needs the consultant brain), told it which guardrails the fix needed to enforce, and handed it off. Five test assertions, no LLM dependency, runs in 60 milliseconds. The whole thing took an afternoon.&lt;/p&gt;
&lt;p&gt;Five years ago this would have been a weekend job. Reading through a stranger’s TypeScript codebase, finding the right hooks, writing the test harness, learning their conventions — it all adds up. Today, the cost of being a contributor instead of just a consumer has collapsed.&lt;/p&gt;
&lt;p&gt;This is the quiet thing the new generation of AI coding tools has actually given me back: code itself. I’ve been a CTO and a fractional CTO for long enough that “still ships code” was edging out of the honest answer. With Claude Code and Codex I can guide the work line by line, ship it, test it, and stand behind it. The barrier to participating in someone else’s project — really participating, not just reporting things — collapsed at the same time.&lt;/p&gt;
&lt;p&gt;That changes the calculus. If the fix is going to take an afternoon and the alternative is working around the bug forever — of course you ship the patch.&lt;/p&gt;
&lt;h2 id=&quot;why-bother--the-consultants-angle&quot;&gt;Why bother — the consultant’s angle&lt;/h2&gt;
&lt;p&gt;There’s a narrow self-interest version of this: a CHANGELOG mention is better positioning than any number of “thought leadership” posts. It’s verifiable. It’s external. It says you live inside the tools your clients are deciding whether to adopt.&lt;/p&gt;
&lt;p&gt;But the broader version is more honest. If you’re going to spend the next decade building inside someone else’s stack, the relationship can’t be one-way. You owe the people maintaining your tools the same diligence you owe your clients.&lt;/p&gt;
&lt;p&gt;Solo consulting can feel transactional in a way that quietly corrodes — you’re always the supplier, the deliverable comes from you, the dynamic flows one direction. Pushing code upstream is one of the few moves that breaks that pattern. It’s small. It costs you a few hours. And it puts you back in the position of being a participant in the ecosystem, not just a vendor charging for outcomes within it.&lt;/p&gt;
&lt;h2 id=&quot;the-practical-takeaway&quot;&gt;The practical takeaway&lt;/h2&gt;
&lt;p&gt;If you’re a solo consultant or a fractional CTO and you’ve been quietly working around the same bug for months: write the issue. If you’ve got a fix in mind, write the PR. The barrier is roughly an afternoon and a bit of conviction.&lt;/p&gt;
&lt;p&gt;The maintainers will thank you. The changelog will have your name on it. And the next time a client asks whether you actually ship code, you’ll have something better than a portfolio. You’ll have a commit history.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/garrytan/gstack/pull/1302&quot;&gt;PR #1302&lt;/a&gt;, if you want to see what an “afternoon” looks like in 2026.&lt;/p&gt;</content:encoded><category>gstack</category><category>open source</category><category>AI</category><category>Claude Code</category><category>consulting</category></item><item><title>Bubble wrap to merged PR: eight hours with gstack</title><link>https://vasko.com.au/lab-notes/bubble-wrap-to-merged-pr/</link><guid isPermaLink="true">https://vasko.com.au/lab-notes/bubble-wrap-to-merged-pr/</guid><description>A mini PC, an open-source Claude Code skill pack, and a Tuesday afternoon spent watching an autonomous AI engineering team catch the bugs I would have shipped.</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A mini PC ordered off Amazon Australia arrived within 24 hours. By mid-morning the next day it was out of bubble wrap and running an autonomous AI engineering team.&lt;/li&gt;
&lt;li&gt;That team (gstack, an open-source Claude Code skill pack) planned, built, reviewed, tested, and shipped a working feature to production in about five hours.&lt;/li&gt;
&lt;li&gt;Three AI-run review phases caught two design mistakes and one critical bug before a line of code was written — including a bug that would have silently corrupted data once the project scaled.&lt;/li&gt;
&lt;li&gt;The AI asked me for help five times, each time with clear options. It never guessed.&lt;/li&gt;
&lt;li&gt;While verifying the deployment, the process surfaced a separate production bug that had been silently broken for weeks.&lt;/li&gt;
&lt;li&gt;Total cost of the AI tooling: zero. It’s all open source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What this actually means:&lt;/strong&gt; the interesting shift in AI-assisted software work right now isn’t bigger or smarter models — those keep improving in the background. It’s the scaffolding built around them. Structured review workflows like this catch the kinds of mistakes that standard AI tools ship, while staying honest about what they don’t know. For anyone watching AI transform knowledge work, this is what the next step actually looks like in practice: less a single robot writing code, more a process that thinks before it builds.&lt;/p&gt;
&lt;p&gt;At 7:47 AM on a Tuesday, a Minisforum UM890 Pro mini PC was still wrapped in bubble wrap on my desk. By mid-afternoon it had shipped a feature end-to-end — Linear tickets to merged PR to production canary — run by an autonomous AI engineering team installed on it earlier that morning. Eight hours. Bubble wrap to merged PR.&lt;/p&gt;
&lt;p&gt;The AI team was gstack — an open-source add-on for Claude Code, written by Garry Tan. It’s built around a sprint structure: plan, build, review, test, ship. I pointed it at one task (a CSV export feature for a personal side project — nine smaller tickets grouped inside one epic in Linear, the task-tracking tool), told it to start, and watched. The full loop ran on its own. It only stopped to ask me for help five times.&lt;/p&gt;
&lt;div class=&quot;not-prose my-10 rounded-md border border-stone-200 bg-stone-50 p-6 md:p-7&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-5&quot;&gt;The stack&lt;/p&gt;&lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4 text-sm&quot;&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;Hardware&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Minisforum UM890 Pro (AMD Ryzen 9 8945HS, 32GB RAM, ~$1,340 AUD)&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;A small, powerful desktop PC.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;Operating system&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Ubuntu Server 24.04 LTS, headless&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;Linux, with no screen attached.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;Remote access&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Tailscale (mesh VPN + SSH)&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;A private network so I can log in from anywhere.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;AI tooling&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Claude Code + gstack (MIT licensed)&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;The AI that does the coding work.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;Task management&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Linear (via MCP)&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;Where I track what needs doing.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;Browser testing&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Playwright + headless Chromium&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;Automated clicks in an invisible browser.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;The app being changed&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Next.js 14 + Supabase + Vitest + TypeScript&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;The underlying website and database.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-wider text-stone-400&quot;&gt;Where it runs online&lt;/p&gt;&lt;p class=&quot;mt-1 font-mono text-xs text-stone-700&quot;&gt;Vercel + Cloudflare DNS&lt;/p&gt;&lt;p class=&quot;mt-1 text-stone-500&quot;&gt;The hosting and domain setup.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Before a line of code was written, gstack ran three independent reviews of the plan itself: a commercial sanity check, a design review, and an engineering review. Same plan, three different perspectives, each scoring it and proposing edits. Anything flagged by two reviewers or more counted as a consensus catch — the thing the author missed in their own framing.&lt;/p&gt;
&lt;p&gt;Three catches landed. Two of them appeared in multiple reviews at once; the third came from the engineering review alone, and was the most important of the three.&lt;/p&gt;
&lt;h2 id=&quot;the-bugs-that-didnt-ship&quot;&gt;The bugs that didn’t ship&lt;/h2&gt;
&lt;h3 id=&quot;catch-1-the-overbuilt-design&quot;&gt;Catch #1: The overbuilt design&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; The plan called for a popup window where users would pick which columns they wanted to export. It looked sensible on paper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The risks:&lt;/strong&gt; Two extra days of building. A more complicated interface for the 95% of users who just want to click “download” and get the whole list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Options considered:&lt;/strong&gt; Ship the popup as planned. Ship a simpler button-first version and add the popup later. Drop the column-picking feature entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it was resolved:&lt;/strong&gt; The CEO reviewer and the design reviewer independently flagged the same issue from different angles — the CEO reviewer on scope, the design reviewer on flow. Resolution: make the button the primary action, tuck the column-picking behind a small menu for the rare user who wants to customise. Ship the simple version today; add complexity only if users ask.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this prevented:&lt;/strong&gt; Two days of unnecessary engineering. An over-engineered interface for almost every user. The kind of scope creep that quietly bloats side projects until nobody wants to work on them.&lt;/p&gt;
&lt;h3 id=&quot;catch-2-the-skipped-tests&quot;&gt;Catch #2: The skipped tests&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; The plan proposed shipping without tests. The reasoning sounded reasonable — the project had no testing framework installed yet, and setting one up was framed as “its own initiative” that could come later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The risks:&lt;/strong&gt; In a codebase that exports data — where a small mistake silently corrupts files rather than breaking loudly — “we’ll add tests later” almost always means “we never will.” Every team has a backlog of code that was supposed to be tested eventually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Options considered:&lt;/strong&gt; Ship without tests and add them later. Ship with a minimum set of tests covering just this feature. Defer the whole feature until the testing framework is in place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it was resolved:&lt;/strong&gt; The engineering reviewer flagged it: “defer tests on a data-export endpoint and you will never come back to them.” The CEO reviewer independently reached the same conclusion from the scope side. Resolution: install vitest (a JavaScript testing framework) and write five tests inside this same pull request. Thirty minutes of additional work. Non-negotiable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this prevented:&lt;/strong&gt; A data-export feature shipping with zero coverage into a codebase with no testing habit. By the time the feature actually shipped, the count was fourteen tests, not five — once the infrastructure was in place, the team kept going.&lt;/p&gt;
&lt;h3 id=&quot;catch-3-the-silent-1000-row-bug&quot;&gt;Catch #3: The silent 1,000-row bug&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; The plan called for reading domains out of the database and returning them as a CSV file. Looked straightforward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The risks:&lt;/strong&gt; The database, by default, only ever returns the first 1,000 results — and it does so silently, with no warning. Once I had more than a thousand domains in my portfolio, my export would have quietly started missing rows, and neither I nor any user would have known. Not a broken file. Not an error. A correct-looking file with the last rows invisibly dropped.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Options considered:&lt;/strong&gt; Ignore the cap and hope the portfolio never crossed a thousand. Raise the cap explicitly in the query. Raise the cap &lt;em&gt;and&lt;/em&gt; verify after the fact that the server actually returned everything it claimed to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it was resolved:&lt;/strong&gt; The engineering reviewer rated this critical — a ship-blocker. Resolution: raise the query cap to 50,000 rows, and add a sanity check that throws an error if the server reports more rows exist than it actually returned. Two lines of code total.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this prevented:&lt;/strong&gt; The fix was one line of code. The cost of missing it could have been months of corrupted data.&lt;/p&gt;
&lt;div class=&quot;not-prose my-14&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-6 text-center&quot;&gt;The run, in numbers&lt;/p&gt;&lt;div class=&quot;grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-y-10 gap-x-8 border-y border-stone-200 py-10&quot;&gt;&lt;div class=&quot;text-center md:text-left&quot;&gt;&lt;p class=&quot;text-5xl md:text-6xl font-serif font-medium text-stone-900 leading-none&quot;&gt;22&lt;/p&gt;&lt;p class=&quot;mt-3 text-sm text-stone-500 leading-snug&quot;&gt;decisions logged by the AI&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;text-center md:text-left&quot;&gt;&lt;p class=&quot;text-5xl md:text-6xl font-serif font-medium text-stone-900 leading-none&quot;&gt;4&lt;/p&gt;&lt;p class=&quot;mt-3 text-sm text-stone-500 leading-snug&quot;&gt;times it asked me for help&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;text-center md:text-left&quot;&gt;&lt;p class=&quot;text-5xl md:text-6xl font-serif font-medium text-stone-900 leading-none&quot;&gt;3&lt;/p&gt;&lt;p class=&quot;mt-3 text-sm text-stone-500 leading-snug&quot;&gt;independent review phases&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;text-center md:text-left&quot;&gt;&lt;p class=&quot;text-5xl md:text-6xl font-serif font-medium text-stone-900 leading-none&quot;&gt;2&lt;/p&gt;&lt;p class=&quot;mt-3 text-sm text-stone-500 leading-snug&quot;&gt;cross-phase bugs caught&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;text-center md:text-left&quot;&gt;&lt;p class=&quot;text-5xl md:text-6xl font-serif font-medium text-stone-900 leading-none&quot;&gt;1&lt;/p&gt;&lt;p class=&quot;mt-3 text-sm text-stone-500 leading-snug&quot;&gt;critical bug that didn’t ship&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;text-center md:text-left&quot;&gt;&lt;p class=&quot;text-5xl md:text-6xl font-serif font-medium text-stone-900 leading-none&quot;&gt;14&lt;/p&gt;&lt;p class=&quot;mt-3 text-sm text-stone-500 leading-snug&quot;&gt;tests added&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;mt-6 flex flex-col sm:flex-row justify-center items-center gap-x-10 gap-y-2 text-sm text-stone-500&quot;&gt;&lt;p&gt;&lt;span class=&quot;font-medium text-stone-800&quot;&gt;~3 hours&lt;/span&gt; autonomous work&lt;/p&gt;&lt;span class=&quot;hidden sm:inline text-stone-300&quot; aria-hidden=&quot;true&quot;&gt;·&lt;/span&gt;&lt;p&gt;&lt;span class=&quot;font-medium text-stone-800&quot;&gt;$0&lt;/span&gt; in AI tooling costs&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;the-team-and-what-each-role-did&quot;&gt;The team (and what each role did)&lt;/h2&gt;
&lt;p&gt;gstack splits the sprint across a set of specialised AI reviewers, each one tuned for a different seat at the table. It’s the same mental model as putting a founder, a designer, an engineer, a QA lead, and a release engineer on a cross-functional team — except all of them are the same underlying AI model, invoked with different instructions and given different things to care about.&lt;/p&gt;
&lt;p&gt;Translated into the roles you’d recognise from any real software team:&lt;/p&gt;
&lt;h3 id=&quot;the-ceo-reviewer&quot;&gt;The CEO reviewer&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Challenges whether the plan is actually worth building. Asks “Is this the simplest version?” and “Could we ship less and still ship value?” — the same questions a founder asks in a product review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Flagged the export popup as two extra days of work for something 95% of users would never touch. Recommended shipping the simple version first and only adding complexity if users asked for it.&lt;/p&gt;
&lt;h3 id=&quot;the-design-reviewer&quot;&gt;The design reviewer&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Looks at the planned interface from the user’s side of the screen — information hierarchy, button labels, accessibility, what happens on each state. The senior designer you’d want catching spec gaps before code starts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Caught the same issue as the CEO reviewer from a different angle — the plan had the primary flow inverted (click button, open popup, choose options, then download), when the right flow is click-and-download, with the popup only as an escape hatch for the 5% of users who actually want to pick columns.&lt;/p&gt;
&lt;h3 id=&quot;the-engineering-reviewer&quot;&gt;The engineering reviewer&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Looks at the technical architecture, test coverage, performance, and security of the plan before any code is written. The staff engineer who gets to say “wait, have we thought about this?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Produced the single most valuable catch of the sprint — the silent 1,000-row database cap that would have quietly corrupted every export once the portfolio crossed a thousand entries. Also flagged the missing tests.&lt;/p&gt;
&lt;h3 id=&quot;office-hours&quot;&gt;Office hours&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Runs forcing questions on a half-formed idea before you commit to building it. The equivalent of sitting down with a Y Combinator partner for thirty minutes and getting asked “Do you actually need to build this?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; &lt;em&gt;Not triggered on this task&lt;/em&gt; — the plan came in sharp enough to skip it. On earlier runs it’s where I pressure-test whether a thing is worth building at all. Available, just not needed this time.&lt;/p&gt;
&lt;h3 id=&quot;the-code-reviewer&quot;&gt;The code reviewer&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Reads the finished code after it’s written but before it’s submitted for QA. Looks for the classes of mistake that slip past the author because they’ve been staring at the same file for too long.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Flagged two issues in the finished code — a subtle bug in how a timer was cleaned up (a race condition that would have occasionally misfired), and missing size limits on user input that could have caused performance issues under adversarial conditions. Five-line fixes each. Both got applied before the PR opened.&lt;/p&gt;
&lt;h3 id=&quot;qa&quot;&gt;QA&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Clicks through the feature in a real browser — the same work a manual QA lead would do. Tests the happy path, the empty state, the edge cases, and anything that could plausibly break.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Ran five end-to-end tests through an invisible automated browser against a local copy of the app. All five passed. Notably, it refused to accept my password pasted into chat — it had me drop the credentials into a temporary file it could shred afterwards, so the password never entered the conversation history.&lt;/p&gt;
&lt;h3 id=&quot;the-release-engineer&quot;&gt;The release engineer&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Handles the mechanics of opening a pull request — re-running all tests, writing a clean PR description, making sure the branch is in a state where a reviewer (or a deploy system) can trust it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Re-ran all 14 tests, typecheck, and lint. All green. Opened the pull request via GitHub’s command-line tool. One thing it couldn’t fix on its own — pushing code over the default secure channel failed from inside its environment — so it asked me for permission to switch authentication methods. Granted, once.&lt;/p&gt;
&lt;h3 id=&quot;the-deploy-engineer&quot;&gt;The deploy engineer&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What it does (the role):&lt;/strong&gt; Merges the PR, watches the deploy land, and runs a safety check against the live site before declaring the feature shipped. Where the rubber meets the road.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it did this time:&lt;/strong&gt; Refused to run its safety check against a temporary preview URL — insisted on the real production address. I didn’t have one configured yet. Adding it turned out to be the moment that surfaced a completely separate bug — production had been silently broken for weeks. That story is two sections below.&lt;/p&gt;
&lt;h2 id=&quot;when-it-stopped-and-asked&quot;&gt;When it stopped and asked&lt;/h2&gt;
&lt;p&gt;The autonomous part of “autonomous” is where AI agents most often earn their bad reputation: pressing on through ambiguity, guessing at credentials, committing things nobody wanted committed. This run had five escalations, each with enumerated options and a recommended path. Not once did it guess.&lt;/p&gt;
&lt;div class=&quot;not-prose my-10 rounded-md border border-stone-200 bg-stone-50 p-6 md:p-7&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-4&quot;&gt;Five moments it stopped and asked&lt;/p&gt;&lt;ul class=&quot;space-y-3 text-sm text-stone-700&quot;&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;Linux’s security policy blocked the automated browser from launching.&lt;/strong&gt; Three options offered (system-wide unload, per-profile exception, &lt;code class=&quot;text-xs bg-white border border-stone-200 px-1 py-0.5 rounded&quot;&gt;--no-sandbox&lt;/code&gt;); recommended per-profile.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;The project needed a configuration file that wasn’t there yet.&lt;/strong&gt; Asked for the values rather than guessing defaults.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;QA needed a login to test the signed-in flow.&lt;/strong&gt; Refused to accept a password in chat. Suggested a shreddable temp file so the password never entered the transcript.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;It couldn’t push code to GitHub using the default authentication method.&lt;/strong&gt; Proposed switching to a different method via GitHub’s command-line tool. One command.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;The deploy tool refused to run its safety check against a temporary preview URL.&lt;/strong&gt; Asked for the real production URL. I didn’t have one configured — adding it revealed that production had been broken for weeks.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;h2 id=&quot;the-production-bug-it-surfaced-by-accident&quot;&gt;The production bug it surfaced by accident&lt;/h2&gt;
&lt;p&gt;That fifth escalation turned out to be the one that mattered most. I added &lt;code&gt;domains.vasko.com.au&lt;/code&gt; via Cloudflare (the domain name service), pointed it at the project on Vercel (the hosting platform), and ran the canary — a safety check that hits the live site right after deploy and confirms the important pages are actually working. The canary failed. Production had been returning server errors for weeks because Supabase (the database behind the app) had a security allowlist that didn’t include any real address I’d been using. Nobody had noticed — I hadn’t sent the URL to anyone.&lt;/p&gt;
&lt;blockquote class=&quot;not-prose my-10 border-l-4 border-teal-700 pl-6 py-1&quot;&gt;&lt;p class=&quot;text-2xl md:text-3xl font-serif font-medium text-stone-900 leading-snug&quot;&gt;The bug was mine. The discovery was the process.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;If the deploy tool had been willing to run the canary against whatever URL was handy, I would never have looked.&lt;/p&gt;
&lt;p&gt;The server errors were also being amplified by LeakIX scanners from DigitalOcean — automated security researchers who probe every new domain within hours of it going live — hammering every path the site had ever exposed. Put a domain on the public internet and you have three hours before someone starts looking. That part I expected.&lt;/p&gt;
&lt;h2 id=&quot;the-moment-it-caught-itself&quot;&gt;The moment it caught itself&lt;/h2&gt;
&lt;p&gt;Before the PR opened, gstack ran its own post-implementation retrospective. I read it back. It had caught itself making a mistake.&lt;/p&gt;
&lt;p&gt;During implementation it had swapped the planned test coverage layer — route-level, as specified in the reviewed plan — for something more pragmatic: module-level, fourteen tests instead of five. The tests were better than the plan called for. But the swap was unilateral, and gstack flagged itself for it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Wrong threshold. Since the plan went through autoplan’s three-review gauntlet, the layer spec was itself a reviewed decision — not an implementation detail. Logged as feedback memory: &lt;code&gt;feedback_escalate_plan_deviations.md&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It named the mistake precisely, diagnosed why (confused “implementation detail” for “reviewed-plan decision”), and wrote the correction to persistent memory so future runs escalate instead of deciding on their own. This is rare behaviour in current AI agents. It is ordinary behaviour in senior engineers after a retrospective.&lt;/p&gt;
&lt;p&gt;I want to name something plainly: Garry Tan built &lt;a href=&quot;https://github.com/garrytan/gstack&quot;&gt;gstack&lt;/a&gt;, Peter Steinberger built &lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;OpenClaw&lt;/a&gt; (247K stars, essentially solo), and Andrej Karpathy articulated the shift in how software is written that put pressure on the whole scene to catch up. None of them charged me anything this morning, and none of them gatekept anything behind a waitlist. Open source drifted for years toward VC-backed “open core” and projects that closed the second they got traction; gstack is 75K stars, MIT, no paid tier, and OpenClaw is the same shape. The pre-corporate open-source ethos is still alive in places, and this morning it did a day’s work for me.&lt;/p&gt;
&lt;p&gt;Right. Back to the receipts.&lt;/p&gt;
&lt;div class=&quot;not-prose my-10 rounded-md border border-stone-200 bg-stone-50 p-6 md:p-7&quot;&gt;&lt;p class=&quot;text-xs font-medium uppercase tracking-widest text-stone-400 mb-4&quot;&gt;Who built this&lt;/p&gt;&lt;ul class=&quot;space-y-2 text-sm text-stone-700&quot;&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;Andrej Karpathy&lt;/strong&gt; — articulated the shift.&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;Peter Steinberger&lt;/strong&gt; — &lt;a href=&quot;https://github.com/openclaw/openclaw&quot; class=&quot;text-teal-700 underline decoration-stone-300 underline-offset-2 hover:decoration-teal-500&quot;&gt;OpenClaw&lt;/a&gt; (247K stars, MIT).&lt;/li&gt;&lt;li&gt;&lt;strong class=&quot;font-medium text-stone-900&quot;&gt;Garry Tan&lt;/strong&gt; — &lt;a href=&quot;https://github.com/garrytan/gstack&quot; class=&quot;text-teal-700 underline decoration-stone-300 underline-offset-2 hover:decoration-teal-500&quot;&gt;gstack&lt;/a&gt; (75K stars, MIT; also President of YC).&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;mt-5 text-xs text-stone-500 leading-relaxed&quot;&gt;My own Sally agent runs on OpenClaw, which is how I found gstack. The open-source AI agent ecosystem isn’t a dozen competing platforms — it’s a small set of composable primitives that different people wire together differently.&lt;/p&gt;&lt;/div&gt;
&lt;figure class=&quot;not-prose my-10&quot;&gt;&lt;img src=&quot;https://vasko.com.au/lab-notes/gstack-session-close-out.png&quot; alt=&quot;gstack session close-out screenshot, showing a table classifying every change in the working tree by who made it (me, gstack, Next.js), with suggested actions, followed by a plain-language summary of the full chain that ran&quot; class=&quot;w-full rounded-md border border-stone-200&quot;/&gt;&lt;figcaption class=&quot;mt-3 text-sm italic text-stone-500 text-center&quot;&gt;gstack’s session close-out. After shipping, it classified every change in the working tree by who made it, flagged one as mild scope creep it should have asked about first, and stated the outcome without ceremony.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The takeaway is not that AI is replacing engineers, and it is not that AI is the future. Hold your existing position on both — this article doesn’t challenge either. What it shows is narrower. Standard Claude Code, left alone, would have built the modal, deferred the tests, and silently truncated at a thousand rows. Same underlying model. Different scaffolding. Different outcome. The frontier worth watching is not bigger models. It is better process around them.&lt;/p&gt;
&lt;p&gt;This was a Tuesday.&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;07:47&lt;/strong&gt; — UM890 Pro out of bubble wrap&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~09:30&lt;/strong&gt; — Ubuntu Server 24.04 installed, Tailscale configured, headless SSH working&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~11:15&lt;/strong&gt; — Claude Code + gstack installed (after a detour for unzip, AppArmor, and one missing &lt;code&gt;.env.local&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~12:00&lt;/strong&gt; — &lt;code&gt;/autoplan&lt;/code&gt; completes: 22 decisions logged, 4 taste gates approved, 2 cross-phase catches (modal overbuilt, tests deferred = never)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~12:45&lt;/strong&gt; — Implementation ships: 8 atomic commits across csv-columns, &lt;code&gt;POST /api/domains/export&lt;/code&gt;, download hook, UI, 14 vitest tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~13:00&lt;/strong&gt; — &lt;code&gt;/review&lt;/code&gt; flags a stale setTimeout race and missing zod input caps. Fixes both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~13:45&lt;/strong&gt; — &lt;code&gt;/qa&lt;/code&gt; runs headless Chromium against local dev server, 5/5 golden paths pass, 95/100 health score&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~14:15&lt;/strong&gt; — &lt;code&gt;/ship&lt;/code&gt; opens &lt;a href=&quot;https://github.com/vaskockorovski/domain-hq/pull/1&quot;&gt;PR #1&lt;/a&gt;, all gates green&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~14:30&lt;/strong&gt; — &lt;code&gt;/land-and-deploy&lt;/code&gt; prompts for a production URL I didn’t have. Adds &lt;code&gt;domains.vasko.com.au&lt;/code&gt; via Cloudflare DNS → discovers prod has been quietly returning 500s and auth is broken on the new origin&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~15:15&lt;/strong&gt; — Supabase auth allowlist fixed, Vercel env vars reconciled, production healthy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~15:35&lt;/strong&gt; — PR merges. Canary verifies production. Done.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-technical-receipts&quot;&gt;The technical receipts&lt;/h2&gt;
&lt;p&gt;The rest of this post is for engineers, architects, and technical readers who want to see what actually happened under the hood. Skip it without guilt if you’re not that person — the article’s finished above.&lt;/p&gt;
&lt;p&gt;For the rest of you: a slightly-less-breezy version of what gstack actually did at each phase. Every phase produced an artefact; every artefact fed the next phase.&lt;/p&gt;
&lt;h3 id=&quot;plan-phase--autoplan&quot;&gt;Plan phase — /autoplan&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Input: 9 Linear tickets pulled via MCP (research → interface → endpoint → UI → client logic → filtering → security → tests → docs).&lt;/li&gt;
&lt;li&gt;Three review subagents ran in sequence: CEO mode (scope/premise), Design (hierarchy/states/a11y/microcopy), Engineering (architecture/tests/perf/security).&lt;/li&gt;
&lt;li&gt;Each review independently scored the plan, flagged findings, proposed edits. Findings that surfaced in two or three reviews became consensus catches.&lt;/li&gt;
&lt;li&gt;Output: a single plan file (&lt;code&gt;main-csv-export-plan-*.md&lt;/code&gt;) with 22 logged decisions, 4 escalated to me, ASCII architecture diagrams, locked microcopy, locked a11y spec, and 9 Linear tickets mapped to one PR with explicit dispositions (merged, deferred, scoped-down, closed-no-work).&lt;/li&gt;
&lt;li&gt;~5 minutes end-to-end.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Most AI-assisted coding starts with a single prompt. This starts with a plan that’s already survived three adversarial reviews and carries its own decision log forward into build.&lt;/p&gt;
&lt;h3 id=&quot;build-phase--implementation&quot;&gt;Build phase — implementation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Executed against the approved plan, not a fresh prompt. The plan file was the source of truth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8 atomic commits in sequence&lt;/strong&gt;, each reviewable on its own, rather than one giant PR diff that’s impossible to reason about. The commit history itself becomes review material; bisect works; each commit has a clean, self-contained scope.&lt;/li&gt;
&lt;li&gt;The 8 commits, one per logical unit:
&lt;ul&gt;
&lt;li&gt;vitest infrastructure (no test runner existed before this).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;csv-columns&lt;/code&gt; module (single source of truth for what gets exported).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/domains/export&lt;/code&gt; (route with zod validation, explicit projection, truncation guard, per-column transforms, UTF-8 BOM, CSV-injection guard).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use-csv-download&lt;/code&gt; client hook.&lt;/li&gt;
&lt;li&gt;Export button + kebab menu + options modal (design-reviewed button-as-primary-action, not the modal-first flow in the original plan).&lt;/li&gt;
&lt;li&gt;14 vitest tests plus bug fix for empty-result path.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/csv-export.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Two &lt;code&gt;/review&lt;/code&gt; fix commits (zod max caps, stale setTimeout ref).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;~25 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; The plan file is the source of truth, not the prompt. Deviations from the plan (like the unilateral test-layer swap) get caught in the retrospective because there’s a written contract to deviate from. Standard prompt-driven AI coding has nothing to deviate from in the first place.&lt;/p&gt;
&lt;h3 id=&quot;review-phase--review&quot;&gt;Review phase — /review&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Static analysis of the diff against main.&lt;/li&gt;
&lt;li&gt;Ran its own subagent review. Flagged 2 issues: a stale &lt;code&gt;setTimeout&lt;/code&gt; race in the download hook (real but low-impact) and missing &lt;code&gt;.max(200)&lt;/code&gt; zod caps on array inputs (defensive).&lt;/li&gt;
&lt;li&gt;Asked before fixing. I said fix both. Five-line changes.&lt;/li&gt;
&lt;li&gt;Produced a PR quality score with deductions explained (8/10: −1 race, −1 plan deviation on test layer).&lt;/li&gt;
&lt;li&gt;~5 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; A second AI pass against the diff catches the class of bug the author missed because they’ve been staring at the same code for too long. Different from the plan-phase reviews — those ran against the plan, this runs against the actual diff.&lt;/p&gt;
&lt;h3 id=&quot;qa-phase--qa&quot;&gt;QA phase — /qa&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Launched headless Chromium via Playwright. Required an AppArmor exception for unprivileged user namespaces on Ubuntu 24.04 — escalated, I applied it.&lt;/li&gt;
&lt;li&gt;Required a &lt;code&gt;.env.local&lt;/code&gt; pointing at the same Supabase project as production. Escalated, I populated it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credentials handled carefully.&lt;/strong&gt; Required auth credentials to test the signed-in flow, and refused to accept a password pasted into chat. Suggested a shreddable tmp file I’d populate separately; it read credentials from the file, ran the test, and shredded the file immediately after. Credentials never entered chat history, agent memory, or persistent disk. Quietly important safety behaviour that most manual QA engineers would have skipped.&lt;/li&gt;
&lt;li&gt;Ran 5 golden paths against a locally-hosted dev server: empty state, happy-path download, selection-based export, filter-based export, options modal flow. 5/5 passed. 95/100 health score (5 points off for minor lint warnings).&lt;/li&gt;
&lt;li&gt;~10 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Automated browser testing that also enforces a credentials hygiene pattern. The same agent that clicks through the UI keeps secrets out of the transcript without needing to be asked.&lt;/p&gt;
&lt;h3 id=&quot;ship-phase--ship--land-and-deploy&quot;&gt;Ship phase — /ship → /land-and-deploy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Re-ran all test suites (14/14 vitest, TypeScript clean, lint clean).&lt;/li&gt;
&lt;li&gt;Branch pushed, PR opened via &lt;code&gt;gh&lt;/code&gt;. SSH auth failed in the agent’s subshell; suggested switching the origin to HTTPS with &lt;code&gt;gh&lt;/code&gt;’s credential helper. One-time fix.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/land-and-deploy&lt;/code&gt; blocked on a missing production URL. I added &lt;code&gt;domains.vasko.com.au&lt;/code&gt; via Cloudflare DNS plus Vercel. First canary failed — the Supabase auth allowlist didn’t include the new origin. Fixed in the Supabase dashboard. Re-ran canary. Green.&lt;/li&gt;
&lt;li&gt;PR merged to main. Vercel auto-deployed. Canary verified: &lt;code&gt;/login&lt;/code&gt; returns 200, &lt;code&gt;/domains&lt;/code&gt; redirects to &lt;code&gt;/login&lt;/code&gt; via middleware (expected), root &lt;code&gt;/&lt;/code&gt; catch-all returns 404 (by design).&lt;/li&gt;
&lt;li&gt;Pre-merge readiness report written. Deploy report saved to &lt;code&gt;.gstack/deploy-reports/&lt;/code&gt;. Session close-out written, classifying every change in the working tree by who made it.&lt;/li&gt;
&lt;li&gt;~15 minutes, including the ~10 minutes I spent fixing the production URL and auth allowlist.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; The deploy phase treated production URLs as a distinct, non-substitutable input. Canarying against a Vercel preview URL looks the same on paper but exercises none of the DNS, CORS, auth, or middleware configuration that actually matters. Refusing the substitution is what surfaced the real production bug.&lt;/p&gt;
&lt;h3 id=&quot;the-artefact-chain&quot;&gt;The artefact chain&lt;/h3&gt;
&lt;p&gt;Every phase produced a file that fed the next phase. No phase started from a fresh prompt; each had the receipts from the previous one to work from.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plan phase&lt;/strong&gt; → &lt;code&gt;main-csv-export-plan-*.md&lt;/code&gt; (the reviewed, decision-logged source of truth).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build phase&lt;/strong&gt; → the plan file plus 8 atomic commits (each a reviewable artefact on its own).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review phase&lt;/strong&gt; → PR quality score with annotated deductions, fed into &lt;code&gt;/qa&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QA phase&lt;/strong&gt; → health score report, screenshots, and golden-path results, fed into &lt;code&gt;/ship&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ship phase&lt;/strong&gt; → pre-merge readiness report, fed into &lt;code&gt;/land-and-deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Land-and-deploy&lt;/strong&gt; → deploy report, canary results, and session close-out (classifying every change in the working tree by who made it — me, gstack, or Next.js scaffolding).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is what “sprint structure” actually means in practice. Not a chain of prompts — a chain of artefacts, each one reviewed and signed off before the next phase starts. The thesis of the article, in technical form: if you want to trust an autonomous AI engineering loop, you have to be able to audit every step after the fact. Artefacts make that possible; chat transcripts do not.&lt;/p&gt;
&lt;p&gt;Bubble wrap to shipped PR: roughly eight hours, with five moments of “the AI stopped and asked” and one genuinely embarrassing discovery.&lt;/p&gt;</content:encoded><category>AI</category><category>gstack</category><category>Claude Code</category><category>open source</category><category>autonomous agents</category></item></channel></rss>