Skip to content

ADR-0020: Update Model Across Three Surfaces (System Image, Coprocessor, Cartridges)

Incorporates by reference: ADR-0011 — Pi system-image update system. ADR-0011 remains the canonical source of truth for surface 1; this ADR positions it and adds explicit decisions for surfaces 2 and 3. Related: ADR-0006, ADR-0011, ADR-0017, ADR-0019, CLAUDE.md Canonical Hardware Specification.

Notion task: GWP-227 (consolidates closed tickets GWP-107, GWP-108, GWP-110, GWP-111, GWP-112, GWP-113, GWP-117).


When ADR-0011 was drafted (2026-04-21), the KN-86 had one update surface: the Pi Zero 2 W’s SD card carrying the full system image. ADR-0011 specified a Panic Playdate-style USB-MSC flow with tryboot A/B rollback, the Tauri desktop flasher, and the .kn86fw container. That decision still stands.

Two later ADRs added new update surfaces without amending ADR-0011:

  • ADR-0017 (2026-04-24) introduced the Pi Pico 2 coprocessor for YM2149 PSG synthesis and SSD1322 OLED driving. The Pico runs its own firmware on its own internal flash. ADR-0017 §“Decision item 6” sketched a Pi-driven UF2 flash flow but punted to a follow-on amendment of ADR-0011 that never landed.
  • ADR-0019 (2026-04-24) committed the cartridge to a full-size SD card carried in a clamshell sled, mounted as USB mass storage. Cart content updates are now file-level operations on the cart’s filesystem.

The original 7 firmware-cluster tickets (GWP-107 through GWP-117) were authored before these shifts and assumed a bare-metal Cortex-M target with embedded-flash A/B partitioning, an in-firmware recovery bootloader, and kn86flash serial signing. The Pi Zero 2 W with full Linux + a Pico coprocessor + SD-card carts is a different topology, and the unified-image assumption no longer fits.

This ADR is a re-scope. It does not invalidate ADR-0011 — it positions ADR-0011 as the surface-1 spec and adds explicit decisions for surfaces 2 and 3.

  1. ADR-0011 is still in Draft. Until surfaces 2 and 3 are resolved, ADR-0011 cannot be promoted to Accepted and Wave 2 implementation cannot enter the engineering pipeline as a coherent batch.
  2. The 7 closed tickets are stale. Their criteria reference flash sectors, BOOTSEL UF2, and Ed25519 signing for a Pico-as-primary target that no longer exists.
  3. The Pico coprocessor has no update story in any active ADR. Stage 1c bring-up needs a flash workflow.
  4. Community-cart distribution needs a placeholder decision so launch fiction doesn’t drift into an unsolved problem.
  • All hardware values are immutable from CLAUDE.md Canonical Hardware Specification. This ADR introduces no new spec values.
  • ADR-0011’s surface-1 architecture (USB-MSC, A/B tryboot, /home/shared save isolation, Tauri flasher, deferred signing) is incorporated wholesale.
  • The .kn86 cartridge container (ADR-0006) and NoshAPI FFI (ADR-0005) are unchanged.
  • v0.1 must fit one Wave 2 pass — anything requiring a new web service, key ceremony, or fiction layer falls out of scope.

