Building a REPL OS in Common Lisp
Building a REPL OS in Common Lisp, Part 0: Why?
Before we write any code, let’s ask some questions.
What Is an Operating System?#
It’s a deceptively simple question. Ask ten computer scientists and you’ll get ten variations on a theme. Since I first started using computers, from MS-DOS, Windows 95, and tasted GNU/Linux for the first time with Red Hat soon after that, BeOS, MacOS and its evolution, Plan9... Operating Systems has been a fascinating topic for me.
Andrew Tanenbaum, in his classic textbook, calls it “the most fundamental system program... whose job is to control all the computer’s resources and provide a base upon which application programs can be written.” Silberschatz (author of Operating System Concepts, aka the Dinosaur Book) and colleagues define it as “a program that manages a computer’s hardware” and “provides a basis for application programs and acts as an intermediary between the computer user and the computer hardware.”


Notice what these definitions share: management and abstraction. The OS sits between you and the machine, making the hardware usable, hiding complexity, providing a foundation.
But what does that actually mean in practice? What are the essential abstractions? When does a program stop being just a program and become an operating system?
Strip away the jargon and an operating system solves a handful of fundamental problems. Each one sounds simple until you think about it.
Memory. Your program needs space to work. But how much space? Where? What happens when two programs both want the same space? Memory management is the art of pretending every program has the machine to itself. Virtual memory, allocation, garbage collection (all variations on the theme of “give me some space, and don’t let anyone else touch it”).
Storage. Memory vanishes when power dies. You need somewhere to keep things. But wait, why does memory vanish? That’s not a law of physics; it’s a property of how we build RAM. It’s volatile by design, by economics, by historical accident.
Imagine memory that didn’t forget. No distinction between “what I’m working on” and “what I’ve saved.” No files, no saving, no loading. Just... information. Data. The Memory: one unified space where things simply exist.
The Lisp Machines glimpsed this world. You didn’t save your work to a file; you saved the image (a snapshot of everything in memory). When you restarted, you didn’t “open” your programs; they were just there, exactly as you left them. Objects persisted. State persisted. The artificial boundary between “running” and “stored” dissolved.
But we don’t live in that world. We live in a world where RAM forgets and disks remember, where a power outage draws a hard line between “now” and “gone.” So we invented filesystems: named containers, hierarchies, the ritual of saving. A disk is just a vast sequence of bytes that is, in essence, meaningless without structure. Files and directories impose order. They’re so universal we forget they’re a workaround. Someone decided that “a named sequence of bytes inside a named container” was a useful abstraction for bridging the gap between volatile and permanent. It stuck.
But every time you hit Ctrl+S (or the equivalent in your system), you’re performing a ritual that only exists because memory forgets. It didn’t have to be this way: non-volatile memory technologies like memristors have existed for years. But history chose differently, and here we are, still saving. Even if almost nobody knows nowadays that the save icon represents an actual floppy disk.
Processes. You want to run more than one thing. Maybe at the same time, maybe taking turns. A process is a program in motion: code plus state plus the illusion of having the CPU to itself. The OS juggles dozens or thousands of these, each believing it’s the center of the universe.
Concurrency. Even within a single program, you might want things to happen simultaneously. Threads, async operations, background tasks. The problem isn’t making things parallel: it’s keeping them from interfering with each other. Shared state is a minefield. Every OS provides tools to navigate it: locks, semaphores, message passing.
The Interface. How do you talk to this thing? Punch cards, command lines, graphical windows, touch screens, voice, or prompts today. The shell is the conversational surface between human and machine. It’s where intent becomes action. Often overlooked, but it shapes everything about how we experience a computer.

