Skip to content

REPL — Onboard Lisp Read-Eval-Print Loop

First-party on-device program #3 (ADR-0042). Canonical for: ADR-0002 §1 — the player-facing Lisp REPL as a release-default, first-class onboard utility on the KN-86 Deckline.

Companion docs:

The Canonical Hardware Specification in ../../../CLAUDE.md governs the 128×75 grid, Row 0 / Row 74 firmware ownership, and the TERM key — see that table; values are not restated here.


The REPL is a release-default, first-class onboard program on every shipping KN-86 Deckline. It is not a cartridge. It is not a developer-only build affordance. It is part of the device.

Per ADR-0002 §1: “A runtime-level utility reachable from the deck home screen (alongside Mission Board, Cipher, Deck State). Presents a standard read-eval-print loop over the same VM defined in ADR-0001.”

GWP-120 landed the toast overlay surface as a KN86_DEV_BUILD-gated dev affordance. GWP-315 promoted that surface to release-default, removed the build-flag gate, and added the persistent surface advertising and per-deck history persistence that ADR-0002 always required.


The REPL is invoked by the TERM key (see CLAUDE.md key matrix). Press TERM with no higher-priority surface active and the REPL toast slides down from Row 1, occupying the upper portion of the content area. The active cartridge — if one is loaded — pauses for the duration of the REPL session; the cart framebuffer is preserved byte-exact and restored on close.

Geometry recost pending. The exact row span / column extents are being recost against the 128×75 grid; this is the open repl.md item in ADR-0027’s Documentation Updates checklist. Until that lands, treat the spans here as proportional, not absolute.

TERM is context-polymorphic (see ADR-0016 §3C.1 priority table). Higher-priority surfaces — CIPHER-LINE seed capture, hot-swap defer, Cipher freeze, cart-scoped binding — claim TERM first. The REPL is the priority-5 default binding: it fires when no higher-priority surface holds the key.

A second TERM press while the REPL is open dismisses it.


The REPL is always reachable, so the firmware always advertises it. The right edge of Row 74 (firmware action bar) carries a permanent indicator:

  • [TERM:LAMBDA] when the REPL is closed.
  • [TERM:EXIT] when the REPL toast is in any visible phase.

The advertisement occupies a right-edge slice of Row 74. It is painted by the runtime AFTER cartridge / bare-deck Row 74 owners write their action-bar content, so the right-edge slice always reflects the REPL state of the world. Bare-deck and cartridge action bars conventionally write Row 74 from the left edge rightward; the right-edge slice is firmware-runtime territory under spec hygiene rule 5. (Exact column extents recost at 128×75 per ADR-0027.)

This is the difference between GWP-315 and the GWP-120 scaffold: the toast itself is still transient (open/close on demand), but the affordance is permanently advertised, so the operator never has to discover that the REPL exists by accident.


ADR-0002 §1: “History: last 32 expressions persisted to deck state. Recalled via CDR on a history cell.”

Two layers carry this:

  1. In-memory working ring (nemacs_repl_history_* in ../../../runtime/src/nemacs.h) — 32 slots × 256 bytes inside the editor Fe arena, used for live history walking with CDR while the REPL is open. Volatile; reset when the editor subsystem reinitializes.
  2. Per-deck persistent ring (repl_history_* in ../../../runtime/src/repl_history.h) — 16 slots × 80 bytes living inside the Universal Deck State on the device’s microSD (4 KB region per the Canonical Hardware Specification). Survives power cycles, cart swaps, and firmware updates. Hydrated into the in-memory ring at boot.

The persistent ring is intentionally narrower (16 vs 32 slots, 80 vs 256 bytes per entry) to fit alongside the snippet library (1540 B) and the cipher persist blob (256 B) inside the 4 KB UDS budget. The per-entry cap (79 chars) is wider than REPL_TOAST_INPUT_MAX (72 chars), so every line the toast can submit fits with headroom.

Ring semantics:

  • Empty submissions silently drop.
  • Consecutive duplicates of the most-recent entry coalesce (no advance) — shell-style dedup.
  • Strings longer than 79 chars are truncated; the truncated form is what persists.
  • Saturates at 16 slots; the oldest entry is dropped on overflow.

Deck-state version: DECK_STRUCT_VERSION bumped from 3 → 4 by GWP-315 to reflect the appended repl_history[1284] blob. Older v3 files are rejected; the deck reinitializes fresh and the REPL history starts empty. (Consistent with the existing strict-mismatch policy in deck.c::deck_state_load.)