The KN-86 ships three surfaces, three on-device payloads, one composite release artifact.

  1. Three surfaces.

    • Surface 1 — Pi system image. Delivered as .kn86fw per ADR-0011 unchanged. USB-MSC + tryboot A/B + Tauri flasher.
    • Surface 2 — Pico coprocessor firmware. Delivered as a UF2 file embedded inside the surface-1 system image at /lib/firmware/kn86-pico.uf2, flashed from the Pi during boot when the version handshake (ADR-0017 §5) detects mismatch.
    • Surface 3 — Cartridge content. .kn86 Lisp source containers per ADR-0006 (tree-walked on load; no bytecode), dropped onto the cart’s SD via desktop card reader. No over-the-wire path in v0.1.
  2. One composite release artifact: .kn86release. A small tarball containing one .kn86fw (which itself contains the Pico UF2), a RELEASE.toml manifest (system-image version, embedded Pico version, minimum NoshAPI version, build hash, SHA-256), and optionally bundled .kn86 carts for first-boot provisioning. The Tauri flasher consumes .kn86release, extracts .kn86fw, and pushes it to the device — Pico UF2 is invisible at the flasher layer.

  3. Pico flash from the Pi during boot. Pico powers on first per ADR-0017 §5 and reports its version. If the Pi’s pinned kn86-pico.uf2 declares a higher version, the Pi pulses BOOTSEL/RESET (proposed Pi GPIO22/GPIO23 per ADR-0017 §6, finalized at bring-up), the Pico enumerates as USB-MSC on the internal hub, the Pi copies the UF2, releases BOOTSEL, the Pico re-handshakes. The Pico flash is automatic and invisible to the user — Row 24 shows “UPDATING COPROCESSOR…” during the ~5–10 s window. Failure aborts boot with a clear error.

  4. Cart content updates are sneakernet in v0.1. User pulls the SD out of the clamshell sled, drops .kn86 files onto the FAT/exFAT filesystem via desktop card reader, reinserts. nOSh’s existing udev-driven hot-swap path (ADR-0019) handles re-mounting. No companion app, no firmware-managed cart writer, no community CDN in v0.1.

  5. NoshAPI version gating per cart. Cartridges declare req_api_version per ADR-0006. nOSh refuses to mount a cart with req_api_version > current and shows a Row 24 message (“CART REQUIRES SYSTEM UPDATE — RUN UPDATE OVER USB”). Forward compatibility is guaranteed by ADR-0005’s append-only NoshAPI vtable.

  6. Universal Deck State preservation. ADR-0011 §“SD partition layout” already isolates /home/shared (p6); this ADR re-affirms surfaces 2 and 3 also never touch p6 (Pico writes to its own internal flash; cart updates write to the cart’s own SD card).

  7. Signing deferred uniformly across all three surfaces. Per ADR-0011 §“Risks” item 6, v0.1 ships SHA-256 integrity only. The embedded UF2’s hash is covered transitively; .kn86 carts use the existing CRC-32 from ADR-0006. When signing arrives, it lands as one follow-on ADR covering all three surfaces with one shared key ceremony.

  8. Release discovery: kn86-deckline.com release-notes page + opt-in email list. No in-device update check in v0.1. The user learns about a release by visiting kn86-deckline.com or by receiving the launch-list email; the email list is captured on the same site (Buttondown-class tooling, ~1 afternoon of setup) and is the primary push channel. An in-device JSON-feed check at boot is out of scope for v0.1 and is not deferred to a named ADR — it requires a Wi-Fi network stack the device does not currently ship and changes the threat model. (Resolves Open Question §6, Option B.)

  9. Dev story is split.

    • Pi: scp to bench rig per ADR-0011 §“Dev ergonomics”.
    • Pico: picotool over USB to a Pico in BOOTSEL mode (manual button hold during prototype bring-up; automatic via the Pi GPIO path once Stage 1c lands). Documented in kn86-pico/README.md (created under ADR-0017 F2).
    • Cart: make cart produces a .kn86; cp to the SD card mounted on the dev machine.

    The full .kn86release build is a once-per-cycle dry run, not a per-iteration loop.

  10. First-boot cart provisioning: pre-installed AND physical cartridges (both). Each launch deck ships with the four launch-title .kn86 files pre-installed on the device’s internal microSD at /home/shared/launch-carts/ and with four physical SD-sled cartridges in the box. The two paths produce bit-identical bytes — the same .kn86 artifact is bundled into the system-image release tarball (and lands in /home/shared/launch-carts/ on first boot or on a clean re-image) and written to the physical cart sleds during fulfillment. Pre-installation is the customer’s safety net against a lost cartridge; the physical sleds are the canonical unboxing ritual and the artifact the fiction is built around. The cost is small (four .kn86 files inside the release tarball, ~1 MB total at current sizing) and well within ADR-0011’s slot-A rootfs envelope. (Resolves Open Question §1, Option C.)

  11. ADR-0011 publication shape: ADR-0020 sits as the umbrella above ADR-0011 (Option B). ADR-0011 stays as the surface-1-only Pi system-image spec; ADR-0020 sits above it as the multi-surface frame. ADR-0011 is not amended in place to absorb surfaces 2 and 3 — that would break the project convention that ADRs are immutable once Accepted. The cross-reference work for ADR-0011, ADR-0017, and ADR-0019 listed in §Documentation Updates lands in this same PR, not a follow-on. Project-wide directive (Josh, 2026-04-25): every ADR ships with Status: Accepted. There is no “Draft” state on the active path. ADR-0011 is therefore promoted to Accepted in this same PR; the ADR audit at §Documentation Updates lists every other ADR flipped here. (Resolves Open Question §2, Option B.)

  12. Third-party cartridge distribution after launch: curated online catalog on kn86-deckline.com (Option B). Post-launch, third-party carts are distributed through a curated online catalog GWP runs on the kn86-deckline.com storefront — authors submit .kn86 files, GWP reviews and approves, customers buy or download from the catalog and either receive a fulfilled physical SD-sled cart by mail or write the .kn86 to a blank cart at home. “Buy new, online store” is the framing: first-party-sold (GWP runs the storefront), explicitly not user-uploaded freeform, explicitly not mail-order-only. Pure mail-order forever (Q3.A) is too restrictive long-term; an open community repo (Q3.C) makes signing/safety load-bearing before v0.1 is ready for it. The curated catalog gives community velocity with editorial control and keeps signing as a once-only ADR (per item 7) rather than a load-bearing v0.1 dependency. The catalog itself is not a v0.1 deliverable — this is a direction-setting decision so launch fiction can talk about it without painting us into a corner. (Resolves Open Question §3, Option B.)

  13. Pico flash status display: full-screen takeover on the main 80×25 grid AND simultaneous CIPHER-LINE echo (Option C plus CIPHER-LINE). During the ~5–10 s Pico flash window, the user sees redundant status feedback on both visible surfaces. Main 80×25 grid: Row 0 status hint, Rows 1–23 dimmed with a centered progress indicator and the “UPDATING COPROCESSOR…” message, Row 24 action bar shows the same status. CIPHER-LINE 256×64 OLED: Row 1 status strip echoes “UPDATING COPROCESSOR” with a tick counter; Rows 2–3 carry CIPHER’s voice line for the event (“rewriting the realtime kernel — hold steady”); Row 4 shows the elapsed-time meter. Both surfaces clear together when the Pico re-handshakes. The redundancy is by design: the user has two visible surfaces during boot, the flash is a once-per-update event, and a coprocessor flash is the kind of “is the device dying or working” moment where unmissable beats subtle. CIPHER on the main grid for this event is sanctioned under the firmware-status-row exception in CLAUDE.md Spec Hygiene Rule 6 (Row 0/24 are firmware-owned) — the main-grid CIPHER copy is rendered as plain status text in the 80×25 grid, not as CIPHER glyphs. The actual CIPHER glyphs render only on the CIPHER-LINE OLED, consistent with ADR-0015. (Resolves Open Question §4, “C or CIPHER-LINE” interpreted as both.)

  14. Ed25519 signing trigger: 5 units in the field. When the 5th deck leaves Josh’s physical custody — beta unit, press loaner, friend’s hands, trade-show table, anything that puts five total units somewhere Josh doesn’t control — Ed25519 signing on all three update surfaces becomes a P0 follow-on ADR. “5 units” is more conservative than the original “100 units in the field” option from the brief and tighter than “first public event”; Josh selected 5 as the trigger because it’s a small enough number that the threat model shift is concrete (someone we don’t trust had physical access) without being so small that signing work blocks v0.1 itself. The signing ADR will cover all three surfaces (system image, Pico UF2, .kn86 carts) in one shared key-ceremony pass per item 7. SHA-256 integrity remains the v0.1 baseline until the trigger fires. The trigger event must be tracked: the next time a deck leaves controlled custody, PM logs the count in Notion against this decision and counts down from 5. Interpretation note: Josh’s literal answer was “5”; original options were A (first beta unit), B (100 units), C (first public event/loaner). 5 units does not exactly map to any of those; we read it as a fourth option Josh was authoring inline — “5 units in the field” — a conservative threshold sitting between A (1 unit) and B (100 units). Documented in commit message. (Resolves Open Question §5, custom threshold.)

  15. Decisions resolved by Josh 2026-04-25. Items 8 + 10–14 above resolve the six Open Questions previously listed in this ADR. The Open Questions section is removed; this ADR is now Accepted in full.


