Dev REPL — Design
Depends on: ADR-0001 (Fe VM substrate), ADR-0002 (player-facing Lisp commitment), ADR-0005 (FFI tier 3 — REPL read-only), ADR-0007 (scripted-mission FFI contract), GWP-154 (Fe VM bootstrapped in v0.1). Hardware reference: See CLAUDE.md Canonical Hardware Specification — this doc does not restate display, grid, font, or input counts.
Responsibilities & owned data (arena, history cell, tutorial state)
Section titled “Responsibilities & owned data (arena, history cell, tutorial state)”The Dev REPL is a nOSh-runtime-owned capability, peer to Mission Board, Cipher, and Deck State browser. It ships on every deck and is always selectable from the home screen. It has no cartridge dependency.
Owned surfaces
Section titled “Owned surfaces”- REPL arena — 24 KB of SRAM (per ADR-0002). Allocated on REPL entry, released on exit. Holds the Fe environment, bindings introduced by the session, intermediate evaluation frames, and the scrollback ring. Reset to empty on every fresh entry (history survives; bindings do not).
- History cell (
*history*) — a cons-list of the last 32 evaluated expressions plus their printed results, persisted to Universal Deck State on REPL exit. This is the canonical “history” object, a normal Lisp cell, addressable from the REPL via the*history*symbol. Players recall earlier expressions byCDR-ing into it. - Tutorial state (
*tutorial-cursor*) — a small byte-field in Universal Deck State tracking tutorial progress:first_boot_seen,car_cdr_completed,quote_eval_completed,lambda_completed,graduated. Four stages plus a “done” terminal. Graduation is latching: once set, tutorial mode never re-arms (unless explicitly reset from Deck State browser). - Snippet library — post-v1 per-deck collection of named Lisp expressions authored in nEmacs, loadable from the REPL. Storage lives in Universal Deck State (see Cross-capability hooks → nEmacs). REPL is a consumer; nEmacs owns the write path.
Not owned
Section titled “Not owned”The REPL does not own: mission contract data (Mission Board), Cipher vocabulary or narration (Cipher), buffer text (nEmacs), the Fe VM itself (shared runtime per GWP-154), or deck-state mutation paths (scripted-mission context only, per ADR-0007).
UI zones
Section titled “UI zones”The REPL runs in TEXT mode. Row 0 (status bar) and Row 24 (action bar) remain nOSh-runtime-owned per CLAUDE.md’s non-negotiable row layout. The REPL draws only Rows 1–23.
| Zone | Rows | Purpose |
|---|---|---|
| Banner | Row 1 | KINOSHITA DEV REPL — Fe VM vX.Y and tutorial-stage indicator when active. |
| Scrollback | Rows 2–21 | 20-row ring of >-prefixed prompts and =>-prefixed results. Oldest entries scroll off the top. |
| Separator | Row 22 | Single horizontal rule (CP437 box-drawing). |
| Prompt line | Row 23 | Structural composition target. Shows the expression under construction and a cursor. Same structural rendering conventions as nEmacs (indentation + bracket glyphs, never literal parens). |
Status bar (Row 0, nOSh-runtime-owned) shows operator handle, credits, reputation, and the indicator REPL in the mode slot. Action bar (Row 24, nOSh-runtime-owned) surfaces context-specific hints: CAR descend · CDR sibling · CONS palette · QUOTE history · EVAL run · BACK exit.
No graphics mode. The REPL is TEXT-only. FFI primitives that draw bitmaps are forbidden (Tier 3 per ADR-0005). If a snippet tries to call gfx-* or text-*, it raises :not-authorized outside-mission.
Input grammar (how CAR/CDR/CONS/QUOTE/EVAL/BACK/LAMBDA/ATOM/NIL drive the REPL)
Section titled “Input grammar (how CAR/CDR/CONS/QUOTE/EVAL/BACK/LAMBDA/ATOM/NIL drive the REPL)”The REPL reuses the device’s universal Lisp-key grammar. Same keys, same mental model as the Mission Board and nEmacs.
| Key | In prompt line (composition) | In scrollback (review) |
|---|---|---|
| CAR | Descend into the first child of the form under the cursor (structural navigation, identical to nEmacs). | Descend into the selected result to inspect it as a cell. |
| CDR | Advance to next sibling in the current list. | Move to next scrollback entry (older or newer per current direction). |
| CONS | Open the predictive palette (same palette engine as nEmacs) to insert a token at the cursor. | — |
| QUOTE | Grab mode: select the current subtree and store a reference in *history*[n] for reuse. | Pull a prior expression from history back onto the prompt line as editable. |
| EVAL | Submit the current prompt line for evaluation. Result is appended to scrollback with a => prefix. | Re-run the highlighted scrollback expression (equivalent to QUOTE-to-prompt then EVAL). |
| BACK | Ascend to parent form. If already at prompt root, exits the REPL (with confirmation if history has unsaved entries). | Return focus to prompt line. |
| LAMBDA | Enter literal-entry mode (numpad multi-tap for identifiers, numbers, strings). Same contract as nEmacs. | — |
| ATOM | Query the type of the form under the cursor. Shows atom, cons, lambda, symbol, number, string, or nil in a transient readout on Row 22. | — |
| NIL | Delete the form under the cursor (cut to clipboard shared with nEmacs, post-v1). | — |
Focus toggle. The prompt line holds focus by default. BACK from prompt root moves focus to scrollback; BACK from scrollback exits the REPL. This mirrors Mission Board’s two-zone focus pattern.
Lifecycle
Section titled “Lifecycle”Invocation from home
Section titled “Invocation from home”The home screen (Bare Deck Terminal HUD) lists firmware utilities. Selecting DEV REPL with EVAL triggers:
- Firmware saves current home-screen context.
- REPL arena (24 KB) is allocated.
- Fe VM is re-entered with a fresh top-level environment. REPL-tier FFI primitives (Tier 3 per ADR-0005) are bound as built-ins.
*history*is deserialized from Universal Deck State into the arena.- Banner and prompt render. If
*tutorial-cursor*is not atgraduated, the tutorial pre-loads the next staged prompt.
Tutorial first-boot flow
Section titled “Tutorial first-boot flow”On very first boot (new deck, never-entered-tutorial state), the Bare Deck Terminal HUD displays a one-time prompt: “FIRST-RUN DIAGNOSTIC AVAILABLE. PRESS EVAL TO BEGIN.” Accepting enters the REPL with *tutorial-cursor* = first_boot_seen and begins the four-stage walkthrough (see Tutorial mode section).
Suspension on cart load
Section titled “Suspension on cart load”Cartridges and the REPL are mutually exclusive, mirroring ADR-0002’s editor/cart decision.
- If the player inserts a cartridge while the REPL is active, the nOSh runtime prompts: “CARTRIDGE INSERTED. SAVE REPL SESSION? (EVAL=YES, BACK=NO)”.
- On
EVAL: history is flushed to Deck State, arena released, cartridge load proceeds. - On
BACK: cartridge insertion is deferred (physical slot remains valid but dormant; the nOSh runtime waits until the REPL exits).
There is no “suspend” — the REPL is not preserved in-place while a cart runs. The cart reclaims the full cart arena (16–32 KB per ADR-0010). Reopening the REPL later re-enters with fresh bindings; *history* is restored.
Teardown
Section titled “Teardown”Exiting via BACK from prompt root (or via home-screen return):
- Confirm if
*history*has new entries since last persistence. - Serialize
*history*(cons-list → byte buffer) into Universal Deck State. - Release the 24 KB arena.
- Return to home-screen context.
History persistence
Section titled “History persistence”History lives in Universal Deck State’s reserved[16] region is too small — this design extends Deck State with a repl_history_len + repl_history[N] field (proposed size: 512 bytes, sufficient for ~32 short expressions). Open question: exact byte budget and whether to store in deck-state SRAM vs dedicated flash region (see Open questions §1).
Memory orchestration (24 KB arena coexistence with active cart; REPL + nEmacs mutual exclusion)
Section titled “Memory orchestration (24 KB arena coexistence with active cart; REPL + nEmacs mutual exclusion)”Per ADR-0002 and ADR-0010, SRAM is tightly budgeted. The REPL’s 24 KB is a first-class citizen alongside:
| Consumer | Budget | Lifetime |
|---|---|---|
| nOSh core + Fe VM resident | ~48 KB code + ~16 KB data | Always resident |
| Universal Deck State | ~4 KB SRAM mirror (flash-backed) | Always resident |
| REPL arena | 24 KB | REPL active only |
| nEmacs arena | 16 KB | nEmacs active only |
| Active cartridge arena | 16–32 KB (configurable per ADR-0010) | Cart loaded only |
| Scripted-mission sub-arena | 4–8 KB (per ADR-0007) | Inside a scripted-mission phase only |
Rule: REPL, nEmacs, and active-cartridge arenas are mutually exclusive. At most one of the three is allocated at a time. Transitions:
- Home → REPL: allocate 24 KB.
- Home → nEmacs: allocate 16 KB.
- Home → cart insert: allocate cart arena.
- REPL ↔ cart: REPL tears down (history persisted), cart loads, fresh arena.
- REPL ↔ nEmacs: REPL offers “save snippet to library” on exit (see Cross-capability hooks → nEmacs); then tears down; nEmacs opens.
- nEmacs ↔ scripted mission: nEmacs’s edit buffer compiles to bytecode, is handed to the mission runtime which allocates its own 4–8 KB scripted-mission sub-arena inside the active-cart arena.
Important contradiction flagged. ADR-0002’s statement that “24 KB REPL + 16 KB editor + active cart must coexist” is not achievable simultaneously on the SRAM budget if we want any headroom for nOSh + display buffers. The resolution — mutual exclusion per above — matches ADR-0002’s “probable approach” but should be ratified explicitly in a memory-orchestration ADR. See Open questions §2.
FFI surface summary
Section titled “FFI surface summary”Per ADR-0005 Tier 3, the REPL binds a read-only subset of NoshAPI. Writes are gated.
Read-only (bound in REPL top-level)
Section titled “Read-only (bound in REPL top-level)”| Category | Primitives |
|---|---|
| Deck state | get-handle, get-credits, get-reputation, has-capability, deck-state |
| Cell inspection | current-cell, list-get, list-length, is-leaf (on nOSh-runtime-owned cells only) |
| Procedural | lfsr-seed, lfsr-next, lfsr-range |
| Display query | display-mode |
| Session metadata | *history*, *tutorial-cursor* (new, REPL-scope) |
Write-gated (visible as bound symbols; raise on call)
Section titled “Write-gated (visible as bound symbols; raise on call)”Symbols like credit-add, rep-modify, spawn-cell, drill-into, phase-advance, mission-complete, text-puts, gfx-*, and sfx-* are defined in the REPL environment (so (describe 'credit-add) shows a signature and doc) but calling them raises a structured error:
(credit-add 100);; => (error not-authorized outside-mission;; :fn credit-add;; :message "Writes to deck state require mission context.")This makes the FFI tier discoverable rather than hidden. Players can read documentation for every primitive without needing the manual.
Forbidden entirely
Section titled “Forbidden entirely”eval-string / load-file / intern from raw strings (consistent with ADR-0007 Tier 3). Reason: prevents arbitrary-code injection into what is, in-fiction, “hardware-trusted” firmware. Players compose Lisp via the structural palette, not by pasting text.
Tutorial mode (concrete sequence of pre-loaded prompts)
Section titled “Tutorial mode (concrete sequence of pre-loaded prompts)”Four stages, each gated by successful evaluation before advancing. Tutorial prompts are pre-composed in the prompt line (player executes rather than composes). Cipher narrates each stage in Row 22 (transient) before the prompt appears.
Stage 1 — CAR/CDR on a toy list
Section titled “Stage 1 — CAR/CDR on a toy list”Cipher (pre-prompt): “Every list begins with a head and continues with a tail. Press EVAL to see the first number.”
Pre-loaded prompt: (car '(1 2 3))
Expected result: => 1
Next pre-loaded prompt: (cdr '(1 2 3))
Expected result: => (2 3)
Advance gate: both evaluated. *tutorial-cursor* → car_cdr_completed.
Stage 2 — QUOTE and EVAL
Section titled “Stage 2 — QUOTE and EVAL”Cipher: “Quote preserves a form. Eval executes one. Observe.”
Pre-loaded prompt: (quote (+ 1 2))
Expected result: => (+ 1 2) (unevaluated)
Next pre-loaded prompt: (eval (quote (+ 1 2)))
Expected result: => 3
Advance gate: *tutorial-cursor* → quote_eval_completed.
Stage 3 — First lambda
Section titled “Stage 3 — First lambda”Cipher: “A lambda is an unnamed function. Apply it.”
Pre-loaded prompt: ((lambda (x) (* x x)) 5)
Expected result: => 25
Advance gate: *tutorial-cursor* → lambda_completed.
Stage 4 — Free play graduation
Section titled “Stage 4 — Free play graduation”Cipher: “You have the grammar. The rest is yours. Try (get-credits).”
Prompt line is empty. The player must compose their first original expression (using CONS to open the palette, etc.). Evaluation of any well-formed expression — regardless of result — graduates them.
Advance gate: *tutorial-cursor* → graduated. Cipher closes: “Tutorial complete. Mission Board awaits.”
Skipping
Section titled “Skipping”From any stage, BACK offers “EXIT TUTORIAL? (EVAL=YES, BACK=CANCEL)”. Exiting skips ahead to graduated. Players can re-enter the tutorial from Deck State browser (RESET TUTORIAL).
Cross-capability hooks
Section titled “Cross-capability hooks”← Mission Board (read-only introspection API; scripted-mission eval dispatch)
Section titled “← Mission Board (read-only introspection API; scripted-mission eval dispatch)”The REPL exposes Mission Board data for inspection. Mutation is forbidden.
| Primitive | Signature | Permission | Returns |
|---|---|---|---|
(mission-board-contracts) | () → list | read-only | List of (id threat payout phase-count domains) tuples for every currently generated contract on the board. id is a symbol, domains is a list of capability symbols. |
(mission-board-contract id) | (symbol) → struct | read-only | Full contract descriptor: (:id :title :threat :payout :phases :description :cipher-intro). |
(mission-active?) | () → bool | read-only | #t if a mission is mid-phase anywhere in the phase chain; #f otherwise. Always #f from the REPL context since REPL and active-cart are mutually exclusive, but the primitive is present for completeness. |
(mission-accept! id) | (symbol) → error | blocked | Always raises '(not-authorized outside-mission :fn mission-accept!). |
(mission-board-refresh!) | () → error | blocked | Regeneration of the board is nOSh-runtime-owned and requires a deck-state-mutation context. Same error. |
Event flow (scripted-mission dispatch, future hook). When a scripted mission is active and the player invokes the REPL from within the mission’s editor context (not the top-level home REPL), a different primitive becomes available:
(submit-solution! expr)— only bound inside scripted-mission scope; evaluatesexpragainst the mission’s acceptance contract (ADR-0007) and returns(:pass)or(:fail clause-list). This is the hook by which the REPL becomes the submission surface for scripted missions, rather than duplicating the evaluator inside the mission-board runtime.
Mission Board agent, please confirm: (a) the shape of the contract tuple above; (b) whether Mission Board maintains its own symbol table for contract IDs or expects the REPL to coin them at read time; (c) whether (mission-board-contracts) should return only visible contracts (post rep/capability filtering) or all generated ones.
← Cipher voice (Null module verbose-monologue trigger; any REPL commands that query Cipher?)
Section titled “← Cipher voice (Null module verbose-monologue trigger; any REPL commands that query Cipher?)”The REPL both consumes Cipher (as a read target for voice samples) and can trigger Cipher monologues.
| Primitive | Signature | Permission | Semantics |
|---|---|---|---|
(cipher-vocabulary) | () → list | read-only | Returns the active vocabulary — list of (term domain definition) tuples from Firmware + every loaded cartridge’s defdomain. Per Null gameplay-spec §5, this is the data source for the CIPHER ANALYSIS bounty. |
(cipher-seed) | () → integer | read-only | Current LFSR seed. Advances on every Cipher generation; reading does not mutate. |
(cipher-generate :seed s :template t) | (keyword-args) → string | read-only | Render a one-shot Cipher passage against the given seed and template class (e.g., :operator-profile, :phase-intro, :diagnostic). Does not advance the live cipher_seed in Deck State — this is a pure function for inspection. |
(cipher-narrate msg) | (string) → error | blocked | Pushing narration to the live Cipher surface is mission-context only. Same :not-authorized outside-mission error shape. |
Null module verbose-monologue trigger. Per Null gameplay-spec (~line 340), Cipher “speaks freely” inside Null. When the Null cartridge is loaded and running, its mission context can expose an additional REPL-like affordance — the player enters the REPL inside Null, and the REPL environment auto-imports an extended Cipher bind: (cipher-monologue :topic ...) which generates longer passages from Cipher’s verbose grammar. This is Null-specific and is not available in the home REPL. The home REPL only sees the read-only inspection primitives above.
Cipher agent, please confirm: (a) whether (cipher-generate) is deterministic given (seed, template, deck-state-snapshot) and whether we should pin the snapshot to avoid side-channel reads; (b) the enumeration of template class symbols — do you want to expose them as a keyword list or a defined enum; (c) whether cipher-vocabulary should expose the full vocabulary or only terms from cartridges the player has actually loaded (per cartridge_history bitfield).
← nEmacs (eval of expression-under-cursor from editor; snippet save/load shared with editor’s library)
Section titled “← nEmacs (eval of expression-under-cursor from editor; snippet save/load shared with editor’s library)”The REPL and nEmacs are tightly coupled via the snippet library and the structural-editing grammar.
| Primitive | Signature | Permission | Semantics |
|---|---|---|---|
(snippet-list) | () → list | read-only | Returns list of (name . signature) pairs from the per-deck snippet library. |
(snippet-load name) | (symbol) → form | read-only | Returns the Lisp form stored under name. Form is a normal cell — the player can inspect it with CAR/CDR, rebind it, or EVAL it. |
(snippet-save! name form) | (symbol form) → nil | write allowed | The REPL’s only non-error write path. Saves a form to the snippet library. Reasoning: the snippet library is deck-local, does not affect mission state, and is the intended surface for REPL → nEmacs handoff. If this is deemed too permissive, fall back to gating via a home-screen confirmation (see Open questions §3). |
(open-in-nemacs form) | (form) → nil | write allowed | Exits the REPL (with history flush), opens nEmacs, pre-populates the buffer with form. Used for “I’ve prototyped this in the REPL, now let me edit it properly.” |
Eval-from-editor (the reverse flow). When the player is inside nEmacs with a cursor on a top-level form, a key combo (proposed: EVAL at top-level, or SYS + EVAL chord) invokes a transient evaluation: the form is sent to a temporary REPL evaluator (not the full REPL surface — just the Fe VM + REPL-tier FFI), result is rendered inline on nEmacs’s Row 22, no history persistence. This avoids forcing an nEmacs → REPL context switch just to test a snippet. Per ADR-0002’s “From the editor, EVAL runs the current top-level form.”
Shared clipboard. When nEmacs enters “grab mode” (per ADR-0008) and the player cuts a subtree, that subtree lives in a shared clipboard addressable from both nEmacs and the REPL as the special binding *clipboard*. The REPL can (eval *clipboard*) to run whatever nEmacs just cut. Size: capped at 1 KB (roughly the largest useful hand-edited snippet).
nEmacs agent, please confirm: (a) whether the snippet library’s on-disk format is source or Fe bytecode (proposed: source, for portability and inspectability); (b) the byte budget you want for the library (proposal: 4 KB, holding 8–16 short snippets); (c) whether *clipboard* is a single-slot register or a ring; (d) your preferred key for eval-from-editor.
Open questions / contradictions
Section titled “Open questions / contradictions”-
History persistence storage. Universal Deck State’s
reserved[16]field (per Capability Model Spec §Structure) is too small for 32 expressions of history. This design proposes a new 512-byte extension. Decision needed: (a) grow Deck State’s persisted region, or (b) store history in a dedicated flash page outside Deck State, or (c) keep history session-only and drop the persistence commitment. ADR-0002 commits to persistence, so (c) is a spec change. -
Memory orchestration contradiction. ADR-0002 acknowledges “24 KB REPL + 16 KB editor + active cart must coexist on 520 KB” and proposes mutual exclusion. This design formalizes mutual exclusion across REPL / nEmacs / active-cart. But the wording in ADR-0002 (“Probable approach: REPL and editor are mutually exclusive with active cartridges (suspending the cart when entering the editor)”) implies suspend-and-resume, while this design chooses tear-down-and-restart (history persisted, bindings lost). The suspend-and-resume path is materially more complex (snapshotting the cart’s arena + phase state) and no spec defines the snapshot format. Recommend: ratify tear-down-and-restart in a follow-up memory-orchestration ADR.
-
Should
snippet-save!be write-gated? It’s currently the lone write path from the REPL. The rationale is that the snippet library is deck-local and doesn’t affect mission state, so it’s safe. But it does create one write-enabled FFI in an otherwise read-only environment. Alternatives: (a) keep it permissive (current proposal); (b) require a home-screen confirmation dialog on save; (c) require snippet saves to go through nEmacs (i.e., REPL can hand off a form for saving but cannot save directly). Josh’s call. -
REPL inside Null. Null gameplay-spec implies a “verbose Cipher monologue” affordance that looks a lot like a specialized REPL. This design proposes the home REPL remains read-only and Null exposes its own mission-context REPL variant with extended Cipher primitives. Is that two-surface split acceptable, or should Null just launch the home REPL with extra primitives bound? The latter is cheaper.
-
Scripted-mission dispatch surface. ADR-0007 defines scripted missions as “player writes a Lisp expression evaluated in the mission context.” Is that expression composed in nEmacs and submitted, or composed in the REPL and submitted via
(submit-solution! …)? This design assumes nEmacs is the composition surface and the REPL is for exploration/prototyping. Mission-Board agent needs to rule on the submission API shape. -
Tutorial re-entry. What happens if the player resets their deck (wipe) — does the tutorial re-arm? This design assumes yes (wipe resets
*tutorial-cursor*to zero). Confirm with Marketing — “re-taking the tutorial” may or may not be a desired feature. -
Action bar customization. Row 24 is nOSh-runtime-owned but its content is context-sensitive. The REPL’s action-bar hints (
CAR descend · CDR sibling · ...) compete with nEmacs’s and Mission Board’s for a consistent visual grammar. Input-system architecture doc does not specify who owns the rendering. Likely a one-day spike to define a shared action-bar spec.