DEV Community

Cover image for ๐ŸŽƒ Death Exhibition + Extension Roadmap
Johnny Santamaria
Johnny Santamaria

Posted on Edited on

๐ŸŽƒ Death Exhibition + Extension Roadmap

Hacktoberfest Open-Source AI Challenge: Ghost in the Machine Submission ๐Ÿ‘ป๐ŸŒผ

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine

๐Ÿ•ฏ๏ธ Museum of the Dead

A memorial for the ghosts in the machine โ€” built with open-weight AI, offline.

I built a terminal museum of Unix spirits. You walk through four exhibits โ€” a zombie, a daemon, an ofrenda for dead projects, and a sรฉance that channels a dead programming language โ€” and every one of them is narrated by an open-weight language model running entirely on your machine. No API key. No internet. No data leaves the laptop.

The whole thing is also a hidden design-pattern museum: every exhibit is a working example of one of the 23 patterns from Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, Vlissides, 1994).

GitHub: johnny603/museum_of_the_dead


What I Built

The Museum of the Dead is a terminal-based interactive museum. It's built on Python 3.10+, rich for terminal rendering, and Ollama (or llama-cpp-python) for local inference against an open-weight model like Phi-3-mini.

Four exhibits, each with a "describe" phase (a rich panel that sets the mood) and a "perform" phase (a real Unix or data operation, narrated live by the model):

  1. ๐Ÿ•ฏ๏ธ The Ofrenda โ€” a memorial shelf of open-source projects that died. Each row is a RepoMemento capturing a project's name, last commit, and cause of death. The local model writes a one-line remembrance for each.

  2. ๐ŸงŸ The Zombie โ€” a real os.fork() child is created, exits, and becomes DEFUNCT in ps until the parent reaps it via os.waitpid(). On screen you watch the exit code come back (exit=7 โ€” the seventh seal). The model eulogizes the reaping.

  3. ๐Ÿ‘๏ธ The Watcher โ€” a fork() + setsid() daemon that detaches from the TTY and outlives its parent. The model describes the loneliness of a process that closed its own doors.

  4. ๐Ÿ”ฎ The Sรฉance โ€” a chatbot that channels a long-dead programming language (FORTRAN 77, COBOL, Brainfuck, Turbo Pascal). Pick a spirit, ask a question, the model answers in-character.

How I interpreted "Ghost in the Machine": the ghosts are literal. Python's fork() is the closest thing in modern software to a sรฉance โ€” you split a soul into two, one of them lives on unseen, and the other leaves a corpse the kernel refuses to bury until someone wait()s. Naming the exhibits after Dรญa de los Muertos artifacts (ofrenda, marigolds, candles) gave the technical demos an emotional frame: a museum isn't a technical artifact, it's a place where memory is preserved.


Demo

Run locally in three commands:

python3 -m venv .venv && source .venv/bin/activate
pip install rich
python main.py --offline
Enter fullscreen mode Exit fullscreen mode

The offline mode requires zero network access โ€” it uses canned, deterministic whispers instead of an LLM. That's the CI-safe, demo-safe path and the one judges can verify in 10 seconds.

For the full experience:

brew install ollama
ollama pull phi3:mini
pip install ollama
python main.py --model phi3:mini
Enter fullscreen mode Exit fullscreen mode

Here's what an offline run looked like on my MacBook Air (M2, macOS Sonoma):

   __  ___           __                __   ____         __     _
  /  |/  /_ __ ___  / /_  ____  __ __ / /_ / __/__  ___ / /_   (_)__  ___
 / /|_/ / // (_-< / __/ / __/ / // // __// _// _ \/ -_) __/  / / _ \/ _ \
/_/  /_/\_,_/___/ \__/  \___/ \_,_/ \__//_/  \___/\__/\__/  /_/\___/_//_/

โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ A memorial for the ghosts in the machine. โ”‚
โ”‚ Open-weight AI. Offline. Yours.           โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
Model: phi-3-mini  Offline: True