Bundle Pi system image + Pico firmware into one binary the Pi unpacks. Rejected because the on-device payloads are fundamentally different (multi-GB rootfs vs. 256 KB UF2); forcing them into one binary buys consolidation in name only.

Option B: Three independent release streams

Section titled “Option B: Three independent release streams”

User downloads three things. Rejected because Pi and Pico are coupled by the UART command protocol (ADR-0017); a Pi update that bumps the protocol must ship with a matched Pico UF2 or the device fails to boot. Forcing the user to track two version streams is bad UX.

Option C: Composite release with embedded Pico UF2 (ACCEPTED)

Section titled “Option C: Composite release with embedded Pico UF2 (ACCEPTED)”

.kn86release is the user-facing artifact. Tauri flasher (ADR-0011) needs no changes — single file in, single device out. Pico flash is an implementation detail of nOSh’s boot sequence.

Option D: Cartridge-as-update-carrier (diegetic “system update cartridge”)

Section titled “Option D: Cartridge-as-update-carrier (diegetic “system update cartridge”)”

Insert a special cart; device reads the image off the cart and self-updates. Rejected for v0.1 (thematically appealing but adds a second tested path). Worth revisiting as a Phase-3+ ritual for collector editions.

Rejected for v0.1. OTA is a multi-quarter project (network UX, Wi-Fi credential capture, captive portals, certificate pinning, partial-download recovery). Adding it changes the threat model. v0.1 stays on the cable.


  • Surface 1 (Pi system image): No change vs. ADR-0011. Wave 2 implementation tasks remain as specified there. Per Decision item 11, ADR-0011 is promoted to Accepted in this same PR.
  • Surface 2 (Pico coprocessor): Gains an automatic Pi-driven flash on every boot when version mismatch is detected. New nOSh boot-sequence integration point. ~5–10 s boot overhead on update boots only; zero overhead on normal boots once versions match. Per Decision item 13, the flash window paints both the main 80×25 grid (full-screen takeover) and the CIPHER-LINE OLED (4-row status echo) simultaneously.
  • Surface 3 (Cart content): Sneakernet only in v0.1. Operators side-load via desktop card reader. Hot-swap on re-insertion uses the existing ADR-0019 path. Per Decision item 12, post-launch curated catalog on kn86-deckline.com is the direction-setting target (not a v0.1 deliverable).
  • ADR-0011 is unblocked and is promoted to Accepted in this same PR (Decision item 11).
  • Pi+Pico version coupling is enforced by construction — embedding the Pico UF2 makes version skew unrepresentable.
  • Cart authors and operators have one clear distribution model. Same workflow as the dev loop.
  • First-boot UX has a safety net (Decision item 10) — pre-installed launch carts protect against lost physical sleds without weakening the unboxing ritual.
  • Pico flash visibility is unmissable (Decision item 13) — both visible surfaces echo the same status; users will not mistake a flash for a hang.
  • Signing has a concrete trigger (Decision item 14) — “5 units out of Josh’s hands” is a calendar event PM can track, not an open-ended promise.
  • Release discovery has a real channel (Decision item 8) — opt-in email list from launch day onward gives GWP a direct push channel without a network stack on the device.
  • The 7 stale tickets retire cleanly into ADR-0011 Wave 2 + ADR-0017 follow-ons + this ADR’s child tasks.
  • Three on-device update paths — three failure modes, three test matrices.
  • No community-cart distribution channel in v0.1 — homebrew carts go via desktop side-load only; the curated online catalog (Decision item 12) is post-launch direction-setting, not a v0.1 deliverable.
  • Pico UF2 ships embedded, not separate — a Pico-only fix still requires shipping a full system-image release.
  • Discovery requires user action (Decision item 8) — visit the site or open the email; no in-device prompt in v0.1. Trade-off accepted to avoid shipping a network stack.
  • First-boot rootfs grows by ~1 MB (Decision item 10) for the four pre-installed launch carts. Within ADR-0011 slot-A envelope.
  • Pre-installed launch-cart manifest must be kept in sync with physical fulfillment — cart-shell stamping and the bundled .kn86 files must produce the same bytes. PM tracks this for each release cut.
  • Signing trigger is a count, not a date (Decision item 14) — PM must track unit dispatches against the 5-unit threshold; no automated enforcement.
  • Curated online catalog is non-trivial post-v0.1 engineering (Decision item 12) — a website + author submission flow + review queue + payments + fulfillment integration. Lands as a post-v0.1 program of work, owned by Marketing/Product with engineering support.
  • F1 — ADR-0011 promotion + cross-references. Closed in this same PR per Decision item 11. ADR-0011 Status flips to Accepted; ADR-0011/ADR-0017/ADR-0019 cross-references land here too. PM scope.
  • F2 — .kn86release manifest spec. Tiny TOML schema, version semantics, SHA-256 coverage. Likely consolidated under existing tools/kn86fw/ (GWP-144). Embedded Systems scope.
  • F3 — Pico flash workflow integration into nOSh boot. Read Pico version, compare embedded UF2 manifest, conditional BOOTSEL/RESET pulse, dual-surface paint per Decision item 13 (main grid + CIPHER-LINE). C Engineer + Platform Engineering scope, post-Stage-1c.
  • F4 — Stale-ticket retirement sweep. GWP-107/108/110/111/112/113/117 marked Closed by ADR-0020 with back-pointer; live work absorbed into GWP-144 + ADR-0017 F2. PM scope.
  • F5 — Three-surface dev workflow doc. Captures scp, picotool, cp to cart. PM + Embedded Systems scope.
  • F6 — Curated cart catalog program plan (per Decision item 12). Marketing/Product writes the program scope; engineering follows. Post-launch.
  • F7 — Launch-cart bundling pipeline (per Decision item 10). The release-build pipeline copies the four launch .kn86 files into the system-image rootfs at /home/shared/launch-carts/ and the same bytes go to the physical-cart fulfillment vendor. Embedded Systems + Manufacturing/PM scope.
  • F8 — Email list infrastructure (per Decision item 8). Buttondown-class list, signup form on kn86-deckline.com, release-notes template. Marketing scope.
  • F9 — Signing trigger watch + signing ADR (per Decision item 14). PM tracks unit dispatches; when count reaches 5, files the signing ADR covering all three surfaces with one shared key ceremony. PM + Security scope, deferred until threshold.

