Building with AI · Part 5
Can It Fail Accurately? aidd v2 and the Windows boss fight
Posted June 9, 2026
Through April, aidd was a very capable shell-and-scripts orchestrator wrapped around other people's CLIs, while the craft investment went into the template and the fleet. In May the priorities flipped. The template got sealed; the tool got rebuilt as aidd v2, a pure TypeScript/Bun runtime with a real web control panel. If I had to compress the last five weeks into one line, it's this: aidd's center of gravity moved from "can it run?" to "can it fail accurately?"
Sealing the template (and the confession that came with it)
On May 5, I tagged Spernakit v3.8.0-lts: terminal v3, patch-only. The honest part: I didn't tag LTS because the template was perfect. I tagged it because it was good enough, and one more month of "just one more refinement" would cost more than any imperfection I'd be locking in. Hundreds of dance commits were producing polish, not product. The template was finished. I was the one who hadn't accepted it.
So the guard rails got built for me: hard fails on dependency drift, on lockfile changes, on manifest staleness, plus canonical-JSON snapshots of the public surface that future gate runs will diff against and refuse. The mechanism is a network of small, cheap checks designed to yell at their own author.
Will the freeze hold? Honest answer: I don't know. The pressure to "improve" a thing you stare at every day is constant, and I've already felt the itch. Ask me in a month.
What sealing actually bought wasn't a frozen artifact, it was permission to stop polishing and point the discipline somewhere new. The first thing built after the seal wasn't an app. It was AgentWatch, a read-only scanner that inventories what my AI agents can actually reach: remote endpoints, broad permissions, credential paths, dynamic tools. The agents have quietly acquired capability surfaces, and making the invisible visible is the first requirement for sane control. Instrument the instruments. That's the theme of everything below.
v2: the cutover, and the first real orchestration bug
aidd v2 came up alongside v1 as a parallel implementation, and the cutover ran the way the dance taught: parity measured against a frozen v1 reference, prompt snapshots regenerated deterministically, defaults kept boring, inspectable, and easy to reverse if validation failed. By May 15, aidd was dogfooding itself - a full audit cycle on its own repo produced 44 findings.
That dogfood run also exposed the first genuinely interesting orchestration bug. aidd's coordinator had learned to propose fleet-level maintenance suggestions. Then coding runs started treating those suggestions as coding work, abandoning the feature backlog to chase advisory maintenance. The fix mattered beyond the bug: coding mode selects feature-backed backlog work; coordinator priority is advice, not a command. That boundary is what keeps autonomous coding reviewable - selected feature, scoped work, matching completion marker. The system exposed a flaw in its own orchestration rules, and the rules got more explicit. The runtime generated problems, exposed them through its own UI, and then corrected the rules that caused them. That's the loop I wanted.
Late May stacked up the operating surface at a release-a-day pace: audit management, command and skill catalogs, invocation telemetry, an MCP server, a Telegram bridge to the coordinator, standalone binary packaging. And the big one, May 22: runs detached from the web process lifetime. The control panel became a viewer of work it doesn't own. The CLI owns the run, writes heartbeats, and the panel reconciles from evidence on disk. aidd web should be a control panel, not the owner of the actual work lifetime. You can start runs from any surface and aidd will be there.
Windows had opinions about that.
The boss fight: Windows process semantics
Every orchestrator that spawns long-lived children on Windows eventually fights the same battles. aidd has now fought four of them, and the fixes are worth writing down because each one was proven with a minimal reproduction, not folklore:
taskkill /Fdelivers no catchable signal, so graceful shutdown can't exist unless you build it. Hence aPOST /admin/shutdownendpoint, so the process can be asked politely from the outside.- Node's
child_process.spawnlets children inherit the parent's HTTP listen socket. A launched app pinned aidd's own port; after a restart, the port stayed LISTENING, attributed to a dead PID, until reboot. Every child of the web backend now spawns throughBun.spawn, which restricts inherited handles. This class of bug appeared three times before the fix was structural rather than local. That's the real lesson. - Bun places children in the parent's job object, and parent exit kills the job.
unref()doesn't help. So a "detached" run died the instant the panel restarted - proven with a repro where the child wrote zero lines and died with an empty stderr. The fix is baroque and correct: a transient hidden PowerShell bridge (Start-Process) breaks the new process out of the job object, the real argv travels in a JSON payload file so user arguments can never inject shell metacharacters, and the run's PID gets reconciled from its first heartbeat. - A healthy hour-long run and a hung one look identical without liveness data. That caused a real misdiagnosis during beta. Runs now persist heartbeat age and activity state, and the UI shows a green/amber/red dot. Silence is not proof.
None of this is in any tutorial (that I came across anyway). All of it is the actual cost of "runs unattended on Windows, first-class."
Rooting out "done"
This past week produced the most instructive run of tiny releases in the changelog. Feature badges were rendering gray for features marked done. The quick fix - alias done to green - actually shipped, and got reverted the next release as the wrong fix. completed is the canonical status; done was invalid data written by an audit prompt that taught agents the wrong vocabulary. The correction went to the source: the prompt got fixed, the feature store now refuses non-canonical statuses outright, and invalid data renders red instead of being quietly laundered into green. Eight days of releases, and the most instructive change was about one four-letter word.
The same week, the same philosophy landed everywhere at once: a 300-line file gate in smoke:qc; a typed WebSocket event contract the frontend must exhaustively handle or the build fails; an amber "Success ยท check failed" state so a clean exit code can no longer mask a failed quality gate; exit codes decoded into operator-readable sentences ("No result emitted - backend exited without emitting AIDD_RESULT"); and a coordinator that got failure-reason recording one release before it got auto-cycling. Deliberately, because automating a process that fails silently just automates confusion...
Put a mechanical check where a hope used to be. That's aidd v2's whole doctrine now.
The next test of that doctrine is the one I'm most nervous about. Everything aidd has ever touched is a project I created, with a contract I wrote. The plan for this week is a front door: point aidd at a codebase it didn't write and have it learn the project - analyze, profile, inventory, audit - without touching a line of the app's own code. If the doctrine is real, it should survive contact with a stranger's repo. We'll find out.