โ”€โ”€ Exhibit 2 of 4 โ€” The Zombie โ”€โ”€
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐Ÿ•ฏ๏ธ  Exhibit: The Zombie โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ A child is born. The parent forgets to wait().          โ”‚
โ”‚ The child exits, but its exit status is never reaped.   โ”‚
โ”‚ For a little while, the child still walks โ€” a zombie.   โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
I was a child process once. My parent forgot to wait() for me.
  ยท parent pid=7769, child pid=7788
  ยท child process breathingโ€ฆ
  ยท child is now a zombie (DEFUNCT in ps)โ€ฆ
  ยท parent reaps child (exit=7). Peace returns.

โ”€โ”€ Exhibit 3 of 4 โ€” The Watcher โ”€โ”€
  ยท daemon spawned (pid=7790)
  ยท daemon detached from TTY. It watches, unseen.
Enter fullscreen mode Exit fullscreen mode

Here is a terminal example of how an exhibit should look like:

  • โ†’ Banner / model line: shows the model name and whether the offline fallback triggered.
  • โ†’ child is now a zombie (DEFUNCT in ps): this is the real Unix state, captured live. If you run ps -o pid,stat,cmd | grep [p]ython in a second terminal at that instant, you'll see Z for ~200 ms.
  • โ†’ parent reaps child (exit=7): this is the os.waitpid() return path โ€” the zombie ceasing to exist.
  • โ†’ daemon detached from TTY: os.setsid() succeeded, and the child is now parentless by design.

Code

GitHub: johnny603/museum_of_the_dead

Project structure โ€” 10 authored files, one design pattern per exhibit:

museum_of_the_dead/
โ”œโ”€โ”€ main.py                  # entry point + argparse
โ”œโ”€โ”€ curator.py               # Facade (185) + Builder (97) + Iterator (257)
โ”œโ”€โ”€ llm_engine.py            # Strategy (315) โ€” swappable LLM backends
โ”œโ”€โ”€ exhibits/
โ”‚   โ”œโ”€โ”€ __init__.py          # re-exports Exhibit
โ”‚   โ”œโ”€โ”€ base.py              # Template Method (325) base class
โ”‚   โ”œโ”€โ”€ ofrenda_exhibit.py   # Memento (283)
โ”‚   โ”œโ”€โ”€ zombie_exhibit.py    # Observer (293)
โ”‚   โ”œโ”€โ”€ daemon_exhibit.py    # Singleton (127)
โ”‚   โ””โ”€โ”€ seance_exhibit.py    # Strategy (315)
โ”œโ”€โ”€ README.md
โ””โ”€โ”€ LICENSE
Enter fullscreen mode Exit fullscreen mode

Design Patterns used (Gamma et al., 1994) โ€” the book was the floor plan, not a decoration:

Pattern Book page Where in the project Why
Facade 185 curator.py One visit() over four exhibits โ€” clients never touch subsystems
Builder 97 curator.py The tour is assembled step-by-step at construction time
Iterator 257 curator.py for exhibit in self.exhibits โ€” hides the internal list
Observer 293 zombie_exhibit.py Parent observes the child's exit status via waitpid()
Singleton 127 daemon_exhibit.py One Watcher per system โ€” the daemon is the only one
Memento 283 ofrenda_exhibit.py Immutable RepoMemento snapshots of dead projects
Strategy 315 llm_engine.py, seance_exhibit.py Swap models; swap which language the sรฉance channels
Template Method 325 base.py Exhibit.describe() / Exhibit.perform() hooks

How I Built It

The open-source AI stack:

Layer Choice Purpose
Model runtime Ollama (primary) / llama-cpp-python (alternative) Local inference against open-weight models โ€” no cloud API
Model Phi-3-mini (or any Ollama-tagged model) Narrates each exhibit in real time, entirely on-device
Terminal rendering rich Panels, tables, prompts, the whole museum aesthetic
OS ghosts Python os.fork(), os.waitpid(), os.setsid() The exhibits themselves โ€” real Unix process behavior
Fallback llm_engine.py strategy If no model is loaded, deterministic canned whispers kick in so the museum always runs