Per CLAUDE.md Spec Hygiene Rule 3. No Canonical Hardware Specification values are introduced or changed. No CLAUDE.md table edits required.

The PR that lands this ADR (PR #106 / GWP-227) ticks all of the following:

  • docs/adr/ADR-0020-firmware-update-model.md — this file. Status: Accepted 2026-04-25. Open Questions resolved by Josh on the same date and baked into Decision items 8 and 10–14; the Open Questions section was removed.
  • docs/adr/README.md — index row for ADR-0020 reads Accepted 2026-04-25; ADR-0011 row reads Accepted 2026-04-25 (promoted in this PR per Decision item 11); ADR-0012 row reads Accepted 2026-04-25 (audit-flip per Josh’s 2026-04-25 directive that no Draft ADRs ship — see audit list below).
  • docs/adr/ADR-0011-device-firmware-update-system.md — Status flipped from Draft to Accepted 2026-04-25 per Decision item 11. Related: line gains ADR-0020. ADR-0011 is not amended in place to absorb surfaces 2 and 3 (umbrella-only, Q2 Option B).
  • docs/adr/ADR-0017-realtime-io-coprocessor.md — §“Decision item 6 (Firmware update path)” gains a back-pointer to ADR-0020 noting the embedded-UF2 model is now ADR-0020’s canonical position.
  • docs/adr/ADR-0019-cartridge-storage-and-form-factor.mdRelated: line gains ADR-0020.
  • docs/adr/ADR-0012-lisp-slot-table-widening.md — Status flipped from Draft to Accepted 2026-04-25 per Josh’s 2026-04-25 project-wide directive: every ADR ships with Status: Accepted; Draft is not on the active path. Audit confirmed ADR-0012 is a real, fully-specified decision with implementation plan, test plan, and rollback path — not a working document.
  • docs/KN-86-Definitive-Guide.md Part 16 (Decision log) — row to be added for ADR-0020. Held for the next docs sweep (F4 follow-on bundle); not a blocker for this ADR’s acceptance.
  • Stale-ticket retirement (GWP-107, GWP-108, GWP-110, GWP-111, GWP-112, GWP-113, GWP-117). Cross-link them to GWP-227. PM, F4 follow-on.

ADR audit (Josh’s 2026-04-25 “no Draft ADRs” directive)

Section titled “ADR audit (Josh’s 2026-04-25 “no Draft ADRs” directive)”

A grep -lE "Status:\s*(Draft|Proposed)" sweep across docs/adr/ was run as part of this PR. Findings:

ADRPre-PR StatusAction in this PRRationale
ADR-0011Draft — awaiting Josh sign-offFlipped to Accepted 2026-04-25Promotion is the explicit purpose of Decision item 11.
ADR-0012Draft — awaiting Josh sign-offFlipped to Accepted 2026-04-25Real, fully-specified decision (slot table layout, dispatcher diff, test plan); blocks GWP-186/187/188. Not a working doc.
ADR-0020ProposedFlipped to Accepted 2026-04-25This file. Decisions resolved 2026-04-25.

ADRs with non-Accepted Status that are not flipped:

  • ADR-0008 (Partially superseded by ADR-0016) — superseded is a valid terminal state; left as-is.
  • ADR-0013 (Superseded by ADR-0019) — superseded; left as-is.
  • ADR-0015, ADR-0016, ADR-0017 (Approved YYYY-MM-DD) — “Approved” is the project’s accepted-and-ready-for-implementation phrasing, equivalent to Accepted; left as-is, no project rule against this phrasing.

ADR-0003 (archived) requires no further action — it was retired 2026-04-21 when the Pico-as-primary target dropped, and the existing archive banner already points to ADR-0011. ADR-0020 does not invalidate that pointer.

A PR that lands this ADR without ticking the first six boxes fails review.


ADR-0011 was written for one update surface and got it right. Three days later, ADR-0017 added a coprocessor with its own flash and ADR-0019 made the cartridge a removable filesystem. Three surfaces, three failure modes, and a stale ticket cluster (GWP-107–117) that pre-dated all of them. This ADR is the re-scope: surface 1 stays exactly as ADR-0011 specified, surface 2 ships embedded inside the Pi system image so version coupling is enforced by construction, surface 3 is sneakernet — pull the SD from the clamshell sled, drop files on it, put it back. One composite .kn86release for the human-facing artifact; three on-device payloads under the hood. No new web service, no signing infrastructure, no OTA — those are deferred as named questions with named owners. Future readers should take three things: (1) ADR-0011 is the source of truth for the Pi update path — this ADR positions, doesn’t replace; (2) the Pico UF2 ships embedded, not separate, and that’s the version-coupling discipline the architecture requires; (3) carts are a removable filesystem now — updates are cp, not flash.