Default On-Device Capabilities — Cross-Capability Interaction Plan
Scope: Coordinates the four nOSh-runtime-owned (nOSh) default capabilities — not cartridge-level features. Four per-capability design docs feed into this synthesis:
- 2026-04-21-mission-board.md — GWP-179
- 2026-04-21-cipher-voice.md — GWP-180 (rewritten 2026-04-24 per ADR-0015; see also archived v1 at
../../_archive/plans/post-v0.1/2026-04-21-cipher-voice.md) - 2026-04-21-dev-repl.md — GWP-181
- 2026-04-21-nemacs.md — GWP-182
Backlog representations of the deferred implementation work: GWP-165 (Mission Board), GWP-166 (Cipher), GWP-168 (REPL), GWP-169 (nEmacs). This plan does not restate the Canonical Hardware Specification — reference that doc for grid / row / arena values.
CIPHER-LINE supersession note (2026-04-24). ADR-0015 (the CIPHER-LINE auxiliary OLED) and its companion
docs/architecture/KN-86-CIPHER-LINE-Grammar-Framework.mdretired every main-grid Cipher surface that earlier drafts of this plan assumed — the MISSIONS-tab Rows 9–12 Cipher rail, the full-screen debrief Cipher passage on Rows 1–23, and the editor-overlay Cipher ambient line on Row 22. The Cipher voice now renders exclusively on CIPHER-LINE (Rows 2–3 scrollback, Row 4 contextual) per the Grammar Framework.CLAUDE.mdSpec Hygiene Rule 6 ratifies this boundary with a single sanctioned exception (Null’scipher-main-grid-escape); no other default on-device capability renders Cipher glyphs on the main grid. Sections 2, 3, and 6 of this plan were revised in GWP-211 to reflect the new render surface.
1. Capability responsibilities at a glance
Section titled “1. Capability responsibilities at a glance”| Capability | Owner | Arena | Primary role | Owned data |
|---|---|---|---|---|
| Mission Board | nOSh runtime | mission_board struct + event bus | Procedural contract generation, board UI, resume slot | Active contract instances, board seed state, resume-slot metadata |
| Cipher voice | nOSh runtime | event ring + coherence stack + merged grammar arena (per-cart ≤8 KB); renders to CIPHER-LINE OLED, not the main grid | Structured-event-driven fragment composer keyed to deck state (five modes: observe / annotate / reflect / drift / silent) per Grammar Framework | Event ring (128 slots), coherence stack (5 slots × fragment hash + time delta), cipher_mode_weights, merged grammar frame (firmware universals + every loaded cart’s cipher-grammar block) |
| dev REPL | nOSh runtime | 24 KB arena (active only) | In-deck Lisp REPL over Fe VM | History cell (persisted to deck state), tutorial cursor, scratch env |
| nEmacs | nOSh runtime | 16 KB arena (active only) | Structural S-expression editor + snippet library | Edit buffer AST, palette state, snippet library index (persisted) |
All four share a single nOSh event bus for cross-capability signalling. No capability polls another’s struct directly, and no capability mutates another’s state.
2. Event bus — canonical event names and ownership
Section titled “2. Event bus — canonical event names and ownership”Publishers own event names and payload field shapes. Subscribers bind without coupling to publisher internals.
Mission Board publishes
Section titled “Mission Board publishes”mission_board_refreshed { board_hash, contract_count }mission_highlight_changed { contract_id, threat_level, phase_count, domain_tags[], payout_credits, narrative_seed, requires_swap }mission_bid { contract_id }mission_accepted { contract_id, scripted, template_handle, grants_mask }mission_suspended_resume_available { mission_id }mission_required_cartridge_missing { contract_id, missing_domain_tag }mission_phase_started { mission_id, phase_index }mission_phase_completed { mission_id, phase_index, outcome }mission_phase_transition_waiting { mission_id, next_phase_cart_tag }mission_completed { mission_id, outcome, reputation_delta, payout_delta }mission_abandoned { mission_id, reason }mission_script_failed { mission_id, failed_clauses[] }
nOSh (firmware core) publishes
Section titled “nOSh (firmware core) publishes”vocab_frame_updated { frame_hash, classes[], domain_tags[] }on cartridge insert/eject. Merged frame = firmware universals + all loaded-cartdefdomainrows.cartridge_inserted { cart_id, manifest }cartridge_ejected { cart_id }
nEmacs publishes
Section titled “nEmacs publishes”nemacs_script_submitted { contract_id, script_source, script_bytecode_handle }nemacs_script_abandoned { contract_id }cipher_on_editor_milestone { kind, context }wherekind ∈ {FIRST_LAMBDA_DEFINED, LONG_EDIT_NO_EVAL, CONTRACT_FAILED_3_TIMES}— low-frequency ambient signal Cipher may choose to narrate or ignore
Cipher publishes
Section titled “Cipher publishes”cipher_line_painted { event_type, mode, fragment_hash, seed_after }— informational, used by QA replay and the REPL scrollback mirror. Fires after each CIPHER-LINE Row-2/Row-3 repaint; includes the Grammar Framework mode that produced the fragment (:observe/:annotate/:reflect/:drift) and the post-advancecipher_seedvalue. Silent mode does NOT emit this event (no paint, nothing to record).
REPL publishes
Section titled “REPL publishes”repl_entered / repl_exited— consumed by Mission Board and nEmacs to enforce arena mutual exclusion
3. Pair-wise interaction contracts
Section titled “3. Pair-wise interaction contracts”3.1 Mission Board ↔ Cipher
Section titled “3.1 Mission Board ↔ Cipher”Direction: Mission Board → Cipher (Cipher is a pure subscriber here; it never mutates board state).
Cipher subscribes to all mission_* events listed above. On each, the nOSh runtime invokes cipher-push-event with the structured event record (type, affect, payload, narrative seed); the Cipher engine runs its mode selector against the current beat and event ring, elects a mode (:observe / :annotate / :reflect / :drift / :silent), and — when not silent — expands the grammar against the merged vocab frame to produce a CIPHER-LINE fragment. See Grammar Framework §§3, 5, 7 for the event-to-utterance pipeline; Mission Board → Cipher event typing is tabled in docs/plans/post-v0.1/2026-04-21-mission-board.md §Cross-capability hooks → Cipher voice.
Determinism fix: Every MissionInstance carries a narrative_seed distinct from cipher_seed. Cipher accepts the seed as an event-payload argument and uses it to seed a per-contract scratch LFSR, so board-driven commentary does not advance the deck’s global LFSR. This preserves QA replay: given a fixed event sequence and a fixed cipher_seed, CIPHER-LINE output is deterministic per Grammar Framework §9 Determinism.
Zones: Cipher renders on CIPHER-LINE Rows 2–3 (scrollback) — not the main grid. Per ADR-0015 and CLAUDE.md Spec Hygiene Rule 6, there is no MISSIONS-tab Cipher annotation rail on the 80×25 display, and there is no main-grid Cipher debrief modal on mission_completed. The Mission Board draws nothing on CIPHER-LINE; its only route to Cipher is the event stream, and the CIPHER-LINE engine owns rendering on that surface. Phase-completion Cipher commentary rides a :result-success / :result-failure event under the :debrief beat and fires on CIPHER-LINE like any other Cipher utterance — see docs/plans/post-v0.1/2026-04-21-cipher-voice.md §5.3 for the post-debrief-modal rationale.
3.2 Mission Board ↔ dev REPL
Section titled “3.2 Mission Board ↔ dev REPL”Direction: both.
REPL → Board: read-only introspection primitives.
(mission-board) → list of (id threat payout phase-count domains)(mission-board-contract id) → full descriptor(mission-active?) → boolean(mission-board-peek n) → n-contract summary (used also by Cipher mirror)(mission-board-gen) → generation metadata (seed, template ref)(mission-regenerate-preview seed rep balance caps overlay) → dry-run, does not advance global stateBoard → REPL: when a scripted mission is accepted with scope grants_mask, REPL binds one additional permission-gated primitive inside the mission scope:
(submit-solution! expr) ; bound only inside scripted-mission scopeBlocked at REPL (always raise '(not-authorized outside-mission :fn X)): mission-accept!, mission-board-refresh!, anything mutating deck state. Per the REPL agent’s visible-symbol policy, these remain bound for (describe 'X) discoverability but error on invocation.
mission-simulate-accept hint-integrity question: keep behind a debug flag, off by default. Will be re-evaluated when scripted missions ship.
3.3 Mission Board ↔ nEmacs
Section titled “3.3 Mission Board ↔ nEmacs”Direction: both. This is the scripted-mission loop — the most complex pair.
Board → nEmacs on scripted-mission acceptance:
nemacs_load_mission( mission_id, buffer_seed, /* starter AST, empty or template */ contract_fn, /* validates output */ error_scope_fn, /* returns node range for error highlighting */ grants_mask, /* ADR-0007 FFI tier */ mission_input /* passed to the form as bound value */)nEmacs → Board on EVAL:
- Contract pass →
nemacs_script_submitted→ firmware firesmission_accepted{ scripted: true }→mission_phase_startedetc. - Contract fail →
mission_script_failed{ failed_clauses[] }→ nEmacs highlights offending node viaerror_scope_fn. - Abandon (
BACKpast root or SYS → abandon) →nemacs_script_abandoned→mission_abandoned.
Resume: nEmacs owns writes to the resume_slot in deck-state flash. Mission Board owns reads. On cartridge_ejected mid-edit, nEmacs force-tears-down and serialises the AST to the resume slot; Board fires mission_suspended_resume_available when the required cart returns.
Palette enrichment (separate from the mission loop): on cartridge_inserted → vocab_frame_updated, nEmacs rebuilds its palette ranking with the new domain_tags[] for the +5 domain boost.
3.4 Cipher ↔ dev REPL
Section titled “3.4 Cipher ↔ dev REPL”Direction: both.
REPL → Cipher primitives:
(cipher-sample :template ...) ; read-only, scratch LFSR, no seed advance — for tutorials(cipher-generate :seed s :template t) ; pure render, deterministic, no seed advance(cipher-vocabulary) ; merged vocab frame(cipher-seed) ; read LFSR state non-mutativelyBlocked at home REPL: cipher-narrate (the side-effecting one that advances the deck cipher_seed and pushes a fragment onto CIPHER-LINE Row 2). It unlocks only inside the Null module’s mission scope as (cipher-monologue :topic ...) per the Null gameplay spec; Null’s monologue may also render on the main grid via cipher-emit-main-grid, authorized by the cipher-main-grid-escape capability (ADR-0015 §3a — the one sanctioned exception to CLAUDE.md Rule 6).
Cipher → REPL: REPL scrollback mirrors all mission_* and cipher_line_painted events as ;-commented log lines so operators can paste full board/Cipher context into bug reports. No direct RPC.
3.5 Cipher ↔ nEmacs
Section titled “3.5 Cipher ↔ nEmacs”Direction: both, low-bandwidth.
Cipher → nEmacs:
- Both subscribe to
vocab_frame_updated. Cipher uses the merged frame for passage composition; nEmacs uses it for palette ranking. Sameframe_hashon both sides. cipher_domain_tokens(cart_id)FFI from nEmacs into Cipher, used only to materialise the domain-tags list at palette construction.
nEmacs → Cipher:
cipher_on_editor_milestone { kind, context }— fires rarely, Cipher decides whether to render an ambient fragment on CIPHER-LINE.LONG_EDIT_NO_EVALandCONTRACT_FAILED_3_TIMESare the candidate ambient triggers. Cipher may stay silent (default for operators withcipher-silent=trueor during active scripted missions).- On
mission_script_failed, the mission’scontract_fncan optionally tag the event with a hint fragment which the Cipher engine surfaces at debrief time.
Never: Cipher does not render inside the editor code buffer (Rows 1–21) and there is no main-grid Cipher ambient overlay on Row 22. Ambient passages, if rendered, appear on CIPHER-LINE Rows 2–3 like any other Cipher utterance — per ADR-0015 and CLAUDE.md Rule 6, there are no main-grid Cipher surfaces inside nEmacs (nEmacs is not the sanctioned exception; Null is). See docs/plans/post-v0.1/2026-04-21-cipher-voice.md §4.2 for the full rationale.
3.6 dev REPL ↔ nEmacs
Section titled “3.6 dev REPL ↔ nEmacs”Direction: both, high-bandwidth.
nEmacs → REPL on EVAL:
repl_eval_form(form, grants_mask, timeout_ms, mem_limit_bytes) → value | errorMission-context EVAL passes the mission’s grants_mask; free-form EVAL passes the home REPL grants. Three arenas may coexist at peak: active cart (~16–32 KB) + REPL (24 KB) + editor (16 KB) ≈ 72 KB of 520 KB SRAM. Safe.
Open question (resolved in this plan, needs ratification): the REPL “evaluator” is a shared service decoupled from the REPL UI. nEmacs can invoke it without “entering” the REPL screen. This is the only way the arena budget works, and both agents assume it.
REPL → nEmacs (the shared snippet library):
(snippet-list) ; read-only, returns names(snippet-load name) ; read-only, returns source AST(snippet-save! name form) ; one exceptional write primitive in the REPL(open-in-nemacs form) ; full-context handoff — REPL tears down, nEmacs opens with form pre-populatedShared *clipboard* binding is writable from both. Library format is canonical Lisp source (not serialised AST), 32-entry index + blob region, owned by nEmacs.
Arena mutual exclusion: REPL and nEmacs cannot be active simultaneously. repl_entered forces nEmacs teardown with auto-save to resume slot; nemacs_entered does the same to REPL history. Active-cart teardown on either is governed by the cart’s arena size (see §5).
4. Shared resources
Section titled “4. Shared resources”4.1 Merged vocabulary frame
Section titled “4.1 Merged vocabulary frame”Owner: nOSh core (not any single capability). Computed at cartridge insert/eject from firmware universal tables + every loaded cart’s defdomain rows. Publishes vocab_frame_updated { frame_hash, classes[], domain_tags[] }. Consumers: Cipher composer, nEmacs palette.
4.2 Universal Deck State
Section titled “4.2 Universal Deck State”Read-only from REPL ((deck-state), (credits), (reputation), (cartridge-history), (cipher-seed)). Writable only via mission context granted primitives or nOSh-runtime-owned transitions. nEmacs does not access deck state directly — everything flows through mission grants_mask.
4.3 Snippet library
Section titled “4.3 Snippet library”Owner: nEmacs (storage + index). Readable + writable from REPL via the three primitives above. Format is Lisp source text, survives firmware/VM version drift and enables link-cable portability between decks. 32 entries, flash-backed.
4.4 Resume slot
Section titled “4.4 Resume slot”Owner: nEmacs writes, Mission Board reads. One AST blob + mission_id + buffer_seed. Fires mission_suspended_resume_available when the matching cart returns.
5. Memory orchestration
Section titled “5. Memory orchestration”Arena coexistence is the single constraint ratified across the four designs. Ratification note to write a new ADR superseding ADR-0002’s softer “suspend” language — flagged by dev REPL agent.
Rules:
- At most one of {REPL, nEmacs} is active at any time. Entering one tears down the other, auto-saving the torn-down subsystem’s state (REPL: history to deck-state; nEmacs: AST to resume slot).
- Active cart coexists with whichever of {REPL, nEmacs} is live, provided cart arena ≤ 32 KB. Nominal peak: cart 32 + REPL 24 = 56 KB, or cart 32 + nEmacs 16 = 48 KB. Both fit in 520 KB SRAM with headroom for firmware.
repl_eval_formcalled from nEmacs spins up REPL arena on demand (no UI entry) and frees post-dispatch unless debugger-hold is set (post-v1.1 feature).- Cartridge swap mid-edit is a hard nEmacs teardown trigger — detect-pin lowering raises an interrupt that pre-empts any in-flight form.
6. UI zone map (default on-device surfaces only)
Section titled “6. UI zone map (default on-device surfaces only)”All zones live within the content area defined in CLAUDE.md. Row 0 (status bar) and Row 24 (action bar) are nOSh-runtime-owned and never drawn by these subsystems directly — they publish hints via nosh_publish_status_hint / nosh_publish_action_hint instead.
The table below covers the primary 80×25 grid. The Cipher voice does not appear on this surface (per ADR-0015 and CLAUDE.md Rule 6, with the single sanctioned Null exception noted below); its render zones live on the CIPHER-LINE auxiliary OLED and are summarized separately.
| Surface | Rows | Notes |
|---|---|---|
| Mission Board (list) | 1–22 | Title row 1, list body rows 2–22 |
| Mission Board inspector (Zone D, inline) | 14–22 | First CAR on highlighted contract; does not lose list context |
| Mission Board inspector (full) | 1–23 | Second CAR promotes to full-screen Pattern 5 |
| REPL prompt + scrollback | 1–23 | Full content area when REPL is the active surface |
| nEmacs code buffer | 1–21 | AST-rendered with structural indentation |
| nEmacs palette | 22–23 | 2 rows × 4 slots = 8 visible candidates (reconciled from ADR-0008’s 3-row proposal) |
| Null’s Cipher main-grid escape | 1–23 | The one sanctioned ADR-0015 exception. Null cartridge only, authorized via cipher-main-grid-escape; rendered by cipher-emit-main-grid, not by the nOSh runtime Cipher engine |
CIPHER-LINE auxiliary OLED surface. Authoritative layout in ADR-0015 §2 and Grammar Framework §2. Summary here for cross-capability orientation only:
| CIPHER-LINE row | Content | Writer |
|---|---|---|
| Row 1 | Status strip (battery, timer, mode, TERM-hint chip) | nOSh runtime + cart via aux-status-render right-edge chip |
| Rows 2–3 | Cipher scrollback (current fragment + previous echo) | nOSh runtime Cipher engine; cart contributes events / grammar / mode-bias |
| Row 4 | Contextual (sub-timer, seed capture, phase meta, link status) | nOSh runtime, optionally biased by cart via aux-* primitives |
All Cipher utterances driven by Mission Board, REPL, nEmacs, or firmware lifecycle events land on CIPHER-LINE Rows 2–3, never on the main grid.
Resolution of the palette row contradiction (nEmacs agent’s flagged issue): ADR-0008’s “rows 23–25” violates CLAUDE.md Row 24 firmware ownership. This plan ratifies palette on rows 22–23, key hints published to Row 24 via nosh_publish_action_hint. ADR-0008 needs an amendment (follow-up action).
7. Decisions and follow-ups
Section titled “7. Decisions and follow-ups”7.1 Ratified by Josh (2026-04-21)
Section titled “7.1 Ratified by Josh (2026-04-21)”cartridge_history = uint32_t. Capability-Model-Spec is canon. Bare-Deck-Terminal-Spec’suint16_tis a stale reference — fix it in a doc-hygiene PR.- Mission Board is a tab. Home-Screen-Design is canon. Capability-Model-Spec’s “primary interface after boot” phrasing needs amending to “primary tab after boot.”
cipher_seed = 32-bit(4-byte LFSR, matches DeckState). Bare-Deck-Terminal-Spec’s “16-bit Galois” is a stale reference — fix it in the same doc-hygiene PR.
7.2 PM calls (2026-04-21)
Section titled “7.2 PM calls (2026-04-21)”These are settled; they are recorded here for future reference and do not require further decision.
- Memory orchestration: tear-down-and-restart, not suspend. Exiting REPL or nEmacs drops the arena; any user-facing state worth keeping was serialised first (REPL history → deck state; nEmacs AST → resume slot). Simpler code, predictable memory, frees SRAM for the active cartridge. A new ADR superseding ADR-0002’s softer “suspend” language is a follow-up doc task — not a blocker for implementation.
- ADR-0008 palette rows: 22–23, not 23–25. Row 24 is nOSh-runtime-owned per CLAUDE.md. ADR-0008 amendment is follow-up doc hygiene.
- Scripted-mission submission routes through nEmacs. REPL’s
(submit-solution!)remains as a non-scripted shortcut for dev use. Agents converged on this; no conflict. - LAMBDA on Mission Board: drop the predicate-macro proposal. Keep LAMBDA’s existing 32-step sequence meaning per Input System Architecture. Tag-filtered bidding lives under CONS.
- SYS → Refresh Board rep cost = 1 point. Adjust after playtest.
mission-simulate-acceptis off by default, gated behind a--dev-modeCLI flag.cipher-silentis a global manifest flag, not per-passage-type.- Ambient-commentary Row 22 overlay fade vs overwrite: deferred to playtest. Not a pre-implementation blocker.
- QUOTE replay in Cipher uses composition tuples, not rendered text. Smaller and composes with later vocab updates.
- Null module uses the home REPL with extra primitives bound inside Null mission scope. No dedicated surface.
- Tutorial re-entry after wipe: deferred to marketing/packaging. Not a pre-implementation blocker.
- PAL-mode palette scroll: QUOTE/LAMBDA (not numpad 2/4, which select slots). Input System Architecture ratifies in the nEmacs implementation wave.
- ADR-0008 status: marked “Accepted (v1.0)” but nEmacs slips post-launch per ADR-0002. Doc-hygiene fix.
- Relay tag-collision: cartridge wins. Cartridge is source; Relay is a content-update layer that cannot override author intent.
7.3 Doc-hygiene follow-ups (not blocking)
Section titled “7.3 Doc-hygiene follow-ups (not blocking)”These happen when the relevant capability promotes out of the deferred catalog. Each gets folded into that capability’s implementation PR.
- Fix
cartridge_historywidth in Bare-Deck-Terminal-Spec (from §7.1.1). - Fix
cipher_seedwidth in Bare-Deck-Terminal-Spec (from §7.1.3). - Amend Capability-Model-Spec’s “primary interface after boot” → “primary tab after boot” (from §7.1.2).
- Write ADR superseding ADR-0002 memory-orchestration “suspend” language with tear-down-and-restart (from §7.2.4).
- Amend ADR-0008 palette-row mockups to rows 22–23; fix its Accepted status line (from §7.2.5 and §7.2.16).
8. Next steps (when these features promote out of the deferred catalog)
Section titled “8. Next steps (when these features promote out of the deferred catalog)”- Resolve open questions 1–6 first — they block implementation.
- Write a new ADR ratifying §5 memory orchestration and §6 palette row resolution.
- Write a companion ADR enumerating the full event bus contract in §2.
- Update ADR-0008 with a superseding note pointing at this plan.
- Engineering can then work the four deferred tasks in dependency order: Cipher grammar (no dependencies) → Mission Board generation (publishes events Cipher needs) → nEmacs (consumes vocab frame + mission dispatch) → dev REPL (ties it all together).
- Null module gameplay spec is the natural integration test — it exercises Cipher, REPL, and deck-state reads simultaneously.
Authored by PM synthesis from four parallel design agents: GWP-179 (Mission Board), GWP-180 (Cipher), GWP-181 (REPL), GWP-182 (nEmacs). Individual per-capability docs in this directory are the authoritative sources; this plan is the integration layer.