These abstractions (memory, storage, processes, concurrency, interface, etc) appear in every operating system ever built. The implementations vary wildly but the underlying problems they solve don’t.
During the years I have noticed that computation looks remarkably similar everywhere you look. Cosmologists have a term for this property of the universe: homogeneous and isotropic. The same physics, the same patterns, the same fundamental forces, no matter where you look. Computing has something similar. Look at any system: Windows, macOS, Linux, Plan9, Hurd (Hurd!), your phone, a game console, an embedded device; and you find the same patterns underneath. Files and directories. Processes. Input and output. Permissions. The five (plus many more) abstractions, wearing different clothes.
Unix won. Or rather, Unix’s ideas won. The hierarchical filesystem, the shell, pipes, the everything-is-a-file philosophy. Even systems that aren’t Unix have absorbed its vocabulary.
But was it always this way? Did it have to be? Consider the alternatives history gave us.
The Lisp Machines of the 1970s and 80s, the same ones that glimpsed a world without files, rejected the process model entirely. There was no separation between “operating system” and “programming language.” The whole environment was Lisp, all the way down. You could inspect and modify any part of the running system. Objects replaced files, so why serialize to disk when you can just persist the object? The image was the system. Memory management wasn’t hidden, it was exposed, controllable, part of the programming model.
Smalltalk took a similar path: a world of live objects, no files in the Unix sense, no processes. The entire environment was malleable, inspectable, changeable while running. Storage wasn’t “saving a file” but “taking a snapshot of the world.”
IBM i, the operating system on what people still call the AS/400 (today it runs on IBM Power, and the old name refuses to die), went further still and put the database at the center. Not a database running on the system, but the database as the system. What Unix calls a file, IBM i calls a record in a table, because the OS speaks in queries, not byte streams. Db2 isn’t installed on top of anything, it’s woven in the core and it turns out you don’t need a filesystem at the bottom of everything. You can put the database there instead, and run the world’s payrolls on it for forty years.
Even in the consumer space, things were different once. PalmOS used flat databases instead of hierarchical files: records and categories rather than directories and subdirectories. It turns out files and folders aren’t the only way to organize information; they’re just the way we happened to standardize on.
And what about BASIC? Those early home computers that booted directly into a programming environment. Type 10 PRINT "HELLO" and you’re programming. Type RUN and you’re executing. Was that an operating system? It managed hardware. It provided abstractions. It let you run programs. The line is blurrier than the textbooks would normally admit.
These weren’t failed experiments. They were different answers to the same fundamental questions. They showed that the abstractions we take for granted are choices, not inevitabilities.
What University Didn’t Teach Me#
I studied computer science. And I remember studying Operating Systems and related topics, and learned decently about many concepts. I used some libraries, built a shell, some network code, a custom scheduler for the Linux Kernel, some bootloading routines, among other things. I read Tanenbaum and Silberschatz, studied the Linux Kernel Unleashed among others and I could explain virtual memory, describe process scheduling algorithms, and diagram the layers of a filesystem. It was fun.
I learned so many concepts, and was able to explain them. I could go to a whiteboard and describe them, and I would devote a lot of time in the practice lab building some of these pieces. But I never had the chance to build something I could feel as a full operating system, even if minimal, even if just conceptual.
So, what is an operating system? Years ago, I stumbled across a project called ECMAchine. It’s a toy operating system implemented in JavaScript, running in the browser, with a user interface inspired by Lisp. A shell, a filesystem, processes, etc. All the familiar concepts implemented from scratch in a few hundred lines of code. If you haven’t yet, you should go try it.
This system does not operate over the metal, but on top of your current operating system. This could be understood as a limitation, but I don’t think so because the hardware was never the point. The point is the abstractions themselves: how they fit together, why they’re shaped the way they are, what decisions you face when building them. Running on top of another OS strips away the noise. No device drivers, no interrupts, no memory-mapped I/O. Just the pure conceptual skeleton: here is a filesystem, here is a process, here is a shell. This is what an operating system is, beneath all the implementation details.
So I analyzed the project and here I see some of these concepts stripped to their essence. No distractions. Just code you could read in an afternoon. The abstractions laid bare.
A filesystem isn’t mystical. It’s a tree of nodes with names and contents. A process isn’t magic. It’s a block of code that runs. A shell isn’t sorcery. It’s a loop: read, evaluate, print, then repeat.
I’ve been fascinated by Lisp for years. Not just as a language, but as a philosophy. Lisp blurs lines. Code is data. Data is code. The REPL isn’t a debugging tool: it’s a way of being. You don’t write a program and then run it; you grow a program while it runs.
The Lisp Machines we talked about in the previous article understood this. When your environment is interactive, when your data structures are visible, when you can modify anything while it executes... “Operating System” and “Application” start to feel like arbitrary distinctions. Where does the language end and the OS begin?
Building an OS in Lisp isn’t just an exercise. It’s a way of experiencing what those engineers in the 70s discovered: that the boundary between using a computer and programming it can dissolve entirely.
The Experiment#
Following the inspiration of ECMAchine, this series builds a toy operating system inside a Common Lisp REPL. Not a real OS: we’re not writing boot loaders or device drivers. Something more like a thought experiment made concrete.
We’ll encounter each of the fundamental abstractions:
- The interface comes first: a REPL that accepts commands, the conversation between human and machine.
- Storage follows: a virtual filesystem, trees of nodes, persistence across sessions.
- Concurrency appears: background tasks, threads, the challenge of things happening at once.
- Memory hides beneath: Lisp handles this, but we’ll see it surface when we think about object identity and persistence.
- Processes emerge implicitly: each command dispatch, each spawned thread.
The code will be simple. Intentionally so. This isn’t about building something production-ready. It’s about seeing the patterns clearly, understanding why the abstractions exist, experiencing the design decisions firsthand.
Every operating system is an answer to the question: “How should a human use a computer?” We’re going to build our own answer.
Next: Part 1: A Simple REPL