diff --git a/.claude/skills/wrapp/template.js b/.claude/skills/wrapp/template.js
index 6507c64..fddcd29 100644
--- a/.claude/skills/wrapp/template.js
+++ b/.claude/skills/wrapp/template.js
@@ -85,7 +85,18 @@ function wire(r) {
// storage and re-renders; keep it idempotent.
live = mountLive(r, reloadState);
}
-async function onReady() { await syncContext(); await loadState(); render(); autostart(); }
+// onReady fires TWICE by design — once from mountConnect's onConnect and once from the
+// returning-user probe above, whichever wins the race. Hydrating from storage on BOTH passes is a
+// real (timing-dependent) bug: the second pass re-reads the run the first pass just saved, which
+// REPLACES the in-memory object the running pipeline holds a reference to. The pipeline then
+// completes into a detached orphan and the UI sits on a run that never finishes. Hydrate once.
+let hydrated = false;
+async function onReady() {
+ await syncContext();
+ if (!hydrated) { hydrated = true; await loadState(); }
+ render();
+ autostart();
+}
/** Re-read everything this wrapp persists, then render. Called on every live nudge. For a wrapp
* that ACCUMULATES items (a library, notes, a task list), read the collection here (see `items`
* below) — never a single growing blob, or two teammates' edits clobber each other. */
diff --git a/ROADMAP.md b/ROADMAP.md
index dda3547..5fbaf53 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -105,7 +105,66 @@ network egress) so a stranger's app can't exfiltrate your data — the basis for
## Next up
-### 0. Autopilot → a real wrapp (prototype landed, SDK not wired)
+### 0. ✅ Autopilot → a real wrapp — SHIPPED (`examples/apps/autopilot.{html,js}`)
+
+Ported to the house template and wired to real generation. `examples/autopilot/` is now the
+superseded prototype; the shipping wrapp is `examples/apps/autopilot.html` + `src/autopilot.js`.
+
+**Resolved first: separate wrapp, not a brandbrain studio.** The verb argument in the original
+entry was decorative — the separation had actually entered the record as a scope guard
+(`OVERNIGHT.md:5-8`, *"brandbrain works; it is not in scope"*), and a third studio was never
+evaluated. It was, and the conclusion holds for three better reasons: (1) brandbrain's studio
+engine is **finite by construction** (`seq.every(id => locks[id])`, `brand-studio.tsx:1347`) and
+has no concept of a decision that recurs — a never-terminating studio means replacing the control
+flow, which is the thing that makes studios cheap; (2) the cockpit is precisely the part that does
+**not** generalise (`compileTasks()` is a hand-written if-chain over brand-specific lock ids, and
+the `!active.path` gate already makes ideabrain projects unable to reach the OS at all); (3) the
+OS is explicitly one-brand-at-a-time (`command-centre.tsx:2525`), which contradicts a portfolio at
+the state level. Plus brandbrain isn't ported (§1), so "inside" would block Autopilot behind the
+most expensive unfinished item here.
+
+**Spec correction made during the build:** `contextKinds` is `["brand","project","idea"]`, not
+`["brand"]`. The original narrowed a wrapp explicitly meant to generalise past D2C back to the one
+D2C-shaped kind, and would have made Autopilot unable to consume an ideabrain output at all —
+breaking the idea→run graduation. `ideaToContext` carries the whole locked playbook keyed by
+decision id, which is richer than `brandToContext`'s five flattened strings.
+
+Done: 0a (house template) · 0b (real `relay.complete` generation, replacing seeded `buildOptions`)
+· 0c (inherit-don't-re-ask, with a take-it-back escape) · 0d (cold open) · 0e (tokens from the
+broker's real `usage`, estimates *labelled* as estimates) · 0f (`collection()`, one company = one
+file) · 0g (build entry, catalog, store card, landing, harness spec + provider routes).
+
+Also ported from brandbrain: the `auto | approve | manual` mode taxonomy (`lib/studio/os.ts:10-13`)
+— as a three-line concept, not code. It is what makes "autonomous" honest.
+
+Bugs found and fixed by verification (all would bite a live daemon, not just the harness):
+`onReady` legitimately fires twice (mountConnect + the returning-user probe), so the cold open had
+to become idempotent; `loadState` must **merge** rather than blind-replace `cos`, or a `mountLive`
+nudge detaches the object generation is writing into and the slate renders empty forever;
+`drafting` had to become a per-company Set (a global boolean makes drafting one company silently
+abandon the next — fatal for a portfolio app); an inherited decision has no options, so the
+upstream-constraint builder read `null` and silently dropped the lent voice out of every
+downstream prompt.
+
+**Storage is proven against a real daemon**, not just the harness:
+`examples/apps/proof/run-autopilot-storage.mjs` drives the actual `src/kit/livestore.js` against a
+throwaway daemon on a temp `RELAY_DIR` and reads the bytes on disk — 19/19: one company = one file,
+editing A never rewrites B (the claim Team Mode's per-file LWW rests on), torn records skipped,
+traversal/dotted ids refused, per-origin isolation. Live token measurement is still blocked (Claude
+Code isn't signed in on this Mac), so the catalog's token figure stays honestly dev-reported.
+
+**Side effect worth more than the wrapp: a catalog-wide race, fixed.** The harness's storage mock
+discarded every write — a deliberate 2026-07-18 choice to keep wrapps cold-opening as fresh users.
+It was also masking a live production bug. `onReady` fires twice (mountConnect + the returning-user
+probe); with storage that actually returns what you wrote, the second pass re-reads the run the
+first just saved and **replaces the in-memory object the running pipeline holds**, so the pipeline
+finishes into a detached orphan and the UI sits on a run that never completes. Backing the mock with
+a real per-page store turned reel/marquee/arcade red instantly. The fix — hydrate once — is now in
+all **21** wrapps carrying the template's `onReady` one-liner, and in
+`.claude/skills/wrapp/template.js` so new wrapps inherit it. (`cartridge.js` already had it; it was
+never propagated.) Harness baseline is now **70/70 with real storage**, up from 68 runs → 64/2/2.
+
+Original plan (superseded)
`examples/autopilot/` holds a **working decision engine** with no model behind it. The state
machine, locking, downstream ripple, persistence and token accounting are real; the option copy is
@@ -141,6 +200,8 @@ not duplicate it: brandbrain publishes `kind: "brand"` via `brandToContext`
Known gaps in the prototype: zero SDK calls; only two seeded company kinds (d2c, saas); the
"+ New company" button opens the token pane instead of seeding a company.
+
+
### 1. brandbrain — full port (the immediate pickup)
Turn the *real* `~/Documents/Projects/brandbrain` into the store's brandbrain (today's store card is
a one-route demo). It's a **port, not a rewrite** — assessed portable: 7 pages (client shells, no
diff --git a/examples/apps/autopilot-landing.html b/examples/apps/autopilot-landing.html
new file mode 100644
index 0000000..4353057
--- /dev/null
+++ b/examples/apps/autopilot-landing.html
@@ -0,0 +1,349 @@
+
+
Trail-buddy warmth — earns the moment with you, never shames the shortcut.
+
dry
Spec-sheet minimalism — no adjective it can't measure.
+
deadpan
Flat, funny, anti-hype — undersells on purpose.
+
+
+
Ad angle redrafted when you changed the voice
+
Real coffee. No kettle. You're 40 minutes past the trailhead. There's no cafe up here — there's this. Tear, pour, look up.
+
+
+
autoDraft the creative for “No kettle”drafted · nothing sent
+
approveRun it on Trail & outdoor newsletterswaiting on you
+
manualFirstlight Lastlight — a second daypartonly you can
+
+
1.2M of 2M tokens budgeted this weekRevenue — not connected
+
+
+
+
+
+
+
+
Category
+
Brand & content
+
on the Switchboard shelf
+
+
+
Build cost
+
420K tokens · 3 updates
+
dev-reported
+
+
+
Price
+
Free core, forever
+
runs on your own Claude
+
+
+
+
+
01
Grounded in a context you lend it
The context comes from wherever you already made it — brandbrain, ideabrain, Bank, or Switchboard reading a live site. Autopilot sees the one you lend it and nothing else: it never copies it, and it never goes looking for another.
+
02
Every option is a card you choose
Nothing is a suggestion you retype. Change the voice and the ad below visibly rewrites, because the slate is one connected draft — and every option you passed on stays on the board.
+
03
Nothing irreversible without you
Every move is staged auto, approve or manual. Auto is what the machine can fully prepare at no risk; approve stages it and waits for your tap; manual is yours to do in the real world.
+
+
+
+
+ Where it sits
+
Three verbs, three wrapps. This is the one that doesn't finish.
+
+
+
+
ideabrain
+
Should this exist?
+
Research, a playbook for your kind of idea, a deck. Ends in a thesis.
+
+
+
brandbrain
+
What is it?
+
Positioning, name, voice, palette, every decision locked. Ends in a brand.
+
+
+
Autopilot
HERE
+
Now run it.
+
The operating slate, week after week. No end state, and several companies on one board.
+
+
+
"Never terminates" means it has no finish screen, not that it works while you're away. There is no background loop and no schedule — Autopilot drafts when you open it, and picks up exactly where you left it. It reads a lent context; it does not go looking through your files for one.
+
+
+
+
+ What it costs
+
Free to run. Pro only when our servers do the work. We never charge for AI access — the tokens are your own Claude quota.
+
+
+
+
Free — the complete core loop
+
The whole cockpit, every company, forever. Nothing in the operating loop is held back — and running a team over your own network is free too, because it costs us nothing.
+
+
Cold open on connect — no form, no prompt, no button to press
+
As many companies on the board as you actually run
+
Real options on every decision, and the downstream redraft when you switch
+
Moves staged auto, approve or manual — you hold the go button
+
+
+
+
Pro — when our servers do the work
PRO
+
Two things sit behind Pro, and neither is AI access: the hosted relay that lets a team sync from anywhere without standing up infrastructure, and encrypted cloud backup.
+
+
A shared board — teammates operating the same company, synced from anywhere
+
Encrypted cloud backup — offline catch-up and new-device restore
+
Your own LAN or a self-hosted relay stays free, forever
+
+
A token budget is not a bill. It is capacity to work — you are deciding how much thinking the company gets this week, on the Claude quota you already have.
Every draft, decision and redraft runs on your own Claude
+
+
+
+
+
diff --git a/examples/apps/autopilot.html b/examples/apps/autopilot.html
new file mode 100644
index 0000000..ca1a2a6
--- /dev/null
+++ b/examples/apps/autopilot.html
@@ -0,0 +1,211 @@
+
+
+
+
+
+ Autopilot — the cockpit for the company you already have
+
+
+
+
+
+
+
+ Autopilot
+
+
+
+
+
The cockpit for the company you already have.
+
Lend it a brand, a project or an idea and it drafts the operating slate with no input at all — voice, ad angle, channel, next move. Choose one and everything downstream rewrites. Nothing irreversible happens without you. Runs on your own Claude via Switchboard — the operator holds no key and never sees your data.
All 34 wrapps booted on a mock window.claude, each lent one of two projects as context, then driven through its real stage-1 pipeline. 68 runs — one per wrapp × project.
+
All 35 wrapps booted on a mock window.claude, each lent one of two projects as context, then driven through its real stage-1 pipeline. 70 runs — one per wrapp × project.
-
64PASS — stage-1 rendered clean
-
2WARN — partial / needs a live backend
-
2FAIL — no stage-1 output
-
68total runs
+
70PASS — stage-1 rendered clean
+
0WARN — partial / needs a live backend
+
0FAIL — no stage-1 output
+
70total runs
01 The two projects
@@ -81,65 +81,69 @@
01 The two projects
02 Results matrix
-
wrapp
switchboard
nailinit
AdForgefounder-stack
jsonObject — forge_concepts_brand
pass
rendered ×3
pass
rendered ×3
Adwallfounder-stack
jsonObject — brand-draft
pass
rendered ×6
pass
rendered ×6
A-Plusfounder-stack
jsonObject — directions
pass
rendered ×3
pass
rendered ×3
Prismfounder-stack
jsonObject — concept-batch-draft
pass
rendered ×6
pass
rendered ×6
Shelffounder-stack
jsonObject — triage
pass
rendered ×4
pass
rendered ×4
Studiofounder-stack
jsonArray — today_shoot_list
pass
rendered ×6
pass
rendered ×6
Reelfounder-stack
jsonArray — script
pass
rendered ×5
pass
rendered ×5
Marqueefounder-stack
text — generate
pass
rendered
pass
rendered
Takefounder-stack
jsonArray — draftScript
pass
rendered ×2
pass
rendered ×2
Identityfounder-stack
jsonArray — person
pass
rendered ×3
pass
rendered ×3
Batchfounder-stack
jsonArray — draft_answer
pass
rendered ×10
pass
rendered ×10
Bankfounder-stack
jsonObject — today's brief
pass
rendered
pass
rendered
Redlinefounder-stack
jsonArray — audit
fail
no model call fired — folder-bound, no page on disk
fail
no model call fired — folder-bound, no page on disk
AdPulsefounder-stack
text — discover_meta_connector_prefix
warn
partial — CSV precursor renders; full diagnosis needs a live Meta connector
warn
partial — CSV precursor renders; full diagnosis needs a live Meta connector
Huddlechat
text — call-turn
pass
rendered
pass
rendered
betterchatchat
jsonArray — starter_suggestions
pass
rendered ×4
pass
rendered ×4
Cartridgeplay-make
jsonArray — pitch-deck
pass
rendered ×4
pass
rendered ×4
Arcanaafter-hours
jsonObject — reading
pass
rendered
pass
rendered
NATALafter-hours
jsonObject — full-read
pass
rendered ×3
pass
rendered ×3
Castplay-make
pass
rendered
pass
rendered (shared harness)
Arcadegame-generator
jsonArray — proposePitches
pass
rendered ×3
pass
rendered ×3
Yearbookimage-generation (viral retro portrait; text-to-image, NOT selfie-based)
reads single context (single (APP.usesContext='single'; brand = await relay.context.active()). Optional — drives the cold-open autostart when present, otherwise the app waits for a typed line.) · pipeline: jsonArraytext
-
switchboard pass
rendered ×3 2 model calls
nailinit pass
rendered ×3 2 model calls
+
switchboard pass
rendered ×4
nailinit pass
rendered ×4
Rizzdating-copywriting
reads persona context (single) · pipeline: jsonArraytext