When the operator accepts a contract on the mission board (../runtime/mission-control.md §5), Mission Control hands the mission struct to the same REPL/nEmacs subsystem documented above, with additional bindings layered into the active environment. This is the Mission Runner (ADR-0029).

The Mission Runner is not a separate buffer or surface. It is the standard REPL with three extra bindings and one extra builtin available while a contract is active:

BindingTypeProvided by
current-missionmission struct (record, see ADR-0029 §3)Mission Control on [EVAL]-accept
mission-paramsproperty list extracted from the struct (:threat, :seed, :objectives, …)Mission Control on accept
phase-chainlist of phase requirement recordsHydrated from UDS phase_chain on accept
(load-capability ...)builtinNoshAPI Mission-context tier (ADR-0005)

The standalone REPL (TERM key, no contract active) sees these bindings as unbound — calling current-mission outside Mission Runner mode raises :no-active-mission. Snippet library, history ring, keymap, animation envelope, and FFI access policy are all identical between the two modes; the only difference is which symbols are bound.

(load-capability :module :seed seed) invokes the named cartridge’s gameplay loop (Hot-Swap-prompting if the cart isn’t currently inserted) and returns a result struct ({:outcome :trace :extracted :turns :bonuses}). The Mission Runner program branches on the result to drive phase progression. See ../runtime/mission-control.md §5.3 and ADR-0005.

The Mission Runner program may be entered manually at the REPL (“REPL the mission”) or driven by an nEmacs-authored script (“script the mission”). Both paths are first-class. Scripts raise the ceiling for experienced operators; nothing in the spec gates the critical path on script authoring.

When the program calls (complete-mission current-mission) or (abandon-mission), Mission Control unbinds the Mission Runner symbols, finalizes the contract economy, and returns to the board.


SurfaceRoleRelationship to the REPL
Bare deck terminal (../runtime/bare-deck-content-brief.md)Teaches Lisp grammar through small bounded exercises (1–3 minutes each).Independent surface; tab content never invokes the REPL. The bare deck’s SYS tab “Restart Tutorial” row enters the REPL tutorial (ADR-0002 §1, GWP-224) — explicit operator action only.
nEmacs structural editor (nemacs.md)Authors snippets, scripts, and scripted-mission solutions.Sibling buffer in the unified ADR-0016 subsystem. Shares keymap machinery, cursor model, FFI bindings. The REPL is the immediate-eval buffer; nEmacs is the deferred-eval editor.
CIPHER-LINE (../runtime/cipher-voice.md)Emits voice fragments on the auxiliary OLED.The REPL toast does NOT paint CIPHER glyphs. CIPHER stays on the auxiliary OLED per ADR-0015.
Per-deck snippet library (../../../runtime/src/snippet.h)Persistent named expressions saved by the operator.Snippets are the named, reusable layer; REPL history is the chronological, deduplicated layer. Both live on the deck’s microSD; both are populated through the REPL’s commit path.

ConcernFile
Toast overlay state machine + animation envelope../../../runtime/src/repl_toast.c
Permanent Row 74 advertisementrepl_toast_render_status_hint in repl_toast.c
Per-deck history ring../../../runtime/src/repl_history.c
Deck-state fieldDeckState.repl_history[1284] in ../../../runtime/src/types.h
Wire-up (TERM dispatch + commit handler chain)the host loop body (nosh_host_step; see ADR-0040 §5.4 — currently in the emulator main.c, lift into libnosh pending)
In-memory working ring../../../runtime/src/nemacs.h (nemacs_repl_history_*)
Teststests/test_repl_toast.c, tests/test_repl_history.c, tests/test_term_key.c

The unified ADR-0016 subsystem (nEmacs + REPL as one) covers the structural editing primitives, keymap-as-Lisp-alist, context-polymorphic dispatch, and Nokia multi-tap. Read ../api-reference/editor-tools/repl.md for the loop semantics, the read-eval-print algorithm, and the Tier 3 FFI access policy.


  • REPL ↔ nEmacs handoff key binding — ADR-0016 §“Known Unknowns” #6 leaves the snippet hand-off binding TBD.
  • Tutorial completion marker semantics — ADR-0002 sign-off left “whether tutorial-mode completion grants a capability bit” as an implementation-time decision; GWP-224 carries the persistence flag (tutorial_completed) but the capability hookup is open.
  • Geometry recost at 128×75 — the toast row span and the Row-74 advertisement column extents are recost against the native-renderer grid per ADR-0027’s Documentation Updates.
  • Persistent ring widening — if the 4 KB UDS budget loosens (post-launch firmware update with a re-laid-out flash region), the persistent ring should widen to match the in-memory 32-slot working ring.