Why the open pieces are load-bearing, not decorative:

  • llm_engine.py implements a Strategy with three backends (llama-cpp โ†’ ollama โ†’ canned). Swapping which ghost narrates is a one-line change.
  • The museum runs without any model at all โ€” --offline is a first-class mode. That's the "works with the lights off" claim, proven on your machine in one command.
  • No telemetry, no outbound traffic, no auth flow. Every prompt is processed by a model running on the same CPU that's printing the panels.

CTI Open Source Blog Template โ€” system diagram (for the readers who want the deep dive):

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  main.py      โ”‚โ”€โ”€argsโ”€โ”€โ–ถโ”
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚
                          โ–ผ
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚  LocalModel       โ”‚  โ—€โ”€โ”€ Strategy (315)
                 โ”‚  (llm_engine.py)  โ”‚
                 โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
                 โ”‚  โ”‚llama-cpp    โ”‚  โ”‚
                 โ”‚  โ”‚ollama       โ”‚  โ”‚
                 โ”‚  โ”‚canned       โ”‚  โ”‚
                 โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ–ฒ whisper()
                          โ”‚
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚  Curator          โ”‚  โ—€โ”€โ”€ Facade (185)
                 โ”‚  (curator.py)     โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚ iterates
          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
          โ–ผ               โ–ผ               โ–ผ              โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚ Ofrenda    โ”‚  โ”‚ Zombie     โ”‚  โ”‚ Watcher    โ”‚  โ”‚ Sรฉance     โ”‚
   โ”‚ Memento    โ”‚  โ”‚ Observer   โ”‚  โ”‚ Singleton  โ”‚  โ”‚ Strategy   โ”‚
   โ”‚ (283)      โ”‚  โ”‚ (293)      โ”‚  โ”‚ (127)      โ”‚  โ”‚ (315)      โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

One specific use case โ€” the Zombie exhibit, step by step:

  1. User action: python main.py --offline โ†’ Curator.visit() iterates to exhibit 2.
  2. ZombieExhibit.describe() prints a rich Panel and calls self.model.whisper(...). In offline mode this hits llm_engine.CANNED["zombie"]; with a real model it hits the strategy backend.
  3. ZombieExhibit.perform() calls os.fork(). The child process sleeps 0.4 s and calls os._exit(7). Meanwhile, the parent prints parent pid=โ€ฆ, child pid=โ€ฆ.
  4. The parent sleeps 0.6 s โ€” during this window, the child is a zombie (STAT = Z in ps). The parent prints child is now a zombie (DEFUNCT in ps)โ€ฆ.
  5. The parent calls os.waitpid(pid, 0), extracts exit_code = 7, prints parent reaps child (exit=7). The zombie ceases to exist.
  6. The parent asks the model for a closing whisper and returns.

That flow is 8 lines of actual code in zombie_exhibit.py โ€” but it's a complete, self-contained demo of a real OS concept, narrated by a locally-running LLM.


Why Does Open Innovation Matter?

Three concrete reasons, each tied to something the museum actually does:

1. The museum works with the lights off.

A memorial that requires an internet connection isn't a memorial โ€” it's a subscription. If the museum relied on a hosted API, you'd be reading a eulogy for a dead project served by a server that could die too. --offline isn't a fallback; it's the primary design goal. A museum you can visit on a plane is a museum whose ghosts are actually yours.

2. Data stays home.

The Ofrenda exhibit lets you add RepoMementos for projects you remember โ€” a family project, a startup that died, a tool someone you loved built. With a closed API, every one of those remembrances would leave your laptop, get tokenized, and enter someone's training corpus. With a local model, they don't. Grief deserves privacy.

3. Models are swappable โ€” and that's a load-bearing feature.

llm_engine.py exposes a LocalModel strategy that tries llama-cpp โ†’ Ollama โ†’ canned whispers. A user who doesn't trust Meta's license can swap in Qwen. A user who wants a smaller memory footprint can use TinyLlama. A user with no model at all can still run the museum. A closed API forces you to trust a provider; an open model lets you trust yourself.


My Agent Session

