In one screen
What happened. An audit of 233 pages produced two shortlists. Design boards went through 21 feedback rounds with Phil. A consolidated prototype became the pixel reference. Stage 1, a static header behind a flag, went live on day three. A PRD for the WordPress-managed menu converged over ten adversarial rounds, was approved from a plain-English pack, converted to 34 sized stories, and built by otto in one day. It was verified on staging and released to production in ten minutes with a rehearsed rollback.
Why the autonomous part worked. The design was frozen and byte-comparable before the PRD existed. An approach memo surfaced the server prerequisites. The PRD carried dated decisions, 18 invariants, and acceptance criteria that quote command output. A panel with a second model family converged it. Story one built the test harness. Verification and the release checklist were stories in the PRD, not afterthoughts. Blocks were resolved by fixing root causes, and every decision was written back into the PRD.
What it cost. About $980 in API spend by the evening of day three, 97 percent of it Fable running routine agents, plus a day of subscription usage for the build. On the first day only about 90 of 438 minutes were agent work; the rest was waiting for humans and relaying feedback by hand.
The proposal. A nine-stage workflow, Feature Rails, with seven human touchpoints and everything else run by agents, living in otto as an otto feature front door plus an otto-feature skill. It reuses what already exists (grill-me, design-shotgun, otto-plan, prd-task-sizer, the release gate) and adds the six things this project had to hand-build: recorded approvals, measured budgets, environment preflight, account rotation, a block-resolution command, and a release stage. otto itself needs a dozen small fixes first, and they make a good first PRD to run through otto.
One addition from outside. A post by Geoffrey Huntley on the morning of the release describes the artefact this project never produced: a high-level design document that the agent is forced to keep current after the build, self-hosted in the application, with the human's intent on top. A reply extends it to a document exported to the team at the end, derived from what the agent actually did, to lower cognitive debt. The rails adopt both, and the design gate becomes the event that starts the next stage.
The four days
Day 1, discovery and design
- Inventory of 220 candidate pages, six Opus auditors, business shortlist in 14 minutes
- Personal audit and an SEO memo in parallel, 9 minutes
- Three design boards on pages.dev, 11 minutes each
- 11 feedback rounds from Phil, relayed by paste
- One regression round, noticed 83 minutes later, reverted in 71 seconds
Day 2, feedback and the pivot
- Phil's feedback JSON was scoped to one board, so site-wide notes never reached the Business menu; fixed by a nine-item carry-over message
- First mention of "update the menu items inside WordPress"
- Approach memo (13 min) and a consolidated prototype (14 min) built in parallel
- About page and sticky-bar work interleaved; a credit outage cost 67 idle minutes
Day 3, sign-off and stage 1
- Phil's voice note transcribed locally; three surfaces updated in 15 minutes
- Launch mode versus full design; full design frozen on an orphan branch with tags
- SEO parity audit: pass with 8 fixes, then 84 link tuples identical on staging
- Stage 1 live behind a flag in 2 minutes; five-lens verification; a11y fixes shipped the same evening
- PRD rounds 1 to 5, including a 174-agent review panel
Day 4, decisions, build, release
- Seven numbered decisions answered in one message; PRD converged at revision 10
- "ok go" at 08:03; 70 minutes lost to an auth check; build launched 09:24
- 24 stories by 13:46, then four blocks, one added story, staging gate 81 of 81
- Production released 17:49 to 17:59 with evidence slots filled
Artefacts and gates
Seventeen artefacts were produced in a chain, and nine gates sat between them. The chain is worth reading because the rails below are mostly this chain made explicit, with the two missing records added.
| Gate | What passes through it | Criteria | Who decides |
|---|---|---|---|
| G1 Audit | Audit page and shortlists | Every page has a verdict, reason and confidence; links split into live today and waiting on a page | Stevan. no written record |
| G2 Design | The round's board and its feedback JSON | Rounds scored 1 to 5; numbered amendments; one round marked sign-off | Stevan and Phil. no written record |
| G3 Parity | SEO crawl audit, link-state spec | Measured verdict; numbered fixes; parity script exits 0 on ordered link tuples | Audit agent, then scripts |
| G4 Adversarial | PRD revisions | otto check severities; exit bar of 0 blockers with majors triaged | Author agent, logged in the PRD |
| G5 Owner go | STE review pack | Seven numbered questions answered; go or no-go | Stevan |
| G6 Sizing | Implementation JSON | One context window per story, unique ids, real dependencies, no cycles, executor set | prd-task-sizer |
| G7 Per story | Each otto story | Every acceptance command's output quoted on PHP 7.2, 7.4 and 8.3; integration-critical stories need a live probe | otto; operator on blocks |
| G8 Launch gate | Candidate on staging by sha | Gate script 81 checks, rollback rehearsal, zero nav lines in the debug log | otto, then an independent re-run |
| G9 Release | Production | Every checklist slot holds a value read back from the system | Operator on Stevan's go |
Why otto succeeded
This is the answer to "how did we prepare otto". Eleven practices, each with the evidence that it mattered.
- The design was decided, consolidated and frozen before the PRD. The prototype's build script asserted link counts and rebuilt byte for byte, so the PRD could demand parity with a file rather than a picture. Story after story reported "parity 0" against that bundle, and the final walker render is byte-identical to stage 1 in production.
- An approach memo ran before any PRD. It surfaced the things that would have blocked a build mid-run: PHP 7.2 in production, the 1,000 form-field limit that would silently drop menu items, the Dante walker skipping WordPress's own hook, Cloudflare purge on save. Each became a prerequisite or a story.
- Decisions were numbered, dated and baked in. Seven owner questions went out with recommended defaults; the answers came back by number in one message, were written to memory as "do not re-ask", and became requirements with acceptance criteria. The PRD's own decisions table, D1 to D24, records rejected alternatives.
- Product invariants gave the executor rules it could not argue with. Eighteen of them: byte-identical legacy output, hidden items never render, every link returns 200 without redirect, SVGs sanitised and URL icons never fetched, one header per request. Stories cite them by id.
- Every acceptance criterion was a runnable command with quoted output. The form was fixed: a command ending in
echo EXIT=$?, the expected result, counts marked "quoted", a live probe for integration-critical stories, and a design-artefact bullet naming the bundle region and frames. The executor could not pass a story by asserting; it had to show. - The PRD converged through a panel, not self-review. Four rounds of a single model checking its own work stayed at one or two blockers. Round five used six lenses plus a second model family, deduplication, and three refuters voting on each finding; 49 of 55 findings stood and the revision that followed cleared the blockers. Declines were logged with reasons so the checker stopped re-raising them.
- Sizing split the work into single-context stories. Twenty-three stories became 34 with letter suffixes, dependencies re-pointed, and human-owned or post-flip stories parked so otto would not run them under a default executor.
- Story one built the feedback loop. otto detected no test loop for a PHP theme, so the first story created the runner, the lint, and the three-runtime harness, and proved the lint against a broken file. Every later story had something to fail against.
- Verification and release were stories too. Baselines, screenshot comparison, link-state checks, Lighthouse, the launch gate script and the human release checklist with evidence slots were all in the PRD. The release on day four followed a checklist otto had written and rehearsed on staging.
- Isolation kept the blast radius small. The build ran in a git worktree on its own branch; staging and production stayed untouched until the gate; the production deploy was proven crawl-neutral on five pages before the menu was activated.
- Blocks were treated as information, not failures. Each block arrived with a measured cause, a discriminating experiment and lettered options. The operator checked the evidence, then chose between amending a budget with a measured number, moving a gate to where the tool could be fixed, adding a root-cause story, or fixing the environment. Every choice was written back into the PRD with a date, and the re-run took 8 minutes instead of 60 because the note said what still stood.
- The model policy was explicit before the build started. Opus by default, Sonnet for checked mechanical work, Fable for the hardest problems, a second model family for independent review. The build ran entirely on Opus and cost nothing beyond subscription usage.
Where time and money went
| Stage | Wall clock | Agent work | Notes |
|---|---|---|---|
| Audit and shortlists | 1 h 05 | ~35 min | 148 pages in 8.5 minutes across six agents; 85 more plus an SEO memo in 9 |
| Design rounds, three boards | ~2 days | ~2 h | 21 rounds; iteration rounds took 1 to 7 minutes of agent time each; the gaps were people |
| Consolidation and freeze | 1 h | ~45 min | Prototype 14 min and 266k tokens; memo 13 min and 205k tokens; archive, orphan branch, tags |
| Stress tests | 1 h 30 | ~1 h | SEO parity audit with 8 fixes; link-state spec and checker |
| Stage 1 launch and fixes | 2 h 15 | ~1 h 45 | Launch in 2 minutes; verification 47 minutes and 941k tokens; a11y branch to production |
| PRD, rounds 1 to 10 | ~14 h elapsed | ~5 h | Revisions 6 to 10 alone took 2.2 hours and 1.25M Fable tokens; majors fell from 28 to 16, mostly repeated split requests |
| Owner decisions | 12 h wait | minutes | Sent 18:59, answered 06:54 next morning |
| Convert, size, preflight | 1 h 20 | ~15 min | 70 minutes waiting on a login that existing tokens already covered |
| otto build, first run | 4 h 22 | 4 h 22 | 24 stories; min 3.9, median 10.4, max 19.8 minutes; no account switch needed |
| Blocks and relaunches | 2 h 30 | ~2 h | Four blocks, operator latency 4, 7, 1 and 3 minutes; one 60-minute story re-run in 8 |
| Staging gate and re-verification | 25 min | 25 min | 81 of 81 twice; 84 link tuples identical to production |
| Wait for go | 1 h 27 | 0 | Notification reached the terminal only |
| Production release | 10 min | 10 min | Eleven checklist steps; two environmental failures re-proven against production's own baseline |
Money. The session's API cost stood at $980.85 on the evening of day three, of which $949.47 was Fable. Almost all of it went on routine agents (auditors, board builders, verification lenses, PRD rounds) that the later model policy assigns to Opus or Sonnet. The day-four build ran on Opus through Max subscription accounts, so the most productive day was the cheapest.
The shape of the time. Agents were fast and humans were the long pole, which is the right way round. The waste was not in the waiting itself but in the friction around it: feedback pasted by hand with quote characters mangled, a regression that sat for 83 minutes, a login check that idled for 70, and a go that waited 87 minutes for a notification that never left the terminal.
Honest misses
Grouped by who could have prevented them. The agent-side ones are mine.
Process and tooling
- Feedback JSON had no scope. Each board's copy button tagged one screen, so Phil's site-wide notes reached only the Personal builder. Fixed by hand once; never fixed in the schema.
- Feedback travelled by paste. Curly quotes broke the JSON, one paste arrived empty, lag ran 0 to 26 minutes, and clipboard images expired before the agent saw them.
- Round numbers were per board. One header change landed as Personal R3, Business R2 and Mobile R4, and Phil's comments keyed "B-r3" while his text said "Option A".
- Reverting to an earlier round lost the icons. Round 6 was built from Round 4, but the tiles only existed in the set-aside Round 5. A round manifest would have caught it.
- No written sign-off exists for the audit or the design. The shortlists became build-script content and the approval lived in chat.
- The PRD was a moving target. Seven amendments landed while the author agent ran, and the stage-1 code arrived mid-draft and forced a rebase.
- Budgets were assumed, not measured. A size ceiling guessed two plaque copies where the archive has three; a Lighthouse target was set on an uncompressed probe; a layout-shift median was compared on a bimodal metric. Three of the four blocks came from this.
- Rollback was documented, never rehearsed on production for stage 1, and one a11y trigger was overridden without telling the owner.
Babysitting and operations
- 70 idle minutes on auth. The conversion failed silently on an expired login; the assistant asked for a fresh login instead of searching for the year-long tokens that already existed.
- The story count drifted one ahead twice, and one "failures in log: 1" was reported as zero.
- Too many notifications. Twenty monitor arms and 47 routine notices in a session that then auto-compacted; a block landed in a gap between them.
- Staging drift was self-inflicted. The flag constant I hard-coded on Wednesday, plus a wrong note about PHP 8.3, blocked the staging story until fixed.
- Fable ran everything until the policy changed on day four: $949 of $981.
- Workflows died silently on a usage limit and were noticed 20 minutes later; results had to be salvaged from journals.
- Production access took 2 h 21 because the key went to an account that could not write, and the deploy's typed confirmation needed a pseudo-terminal.
- otto itself: a block reason with an apostrophe crashed a run; the status file counted parked stories as passed; every run exits 1 even when done; the iteration budget is silently capped.
Feature Rails
The workflow below is the chain above made explicit, with the two missing records added, the hand-built pieces turned into fixtures, and the human touchpoints reduced to seven decisions a non-developer can make. Stages with a highlighted border need a person.
Needs a human decision at the gate. Everything else is agent-run, with escalation cards for the exceptions listed under Guardrails. Each approval is an event: the state file advances and the next stage starts without a further prompt. One artefact cuts across stages 3 to 9, the living design document described after the guardrails.
The nine stages
1. Intake
Driver: the teammate. Agent: Opus.
A bounded interview in the grill-me style: one question at a time, each with a recommended answer, stopping when the branches are closed. In parallel the agent harvests context from the codebase and the site the way the audit did on day one: menus and pages from the database, noindex flags, inbound links, existing templates and unmerged branches.
Artefacts: brief.md (problem, audience, scope, non-goals, constraints, success measures) and decisions.md (numbered, dated, who answered). Gate: the driver confirms "that is what I mean" from a one-screen summary.
Evidence: the seven numbered decisions answered in one message; the audit brief and inventory that made six parallel auditors consistent.
2. Audit
Driver: agents, teammate ticks the result. Agent: Opus auditors, one per batch.
Only when the feature touches existing content or data. Parallel auditors work from one shared brief with pre-computed signals and return verdicts with confidence. A synthesis produces the shortlist table the design stage will consume.
Artefacts: batch reports, audit page, shortlist with status pills. Gate G1: the driver approves the shortlist on the page itself and the approval is recorded with a hash of what was approved.
Evidence: 233 pages audited in under 20 minutes of agent time; verdict vocabulary NAV, HUB-LINK, MERGE, RETIRE, NOT-BUSINESS with confidence H, M, L.
3. Design
Driver: the teammate rates and comments. Agent: Opus for boards, Fable when the bar demands it.
Design shotgun boards, four variants plus the current state, built by extracting the live theme rather than inventing one. Each board has a stable URL, rounds stacked newest first, and a feedback form that submits directly rather than by paste. Feedback items carry a scope (this board, all boards, site-wide) so a note about the header reaches every builder. Variant ids are board-prefixed and each round has a manifest of what it contains, so a revert is "base round N plus these deltas" and never loses a part.
As soon as two boards exist, a consolidated prototype is built with a build script that asserts link counts and reproduces byte for byte. It carries launch and full modes when content is not ready. On sign-off the design is frozen: archive with README, orphan docs branch, annotated tag, and a rule that revisions happen in the prototype and are re-frozen as v2.
Artefacts: boards, feedback JSON per round, prototype bundle with contract README, icons with manifest, frozen archive. Gate G2: approved.json with variant, round, sha, who and when.
Evidence: 21 rounds; a voice note transcribed and turned into three updated surfaces in 15 minutes; the prototype's parity assertion caught drift that "would bite the WordPress build".
4. Stress test
Driver: agents. Agent: Opus lenses, gpt-6-astra as an independent voice.
Before any PRD, the prototype is attacked on the axes that produce regressions: crawlability and SEO parity against the live site, link states across breakpoints, accessibility contrast, responsive screenshots, and performance budgets. The rule that would have saved three of four blocks: every budget in the PRD is a number measured on the target host, with its stability probed, never a guess.
Artefacts: a measured verdict with numbered fixes, applied back into the prototype; a parity script; a link-state spec; a budget file keyed by host and sha. Gate G3: verdict PASS or PASS WITH FIXES, fixes applied, script exits 0.
Evidence: the SEO audit found a noindex hard stop and ten hidden links returning 404 before anything shipped.
5. Approach and PRD
Driver: agents. Agent: Opus writer, six lenses, gpt-6-astra checker, Fable if convergence stalls.
An approach memo first: options with a recommendation, data model, integration points verified at file and line, prerequisites, risks, and a sketch of stories. Then otto-plan writes the PRD with the decisions baked in, invariants, and the story anatomy above. Convergence uses one writer per revision, a panel of independent lenses including a second model family, refuters voting two of three on each finding, and a bounded check loop that stops on oscillation and logs declines with reasons. The exit bar is agreed with the owner before the loop starts, because the skill's "SHIP" and the project's "zero blockers, majors triaged to the sizer" are different bars.
Artefacts: memo, PRD with review log, check reports. Gate G4: the agreed bar, recorded in the review log.
Evidence: single-model rounds 1 to 4 stayed at one or two blockers; the panel round produced the revision that cleared them; rounds 6 to 10 never raised a blocker again.
6. Review pack
Driver: the teammate. Agent: Opus writer, Sonnet lint, a cold-read critic.
A plain-English page in ASD-STE100 style with fixed sections: what this is, what editors will and will not be able to do, the stories and who builds them, decisions taken, manual steps done and to do, at most five risks, review history. Sentences are linted to 20 words. A critic reads it as "the owner, who will not open the PRD" and asks whether a go could be given from the pack alone. Open questions go out numbered with recommended defaults.
Artefacts: the pack on a private URL, the answers. Gate G5: go or no-go recorded in approvals[] with the pack's hash.
Evidence: "I don't want to read through the whole PRD"; 0 of 330 sentences over the limit; answers by number.
7. Preflight
Driver: agents. Agent: Opus for conversion and sizing, Sonnet for checks.
Convert the PRD, size it, lint dependencies, park human-owned and post-flip stories, set executors from the model policy. Then the environment manifest: auth probed in otto's exact environment, an account pool with usage thresholds, a worktree on a fresh branch, staging and production shas recorded, feedback loops declared for the stack, baselines captured on each host, and every precondition a later story will need (flag mechanism, runtime versions, a short-lived admin cookie, whether a shared host is free to take over). A failed precondition is fixed here, not discovered as a block six hours in.
Artefacts: implementation JSON with README, environment block, worktree. Gate G6: sizing lint passes and every precondition returns its expected value.
Evidence: the four staging preconditions that blocked the last story were all knowable at 09:00.
8. Build
Driver: otto, babysat by an agent. Agent: executor per story from the policy, Opus by default.
otto runs detached with a status heartbeat that names the current task. The babysitter watches one event stream (pass, fail, stall, block, exit, account switch, environment touch) and reports by exception with an hourly digest; routine passes never interrupt anyone. Progress and PRD state are backed up every few minutes. A rubric spot-checks the risky stories: security, external services, environment safety, owner decisions. A block opens a triage card with the acceptance criteria, the executor's evidence and its options; the babysitter classifies it and applies the default resolution for its class, then records the decision in the PRD and relaunches. Waivers of non-functional targets, shared-environment takeovers and anything touching production go to the human as an escalation card.
Artefacts: commits, progress log, spec index, operator decisions in the PRD. Gate G7: each story's criteria quoted and passing.
Evidence: 30 of 30; four blocks resolved in 15 operator minutes; the root-cause story that turned a kilobyte of bloat into a byte-identical render.
9. Verify, release, present
Driver: agents, the teammate says go. Agent: Opus operator.
The candidate goes to staging by sha, the gate script runs with per-host baselines, the rollback is rehearsed, and an independent re-run confirms it. The release follows a checklist with evidence slots: backup, crawl-neutral deploy proven on real pages, purge proven by cache ages, seed and activate, the gate on production, a live edit proven to reach the edge, evidence committed. Then the driver gets a finished-product card: the staging or production URL, what changed in plain English, the evidence summary, and the one-click rollback. A retrospective like this one is generated from the run's own records and its lessons flow back into the templates.
Artefacts: gate logs, filled checklist, evidence commit, the card, the retro. Gates G8 and G9: gate 0 failures on staging; every production slot filled from the system.
Evidence: the ten-minute release; two environmental failures re-proven rather than waived; rollback rehearsed 8 of 8 on staging.
Phil's walkthrough
What the North Star looks like from the driver's seat. Seven touchpoints, none of which require reading a PRD or opening a terminal beyond the first line.
- Start. Phil opens Claude Code in the project and types one line./otto-feature "Add a rate-alerts page under Personal, with an email signup"The agent asks eight to twelve questions, one at a time, each with a suggested answer he can accept with a word. It shows a one-screen brief and asks "is this what you mean?"
- Audit. If the feature touches existing pages, a shortlist arrives on a private URL fifteen minutes later. He ticks the rows he agrees with and presses approve on the page.
- Design. A board with four variants arrives. He rates each, writes comments, marks which notes apply site-wide, and submits from the page. Rounds stack on the same URL until he presses sign off. A consolidated prototype follows with launch and full modes.
- Stress tests. He gets a short card: what was checked, what was fixed automatically, and anything that needs a decision (rare).
- Review pack. A five-minute page in plain English with numbered questions and suggested answers. He answers by number and presses go. That is the point at which cost starts.
- Build. Silence, apart from a digest and any escalation card. A card looks like: "Story 15 needs a decision. The performance target was set on a slower server than production. Options: accept as measured (recommended), re-measure, or fix the content shift. Reply with a letter."
- Release. "Ready on staging, 81 of 81 checks, rollback rehearsed. Say go to release." He says go, and ten minutes later the finished-product card arrives with the live URL, the evidence, and the rollback button.
Guardrails
The rails refuse to do these without a named human, and record who said yes:
- Any change to production, including database edits and cache purges outside the release checklist.
- Taking over a shared environment that holds someone else's deploy.
- Waiving a non-functional target (performance, accessibility, size) rather than fixing the cause.
- Merging content branches that have not been signed off.
- Spending past an agreed budget, or escalating the model tier for routine work.
- Restarting a session while agents are running.
The document that comes after
On 25 September, a few hours before the release, Geoffrey Huntley posted a pattern for the moment after an intense agent-assisted build: "Before AI, we would start with a HLDD. After AI, I am increasingly finding that the HLDD comes afterwards." The mechanics: "pre-commit hooks and skills that force the agent to automatically update the HLDD, and you self-host the HLDD in the application as documentation. Top of fold is still the human crafted why, measurement of success and how it will be measured. Below the fold is the how," with the author's own intent as editor remarks on each subsection. One reply, the one Stevan picked out, goes a step further: a skill that exports a design document to the team at the end, derived from the HLDD and what the agent actually did, with diagrams, so the team understands what the change was and why. Its stated goal is to lower cognitive debt, Margaret-Anne Storey's term for the debt that "lives in the brains of developers and affects their lived experiences and abilities to go fast or to make changes".
The same day a public repository shipped the pattern end to end: one canonical architecture document with owner intent, observable success criteria, implemented components, data flows, trust boundaries and qualified acceptance gaps; a maintain-hldd skill discoverable by both Codex and Claude; a staged pre-commit check that refuses a source change without a substantive design-document change, where whitespace and dates alone do not count; the same check in CI on every commit; and a read-only reader inside the app, bundled at build time so it works offline.
Why this project needed it
The build left behind 35 commits, about a dozen PHP modules, more than a thousand automated checks, and a Tools page. The documents that exist are for the agent (a 280 KB PRD whose run copy is gitignored), for the release (the evidence file), or for other executors (the spec index, a module table nobody would read for pleasure). Nothing tells Phil's team in five pages how the menu system works, why it is shaped that way, and what to do when it misbehaves. That gap is cognitive debt, and it is the debt that grows fastest precisely when otto is productive: the more stories pass unattended, the less anyone on the team understands.
PRD before, HLDD after. These are different documents for different readers, and the rails need both. The PRD is the agent's contract: it must exist before the build, it is written for a machine that quotes command output, and it is disposable afterwards. The HLDD is the team's understanding: it can only be finished after the build, it is written for people, and it lives with the code for as long as the code does. Huntley's "the HLDD comes afterwards" does not contradict "PRD first"; it names the artefact that PRD-first workflows forget.
What the rails adopt
- A living HLDD from stage 3 onward. Top of fold, human-written and owner-edited: the why, what success looks like and how it is measured, lifted from the brief and the decisions log. Below the fold, agent-maintained: the approved design and its reasons, implemented components, data flows, seams and invariants, trust boundaries (the sanitiser, capability gates, what is never fetched), operations (the Tools page, flush and purge, rollback), and qualified gaps (parked stories, waivers, follow-ups). Editor remarks are allowed anywhere. Diagrams are Mermaid so they render in the repository and in the app.
- The design gate writes the first section. When the teammate approves a design, the approval is not only a URL in
approved.json; the chosen variant, the rounds it took and the reasons become the HLDD's design section. The same approval event advances the state file and starts the stress tests and the approach memo. Design is a gate that triggers work, as Stevan read it, and it also seeds the document. - A guard in the loop. otto's commit verification already checks that a story committed something; it gains the rule that a story touching source paths must also make a substantive change to the HLDD sections it affects, or the story does not pass. The executor prompt gets a
maintain-hlddconvention alongside the existing spec-index one. - Self-hosted where the operators are. For a WordPress theme that means a read-only Tools page, "About this navigation", rendering the bundled Markdown next to the seed and activate buttons that already carry explanatory copy. For an application it is a documentation route.
- An export at stage 9.
otto hldd exportderives the team document from the HLDD, the progress log, the operator decisions and the evidence: what changed, why, the diagrams, how to operate it, how to roll it back. That is the finished-product card grown up, and it is what Phil's team reads instead of the PRD.
For Navigation 2026 now. The seed material exists: the PRD's overview and decisions, the spec index, the Tools page copy, the release evidence and the three verdicts. A one-story job produces docs/HLDD-navigation.md with three diagrams (render seam and fragment filter, cache generation and invalidation, the menu-item field model) and an in-admin reader, and it is a good candidate for the first run of the rails on this codebase.
Putting it in otto
otto today is a 2,201-line bash loop with a planner, an adversarial checker, an executor prompt, a stall watchdog and a status file, plus three skills: an index, otto-plan and otto-orchestrate. Its smoke tests pass 44 of 44. The rails need four kinds of change, in this order of value.
A front door
An otto feature <slug> command that owns a resumable state file, otto/features/<slug>/feature.json, with the current stage, artefact paths and hashes, and approvals. The conversational stages (intake, design feedback, review pack) live in an otto-feature skill because bash cannot ask questions; the mechanical stages call the existing commands. otto-orchestrate already sketches this shape and becomes the skill's later half.
Fields the JSON is missing
| Field | Purpose | Hand-built equivalent this time |
|---|---|---|
designArtifacts[] | path, URL, sha256, variant, approvedBy, approvedAt | The frozen archive, tags, and a README |
approvals[] | gate, by, at, ref, waivers | Chat messages and a memory file |
environment | preconditions with command, expected value and on-fail action; hosts with runtime and baseline directory; declared feedback loops | Prose in the requirements block; the four staging blockers |
release | steps with command, confirmation, rollback and evidence slot; canary | nav-release-gate.md, filled by hand |
operatorDecisions[] | story, kind (waive, amend, addStory, environment), by, at, backup | Dated suffixes on acceptance criteria, operatorNote, four backup files |
owner on a story | otto or human | A parked flag added by the converter so otto would not run NAV-001 |
hldd | path, required sections, guard on or off, in-app reader route | Nothing; the spec index and the Tools page copy are the nearest things |
Commands the run needed and improvised
otto approve <gate>writes an approval with the artefact hash.otto preflight <env>checks the environment manifest and captures baselines on that host.otto resolve <story> waive|amend|add-story|envbacks up the PRD, records the decision, resets block counters and relaunches.otto release <env>runs the release steps and fills the evidence slots.otto hldd check|exportenforces the design-document guard and exports the team document at the end.- An account pool: tokens, usage read per iteration, thresholds, and a switch that stops, checkpoints, re-pins and relaunches. Two scripts in the CTW repo did this by hand and were never exercised.
OTTO_NOTIFY_CMDfor a push to a phone, because a terminal notice waited 87 minutes.
Fixes to the loop itself
| Fix | Evidence |
|---|---|
Make plan convert fail loudly when auth is missing, and check auth before every run | Exited 1 in two seconds with no output; conversion done by hand; 70 idle minutes |
| Escape block reasons safely | A reason with an apostrophe killed run four with "unmatched single quote"; nothing was parked and the status file was never written |
Count only passes:true in the status file; add the current task and a heartbeat age | Status said 34 of 35 while the PRD had 30; the babysitter's count drifted |
| Distinct exit codes, and stop capping the iteration budget to the pending count | Every run exits 1 even when done; a budget of 45 became 29 and blocks consumed iterations |
| Back up the PRD and progress files, or keep them out of gitignored paths | A five-minute copy loop ran alongside every launch |
| Detect rate limits in text mode and support a token pool | The check only fires on JSON output; claude ran in text mode |
| Declare feedback loops in the JSON when detection finds none | "No feedback loops detected" for a PHP theme with a full harness |
Honour owner: human instead of running unknown executors under the default | NAV-001 had to be parked by hand |
| Publish the template fields the code already understands | executor, parked, blockedReason, statusNote, artifactComplete are code-only |
What to reuse rather than build
- grill-me for intake: one question at a time with a recommended answer. Add the artefact it lacks,
decisions.md. - design-shotgun for boards. Its binaries are not installed on the devbox, so the boards were hand-built; either install it or adopt the hand-built board generator, which already has stars, comments and copy-JSON, and add direct submission plus the scope field.
- otto-plan and prd-task-sizer as they are, with the panel workflow from day three as the check loop.
- autoplan and the plan-review lenses as panel members; they already score design, engineering and developer experience.
- The release gate and checklist written by NAV-014 and NAV-019, generalised: baselines keyed by host, a consent-cookie option for hosts running Cookiebot, and a flush-then-fresh-hit purge proof instead of an unmeasured TTL.
- ship, land-and-deploy and canary do not fit CTW's deploy script with its typed confirmation; the release block needs a per-project deploy adapter, which the checklist already describes.
Building it with otto
Improving otto with otto is feasible and is the right first test of the rails, because the repo is small, private, and has a fake-executor smoke suite that can serve as the feedback loop. The shape of the PRD:
| Phase | Stories | Why first |
|---|---|---|
| P1 Loop fixes | Loud convert failure and auth preflight; safe block reasons; honest status counts with current task; exit codes and iteration budget; backups; notify hook; declared feedback loops; human-owned stories; template fields; the HLDD guard and maintain-hldd convention in the executor prompt | Each is small, testable with a fake scenario, and removes a hand-built workaround |
| P2 Run resilience | Account pool and rotation with usage read; text-mode rate-limit detection; otto resolve; operator decisions in the JSON; triage card generator | Turns the babysitter's improvised scripts into product |
| P3 Front door | otto feature state file; otto-feature skill: intake, audit dispatch, design stage with board generator and scoped feedback, stress-test lenses with measured budgets, approach memo, PRD panel, review pack with lint and critic, approvals | The teammate-facing surface; depends on P1 and P2 |
| P4 Release | Environment manifest and otto preflight; per-host baselines; otto release with evidence slots and a deploy adapter; otto hldd export as the finished-product document; retro generator | Closes the loop from staging to production and back into templates |
How to run it safely. otto would be editing its own running script, so the run uses a pinned copy through OTTO_HOME against a worktree of the repo. The smoke suite plus shellcheck is declared as the loop, every story adds one fake scenario, and the skills are installed only after merge because installed skills go live immediately. Expect the checker's rubric to produce noise on a bash repo; the panel should be told the domain. Model routing per the policy: Opus for the loop and skill code, Fable for the teammate-facing copy and the skill prose, Sonnet for mechanical scenarios, gpt-6-astra as the checker.
Suggested next step. Run stages 1, 5 and 6 of the rails on otto itself: an intake interview for the front door, an otto-plan PRD for P1 and P2 converged by the panel, and a review pack for you to approve. That is about a day of agent time and produces a working, self-hosted example of the whole loop before anyone touches the design stage.
Templates to lift verbatim
These already exist in the docs folder and the worktree, and they are the fixtures the rails should ship with. Paths are under docs/nav-audit-2026-09-22/ unless noted.
| Template | Where |
|---|---|
| Audit brief and follow-up brief; batch output shape | BRIEF.md, personal/PBRIEF.md, 2-products.md |
| Board generator with stars, comments and feedback JSON | mega-menu-shotgun-personal/build.py |
| Consolidated prototype with launch and full modes and contract README | complete-menu/build.py, complete-menu/dist/README.md |
| Frozen-design README, roadmap with stage rules | full-design-archive/README.txt, ROADMAP.md |
| Measured audit with hashed inputs, numbered fixes and a markup contract | seo-crawlability-parity.md, nav-parity-check.py |
| Link-state spec and checker | nav-link-states.md, complete-menu/states_check.py |
| Approach memo | implementation-approach.md |
| PRD skeleton, decisions table, invariants, review log, example stories | prd-nav-2026.md (NAV-007, NAV-013, NAV-001) |
| Convergence workflow (six lenses, second model, refuters, bounded check) | The workflow script from day three, saved beside the reports |
| STE review pack; convert and size README | prd-review-pack.html, prd-implementation-nav-2026.README.md |
| Progress-log header; spec index convention | worktree progress-prd-implementation-nav-2026.txt, spec/README.md |
| Release gate with evidence slots; filled record | theme tests/fixtures/nav-release-gate.md, prod-launch-verification/stage2-release-2026-09-25.md |
| Verification verdict with pre-declared rollback triggers; fix verdict | prod-launch-verification/VERDICT.md, post-launch-fixes/VERDICT.md |
| Failover and usage-guard scripts (to be absorbed into otto) | scripts/otto-nav-failover.sh, scripts/otto-nav-usage-guard.sh |
| Seed material for the navigation HLDD | prd-nav-2026.md §1 and decisions, worktree spec/README.md, the Tools page copy in inc/nav/menu-seed.php, the three verdicts |
How this was produced
Eight Opus agents worked in parallel from a condensed reader over the 7,770-record session transcript (31.8 MB, 22 to 25 September), the docs folder, the build worktree, and otto's source at commit 4bab194. Six covered the transcript by phase, one mapped the artefact chain, one mapped otto's internals and re-ran its smoke tests. Their reports, about 13,700 words with line references, are saved in the docs folder next to this page's source (retro-sources/) and were synthesised by the session's own model. Where two reports counted differently, the artefact-chain count was used. Every figure above traces to a transcript line, a file, or a command output in those reports. The design-document section draws on Geoffrey Huntley's post of 25 September 2026 (read through a tweet mirror; the replies are behind X's login wall, so only the reply Stevan quoted is used), the same-day pull request in vivary-dev/Vivary-New that implements the pattern, and Margaret-Anne Storey's definition of cognitive debt as quoted by Simon Willison in February 2026.