- JavaScript 51%
- Python 26.3%
- C 20.4%
- Shell 1.1%
- Makefile 0.5%
- Other 0.7%
| .agents/skills/write-reimplementation-specs | ||
| .github/workflows | ||
| cadr-web | ||
| docs | ||
| scripts | ||
| site | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| manifest.scm | ||
| README.md | ||
lisp-machine-container-museum
This repo launches two Lisp Machine environments in Guix containers:
CADR / LM-3: fully public, based on the LM-3 upstream Fossil reposOpen Genera: requires your own purchasedopengenera2.tar.bz2
If you only want one system to start exploring, use the CADR path first.
Historical, architectural, and artifact research lives in the museum knowledge base. It includes the current analysis of Open Genera VLOD world files.
The same canonical Markdown is published as a Genera-profile GitHub Pages site:
https://htayj.github.io/lisp-machine-container-museum/
Build the site locally with Pandoc and the pinned public Genera Fonts release:
curl -L --fail \
https://github.com/htayj/genera-fonts/releases/download/v0.1.1/Genera-fonts-latin-v0.1.1.tar.gz \
-o /tmp/Genera-fonts-latin-v0.1.1.tar.gz
echo "a72cfaa9ed6c418ba751d4a32d3cf715b1b3e6edd44acdc144f297d0c915b3cf /tmp/Genera-fonts-latin-v0.1.1.tar.gz" \
| sha256sum --check
python3 scripts/build-docs-site.py \
--font-archive /tmp/Genera-fonts-latin-v0.1.1.tar.gz
python3 -m http.server 8000 --directory _site
The generated _site/ tree is ignored. The Pages workflow builds it from docs/
and does not commit generated HTML or Genera font data to this repository. See
Publishing the museum documentation site for the
interface behavior, font provenance, build boundary, and validation procedure.
What you need
- Guix installed and working on the host
- X11 available on the host
- for Open Genera only: a local
opengenera2.tar.bz2
If X11 authentication fails, allow your user explicitly:
xhost +SI:localuser:$USER
Which emulator to use
Use CADR / LM-3 if you want:
- a fully public system
- the simplest first boot
- the MIT LM-3 environment from
tumbleweed.nu
Use Open Genera if you want:
- Symbolics Genera
- your own commercial Open Genera world
- a closer approximation of a later Symbolics workstation environment
CADR / LM-3
Launch
./scripts/cadr-guix-container.sh --mode run
The first run will:
- start a Guix container with the build/runtime dependencies for
usim - create a local Fossil checkout in
./l - cache the supporting upstream Fossil repositories in the repo root
- build and launch the emulator
What a successful boot looks like
A successful cold boot normally lands in a Lisp Listener. That is not a blank system; it is the expected usable prompt.
On the first run, the bootstrap and build can take a while. Later runs should be much faster.
Keyboard notes
The default CADR key mapping in usim includes:
F1= SystemF2= NetworkF3= StatusF4= TerminalF5= HelpF6= Clear Input
If you are unsure what to do next after boot, F5 is the first key to try.
Headless computer-use harness
The computer-use harness runs usim on a private, authenticated Xvfb display and
drives its real X11 window through keyboard and mouse events. It is intended for
repeatable behavioral checks and evidence screenshots without taking over the host
desktop.
Check the harness dependencies and prepared CADR artifacts:
./scripts/cadr-computer-use.sh doctor
Start a named session and inspect its status:
./scripts/cadr-computer-use.sh start --session research
./scripts/cadr-computer-use.sh status --session research
The first start creates private copies of the System 303 disk and source trees. A
later plain start cold-boots that existing private runtime. Use --fresh to discard
and recreate the private runtime from the current public base before cold booting a
stopped session:
./scripts/cadr-computer-use.sh stop --session research
./scripts/cadr-computer-use.sh start --session research --fresh
Fresh preparation builds and validates a staged runtime before installing it. The state root, each session, and each completed private runtime carry harness ownership markers; the tool refuses to adopt similarly named existing directories without them. A pre-hardening session directory therefore cannot be reused automatically: preserve it as evidence or remove it manually after review, then choose a new session name.
Wait for an elapsed interval or a stable screen, then send keyboard input:
./scripts/cadr-computer-use.sh wait --session research --seconds 10
./scripts/cadr-computer-use.sh wait --session research --stable-for 3 --timeout 180
./scripts/cadr-computer-use.sh key --session research help
./scripts/cadr-computer-use.sh type --session research --enter '(+ 2 3)'
help is the portable key alias for F5; the other aliases are listed by the
script's key --help output. Mouse coordinates address the 768 by 963 CADR client
window. Host button 3 is the CADR head/rightmost button:
./scripts/cadr-computer-use.sh mouse --session research move 400 500
./scripts/cadr-computer-use.sh mouse --session research click 400 500 --button 3
Capture the exact client window and stop the session cleanly:
./scripts/cadr-computer-use.sh screenshot --session research --label listener
./scripts/cadr-computer-use.sh stop --session research
Screenshots, JSON provenance sidecars, private disks and source trees, saved emulator
state, credentials, and logs stay in the ignored
build/cadr-computer-use/<session>/ directory. stop --discard deletes that named
session instead of retaining it. Current sidecars distinguish revisions in the
public checkouts at start from the private sources' copy-time revisions; they also
hash the private source trees, usim executable, and private microcode and symbol
files used by the run. Toolchain provenance records the Guix channel, manifest hash,
Python version, and resolved commands as well. Immediately before launching the
shared usim executable, the supervisor reacquires the preparation lock, hashes it
again, compares that value with usim_sha256_at_start, and performs the launch while
still holding the lock. A mismatch fails the start. The execution-time hash is kept
in session state; screenshot sidecars retain only the start-time hash.
A normal stop reports forced_stop: false and state_may_be_incomplete: false. If a
process exceeds its shutdown timeout and must be killed, the session is labeled
forced-stopped with both flags true; do not treat that saved emulator state as a
known-complete warm-resume artifact.
Warm start --resume is experimental. In the 2026-07-17 smoke run, restoring the
state written by a clean stop halted immediately at microcode ILLOP; a subsequent
--fresh cold boot worked. Do not currently rely on --resume for session
persistence. See the
harness architecture and observation record
for provenance and the exact evidence boundary.
Raw screenshots remain in the ignored session tree. A small number may be copied into the museum only after an image- and use-specific copyright review under the runtime screenshot publication policy. Curated screenshot files are outside any blanket project license.
Useful CADR commands
Validate the container toolchain without starting the GUI:
./scripts/cadr-guix-container.sh --mode run --verify
Only create the upstream checkout:
./scripts/cadr-guix-container.sh --mode run --bootstrap-only
Prepare the repositories and build usim without launching the GUI:
./scripts/cadr-guix-container.sh --mode run --prepare-only
Open a shell inside the prepared environment:
./scripts/cadr-guix-container.sh --mode shell
Update the upstream LM-3 checkout:
./scripts/cadr-guix-container.sh --mode update
LOD helpers
The repo also includes a helper around the upstream lod and diskmaker
tools for inspecting world-load bands and swapping them into the disk image.
List built-in aliases:
./scripts/lod-helper.sh list-releases
Inspect the default public System 303 load band:
./scripts/lod-helper.sh inspect 303-dist
Install a different public load band into the disk image:
./scripts/lod-helper.sh install 303-dist
Built-in aliases currently include:
303-dist303-cold100-dist100-cold
Open Genera
Open Genera is not included in this repo. You must supply your own purchased archive, for example:
~/opengenera2.tar.bz2
Do not use the Open Genera archive that is commonly available on well known public piracy websites. Using infringing copies is illegal. Obtain Open Genera legitimately instead, for example by purchasing it from Symbolics: http://www.symbolics-dks.com/
Nothing extracted from that archive is checked into git. Runtime files are
staged under ./.lm-home/opengenera/.
One-time host network setup
Open Genera expects a host TAP device named tun0.
Create or repair it with:
sudo ./scripts/opengenera-host-net.sh up --user "$USER"
Check its current state with:
./scripts/opengenera-host-net.sh status
Despite the name, tun0 must be a TAP device, not a TUN device. The helper
script handles that for you.
Writable site storage on the host
To create a local site and save a reusable world, Genera needs a writable host
file service. In this repo that is expected to be an NFS export visible to the
VLM at the default Open Genera host paths:
HOST:/var/lib/symbolics/sys.sct/site/ and
HOST:/usr/opt/VLM200/lib/symbolics/.
Prepare it with:
sudo ./scripts/opengenera-host-nfs.sh up
Check the export and the Genera-facing paths with:
./scripts/opengenera-host-nfs.sh status
This helper exports writable directories for:
- site files under
/var/lib/symbolics/sys.sct/site/ - saved
.vlodworlds under/usr/opt/VLM200/lib/symbolics/
That means the standard Genera defaults from the installation guide can be used without editing them to repo-specific paths.
If you want to remove just these dedicated exports later:
sudo ./scripts/opengenera-host-nfs.sh down
NFSv2 fallback when the host kernel only offers NFSv3/v4
Some modern host kernels expose only NFSv3/v4, but Open Genera's built-in NFS
client expects NFSv2. When rpcinfo -p 127.0.0.1 does not show program
100003 version 2, use the bundled Nimble userspace NFSv2 server instead.
Start it with:
sudo ./scripts/opengenera-host-nimble.sh up
Check it with:
./scripts/opengenera-host-nimble.sh status
This helper stops conflicting kernel RPC/NFS services if needed, starts Nimble, and exports the standard Open Genera paths directly, so inside Genera use:
DIS-EMB-HOST:/var/lib/symbolics/sys.sct/site/for site filesDIS-EMB-HOST:/usr/opt/VLM200/lib/symbolics/for saved worlds
Launch
./scripts/opengenera-guix-container.sh --mode run --archive ~/opengenera2.tar.bz2
The first run will:
- start a Guix container with the required runtime tools
- download the public
snap4.tar.gzLinux VLM runtime into./.lm-home/opengenera/downloads/ - extract the official
Genera-8-5.vlod,VLM_debugger, andsys.scttree from your archive - build a small local compatibility shim for modern Linux
- start the VLM
What a successful boot looks like
You should see the VLM POST complete and a Cold Load stream appear. The usual non-fatal early warning is:
genera (Cold Load): Unable to allocate a modifier for the Hyper key.
That warning by itself is not a failed boot.
Genera headless computer-use harness
The Genera computer-use harness runs the real VLM client on an authenticated private
Xvfb display and sends keyboard and mouse events through XTEST. Unlike the ordinary
launcher above, it runs the native VLM in a Bubblewrap sandbox with separate user,
network, PID, IPC, mount, and hostname namespaces. The sandbox sees a read-only Guix
store, the exact private X socket and two read-only launch helpers, and its writable
session runtime; it does not see the host home, repository, ordinary runtime sockets,
or a default network route. Its private tun0 exposes no NFS, Nimble, FTP, or other
guest file service. The private Xvfb disables MIT-SHM and verifies its absence before
launch, preventing the out-of-namespace X server from attaching host shared-memory
segments for the VLM.
Two tracked compatibility modules remain deliberately narrow. One suppresses only
the redundant legacy
/sbin/ifconfig tun0 10.0.0.1 dstaddr 10.0.0.2 netmask 255.255.255.0 request after
the private interface is configured; every other system(3) command retains normal
libc behavior. The X module installs typed event converters only on displays
returned by XOpenDisplay, replaces only zero timestamps on seven core input event
types, locally handles the byte-exact typed modifier request, and pairs its typed
grab suppression with the matching ungrab. Direct tracing also established that the
main Genera display relays a guest X11 byte stream through write(2). Only on
descriptors belonging to those wrapped Displays, the module replaces the exact
observed eight-byte
GrabServer-plus-GetModifierMapping request and exact 148-byte
SetModifierMapping request with sequence- and reply-compatible requests accepted
by the private Xvfb. The replacement extension name must first be live-verified
absent. No read-family API is interposed. The globally wrapped write(2) delegates
every untracked or nonmatching write byte-for-byte, including TAP, world-image,
ordinary-file, and unrelated-socket writes. A separate supervised process answers
one observed RFC 868 request with one raw Ethernet reply entirely inside the
throwaway network namespace. The harness reports running only after it has observed
both exact X substitutions, validated packet evidence, and a successful responder
exit.
Check the harness tools and currently staged Open Genera inputs:
./scripts/genera-computer-use.sh doctor
Start a named session from your purchased archive and inspect its status:
./scripts/genera-computer-use.sh start \
--archive ~/opengenera2.tar.bz2 \
--session research \
--fresh
./scripts/genera-computer-use.sh status --session research
The first start verifies the purchased archive and exact known Genera 8.5 world,
debugger, and public snap4 VLM identities. It then places private copies of those
inputs, both compatibility modules, the RFC 868 responder, generated host files, and
configuration under the ignored session tree. Immediately before execution the
supervisor rehashes every private artifact and tracked launch source. The ordinary
launcher stages inputs but is not retained as another runtime layer. A later start
with the same required --archive but without --fresh reuses the private inputs;
--fresh replaces them from the verified base. Every start creates a new VLM
process; the harness has no host-process snapshot or resume mechanism. A reused
private world could contain an explicitly saved in-guest state, which requires
separate verification.
Wait for the main client, capture it, and send input only after verifying the current
screen. main is the default target, but spelling it out makes a research transcript
unambiguous:
./scripts/genera-computer-use.sh wait --session research --window-kind main
./scripts/genera-computer-use.sh screenshot \
--session research --window-kind main --label initial-display
./scripts/genera-computer-use.sh key \
--session research --window-kind main return
./scripts/genera-computer-use.sh type \
--session research --window-kind main 'text to type'
Open Genera changes X clients while moving from Cold Load to its main display. The
harness therefore requires an exact --window-kind target on wait, key, type,
mouse, and screenshot. The choices are main, cold-load, debugger, and
genera-other; omitted means main. It rediscovers candidates for every operation
and fails closed when zero or more than one client has the requested kind. Commands,
action records, and screenshot sidecars identify the selected X window by kind, ID,
title, and geometry. Mouse coordinates therefore refer to that observed client's
geometry, not the fixed CADR dimensions:
./scripts/genera-computer-use.sh mouse \
--session research --window-kind main move 600 450
./scripts/genera-computer-use.sh mouse \
--session research --window-kind main click 600 450 --button 1
The preserved VLM Debugger normally uses the Cold Load client, so target it as
cold-load. The debugger kind is a reserved title classifier for a distinct X
client whose title explicitly contains VLM Debugger; D04 did not observe such a
separate title and does not treat that selector as a verified VLM-debugger route.
Portable Genera-key aliases are select (F1), function (F3), suspend
(F4), resume (F5), clear-input (F10), complete (F11), end
(KP_End), and help (F12). Existing aliases include rubout, abort, super,
return/enter, space, and escape. These are host X-key translations; the
meaning of a key still depends on the active Genera input context.
For example, after choosing Emergency Break on the main System Menu, target the
separate Cold Load client explicitly. The Cold Load reader activates a completed
form with the Genera End key rather than Return:
./scripts/genera-computer-use.sh wait \
--session research --window-kind cold-load
./scripts/genera-computer-use.sh screenshot \
--session research --window-kind cold-load --label emergency-break
./scripts/genera-computer-use.sh type \
--session research --window-kind cold-load '(+ 40 2)'
./scripts/genera-computer-use.sh key \
--session research --window-kind cold-load end
./scripts/genera-computer-use.sh screenshot \
--session research --window-kind cold-load --label emergency-break-result
./scripts/genera-computer-use.sh key \
--session research --window-kind cold-load resume
./scripts/genera-computer-use.sh wait \
--session research --window-kind main
Capture the resulting exact client window and stop the VLM:
./scripts/genera-computer-use.sh screenshot \
--session research --window-kind main --label after-input
./scripts/genera-computer-use.sh stop --session research
On stop, the supervisor sends SIGTERM to the actual VLM process, waits for its
prompt, and answers yes. A pipe trace proved that the current public VLM reads the
answer and begins cleanup, including breaking its X connection. This VLM artifact
then stalls while terminating its Cold Load channel, so the bounded stop finishes
with SIGKILL. stop consequently returns status 2 and records
forced_after_confirmed_shutdown_stall: true, forced_stop: true, and
state_may_be_incomplete: true; it must not be described as orderly host shutdown.
Neither the accepted confirmation nor forced cleanup invokes Genera's in-guest Save World operation. The harness does not create a process checkpoint or expose an NFS, Nimble, or FTP service. Absent a separately verified in-guest Save World action, unsaved Lisp memory, editor buffers, definitions, and processes are discarded with the VLM process. See the linked evidence guide for the mutex-level shutdown finding and the exact scope of that observation.
Each generation has a generation-scoped run record, updated atomically while active
and preserved before the next generation, plus an ordered action log. Input
operations record intent before XTEST dispatch and a linked succeeded or
failed-or-partial outcome afterward. Screenshots,
sidecars, logs, Xauthority, configuration, and all licensed or runtime copies stay
under ignored build/genera-computer-use/<session>/. Use
stop --discard to delete a stopped named session. Selected runtime screenshots may
be copied into the museum only after an image- and use-specific copyright review;
permission or a documented U.S. fair-use basis can support publication. The raw
session remains ignored, and curated screenshot files remain outside any blanket
project license. See the
runtime screenshot publication policy
and the
Genera harness architecture and evidence guide.
Useful Open Genera commands
Validate the container setup without launching the GUI:
./scripts/opengenera-guix-container.sh --mode run --verify --archive ~/opengenera2.tar.bz2
Stage the runtime without starting it:
./scripts/opengenera-guix-container.sh --mode run --prepare-only --archive ~/opengenera2.tar.bz2
Open a shell in the prepared runtime:
./scripts/opengenera-guix-container.sh --mode shell --archive ~/opengenera2.tar.bz2
Extra arguments after -- are passed through to genera itself. That lets
you override the default .VLM configuration if needed.
Open Genera notes
- Only run one Open Genera instance at a time against
tun0. - If you launch a second instance while one is already running, you can see
Device or resource busy. - The launcher uses your purchased world/debugger, but the Linux-hosted VLM
runtime comes from the historical public
snap4package. - The launcher includes a small compatibility preload because the historical
runtime still tries to use legacy Linux networking behavior and X11 keyboard
modifier remapping that modern unprivileged/Xwayland setups reject. The same
preload also answers the VLM's early RFC 868 time request locally, so no host
timedaemon or privileged UDP/37 listener is required.
Font recovery
Recover the public CADR font sources from a checkout of
mietek/mit-cadr-system-software:
python3 scripts/extract-cadr-fonts.py \
/path/to/mit-cadr-system-software/src/lmfont \
--output build/fonts/cadr \
--clean \
--strict
This reads the historical source representations under src/lmfont; it does not
inspect a CADR load band or heap. The curated public outputs and full format notes are
in the CADR font recovery article.
Recover the fonts resident in your own licensed Genera world:
python3 scripts/extract-genera-fonts.py \
.lm-home/opengenera/runtime/Genera-8-5.vlod \
--output build/fonts/genera \
--clean
That command writes BDF files, normalized JSON, and PNG font sheets. All Genera outputs are derived from licensed media and remain in the ignored local build tree; do not commit or redistribute them. See the Genera extraction procedure and evidence-graded font catalog.
On-line help recovery
Recover the source-integrated help from the pinned public MIT CADR System 46 tree:
python3 scripts/extract-cadr-help.py \
--source /path/to/mit-cadr-system-software/src \
--output docs/assets/mit-cadr-online-help \
--clean
The checked-in result contains exact public source contexts, four standalone ZWEI artifacts, catalogs, and the source license. See the CADR help recovery article for the inclusion rule and optional metadata-only LM-3 cross-check.
Recover the installed documentation from your own licensed Genera sys.sct tree:
python3 scripts/extract-genera-help.py \
--sys-sct .lm-home/opengenera/runtime/sys.sct \
--output build/help/genera \
--clean
This decodes the Document Examiner's Sage Binary databases, copies three reviewed
standalone help files, and inventories source-level help declarations without
executing Genera code. Every output derived from Genera remains under the ignored
build/help/genera/ tree and must not be committed or redistributed. See the
Genera help recovery article.
Day-to-day use
Once either machine finishes booting, the normal place to work is a Lisp Listener.
Practical first steps:
- Click into the Lisp Machine window.
- Try the help key for the emulator you launched.
- Treat the Listener as your REPL and command entry point.
If you stop the emulator process abruptly, unsaved in-memory state is lost.
Files and caches
The repo creates a few local working directories:
./l/for the LM-3 upstream working checkout./.lm-home/for container home directories and runtime caches./*.fossilfor cached Fossil repositories
These are intentionally ignored by git.