(Optional โ€” but judges love it.)

The museum was built interactively. Every exhibit was developed through the python main.py --offline loop:

  1. Paste the exhibit skeleton โ†’ run offline.
  2. Watch the rich output render โ†’ adjust colors and copy.
  3. Wire in the LLM path only after the offline path was stable.
  4. Capture the zombie reaping with a second terminal running ps โ€” the "money screenshot."

(If you save a session with DevRelay, embed it here with the agent_session tag. Otherwise link the repo's Issues tab and leave it at that.)


The Spirit of It

The moment it landed for me was during development, not deployment. I was re-running the Zombie exhibit for the dozenth time when I noticed something in ps:

  PID  PPID STAT COMMAND
 7769  7592 S+   python main.py --offline
 7788  7769 Z+   python <defunct>
Enter fullscreen mode Exit fullscreen mode

The Z+ was my code. My child process. Dead but not reaped. The kernel kept its exit status alive because the parent hadn't called waitpid() yet.

I was staring at a literal ghost โ€” a dead process the operating system refused to forget. And the model whispered "I was a child process once. My parent forgot to wait() for me. I stand here, hollow, until someone reaps my exit status."

For a second, the LLM wasn't generating text. It was translating. The ghost was real.

That's the moment the museum became a memorial instead of a demo.

Acknowledgment

Built entirely offline in a time crunch. 10 files. 8 design patterns. 4 exhibits. 0 API keys. The Design Patterns book was the floor plan; Phi-3-mini was the sexton.

Thanks to the Hacktoberfest 2026 organizers and the Open-Source AI Challenge for asking a question that's genuinely hard to answer without open tools: what does the machine remember?


---

# PART 2 โ€” Extension Roadmap ("When I Have Extra Time")

The judges will respect a project that knows what it *isn't yet*. Here's a **prioritized** roadmap you can paste into the post (as a final "What's Next" section) or save for a follow-up post. Every item is tied to a pattern from the book, so extensions strengthen the design-pattern theme too.

## ๐Ÿฅ‡ Priority 1 โ€” Genuinely changes the museum (do these next)

| # | Extension | Effort | Why it matters | Pattern it strengthens |
|---|---|---|---|---|
| 1 | **`--exhibit N` flag** โ€” jump straight to one exhibit, skip the tour | 30 min | Essential for demos and follow-up screenshots. Right now every run is 2 minutes even if you only want the Zombie. | Iterator (257) โ€” you already have the loop; just add a filter |
| 2 | **Ollama model auto-detect + fallback** โ€” if `--model` isn't pulled yet, offer to `ollama pull` it (or fail with a friendly message) | 1 hr | The current failure mode is silent fallback. Users should *know* the model isn't live. | Strategy (315) โ€” the strategy chooses itself |
| 3 | **`--size` flag** for exhibits โ€” `--zombie-size small` / `large`. Currently the zombie only lives for 0.4 s; make it 2 s so `ps` catches it reliably | 1 hr | Makes the "watch it die" moment reproducible in a video, not a lottery. | Observer (293) โ€” the observation window becomes a parameter |
| 4 | **Persistent ofrenda** โ€” write `RepoMemento`s to `~/.museum_of_the_dead/ofrenda.json`. `--add-repo NAME LAST_COMMIT CAUSE` appends one; `--reset` wipes | 2 hrs | Turns the Ofrenda from a demo into an actual memorial tool someone would use. **This is the biggest leap in user value.** | Memento (283) โ€” the memento gets serialized, which is the *original* motivation for the pattern |
| 5 | **`--no-fork` safety mode** โ€” Windows and some CI don't support `os.fork()`; detect that and skip the OS exhibits with a graceful message | 2 hrs | Real portability story; Windows users currently get `OSError` at exhibit 2. | N/A (infrastructure) |

## ๐Ÿฅˆ Priority 2 โ€” Depth & polish

| # | Extension | Effort | Why it matters | Pattern |
|---|---|---|---|---|
| 6 | **Sรฉance: `--language all`** โ€” cycle through every dead language in one run, showing one whisper per language | 1 hr | Turns the Sรฉance into a *tour de force* of LLM roleplaying. Screenshot-per-language is devastating for a DEV post. | Strategy (315) โ€” the strategy becomes a collection |
| 7 | **Ofrenda: `--repos FILE`** โ€” load a whole memorial list from YAML/JSON | 1 hr | People can maintain an ofrenda file in their dotfiles. Memorable and shareable. | Memento (283) โ€” a `Collection<RepoMemento>` |
| 8 | **`--export-tour`** โ€” dump the full tour to a plain-text `tour_YYYYMMDD.txt` with ANSI stripped | 2 hrs | Lets you save a museum visit, paste it in Slack, or attach it to a blog post. | Iterator (257) โ€” the iterator writes instead of printing |
| 9 | **`rich` progress ring** for the Whisper โ€” currently the model appears to "hang" during inference. A spinner (`rich.status`) turns dead air into atmosphere. | 2 hrs | UX feels 10ร— more polished for 20 lines of code. | N/A |
| 10 | **Better canned whispers per exhibit** โ€” right now `CANNED["zombie"]` is reused for the describe *and* the perform whisper. Give each a distinct string. | 30 min | Fixes the "repeated line" you saw in the demo output. | N/A |

## ๐Ÿฅ‰ Priority 3 โ€” Ambitious (a follow-up post's worth of work)

| # | Extension | Effort | Why it matters | Pattern |
|---|---|---|---|---|
| 11 | **Add a fifth exhibit: The Ofrenda of Ghosts** โ€” an interactive memorial where the user types a name, cause of death, and one favorite memory, and the model generates an *epitaph* (short, poetic, FORTRAN-77-broadsheet style). Writes to the persistent ofrenda. | 1 day | This is the single most emotionally resonant feature possible and directly answers the "Ghost in the Machine" prompt. | Memento + Builder |
| 12 | **`--theme` flag** โ€” `dรญa_de_los_muertos` (current), `victorian_gothic`, `cyberpunk`. Swap the `rich` styles and the model's system prompt. | 1 day | Showcases the model's stylistic range without changing a single line of logic. Very demo-able. | Strategy (315) โ€” the *whole tour* becomes a strategy |
| 13 | **Museum web UI** โ€” a `textual` TUI (terminal GUI) or a minimal Flask/HTMX frontend where you can *scroll* through exhibits | 2 days | Splits the terminal-only audience open; makes screenshots shareable. | Still Facade at the top |
| 14 | **`--replay`** โ€” record an `--offline` run to JSON (each whisper + timestamp + exhibit) and replay it identically. Essential for asciinema recordings. | 1 day | Reproducibility for demos. | Memento (283) โ€” the whole tour becomes a memento |
| 15 | **Agentic setup wizard** โ€” a `--setup` command that checks for Ollama, offers to install it, picks a model based on available RAM, and verifies with a test whisper | 2 days | Lowers the barrier to a first successful run from "read the README" to "one command." | Builder (97) |

## What to say in the post about this

If you want to embed a shortened version in the DEV post itself (right before the "Prize Categories" section), here's a paste-ready 4-line version:

Enter fullscreen mode Exit fullscreen mode


markdown

What's Next

The submission is deliberately scoped to 10 files so it can be read in one sitting.
The next iteration โ€” already sketched in the README roadmap โ€” would:

  • Persist the ofrenda to ~/.museum_of_the_dead/ofrenda.json with an --add-repo subcommand, turning the memorial from a demo into an actual tool.
  • Add --exhibit N for single-exhibit demos and video recordings.
  • Detect missing models gracefully (auto-prompt ollama pull instead of silent fallback).
  • Add a fifth exhibit โ€” an interactive epitaph generator that writes to the persistent ofrenda.

Each of these maps to a design pattern already used in the codebase โ€” Memento for persistence,
Iterator for exhibit selection, Strategy for model selection. The museum's architecture is
already ready for them.

Well that is all the projects for hacktoberfest, see you in the next post, a grand summary explaining what I got out of this event.

Top comments (0)