Lacuna Scheme
A modern Scheme for the LLM-authored, Unix-native era.
Reading this book
The purpose of computing is insight, not numbers.— Richard Hamming, Numerical Methods for Scientists and Engineers, McGraw-Hill, 1962, preface.
This is a book about a programming language, written for engineers who have read a few books about programming languages and are tired of the genre. It makes four cases. The historical case is that Lisp was designed in 1958 for exactly the workload that fell into our laps in 2022, and that the seventy years between were a long pause and not a long argument. The technical case is that a small, regular, homoiconic language is the substrate that survives an author who is not always human. The compositional case is that workers, supervisors, refusal protocols, and capability tiers want to be values in a language, not annotations on a wiki page. The audience case is that the engineer reading a daemon at three in the morning will spend that minute reading and not parsing.
The book is also pedagogical. By the time you finish Chapter 2 you can read Scheme. By the time you finish Chapter 10 you can write a supervised worker in the Lacuna dialect. The exercises are the document; the document is a surface you can write on. In the first slate of the next chapter, the line (define your-name "...") binds the variable the rest of the book uses to address you. The default is "friend." If you would like the book to know your name, set it once and it will know it for the rest of the read.
The voice is plain. The claims are footnoted. Where the historical record is thin, the prose says so. Where another language already does the thing well, the prose says so and explains what Lacuna adds. No section tries to convince by tone. If a paragraph reads like a startup landing page, treat it as a bug.
The terminal-green text — the chapter numerals, the keyword highlighting, the small caret in the slates — is the single accent reserved for this kind of object. Everything else is grayscale. Hard corners, no shadows, no ornament. The book is meant to be readable in print and on a black laptop screen at midnight, with the same mood.
The interpreter the slates are talking to is Scheme engine, an R6/R7 Scheme written in JavaScript by Yutaka Hara, MIT-licensed, vendored locally as a sibling file. Everything you define persists across slates until you click reset in the colophon. The Scheme you type is real Scheme; the slates are not pictures.
What Node did for JavaScript
I think most of us would agree that JavaScript is the only really portable runtime — and yet we don't use it for serious things. Why?— Ryan Dahl, JSConf EU, Berlin, November 8, 2009.1
In November 2009, on a small stage at JSConf EU in Berlin, a then-unknown engineer named Ryan Dahl introduced a piece of software he had spent the previous nine months writing. He called it Node. The talk was titled, in the simplest possible terms, Node.js, evented I/O for V8 JavaScript. The audience was perhaps two hundred people. The talk was thirty minutes. It received a standing ovation. Within six years the runtime he described that afternoon would be one of the most widely deployed pieces of software in the world.
The narrative we are interested in is not the rocket-ship part. The narrative we are interested in is what came before the rocket ship — the conditions Dahl was responding to, the structural pieces he had assembled, and the cultural turn that followed. JavaScript had been universally available for fourteen years and was, in November 2009, still considered a toy. It was the language you wrote a calendar widget in. The serious people wrote Java or C++ or, on a good day, Python. JavaScript was for the browser; the browser was where junior engineers cut their teeth before they grew up and learned a real language. This was the consensus.
Node did not change the language. Node added four things around it, and the four things turned out to be enough.
The four ingredients
The first ingredient was the runtime. A way to execute JavaScript outside the browser without the browser's safety harness. Dahl chose Google's V8, which had been released the year before for Chrome and was the fastest JavaScript engine on the planet. He embedded it. The choice was load-bearing — without V8 the runtime would have been slow, and without speed JavaScript would have remained, in fact and in reputation, a scripting language for trivial workloads.
The second ingredient was libuv, the asynchronous I/O library Dahl wrote (originally as a wrapper around libev and libeio, later as a single portable layer). libuv expressed a simple thesis: most server programs spend almost all their time waiting on I/O, and the right primitive for that wait is an event loop, not a thread per request. The decision was structural. A thread-per-request server scales until you run out of memory; an event-loop server, given a sane I/O API, scales until you run out of network. JavaScript already had the right shape for this: callbacks were everywhere in the browser, and the language had no native threads to fight against.
The third ingredient was npm, the package manager. Isaac Schlueter released the first version of npm on January 12, 2010, three months after Dahl's JSConf talk.2 Influenced by Perl's CPAN and PHP's PEAR, npm was lightweight, community-curated, and run as a service. The decision to make package installation a one-line command — npm install <name> — was, in retrospect, the single most consequential decision in the Node ecosystem. It meant that any working programmer could publish a useful library by lunchtime and have a thousand strangers depending on it by dinner. The flywheel started turning and never stopped.
The fourth ingredient was the audience. Every front-end engineer already knew the language, and every front-end engineer wanted a server. Within twenty-four months the same person who had written the calendar widget was writing the server that fed it data. The transition cost was almost zero. The number of people who could plausibly hire themselves out as a Node developer was, by 2012, larger than the number of people who could hire themselves out in any other server-side language. This was not a fluke. This was structural.
A timeline, briefly
The total period from "interesting demo" to "default" is under a decade.
The thesis we are echoing
The operating thesis the rest of this book tries to earn is that the same four ingredients are now present for Scheme. The runtime is a tiny, well-understood interpreter we can implement in Rust in a few thousand lines and embed everywhere. The package manager is a problem the Lisp world has solved, badly, several times, and we know which pieces to keep and which to drop. The FFI is PyO3 and the C ABI, both mature. And the audience — the place the analogy bends most interestingly — the audience is not the population of human programmers. The audience is the population of language models that write code. What Node did for JavaScript, we are doing for Scheme.
Where the analogy breaks
Be honest about it. The Node story had structural advantages we do not have. V8 was paid for by Google's browser ambitions and arrived free; Scheme has no equivalent windfall. JavaScript had a captive audience of people who already knew the language; Scheme has academics, Guix users, a handful of Clojure refugees, and the model. JavaScript had a single, dominant, widely-deployed implementation by 2010; Scheme has Guile, Chicken, Racket, Chez, Gauche, MIT, Chibi, Gambit, and at least a dozen serious smaller ones. JavaScript's syntax was familiar to anyone with C heritage; Scheme's was not. These are real headwinds.
What replaces them is the typist. In 2010 the typist was a front-end engineer and the language had to be approachable to that person. In 2026 the typist is, increasingly often, a model — fine-tuned, structured-decoding-aware, more comfortable with regular grammars than with irregular ones. The model is the thing the language has to be approachable to first; the human is the second reader, not the first writer. That inversion is the whole hinge of the argument, and it is enough to make the four-ingredients analogy work even with the bent parts.
Try-it #1
The first slate. Click run. The expression evaluates and the value appears below the input. The next sentence tells you what just happened.
The number you just saw — — is the value of the expression inside the parentheses. The expression has three parts: a procedure, written first, and two arguments, written after. The whole thing is a list, and the rule for evaluating a list is uniform: evaluate the head as a procedure, evaluate the arguments left to right, and apply the procedure to the arguments. That rule is, in a real sense, the entire language.
Notes
- Ryan Dahl, "Node.js," JSConf EU, Berlin, November 8, 2009. Recording at youtube.com/watch?v=ztspvPYybIY; see also en.wikipedia.org/wiki/Node.js.
- Isaac Z. Schlueter, npm initial release, January 12, 2010. en.wikipedia.org/wiki/Npm.
A field guide to Scheme in fifteen minutes
Programs must be written for people to read, and only incidentally for machines to execute.— Harold Abelson and Gerald Sussman, Structure and Interpretation of Computer Programs, MIT Press, 1985, preface.
This chapter teaches you Scheme. It is the only chapter that does that. After it, the rest of the book assumes you can read code blocks without commentary. The interpreter the slates are talking to is Scheme engine, an R6/R7 Scheme written in JavaScript that lives in your browser. Everything you define persists across slates until you reset the document.
Naming yourself
Define a variable. The form is (define name value). Change the string to your own name if you wish, then run.
Welcome, friend. The variable you just defined is now in scope for the rest of the document. The bound value appears in every place we mention your name from here on, and updates if you change it.
Atoms, lists, and the application rule
Scheme has atomic values — numbers, strings, booleans, symbols, the empty list — and lists, written between parentheses. A list whose head is a procedure is a function call; a list quoted with a leading apostrophe is just data.
The first element of the result is the number five. The second is the list (+ 2 3) itself, undisturbed because the quote stopped the evaluator from treating it as a call. The difference between code and data, in Lisp, is one tick mark.
Functions
The form (define (name args) body) binds name to a procedure. The form (lambda (args) body) creates an anonymous procedure. The first is sugar for the second.
Seven squared is forty-nine. In any later slate, (square 9) returns ; the definition persists.
If and cond
Two forms for conditional evaluation. if is the binary form; cond is the multi-clause form. Both are expressions, not statements; they return values.
Recursion
A function calls itself. The base case stops the recursion; the recursive case shrinks the input.
Ten factorial is .
Closures
A function created inside another function remembers the variables it was defined with. The inner lambda sees the outer let's binding of n, and each call updates that binding.
Higher-order functions
Functions are values. map applies a function to every element of a list; filter keeps the elements for which a predicate is true; fold-left reduces a list to a single value.
Tail calls
A recursive call in tail position — the last thing the function does — does not grow the stack. The Scheme standard requires it.3
The slate counts to one hundred thousand without consuming stack. Proper tail recursion is in the language standard, not the implementation.
Quote and quasiquote
A quoted form is data, not code. Quasiquote is the same with holes punched in it: anything prefixed with comma is evaluated; anything prefixed with comma-at is evaluated and spliced.
A small macro
A macro is a function from syntax to syntax. syntax-rules describes the pattern; the expander rewrites the source before evaluation. The macro below defines a one-armed conditional.
You can read Scheme now. The rest of the book assumes you can.
Notes
- Proper tail recursion is mandated by R5RS §3.5 and R7RS-small §3.5. small.r7rs.org.
The McCarthy dream, and why LLMs are its answer
A computer can be programmed to use words of English and to refer to objects mentioned in English sentences. The programs are to manipulate these in such a way that the behaviour of the computer is reasonable.— John McCarthy, "Programs with Common Sense," Mechanisation of Thought Processes symposium, Teddington, November 1958.4
John McCarthy, friend, was thirty-one years old when he proposed the Advice Taker. The machine is to be told things in something close to ordinary language. It is to reason about what it has been told. It is to apply general advice to particular situations. This is a 1958 paper. Two years later McCarthy published Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I, in the April 1960 Communications of the ACM.5 It defines Lisp — list processor — as a notation for partial recursive functions over a class of objects called S-expressions. The whole language fits inside the paper.
The thing to notice about the 1960 paper is what kind of thing it is making. Not a programming language for a specific machine. A notation for symbolic computation — programs that manipulate, transform, and emit other programs. The motivating use case in McCarthy's mind was the Advice Taker, which would have to read its instructions, build representations of them, reason about them, and produce new instructions. The mechanism on which all of that depended was that programs and data had to share a representation. Lisp's S-expressions are that representation. Code is data; data is code; the rule for evaluation is uniform. This is the property called homoiconicity, and McCarthy did not call it that; he just wrote it down because the system needed it.
Between 1960 and 1980, the dream was alive. MIT and Stanford AI labs were full of programs that manipulated programs. Symbolic algebra (Macsyma); theorem provers; planners that constructed plans as data and then executed them. Lisp Machines arrived in the late 1970s — bespoke hardware where the microcode understood Lisp directly. Symbolics, LMI, and TI Explorer built and sold these machines into the early 1980s. For a brief period it looked as though the future of computing was a Lisp machine on every researcher's desk.
Then the AI Winter. The first winter began around 1987, when the DARPA Strategic Computing Initiative wound down and the expert-systems industry collapsed. The Lisp Machine companies were the first casualties; commodity workstations were getting fast enough to run Lisp on a generic CPU. By 1996 Symbolics was defunct.6 Lisp, fairly or not, was bound up with the failure of symbolic AI.
What replaced symbolic AI was the statistical turn. Speech recognition went from rule-based to hidden Markov models. Machine translation went from interlingua to statistical alignment. Computer vision went from heuristics to convolutional networks. By 2012 the deep-learning thesis was paying off; by 2017 the transformer architecture had arrived; by 2022 a transformer trained on a large corpus of internet text was demonstrably able to take advice in ordinary English and reason about it. The reasoning was uneven; the failure modes were novel. The system was, by any honest reading, the most successful realization to date of McCarthy's Advice Taker.
The Advice Taker had been described in 1958, abandoned by 1990, and built — by an unrelated research community using completely different mathematics — by 2022. The intervening sixty-four years were not an argument that the dream was wrong. They were a long search for the right substrate.
Why the language now matches
The substrate that turned out to make the Advice Taker possible — large transformers trained on internet text — has, as a side effect, a property no one designed for. The language LLMs find easiest to generate well is Lisp. The reasons are technical and not subtle.
First, Lisp's surface syntax is regular. A Scheme program is a tree of S-expressions. The grammar of S-expressions is one production. The grammar of Python is dozens of pages. A model decoding tokens has a much easier time staying syntactically valid in a one-production grammar than in a thirty-page one. Constrained decoding is two lines of EBNF for Scheme; it is a research project for Python.
Second, the model's primary failure mode in Lisp is a single, mechanically detectable, mechanically repairable class: unbalanced parentheses. The Python failure modes — wrong indentation, slightly wrong operator precedence, statement-vs-expression confusion — are silent and produce code that runs and is wrong.
Third, homoiconicity collapses two competences the model otherwise has to learn separately. In Python or Rust, to write code the model masters the textual surface; to manipulate code it additionally masters an AST library. In Scheme there is one structure — the S-expression — and the reader, the macro expander, and the printer all consume and produce it. Anything the model emits is by construction a tree the runtime can analyze.
Fourth, the unit of meaning in a Lisp program is the form, not the line. A diff between two versions of a function is naturally expressible as a tree rewrite. A model proposing a change can emit the new form, not a unified diff. The wire protocol between author and reader matches the substrate.
Try-it #2
The program is a value the language can pull apart with the same operations it uses on any other list. That is the property McCarthy wrote down in 1960 because he needed a machine that could read its own advice. The machine is here now. This is why LLMs need Lisp.
Notes
- McCarthy, "Programs with Common Sense," Mechanisation of Thought Processes, Teddington, November 1958; reprinted in Minsky, ed., Semantic Information Processing, MIT Press, 1968. www-formal.stanford.edu/jmc/mcc59.html.
- McCarthy, "Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I," Communications of the ACM 3:4 (April 1960), 184–195. DOI 10.1145/367177.367199.
- Symbolics filed for Chapter 11 in 1993 and became defunct on May 7, 1996. en.wikipedia.org/wiki/Symbolics; softwarepreservation.computerhistory.org/LISP.
Lisp is easy to learn now
There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies; and the other way is to make it so complicated that there are no obvious deficiencies.— C. A. R. Hoare, "The Emperor's Old Clothes," 1980 ACM Turing Award lecture, CACM 24:2 (February 1981), 75–83.
There is an old claim — old enough that the people who made it have, in many cases, retired — that Lisp is hard to learn. It came from the 1980s and 1990s, when the typist was always a human and the human had to balance the parentheses by eye. The complaint was real. It is no longer the constraint.
The historical argument had three parts. Parentheses. Mental model. Ecosystem inertia. None were arguments about the language as such; they were arguments about the practice of writing it at scale. Paredit (Taylor Campbell, around 2004) made the parentheses balance themselves and gave the editor a vocabulary for moving them. Parinfer (Shaun Lebron, 2015) took indentation-as-structure and applied it to Lisp. The functional idiom Lisp had quietly held for decades — first-class functions, immutability, comprehensions and folds, persistent data structures — turned out to be the idiom every mainstream language adopted by 2020. Python grew comprehensions and lambdas. JavaScript grew arrow functions and array methods. Java grew streams. Rust grew closures and iterator chains. The Lisp programmer of 1990 would, in 2026, recognize half of every modern language. The mental model has converged.
The new empirical fact
When the typist is a fine-tuned language model, the parentheses are scaffolding rather than barrier. The model emits trees more reliably than it emits indentation. The mental-model question is answered by exposure: a few thousand curated examples teach a model the dialect. Constrained decoding, the technique by which a model is forced to emit only tokens that keep the partial output syntactically valid, is a two-line EBNF grammar for S-expressions. It is a research project for any irregular language.
The empirical claim is local and stated as such. One open-source 14B coder base, LoRA-fine-tuned on roughly seventy-five thousand mixed pairs — Unix systems work, Lisp and Scheme idioms, refusal patterns — reached working fluency in a Scheme dialect after about seven hours of training on commodity Apple Silicon. The validation tasks included writing a six-state supervised worker, recognizing a tier-denied call and emitting the canonical refusal triplet, and translating between sibling dialects on a small held-out set. Local measurement; not peer-reviewed; reported here as the empirical floor, not the ceiling. A larger model on a larger corpus would reach the same fluency in less time. The point is that the floor is already low.
The finding generalizes. The reason is structural: a grammar with one production is cheaper to teach a model than a grammar with thirty pages. The reason has nothing to do with model family, training framework, or hardware. It has to do with the shape of the language. Any reasonably competent coding base, given a small corpus of well-shaped Scheme, learns to write competent Scheme. The historical objection — that the language is hard — has flipped, in the case where the typist is a model. The model finds Scheme easier than Python by an order of magnitude in syntactic terms, and within the same order of magnitude as Python in semantic terms.
Consequences for the human reader
For a human approaching Scheme cold in 2026, the language is no harder than Python in most respects, and easier in several. The grammar is one rule. The evaluation rule is one rule. Conditionals and binding forms are expressions, not statements; the language has fewer special cases than the average modern language. Where the human historically struggled was the parenthesis-balancing and the unfamiliarity of prefix notation. Modern editors solve the first; ten minutes of reading solves the second. Chapter 2 of this book solves both inside half an hour.
The remaining hard parts of Scheme are the hard parts of any language: thinking recursively when you have not before, designing data so the program writes itself, choosing what to abstract and what to leave concrete. Those are not Scheme problems. They are programming problems, and they are easier in Scheme than they are in most other languages because the syntax does not get in the way of the thinking.
A history of Scheme on Unix, 1958 to 2026
A programming language is low level when its programs require attention to the irrelevant.— Alan J. Perlis, Epigrams on Programming #8, ACM SIGPLAN Notices 17:9 (September 1982).
Scheme is a dialect of Lisp. Lisp is the second-oldest high-level programming language in continuous use; only Fortran is older. To understand why Scheme is the way it is, and why the people who love it sound the way they do, we have to start sixty-eight years ago in a small office at MIT. We will not race. There are roughly twenty episodes to walk through, and we will give each one the breath it needs.
1958–1962. The origin.
In the autumn of 1958, John McCarthy proposed a programming language for artificial intelligence research. He wanted a language in which symbolic expressions — knowledge, in the abstract, as a tree of nested labels — could be manipulated as easily as numbers. The result, two years later, was the paper published as Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I, in the April 1960 issue of Communications of the ACM. Lisp — short for "list processor" — was the implementation that grew up around it. Part II was never published.
McCarthy's original conception is worth quoting carefully. He wanted a system capable of common-sense reasoning — what he called the Advice Taker, sketched in the Teddington paper of November 1958. He imagined a machine, given a set of axioms about the world and a goal, could deduce a sequence of actions. The language was a step toward that goal, not the goal itself. The cleverness was in the data representation: symbolic expressions could carry arbitrary structure, and a small set of recursive functions could walk that structure. The mechanism was so transparent that McCarthy realised, after the fact, that Lisp could express its own interpreter in about a page. The page exists; it is called the meta-circular evaluator; we will return to it at the very end of this book.
The first implementation, by Steve Russell on the IBM 704 at MIT, came shortly after; Russell read the paper's definition of eval and recognized that the meta-circular evaluator could just be run. The LISP 1.5 Programmer's Manual followed in 1962. The industrial fate of Lisp, however, diverged from McCarthy's research vision almost immediately. The language became the working tool of the new field of AI; the AI field became, by association, "the Lisp field." The marriage was binding in both directions, and the consequences took thirty years to play out.
1960s–1970s. The Maclisp family.
Through the 1960s and 1970s, Lisp fragmented into dialects, each tied to a particular institution. MACLISP grew at MIT — the M was for "Project MAC," the predecessor of the AI Laboratory. BBN-LISP became Interlisp at Xerox PARC. There were Stanford-LISP, Cambridge-LISP, Standard LISP at Utah. Each had its own evaluator, its own libraries, its own programming culture. There was no standard. To move a program from one Lisp to another was the work of a graduate-student summer.
This is the moment of original sin in Lisp's industrial story. The fragmentation was not initially seen as a problem — each dialect served its local research community well — but it meant there was no shared body of code, no shared library culture, no shared tooling. When the rest of the industry began standardising, the Lisp community could not. We will see the consequences when we get to 1984.
1975. Sussman and Steele.
At the MIT AI Lab in December 1975, Gerald Sussman and Guy Steele wrote a memo. They had been studying actor-based concurrency, in particular Carl Hewitt's work, and wanted to build a small interpreter that captured the essence of message-passing in the simplest possible way. The result was AI Memo 349, Scheme: An Interpreter for Extended Lambda Calculus.7 Forty-three pages. A Lisp with three things its peers did not have: proper lexical scope, first-class procedures with closures, and a guarantee that tail calls be implemented without consuming stack.
The clarity of the design was its own argument. Most of what Lisp had accumulated in fifteen years — special variables, dynamic scope, fexprs, the unprincipled extensions — was discarded. What remained was a language that fit on a postcard and could express, by carefully chosen evaluation rules, both functional and imperative styles, both eager and lazy data, both procedures and continuations. AI Memo 349 was followed by a series of further papers — the Lambda Papers, 1976–1980 — that built up the theory of lexical scope and tail calls and, in Lambda: The Ultimate GOTO (1977), showed that procedure calls could subsume the role of unstructured jumps in a way that preserved the programmer's ability to reason about control flow.8
Scheme, in 1975, was a small academic Lisp. By 1985 it would be the language SICP was written in, and through SICP the dialect would teach a generation of working programmers what programming could feel like. By 1990, every serious computer-science department in North America taught its introductory course in Scheme.
1980–1987. The Lisp Machine era.
Through the early 1980s, three companies built computers whose native machine language was Lisp. Symbolics was founded on April 9, 1980, by MIT AI Lab alumni; LMI was a sibling spinoff; Texas Instruments built the Explorer line under licence. The machines were extraordinary objects — a Symbolics 3600 had a custom processor, a tagged memory architecture for garbage collection, a software stack written from the boot ROM up in Zetalisp, a development environment in which every primitive was inspectable and editable at runtime. They cost approximately a hundred thousand dollars. They were sold to the Department of Defense, to research laboratories, to large corporations who wanted to be at the AI frontier.
Then, around 1987, the market collapsed. The story is well-documented. The collapse was not because Lisp Machines failed at their job — by all accounts they were astonishing tools. The collapse was because Moore's Law caught up. By 1987 a Sun workstation cost a tenth of a Symbolics 3600 and ran Lisp fast enough. The custom-hardware advantage evaporated; DARPA reduced AI funding as the Strategic Defense Initiative wound down; Symbolics' revenues, which had peaked in the mid-1980s, fell through the late 1980s and the company filed for bankruptcy protection in 1993 and was defunct by May 1996.
This is the moment that, in the Lisp community's collective memory, became the catastrophe. It was the moment "Lisp" became, in the wider industry, a tarred word — associated with the AI hype that had just deflated, associated with expensive hardware that had just become obsolete, associated with a research culture that had just lost its industrial backers. It was, in retrospect, an unfair association. Lisp the language had not failed. Lisp the hardware platform had failed. The wider industry did not distinguish.
1984–1998. The Common Lisp / Scheme schism.
Two standards efforts ran in parallel through this period and ended badly for both. On the Common Lisp side, the project was an attempt to unify the Maclisp family. Guy Steele edited Common Lisp the Language (CLtL1) in 1984, then CLtL2 in 1990, then ANSI Common Lisp in 1994. The language that emerged was vast — perhaps the largest programming language ever standardised. It contained CLOS, the Common Lisp Object System. It contained a multi-method dispatch model, condition handling, the loop macro, pretty-printing, sequences, packages, optimisation declarations. The standard was over a thousand pages.
On the Scheme side, the standards arrived as R1RS through R5RS, ending in 1998, each maybe fifty pages. R5RS was the canonical small Scheme report; it remained the de facto standard for almost a decade. R6RS in 2007 was an attempt at a larger, more uniform language modelled in part on Common Lisp lessons; the formal ratification was complete, but the implementer community largely declined to adopt it whole. The community-side reaction, not the committee's, is what people remember as "the rejection." R7RS-small, edited by Alex Shinn, John Cowan, and Arthur Gleckler and ratified in 2013, is the small Scheme of the post-schism era.
The structural problem is visible in the timing. Common Lisp's standardisation peaked in 1994 — the same year C++ was standardising into ISO/IEC 14882 and Java was about to be released. The Lisp world was arguing about CLOS; the rest of the industry was choosing the language they would write systems in for the next thirty years. By the time CLtL2 shipped, the window was closed. Scheme, meanwhile, was so small and so divided that the question "which Scheme" had a different answer in every department. There was no industrial Scheme to bet on.
1994. scsh.
In 1994 Olin Shivers, then at MIT, published a paper and an implementation called scsh — a Scheme shell, built atop Scheme 48.9 The thesis was direct: a functional language with first-class procedures and macros is a better surface for systems programming than the shell languages of the 1970s. Shivers built process notation in which a pipeline like ls | grep foo | wc -l could be expressed as a Scheme form with proper scoping, structured error handling, and composable transformations. The acknowledgements section of the scsh paper became, in its own right, a famous piece of writing — a long aria about why the maintainer of a public-good open-source project burns out. It is worth reading; it explains, in human terms, why one of the most talented Scheme implementers of his generation eventually moved on to other projects.
scsh was correct. It was beautiful. It is still maintained. It did not become the default shell on any Unix system. The reasons are instructive: scsh required you to install a Scheme implementation that supported its specific dialect; it had its own packaging conventions; it was slow to start, a real liability in shell use; the community of practitioners was, at peak, perhaps a few hundred. The lesson is not that scsh was a bad idea. The lesson is that a beautiful language without an audience and a runtime is a beautiful language without an industry.
1993–present. GNU Guile.
In 1993, Tom Lord forked a Scheme implementation called SCM (by Aubrey Jaffer) into a project he called GEL; Jim Blandy renamed it Guile. The intent was to provide the Free Software Foundation with a small, embeddable extension language — a serious alternative to Tcl, which Richard Stallman had publicly criticised. Guile became GNU's official extension language in 1995, and a slow climb began. Beginning around 2008, Andy Wingo started contributing seriously to the project; by 2010 he was the maintainer in practice; by 2014 Guile 2.0 had a real compiler producing real bytecode; by 2020 Guile 3 had a baseline JIT and was, for the first time, fast enough to embed in production software at speed.
Wingo's "Lessons Learned from Guile, the Ancient & Spry" (February 7, 2020) is the closest thing to a documented engineering education for a working language hacker in the public record.10 The lesson the operator most often quotes from it, paraphrased: hill-climbing on one dimension at a time is insufficient when the dimensions have not been chosen well. Choose the dimensions first. Then climb.
2000–present. Chicken.
Felix Winkelmann's Chicken Scheme, begun in 2000, compiles Scheme to portable C via Henry Baker's "Cheney on the M.T.A." technique. Small static binaries; usable FFI; working tail calls through the trampoline. Chicken's egg system was the first Scheme packaging system that worked in a recognizable sense. By the late 2010s, Chicken held steady at perhaps a few thousand active developers. It is still maintained, still improving (5.x landed in 2019), and still — if you want to ship a Scheme binary today — the place we would point a working programmer.
1995–present. Racket.
Matthias Felleisen founded the PLT research group in 1995 with one explicit goal: build a pedagogic programming environment for novice programmers based on Scheme. Matthew Flatt assembled the original virtual machine. The IDE — first DrScheme, later DrRacket — became, by general consensus, the best learning environment for any programming language. How to Design Programs (Felleisen, Findler, Flatt, Krishnamurthi, first edition 2001, second 2018) became the canonical text. Around 2010, PLT renamed Scheme-the-dialect to Racket-the-language. The Racket of 2026 is its own thing — a language laboratory with first-class language extension, a contract system, a typed dialect, sublanguages for proofs. It is one of the most intellectually serious projects in programming languages anywhere. Its industrial footprint is small.
2007–present. Clojure.
In October 2007, after roughly two and a half years of full-time work funded by his savings, Rich Hickey released a new Lisp dialect called Clojure. Clojure was a deliberate departure in several ways. It hosted on the JVM, deliberately, to inherit Java's library ecosystem and operational maturity. It made immutable data structures the default and provided efficient functional updates. It had an opinionated concurrency story built around software transactional memory and atomic references. It rejected Lisp's traditional macro system in favour of a hygienic, namespace-aware variant.
Clojure escaped academia. Within five years it had a packaging system (Leiningen, later Deps), a working web framework, a small but real industry. Hickey's talks — Are We There Yet? (2009), Hammock-Driven Development (2010), Simple Made Easy (Strange Loop, September 19, 2011),11 The Value of Values (2012) — became part of the working programmer's curriculum, far beyond Clojure itself. By 2026, Clojure has roughly two hundred thousand active practitioners. It is the only Lisp dialect a working CTO can hire for without difficulty.
The lesson of Clojure is that a Lisp can escape the curse of the parentheses if it pays the entry fees: a host with real industrial libraries, an opinionated set of defaults that take a position on hard questions, marketing aimed at working programmers rather than language enthusiasts, and a community willing to grow up. Clojure paid all four fees. The Schemes did not.
2010. Quicklisp.
For most of Common Lisp's history, getting a library installed was an adventure. You hunted around cliki.net, you tried asdf-install, you fought a Common Lisp implementation that did not quite agree with your operating system about file paths. Zach Beane released Quicklisp around 2010,12 modeled in part on lessons from clbuild. The novelty of Quicklisp was the curator role: rather than continuously fetching the latest of everything, Quicklisp publishes a monthly snapshot — a known-good set of versions of the entire library universe, tested against the major implementations. Installation became (ql:quickload "library-name"). The lesson is that the packaging problem in a small language community is solvable by one disciplined person willing to do the curation. The discipline is the hard part. Beane has done it, with one or two breaks, for fifteen years.
2001 / 2010. Norvig leaves Lisp.
Peter Norvig, the author of Paradigms of Artificial Intelligence Programming (1992) — perhaps the single greatest book of programs ever written, all in Common Lisp — left Common Lisp for Python around the turn of the millennium. He has explained the move several times. The most candid version is on Hacker News, October 2010, where he writes: "I came to Python not because I thought it was a better/acceptable/pragmatic Lisp, but because it was better pseudocode."13 The students who used Norvig's AI textbook struggled to map from pseudocode to the Lisp implementation. Python — at the time, an unsettled language with a small community — turned out to be closer to the pseudocode in the book.
Two things to notice. First, Norvig did not say Lisp was worse. He said Python was closer to how people already read code. Second, this happened around 2001 — two years before Norvig became Google's director of research. The mass-availability of Python and the mass-unavailability of an industrial Common Lisp were not accidents; they were cause and effect of the language shift in the field. Norvig moved with the field; the field moved away from Lisp.
1994–2009. The Naggum era of comp.lang.lisp.
A particular Lisp culture lived on Usenet from roughly 1994 to 2009 — argumentative, erudite, often unkind, and shaped above all by the Norwegian programmer Erik Naggum. The archive — much of it preserved by Zach Beane at xach.com/naggum/articles — is staggering technically and essentially undisciplined socially.14 Newcomers asking elementary questions were routinely flamed; non-Lispers asking about C interoperability were treated as heretics; the dominant register was a kind of weary intellectual contempt for the rest of the industry. Naggum himself was, by all reports, deeply principled and deeply combative; he changed many people's view of computing, and he scared many more away.
The cultural diagnosis is uncomfortable but important. Lisp, as a community, did not welcome people. Newcomers were tested before they were taught. The contrast with the Python community of the same era — gentle, patient, schoolteacher-shaped under Guido van Rossum — could not have been sharper, and the contrast is, in retrospect, decisive in explaining which language attracted the next generation of working programmers.
2013. R7RS-small.
In 2013, after several years of careful work, the Scheme Language Steering Committee ratified the Revised7 Report on the Algorithmic Language Scheme, in two parts: R7RS-small and R7RS-large. R7RS-small, edited by Alex Shinn, John Cowan, and Arthur Gleckler, is the small, durable Scheme — a report that fits in one's head and that almost every modern Scheme implementation tracks. R7RS-large was intended as the larger ecosystem standard; its progress has been slow, and as of 2026 the successive colour-named drafts (Red, Tangerine, others) are still being worked through. The honest assessment is that R7RS-large will eventually arrive, in pieces, but it is not the centre of gravity. R7RS-small is. Lacuna Scheme tracks R7RS-small.
2012–present. Guix.
In 2012, Ludovic Courtès began work on a functional package manager called Guix, inspired by Nix. The novelty was that the package definitions, the build recipes, and the system configuration would all be written in Guile Scheme. A Guix System is a Linux distribution whose entire userspace is described in one or more .scm files. Roll back a deployment with a single command; reproduce an exact userspace on a different machine; describe a custom kernel and init system in a few hundred lines of Scheme. Guix is, in 2026, the most operationally serious use of Scheme at scale. It is in production at HPC centres, scientific computing labs, and small companies who like to know what is running on their machines. The lesson of Guix is that Scheme works for systems work, given the right primitives.
GNU Mes and bootstrappable builds.
A separate, quieter thread under the rubric of "bootstrappable builds" — most visibly through Jan Nieuwenhuizen's GNU Mes project — asks: if I do not trust any binary on my disk, can I assemble a working C compiler from a small, auditable, hand-written kernel? Mes's answer is yes, in approximately three hundred bytes of seed binary, expanding outward through a Scheme interpreter, a tiny C compiler, and from there to GCC. The work matters in part for security — supply-chain attacks become much harder if the entire toolchain can be reconstituted from a small kernel — and in part for the demonstration that Scheme is a reasonable language to use as the bootstrapping interpreter.
Modern small Lisps.
Janet (Calvin Rose, 2017): a small dynamic Lisp with built-in PEGs, fibers, and a Lua-like embedding story. Fennel (Phil Hagelberg, 2016): a Lisp that compiles to Lua and rides Lua's runtime. Carp (Erik Svedäng, 2017): a statically-typed Lisp targeting native code with linear types. S7 (Bill Schottstaedt): a small Scheme designed for embedding, used in some music-DSP environments. Cyclone Scheme and Loko Scheme are R7RS-faithful implementations with serious attention to compilation strategy. Sagittarius is an R6RS/R7RS-compliant implementation with cryptography libraries. None of these have escaped to industrial use; each is a teaching object that helps clarify what trade-offs are available in a small-Lisp design.
2023 onward. Guile Hoot.
Andy Wingo, working at Igalia and the Spritely Institute, has spent the last several years writing Hoot — a Scheme-to-WebAssembly compiler that lets Guile programs run in the browser. Hoot does not compile Guile's C runtime through emscripten; it writes a dedicated compiler backend producing small, native-feeling WebAssembly. The output is measured in kilobytes, not megabytes. As of late 2024, with WebKit shipping WebAssembly GC and tail calls by default, Hoot programs run on every major browser. The work is a serious statement that a Scheme can live in the browser as a first-class citizen.
2024–2026. The turn.
Large language models began producing useful program text around 2022; by 2024 they were producing it at industrial volume. The 2024 papers on constrained decoding — Outlines, XGrammar, Microsoft Guidance — made the syntactic-regularity argument operational. The 2025 papers on agentic code generation made the homoiconic-tree argument operational. The economic argument — that fine-tuning a 14B model on a domain-specific corpus costs single-digit tens of thousands of dollars — made it tractable. By mid-2026 the working assumption in most professional software shops is that the first draft of any function will come from a model.
This is structurally an inflection point for Lisp-family languages, in a way that is not at all obvious unless you sit with it. The models are sensitive to the homoiconicity of the source language. Code that is itself a tree is easier for the model to manipulate than code that is text with a separate parser; the model's internal representation and the source representation align. Macros — long the rhetorical centerpiece of Lisp's pitch and the practical reason many engineers shied away — turn out to be exactly the right kind of leverage when the author of the macro is not a human reluctant to learn a new tool, but a model perfectly happy to generate a small DSL for a problem.
What we learn
- Fragmentation is fatal. The 1970s Lisp dialect explosion was not, in its day, seen as a strategic problem. It turned out to be the strategic problem.
- A standard without a runtime is not a standard. R5RS, R6RS, R7RS-small are admirable documents. Each was followed by a decade in which the question "which implementation" had a different answer in every conversation.
- A community without manners loses the next generation. Naggum-era comp.lang.lisp drove away people who would, in another community, have become contributors. The contrast with Python is the case study.
- Hardware bets are bets on Moore's Law. The Lisp Machine companies bet that custom silicon would always be the fastest place to run Lisp. They were wrong by about five years.
- The packaging story is the language. Quicklisp arrived in 2010, twenty-six years after CLtL1. In those twenty-six years, Common Lisp acquired the reputation that you could not get anything done in it.
- The typist matters. JavaScript escaped its toy reputation when serious programmers started typing it. Scheme is, in 2026, beginning to escape its academic reputation because the typist is — increasingly — a model whose architectural biases happen to favour it.
- Be a tool the shell calls, not the shell. The scsh experience is that even a beautiful shell loses to
bashon the latency-and-familiarity axis. A Scheme that ships as a tool succeeds where a Scheme that ships as the environment does not. - Geiser-grade Emacs integration on day one. Half of the available audience already runs Emacs. The cost of supporting them is small; the cost of asking them to move editors is large.
Notes
- Sussman and Steele, MIT AI Lab Memo 349, December 1975. dspace.mit.edu/handle/1721.1/5794; index of the Lambda Papers at research.scheme.org/lambda-papers.
- Steele, "Lambda: The Ultimate GOTO," MIT AI Lab Memo 443, 1977. Part of the Lambda Papers sequence.
- Shivers, "A Scheme Shell," MIT AI Lab TR-1635, 1994. ccs.neu.edu/home/shivers/papers/scsh.pdf.
- Wingo, "Lessons Learned from Guile, the Ancient & Spry," February 7, 2020. wingolog.org/archives/2020/02/07/lessons-learned-from-guile-the-ancient-spry.
- Hickey, "Simple Made Easy," Strange Loop, September 19, 2011. Recording at infoq.com/presentations/Simple-Made-Easy.
- Beane, Quicklisp. quicklisp.org/beta.
- Norvig, Hacker News comment, October 2010. news.ycombinator.com/item?id=1803815. See also Norvig's collected writing at norvig.com.
- Naggum archive curated by Zach Beane at xach.com/naggum/articles.
Why Scheme did not break out — a candid post-mortem
A LISP programmer knows the value of everything, but the cost of nothing.— Alan J. Perlis, Epigrams on Programming #55, ACM SIGPLAN Notices 17:9 (September 1982), 7–13.15
There is no single reason Scheme did not become an industrial language. There are nine reasons, in rough order of importance. They are entangled, but they are separable. For each, we name the design commitment Lacuna Scheme makes in response. The point is not to settle old scores. The point is to learn.
1. McCarthy's vision versus Lisp's industrial fate
McCarthy designed Lisp to be a language for symbolic reasoning. The industry that grew up around it, particularly in the 1980s, was the AI industry, and the AI industry of that era was selling expert systems — rule-based programs claiming to capture the judgment of human experts. When expert systems failed to scale to real problems and the AI funding collapsed in 1987, Lisp went down with them. The failure was AI's, not Lisp's, but the public could not separate the two. McCarthy's research vision was about a tractable representation of knowledge; the industrial vision was about replacing doctors and engineers. The latter was overpromised and unrealised; the former was a reasonable research program that produced lasting tools. Lisp inherited the public verdict on the latter.
What Lacuna does. Lacuna Scheme is not an AI language. It is a systems language whose author happens, increasingly often, to be a model. We do not market the Lisp lineage as "the AI language"; we market it as a language designed for symbolic manipulation that turns out to be a good shape for both deterministic systems work and machine authorship. The distinction is real and we make it explicitly.
2. The cultural diagnosis
Perlis's epigram is the most famous one-line diagnosis of Lisp culture. The version we would give in 2026 is gentler but related: Lisp culture rewarded depth over breadth, elegance over completeness, and the love of the language over the love of the user. The clearest illustration is the Naggum era of comp.lang.lisp: a community of extraordinary talent that systematically scared away anyone less talented. A new programmer arriving in 2003 with an honest question often received a careful, accurate, technically-perfect answer wrapped in contempt. They did not come back.
What Lacuna does. The project is run with explicit community norms. Onboarding is structured. Code-review feedback is kind. We model the Python community's manners, not the comp.lang.lisp register. We have written this commitment down because it is the kind of thing one cannot fake, and one cannot afford to forget.
3. The packaging tarpit
The single largest cause of Lisp's failure to consolidate was the absence, for the first thirty-five years of its existence, of a working package manager. Common Lisp had no shared packaging story until Quicklisp around 2010. R5RS had no packaging story at all. Each Scheme implementation had its own conventions, its own incompatible module systems, its own ideas about file paths. A working programmer who wanted to use ten libraries in one project would, in the worst case, become the de facto integrator of those ten libraries. The cost was invisible to the existing community — they had paid it already — and decisive to the newcomer.
What Lacuna does. A package manager from day one. One canonical registry, content-addressed by hash, with lockfiles, semver, capability annotations on each package, and monthly frozen distributions in the Quicklisp tradition. We have looked at npm, Cargo, pip, and Quicklisp, and we are shipping Cargo's discipline with Quicklisp's curation cadence.
4. FFI fragmentation
"Use libcurl from Scheme" is a different sentence in every Scheme. Guile's FFI, Chicken's FFI, Racket's, Gambit's — all different. A library wrapping libsqlite had to be rewritten for every Scheme it wanted to support. In practice, this meant most libraries supported one Scheme, and choosing a Scheme meant choosing a sub-ecosystem within an already-small ecosystem.
What Lacuna does. One implementation. One FFI: Rust's FFI. Calling C is what Rust does; calling Python is PyO3; calling Wasm modules is wasmtime; calling system libraries is the system's. We do not invent a Scheme FFI standard. We adopt Rust's, and the language inherits Rust's interop work for free.
5. Tooling fragmentation
SLIME and SLY for Common Lisp under Emacs. Geiser for Scheme under Emacs. DrRacket for Racket. The LSP wave that hit other ecosystems in 2018–2022 mostly passed Scheme by. The lack of a canonical editor-and-LSP story meant the experience of writing Scheme depended sharply on which Scheme plus which editor plus which set of plugins; each combination was its own dialect of pain.
What Lacuna does. One LSP server, written in Rust, shipped in the same crate as the runtime. A Geiser backend so Emacs users get the full experience day one. A VS Code extension and Helix/Zed integration wrapping the same LSP. A canonical formatter and a canonical linter — both opinionated, neither configurable.
6. The marketing failure
The pitch most Schemes made for themselves was an aesthetic pitch — Scheme is elegant, Scheme is mathematically clean, Scheme is what programming should feel like. This was true. It was also the wrong pitch for working programmers, who were choosing languages by deployment characteristics, library availability, hiring pool, and the question "will my CTO let me." A working programmer in 2010 who heard "Scheme is elegant" heard a euphemism for "Scheme is not industrial." They were not wrong.
What Lacuna does. We lead with deployment characteristics. The pitch is "small static Rust-built binary; Cargo-grade packaging; PyO3 Python interop; LSP day one; capability tiers in the language; reproducible builds." The aesthetic case is true but it is not the first paragraph of the README. It is the third paragraph.
7. The Common Lisp / Scheme schism
From the late 1970s onward there were two camps within Lisp, and they could not agree. Common Lisp wanted a large industrial language with everything built in; Scheme wanted a small careful core with everything optional. Both positions were defensible. The damage was that, while they argued, C++ took the systems space and Java took the enterprise space. The two camps spent the period 1984–1994 — exactly the formative decade of the modern software industry — fighting each other instead of competing with the outside world.
What Lacuna does. We are not picking the fight. We track R7RS-small, the post-schism baseline. We borrow from Clojure where Clojure has good ideas — immutability defaults, namespaces, the contracts-of-care community. We borrow from Common Lisp where it has good ideas — restartable conditions, REPL ergonomics. We do not need a side in a war that ended thirty years ago.
8. Why Clojure escaped
Clojure was the Lisp that broke out. Its escape is worth modeling. Hickey paid four entry fees. He chose a host VM (the JVM) so that Clojure inherited a large library ecosystem and a credible deployment story. He took opinionated positions on hard questions (immutability by default, software transactional memory, namespaces) so that working programmers had defaults to follow rather than designs to fight. He marketed to working programmers via talks aimed at their concerns, not at language-design enthusiasts. And he ran the community with care, modeling — and enforcing — manners.
What Lacuna does. We are paying all four fees, with adjustments for the era. The host is Rust (not the JVM, because Rust is where serious systems work now lives). The opinionated defaults are tier-permissioned bindings, immutable data, a state-machine spine for workers. The marketing is to working programmers and to model-assisted programmers. The community norms are Python's, not comp.lang.lisp's. What Lacuna does not adopt: surface divergence from R7RS. The dialect remains an R7RS-small Scheme with namespaced extensions; we do not invent new core syntax.
9. The Emacs Lisp paradox
Here is a fact that surprises people: Emacs Lisp is the most-deployed Lisp on Earth. There are more running Emacs Lisps in the world than there are running Common Lisps, Schemes, and Clojures combined, by a healthy margin. Every Emacs user runs Emacs Lisp every day. And yet almost no one chooses Emacs Lisp for new work. It is dynamically scoped where the world has moved to lexical scope; it is single-threaded where the world wants concurrency; its standard library is Emacs-shaped, not general-purpose. The most successful Lisp in deployment is the one nobody starts from.
What Lacuna does. Emacs is family. Lacuna Scheme is not trying to replace Emacs Lisp. It is the language an Emacs Lisp programmer reaches for when they want to write something outside Emacs — and the language a fresh contributor to GNU userspace reaches for when they want a small daemon, a system tool, a packaging helper. The relationship is cooperative. An (emacs-eval …) bridge ships day one.
The summary, in one paragraph
Scheme had real technical merit and almost everything else against it. The community was small and unkind to newcomers; the packaging story was non-existent; the FFI was fragmented; the tooling was scattered; the hardware bet failed; the marketing was wrong; the standards arrived late; and the one Lisp that escaped — Clojure — did so by paying entry fees the Schemes refused to pay. Lacuna Scheme is the Scheme that pays the entry fees. The technical merit was never the problem. The deployment story was. We are fixing the deployment story.
Notes
- Perlis, "Epigrams on Programming," ACM SIGPLAN Notices 17:9 (September 1982), 7–13. ACM DL listing at dl.acm.org/doi/10.1145/947955.1083808. The full text of the epigrams is mirrored at cs.yale.edu/homes/perlis-alan/quotes.html; epigram #55 is the Lisp-value-and-cost one quoted above.
Pitfalls
The first principle is that you must not fool yourself — and you are the easiest person to fool.— Richard Feynman, Caltech commencement address, 1974.
This chapter argues against the rest of the book. It is the load-bearing chapter. If the reader is going to disagree with the proposal, this is the place to find the disagreement made well. Where the rest of the document tries to earn its claims, this chapter tries to break them.
Where this proposal is most likely wrong
The skeptical reading of this entire document is "another Lisp." It runs as follows. The world has seen many Lisps. Each one was, by the lights of its designers, the one that would finally consolidate the lineage and reach the mainstream. None has. The 2026 conditions — model authorship, model fluency in s-expressions, the maturity of Rust, the consolidation around R7RS-small — are real, but they are also the kind of conditions whose effects historically take twenty years to assess, and the proposal is being judged on a ten-year horizon. The most likely outcome, by base rate, is that Lacuna Scheme reaches a small community of devoted users, a few production deployments inside the studio and a few adjacent organizations, and never breaks out. The historical prior for new Lisps is overwhelming on this side.
The argument deserves to be taken seriously. The base rate is what it is. The most honest thing we can say in response is that we accept the base rate as the prior and intend to update it through work, not through claims. The conditions under which the prior wins are concrete: if model authorship matures faster in other languages than the homoiconicity-asset argument predicts; if the package-manager discipline lapses within a year of v0.2; if the LSP turns out to be a piece of infrastructure that consumes maintainer time without paying it back; if the cross-dialect bridge with the sibling browser dialect produces more confusion than reuse; if the small core, once shipped, cannot resist the temptation to grow features. Each of these is a plausible failure mode. We are watching for them.
The mistakes others have made
The graveyard is large. Specific monuments worth visiting:
R6RS overreach. R6RS was ratified in 2007 by the formal process. The implementer community, when faced with the report's scope and its insistence on particular module and condition-system designs, largely declined to adopt it in full. The framing usually offered — "the community rejected R6RS" — is community-side, not committee-side, and the prose should reflect that. What the episode teaches is that a standards body can ratify a document the implementers will not implement, and when that happens, the standard has not in fact been adopted. We track R7RS-small because R7RS-small is the standard the implementers actually implement.
CLOS-versus-records framework wars. Common Lisp's object system, CLOS, is a research-grade multi-method dispatch architecture with a metaobject protocol. It is one of the great pieces of language design. It is also overkill for many of the problems people use objects to solve, and the working programmer's reaction to "you have to learn CLOS first" was often to learn another language instead. Schemes, in their resistance to CLOS, adopted a profusion of record-and-struct systems — SRFI-9, SRFI-99, SRFI-131, plus implementation-specific records — none of which interoperate cleanly. The lesson: the absence of a canonical "this is how you make a struct" choice is its own failure. Lacuna Scheme ships SRFI-9 / R7RS records as the default and resists the temptation to add another.
Lisp Machine vertical integration. Symbolics built its own silicon, its own operating system, its own development environment, its own everything. Each layer was load-bearing on the layer below; when commodity workstations got fast enough, every layer's investment was at risk simultaneously. The lesson: do not pile vertical bets. Lacuna Scheme runs on a commodity Rust runtime, talks to commodity Unix, and assumes commodity hardware. The bet is on the language, not on a stack.
Elegance-for-elegance's-sake. The Lisp culture, at its worst, treats internal beauty as the goal rather than as a means to external usefulness. The result is a community that produces beautiful objects that nobody outside the community uses. The lesson: aesthetic claims are credentials, not arguments. We are aware that aesthetic claims are easy to make in Scheme. We try to keep them out of the marketing.
ASDF accretion. The Common Lisp build system, ASDF, started simple and accreted complexity over fifteen years until it was, in the working programmer's experience, the second-hardest part of Common Lisp after the lack of packaging. The lesson: build systems are dialect-shaped. Once a system is in production, the cost of replacing it with something simpler is high enough that complexity wins by default. We are shipping lacs simple, and we will resist accretion with a public RFC process.
Racket's bounded reach. Racket has the deepest tooling, the most ambitious research program, and the most welcoming community of any Lisp in 2026. It has, in industrial deployment, a small footprint. The lesson is that world-class tooling is necessary but not sufficient. Audience and use case must align with the tooling's affordances. Racket aligns with pedagogy and research; Lacuna Scheme aligns with operator daemons. If our use case is wrong, our tooling will not save us.
scsh's one-maintainer tarpit. Olin Shivers' scsh demonstrated that one extraordinarily talented person can produce, alone, software that does not survive when the person moves on. The lesson is structural: bus factor matters. Lacuna Scheme builds for a governance model that lets the maintainer-of-record rotate. We will fail this lesson if we are not careful, and we know it.
The mistakes we will probably make
Honest self-prediction. The first is the runtime choice. Rust is right today. Rust may not be right in 2032. The cost of switching runtimes is the kind of cost projects either pay or do not survive. We will discover, somewhere in the 0.7 era, whether Rust is the durable choice or merely the right-now choice, and we will pay the cost we have to pay.
The second is over-investing in Emacs. Emacs is the obvious editor for the first thousand contributors. It is not obviously the editor for the next hundred thousand. We will probably spend more on Geiser integration than the eventual share of Emacs users warrants, and we will probably underinvest in whatever the next-gen editor turns out to be. We try to mitigate this by writing the LSP first and the Geiser backend as a wrapper.
The third is the licence. We are choosing GPLv3+ for the runtime, LGPLv3+ for the stdlib, AGPLv3 for hosted services. The audience that prefers permissive licenses (MIT, Apache) is large and we are choosing not to optimize for it. Clojure made the opposite choice and benefitted. The bet here is that the GNU alignment is durable and the audience that cares is the audience worth having. We may be wrong.
The fourth is the package manager. Every package manager, given time, accretes the failure modes of npm. Quicklisp's monthly-curation model resists this; Cargo's lockfile-and-content-addressing resists this; neither resists it forever. We will, somewhere between v0.5 and v1.0, encounter a supply-chain incident that the current design did not anticipate, and we will have to harden against it.
The fifth is standard-library bloat. The R7RS-small discipline limits the core; we are committed to roughly two hundred procedures in the standard library at v1.0. The pressure to add the two-hundred-and-first will be continuous. We will not always resist it.
The sixth is the capability model. We have chosen five tiers — READ_LOCAL, WRITE_LOCAL, READ_NET, EXEC_USER, EXEC_ROOT. Five is plausible. Five may be wrong. The capability literature offers cuts at three (sandbox, user, root), at seven (with finer-grained network and IPC tiers), and at fully-general object-capability designs (Joe-E, E, Pony) where every reference is a capability. We are choosing five because five fits in the operator's head and because the audit log is cheaper. If five turns out to be wrong, we add an additional tier through an RFC and pay the cost.
The seventh is the typist. We are tuning a model to write the dialect natively. The temptation will be to let the model's idioms become canonical, which would bake our training biases into the language. The hedge is that the dialect is published, the shim packs are published, and any other model family can write it. The hedge is real but not free. We will probably ship a few constructions that read fluently to our model and awkwardly to others, and we will have to clean them up.
Where parts of this proposal may be stupid
Three or four explicit risks we cannot yet rule out.
The first: the homoiconicity-asset argument may be a phase. Models that write Python well today may write Python much better in two years, and the per-token advantage Scheme enjoys may narrow. The structural argument — that one production beats thirty pages — is durable; the empirical advantage may not be as large in 2028 as it is in 2026.
The second: capability tiers in the language may turn out to be redundant against OS-level mechanisms (seccomp, pledge, capsicum, Landlock). Defense in depth says the redundancy is fine, but if every tier check is doubly checked by the kernel, the language-level tier is paying its own cost for marginal additional safety. We have not yet shown the language-level tier earns its cost in production. We intend to.
The third: cross-dialect interop with the sibling browser dialect — sharing a core, binding different primitives — may produce more confusion than reuse. Two dialects that look alike but behave differently are, in some user's first reading, worse than two clearly different languages. We have committed to one core crate depended on by both environments, but the operator's experience of moving between them is not yet field-tested at scale.
The fourth: the small-security-companion idea — a separate narrow LLM that reviews privileged actions dialectically — is architecturally clean but practically unproven. Two models do not always disagree productively. They sometimes converge on the same wrong answer because they were trained on the same wrong corpus. The companion's value depends on the diversity of its training set from the authoring model's, and on the operator's willingness to override when the companion is wrong. Neither has been measured at production scale.
Homoiconicity and security — both sides
The homoiconicity property cuts both ways and the chapter cannot afford to pretend otherwise.
On the asset side: code-as-data is a real security advantage. A program that is a tree can be statically inspected, transformed, verified, and rewritten before execution. The runtime can examine an unevaluated form and decide whether to permit it. The tier checker can examine the primitive at the head of a form and decide whether the calling context holds the necessary grant. The audit log can record the form itself, not a reconstructed description of what the program was trying to do. None of these are available with the same first-class ease in any other language family. The expressive power of macros — long Lisp's distinctive feature — is the same property by another name: programs that manipulate programs, with the runtime treating both as values of the same kind.
On the risk side: code-as-data is dangerous when the data is untrusted. eval applied to a string from the network is the canonical Lisp security incident, and the language gives no automatic protection against it. The Lisp community has lived with this for sixty-eight years; the discipline is well known (do not eval untrusted input; sandbox if you must; whitelist the primitives the sandbox can reach). What we add to the discipline is the tier system: an untrusted form, evaluated in an environment that has been stripped of capability-bound primitives, cannot reach the filesystem or the network even if it wanted to. The form is harmless because the environment is. This is the standard defense in capability-based systems (E, Joe-E, Caja) ported to a Scheme.
The net is not clean. The asset and the risk are facets of the same property. We do not pretend the risk is zero. We do claim the discipline is well understood, and that the tier system makes the discipline cheaper to enforce.
What would falsify the case
Concrete signals, by horizon.
Within one year, the case is falsified if: the runtime cannot be brought to feature-completeness against R7RS-small; the package manager produces a supply-chain incident the design did not anticipate; the LSP server consumes more maintainer time than it returns in contributor uplift; the operator surface does not converge to a single static binary; or no external contributors arrive once the repository is opened.
Within three years, the case is falsified if: model authorship in Python improves to the point that the per-token advantage Scheme enjoys collapses; the cross-dialect bridge with the sibling browser dialect produces more user-visible confusion than reuse benefit; the Geiser audience does not adopt Lacuna Scheme even after working integration ships; or a competing Scheme runtime (Guile, Chicken, or a hypothetical Rust-based alternative) reaches feature parity with significantly broader adoption.
Within five years, the case is falsified if: the language has not produced a single production deployment outside Lacuna Labs; the model trained on the dialect cannot be replaced without retraining downstream tooling; or the security model is breached in a way that the tier design did not predict.
The case is also potentially falsified by the absence of disagreement. If no skeptic engages this chapter on its own terms, that is its own signal — that the document is being read by people who already agree, and the work is therefore not reaching the audience it needs to reach.
The twenty-five reasons
The proper criterion for a programming language is not how short the programs are but how easily they can be modified, with confidence.— Guy L. Steele Jr., Growing a Language, OOPSLA 1998 keynote.
Twenty-five technical claims, ordered from the most foundational to the most consequential. Each one is one claim. We give the claim, the evidence, the design implication, the skeptic's counter, an honest answer, and a note on why this matters now (2026). Five include a slate that demonstrates the claim in the live interpreter. Aesthetic reasons have a place; they are not in this chapter.
Homoiconicity is the substrate the LLM era was waiting for.
A language whose surface syntax is its abstract syntax tree collapses the distinction between writing code and manipulating code. For a typist that produces tokens by sampling, the cost of staying inside a small regular grammar is much lower than the cost of a large irregular one. Empirically, by mid-2025, fine-tunes of mid-sized models on Scheme corpora produced higher-quality Scheme per token than the same models produced Python. We do not have a complete theory of why, but the structural argument is unambiguous and the observation is robust. The design implication is the entire shape of this document: build for the tree-shaped typist first, the line-shaped reader second.
LLMs write Python fine. Why does homoiconicity matter?Models do fine with Python in the limit; they make fewer syntactic mistakes per token in Scheme. The benefit compounds when the model is editing existing code, not just generating from scratch — which, in 2026, is the dominant mode. Why now: the typist is changing. Scheme's regularity was a small advantage when humans typed it; it is a larger advantage when models type it.
Capability tiers as first-class language primitives.
Following Dennis & Van Horn 1966, Lampson 1971/1974, and Saltzer & Schroeder 1975,16 Lacuna Scheme exposes a ladder of tiers — READ_LOCAL, WRITE_LOCAL, READ_NET, EXEC_USER, EXEC_ROOT — as bindings in the language's environment, not as runtime decoration. A worker declares its maximum tier in its header; the supervisor enforces it; every primitive call is mediated at the binding. There is no ambient authority. The design is straight off the eight principles of Saltzer-Schroeder: economy of mechanism, fail-safe defaults, complete mediation, open design, separation of privilege, least privilege, least common mechanism, psychological acceptability.
This is what seccomp / pledge / capsicum already do at the OS level.Yes, and we use those mechanisms underneath where appropriate. The difference is that the tier lives in the language — visible to the human reading the source, visible to the model generating it, visible to the audit log. Defense in depth. Why now: code authored by a model needs runtime guarantees the model cannot subvert. Tier-permissioned bindings are exactly those guarantees, expressed in a form the model can also read.
Proper tail calls are the spine of long-running daemons.
The supervisor loop is a tail-recursive call back into the read-event step. R7RS-small mandates that a self-call in tail position consumes no stack; this means a worker can run for hours, days, or years without leaking frames. The class of bug that "the daemon crashed after running for a week and we cannot figure out why" — almost always a stack leak from a recursive structure the implementer did not realize was non-tail — is taken off the table at the language level. The loop reaches without consuming stack.
Every modern language has a way to write a loop without blowing the stack.Some have while; some have tail-call optimization as a sometimes-promise. R7RS makes it a guarantee. The supervisor's correctness proof rests on the guarantee, not on the implementation's discretion. Why now: the daemons we are building are designed to outlive their operators' attention.
Hygienic macros give domain extension without compiler PRs.
R7RS-small specifies a hygienic macro system. Macros that cannot accidentally capture or be captured by names in their use site. The practical consequence is that a domain expert (or a model) can write a tiny DSL for a problem — a query language, a state-machine builder, a contract DSL — without worrying about variable hygiene. The macros compose. They are simply functions from syntax to syntax. The reference is Kohlbecker, Friedman, Felleisen, Duba (LFP 1986); the syntactic-closures and syntax-case mechanisms followed, with Dybvig, Hieb, and Bruggeman (LASC 1993) being the canonical syntax-case reference.
Macros are a footgun.Unhygienic macros are. Hygienic macros are a tool. The distinction is a forty-year-old technical result, but the cultural memory of unhygienic macros has lingered. Lacuna Scheme ships hygienic-only. Why now: DSLs are the natural form of model-authored leverage. The model writes a small language for its problem, then writes the program. Hygienic macros make the small language safe.
First-class continuations make supervised state machines cheap.
Delimited continuations — the shift/reset family, or equivalently Felleisen's prompt/control — give us the ability to capture and resume an in-flight computation. In practice, this is how a supervisor pauses a worker mid-action and resumes it after a wait or an escalation. We do not need green threads or fiber libraries; the language gives us the primitives. Full undelimited call/cc is powerful and rarely needed; the leaning is to expose delimited continuations only in v0.1 and defer full call/cc behind a flag.
Async/await is more familiar.It is, and it is less general. Async/await is a special case of delimited continuations — one that has been syntactic-sugared into languages that did not have continuations to begin with. Why now: worker fleets need exactly this combination: long-running loops, pauseable mid-action, restartable from a saved state.
A typed condition system beats exceptions for ops code.
Common Lisp's condition system — Kent Pitman's design, formalized in CLtL2 — separates the act of signalling a condition from the act of handling it, and allows a handler to restart the computation at a named point chosen by the signaller. The practical consequence is that an ops daemon can handle "the disk is full" by surfacing the condition to the operator, letting the operator choose a restart (free space, switch volumes, abort), and resuming the computation from where it stopped. Exceptions, in their throw-and-unwind form, cannot do this; they have already destroyed the context by the time the handler runs. Lacuna's typed error returns from worker states are the same discipline in a smaller dress.
No one uses restarts.No one uses restarts outside Common Lisp, because most languages cannot express them. Inside Common Lisp, the working programmer reaches for them several times a day. Why now: ops code that an LLM authors needs structured error returns the model can reason about — not silent unwinds.
REPL-driven development matches AI-assisted tempo.
A Scheme programmer at a REPL types an expression, sees the value, types another. The state of the world — the bindings, the open files, the test fixtures — persists between expressions. This is exactly the rhythm an LLM uses when it is acting through a tool layer: tiny actions, immediate observations, course corrections. Where Python's print-and-rerun cycle costs the model a context-window of stale state, the REPL costs nothing. The model proposes, the REPL evaluates, the model adjusts. Sub-second cycles.
Jupyter is the same shape.Yes — and Jupyter is, structurally, a REPL with a richer output channel. The Scheme REPL was the original; the design predates the laptop. We are not arguing that REPLs are new. We are arguing that they fit. Why now: model-mediated programming is a tight feedback loop. Languages that were already tight feedback loops have a head start.
Image-based development survives the AI-authored workday.
Smalltalk and Common Lisp both make the working image — the bindings and state at a given moment — a first-class object. Restarting from a saved image preserves your work. Lacuna Scheme inherits this discipline through a session format that captures current bindings, open ports, and worker leases. The supervisor restores them on restart. Operators never lose their work to a crash; an overnight LLM session is recovered in full when the operator wakes.
Containers solve this already.Containers preserve filesystems, not interpreter state. The session format preserves interpreter state, which is the thing the operator actually loses. Why now: an operator running a long-tail LLM session expects their environment to be there in the morning. Image continuity is no longer a niche convenience.
Rust as the implementation language is now the obvious choice.
Memory safety in the runtime, where a buffer overflow in the evaluator would be a CVE-grade event. Production-grade async I/O via Tokio. Capability-bound filesystem via cap-std.17 A single static binary in the tens of megabytes. Cargo as the build system, with lockfiles, content-addressed caches, an offline mirror story, and a registry the language inherits for free. Matt Paras's Steel18 demonstrates that an embeddable Rust-implemented Scheme is tractable. The runtime engineers do not need to invent these things; they pick them up from the host platform.
You could have written it in C.We could have. Rust gives us memory safety in the runtime, a constraint a Lisp interpreter is not in a great position to relax. The cost-benefit is in Rust's favour. Why now: Rust is, in 2026, the lingua franca of new systems work. Writing a runtime in Rust is no longer adventurous; it is the default.
PyO3 makes Python a first-class citizen, not a stepchild.
PyO3 in 2026 is a mature project — it underwrites Polars, pydantic-core, and Ruff. From Python's side, import lacuna_scheme exposes an evaluator with shared CPython types. From Scheme's side, (import-python "numpy") exposes numpy as a Scheme module. The Jupyter kernel hosts both languages in the same notebook. The boundary is a few hundred lines of binding code, generated, not hand-written. Chapter 11 is the long form.
Most language interops are leaky.Most are. PyO3's discipline of explicit type boundaries makes it tractable; the cost is some boilerplate we automate away. Why now: Python is where the data-science libraries live and will live for the foreseeable future. Fighting that is dumb. Cousinhood is the rational stance.
One package manager that hosts both .scm and .py shims.
The Lacuna registry hosts native modules and shim packages wrapping pip-installable Python packages. lacs add numpy resolves to a shim that ensures the host Python has numpy and exposes it as a Scheme module. We do not reimplement numpy in Scheme; we wrap it. The same registry hosts pure-Scheme libraries, Rust crates wrapped for Scheme consumption, and Python shims. One lacs add, three sources of truth, one lockfile.
A polyglot registry is a maintenance nightmare.It is, if you let it. The discipline is to keep the shim layer thin: the shim ensures the dependency is present and exposes its surface; it does not vendor or version the underlying package. The native packages are bit-reproducible; the shim packages defer versioning to pip. Why now: the boundary between languages is increasingly load-bearing in production code. The registry that respects the boundary is the registry that survives.
Deterministic builds belong in the language, not the build tool.
Two builds of the same source on the same host produce the same binary, bit-for-bit. The lineage is GNU Mes — the bootstrappable-build project — and Guix's discipline of content-addressed packaging. The practical benefit is auditability: an operator can verify that what is running matches what was built matches what is in source. The xz attack, the SolarWinds attack, and every supply-chain compromise of the last five years has been a consequence of build opacity. Determinism is the floor.
Most builds are not deterministic and the world has not ended.It has, slowly. The threat model is now mainstream. The tools to do this exist. Why now: the supply-chain literature has converged on reproducibility as the load-bearing property. We are inheriting the consensus.
Structural editing is the AI-authoring UX.
The language's grammar is so regular that the editor can treat the program as a tree, not text. Paredit, lispy, smartparens — each gives the working programmer a vocabulary for moving parentheses, slurping siblings, transposing forms. Once internalised, structural editing is faster than text editing for languages that have a regular grammar to support it. Scheme has, by construction, exactly such a grammar. The LSP server emits structural-edit commands (form-level wrap, splice, replace) the model can produce and consume directly.
Tree-sitter gives me this in every language now.It does, with varying quality. For Lisps, the editor has always known the tree; the experience is decades-mature. Why now: an AI-assisted editor that proposes structural edits naturally pairs with a language that already accepts them. The hand-off is friction-free.
Cortex-event-style structured logging is native to s-expressions.
Every Lacuna Scheme worker emits structured events into a shared journal. The event has a fixed shape: (define-record cortex-event (kind payload at source-tag correlation-id)) — S-expression values the worker emits and the operator's surface consumes. A worker emits, a card displays, an audit log records — one shape, three readers. The bridge is the shape, not a translation layer. The operator surface and the daemon surface share the event format; the count of formats is one, not zero.
You are inventing yet another event format.We are using one event format across two dialects. We have looked at OpenTelemetry, CloudEvents, and journald's shape; this is the smallest shape that covers our needs. Why now: the operator surface and the daemon surface are converging. The event is the bridge.
Object capabilities are the only ops security story that scales.
Identity-based access control — "this user can read these files" — does not survive at the scale of a fleet of LLM-authored daemons. Every daemon would need an identity; every identity would need a policy; the policy matrix is quadratic in the number of daemons. Object-capability systems collapse the matrix: a daemon holds a capability to a resource, period. No identity, no central policy. The literature, from Dennis & Van Horn 1966 forward, is forty years deep. We are not pioneering; we are choosing the well-formed alternative.
RBAC works in practice.RBAC works at the scale of organizations with HR. It does not work at the scale of one operator running a hundred daemons that each spawn ten more. Why now: the daemon population is about to grow by an order of magnitude. The policy system that does not scale needs to be replaced before it has to be.
The five-tier capability ladder maps onto Unix file modes.
Unix's file-mode bits give us read/write/execute for owner/group/other. Lacuna's five tiers — READ_LOCAL, WRITE_LOCAL, READ_NET, EXEC_USER, EXEC_ROOT — map cleanly onto operator-readable mental models because Unix users already think in those categories. We are not inventing a new mental model; we are naming a familiar one. A worker that needs EXEC_ROOT is asking for sudo, in vocabulary the operator already knows.
Five tiers is arbitrary. Why not seven? Why not three?Five fits in the operator's head. Three under-discriminates (sandbox-vs-root collapses too many cases). Seven over-discriminates (operators stop reading the labels). Five is the empirical sweet spot from interface-design literature; we are using the standard cut. Why now: the petition-and-consent flow has to be operator-readable. Five fits on a small surface.
Supervised state machines beat ad-hoc retry loops.
Every worker is a Mealy automaton with five states. Every transition returns a discriminated descriptor. The supervisor knows how to drive any worker because the spine is the same for every worker. Ad-hoc retry loops — the for attempt in range(3): pattern in Python — survive in small codebases and rot in large ones. Supervised state machines, formalized once, scale linearly with the number of workers and constantly with the number of failure modes per worker. The lineage is Reid Simmons' Task Control Architecture and the broader Brooks / 3T / RAPs / HTN literature on layered reactive-sequencing-deliberative control.19
This is over-engineering.It is, for one worker. It is exactly right for a hundred workers. We are designing for the hundred. Why now: workers are the unit of computation in the LLM-supervised system. We are formalising the unit now, before there are millions of them.
Hot reload is a deployment story, not a development toy.
A running daemon can (load "patch.scm") at its admin socket and pick up new definitions on the next supervisor tick. The discipline was load-bearing for Ericsson's AXD301 telephony switches in the 1990s — multi-year uptimes through hundreds of in-place upgrades. The same discipline maps onto operator daemons that cannot afford to drop their lease and restart. The patch is a form; the form is loaded; the next tick sees the new definitions; the worker's state carries through.
Containers replace this with blue-green deployment.For stateless services, yes. For stateful workers with in-flight leases and external locks, blue-green is more expensive than hot reload. Why now: the daemons we are building are stateful in ways the stateless-service architecture cannot collapse away.
GPLv3+ for the runtime, LGPL for the stdlib (linking-friendly).
Runtime GPLv3+. Standard library LGPLv3+. Documentation CC BY-SA 4.0. Hosted services AGPLv3. The choice is deliberate: the GNU world has been Scheme's natural home (Guile is GNU's official extension language); the LGPL keeps the standard library linkable into proprietary applications; the AGPL anchors network-service work against rent extraction. The licences are not afterthoughts; they encode the cooperative-development relationship we want with the broader free-software community.
MIT/Apache would attract more enterprise adoption.It would. We are choosing community over adoption velocity. The Clojure community made the opposite choice, with success; we are choosing differently with eyes open. Why now: the right alliances are made early. We are aligning with GNU now, not after the fact.
GNU ecosystem fit: Emacs, Guile, Guix, gdb.
The GNU userspace is the natural deployment target. Emacs is the canonical editor; Geiser is the canonical Scheme integration; Guile is the canonical extension language; Guix is the canonical reproducible-build system; gdb is the canonical debugger and supports Scheme-side breakpoints through a small extension. Lacuna Scheme is built to live among these, not against them. We submit packages to Guix; we ship a Geiser backend; we make the gdb integration work.
The GNU world is small.It is small and deeply committed. We would rather have ten thousand committed users than a hundred thousand casual ones. Why now: the GNU world is having a quiet renaissance in 2026. The eglot/lsp-mode story is mature; the org-babel-plus-LLM story is mature. The plumbing we need is already in place.
Formal verification of small kernels is suddenly tractable.
The Lacuna Scheme interpreter is small enough — a few thousand lines of Rust — that the tier-check kernel, the supervisor handshake, and the refusal protocol are tractable for formal review. CompCert (Xavier Leroy 2009) is the existence proof that a realistic compiler can be formally verified; seL4 is the existence proof that a microkernel can be. Both took years; both produced load-bearing assurance. The small Lacuna core is sized to fit the same kind of effort.
You will grow the core.We will, in the bindings layer. The core stays small. R7RS-small was sized to fit one person; we keep the core within that. Why now: the cost of one engineer-week of audit time is buying serious assurance for a load-bearing piece of infrastructure. The economics favour the small core.
Message-passing concurrency ports cleanly from Erlang.
The supervisor protocol is the OTP supervision tree in Scheme dress. Mailboxes, leases, heartbeats, restart strategies, "let it crash" — Armstrong's 2003 thesis is the canonical reference. We are not reimplementing the BEAM. We are porting the discipline. A worker is a process; a process has a mailbox; messages are values; supervision is structural. Chapter 9 is the long form.
This looks like Erlang OTP.It is OTP-shaped, deliberately, without being Erlang. The worker body is Scheme. The supervisor's scheduling is the OS, not the BEAM. We pay respect to the lineage and port what ports. Why now: supervised process trees are the right shape for LLM-coordinated work. We are codifying the shape early.
LLM-authoring is dramatically cheaper in s-expressions.
Per token, the model emits more useful program in Scheme than in any other language we have measured. The per-token cost of model authorship is the dominant cost of operator-facing LLM work in 2026; reducing it by a factor of two changes the economics. We do not have public benchmarks yet — the work is internal — but the direction is consistent across model families, training sizes, and tasks. The structural argument is one production; the empirical argument is the per-token measurement.
The measurement is internal. Show me.We will. Public benchmarks in v0.5. For now, the structural argument carries: a one-production grammar costs fewer tokens than a thirty-page grammar to stay syntactically valid in. The empirical work is confirming what the structure predicts. Why now: the cost-per-token of model authorship is becoming the dominant cost line in operator-facing AI.
R7RS-small as the durable baseline.
The 2013 Revised7 Report is small enough to fit in one's head, large enough to cover what a working programmer needs, and stable enough that no one is going to break it. Every modern Scheme implementation tracks it. Lacuna Scheme tracks it. We do not invent a new language; we implement an established one and bind useful primitives to it under a namespaced extension prefix.
R7RS-large is where the action is.R7RS-large is incomplete and likely will be for years. R7RS-small is the load-bearing piece; large is additive. Why now: the discipline of tracking a published standard rather than inventing a new one is exactly the discipline the Lisp community historically declined to follow. We follow it.
The "hands and feet of Unix" operator daemon needs a language readable at 3am.
The final reason is the operational one. The daemon you are reading at three in the morning, because the alert woke you, has to be readable. Not clever. Not elegant. Readable. The form names what it does. The tier names what it can touch. The state machine names what it is doing next. The Cortex event names what it just did. There is no hidden control flow, no global ambient authority, no implicit context. A graybeard Unix engineer reading the source for the first time at 3am can find the bug in the time it takes to make coffee. The argument the whole book has been building toward is this one: a language designed for the model to write and the engineer to read at 3am is a language whose constraints align.
This is romanticism.It is also operations. The cost of three-in-the-morning incident response, summed across a fleet, dominates the cost of three-in-the-afternoon development. We are optimizing for the cost that matters. Why now: the daemon population is growing faster than the operator population. The operator-readability of the daemon is the constraint that determines whether the fleet survives.
Notes
- Dennis and Van Horn, "Programming Semantics for Multiprogrammed Computations," CACM 9:3 (March 1966), 143–155 — the older primary the usual Lampson/Saltzer-Schroeder citation chain often skips. Lampson, "Protection," 5th Princeton Conference on Information Sciences and Systems, 1971; reprinted ACM SIGOPS Operating Systems Review 8:1 (January 1974), 18–24. Saltzer and Schroeder, "The Protection of Information in Computer Systems," Proceedings of the IEEE 63:9 (September 1975), 1278–1308.
- cap-std, the capability-bound Rust filesystem API. github.com/bytecodealliance/cap-std.
- Matt Paras's Steel, an embeddable Rust Scheme. github.com/mattwparas/steel.
- Simmons, "Structured Control for Autonomous Robots," IEEE Transactions on Robotics and Automation 10:1 (February 1994), 34–43. The broader layered-control literature includes Brooks's subsumption architecture, Bonasso's 3T, Firby's RAPs, and Erol/Hendler/Nau's HTN — see Russell and Norvig, Artificial Intelligence: A Modern Approach, 4th ed., 2020, chapter 26 for a survey.
Erlang and the message-passing lineage
Errors are not unusual events. Errors are the normal case.— Joe Armstrong, Making Reliable Distributed Systems in the Presence of Software Errors, PhD thesis, Royal Institute of Technology, Stockholm, December 2003.
Joe Armstrong, Robert Virding, and Mike Williams started work on Erlang at Ericsson's Computer Science Laboratory in 1986. The domain — telephone exchanges — imposed constraints few other domains imposed: zero-downtime upgrades, microsecond context switches between thousands of small processes, fault containment that would not let a bug in one call take out an exchange. Armstrong defended his PhD thesis at KTH on November 20, 2003; the document is the canonical record of what Erlang was for and what it taught.
The thesis is direct. Processes are cheap and isolated, with no shared state. They communicate by asynchronous message passing through mailboxes. Errors are not handled in the process that caused them; they are observed by a supervisor in a separate process whose job is to decide whether to restart, escalate, or kill. "Let it crash." Supervision trees compose the recovery policy. Hot code reload is a first-class feature. The BEAM virtual machine has been in production at Ericsson for forty years.
The intellectual lineage is older than Erlang. Carl Hewitt, Peter Bishop, and Richard Steiger published A Universal Modular Actor Formalism for Artificial Intelligence at IJCAI in 1973;20 actors as the single primitive of computation, each a private state machine reachable only by messages. Tony Hoare's Communicating Sequential Processes appeared in Communications of the ACM in August 1978;21 CSP is the calculus of synchronous channel-based communication, whose descendants run through occam, Go's goroutines, and the CSP-inspired wing of Rust's concurrency story. Erlang is the production-grade reduction applied to a real industrial domain over decades.
Where Erlang succeeded is documented: WhatsApp (one server per two million connections), RabbitMQ, Riak, ejabberd, Cowboy, the Discord backend (millions of concurrent users on a small fleet). Where it did not is also documented: never became a general-purpose industrial choice. The reasons are mostly cultural. The syntax is opinionated; tooling was idiosyncratic for a long time; the audience for "soft real-time fault-tolerant distributed system" was small relative to "ordinary web application." Elixir broadened the audience but did not change the underlying VM.
Why this matters for Lacuna Scheme
The Lacuna worker spine is OTP-shaped in Scheme dress. The five canonical states are the states of a supervised process. The descriptors — (next state ctx), (wait ms), (after signal state ctx), (act verb args on-result), (escalate kind detail), (refuse tier clause adjacent), (done) — are the moral equivalent of OTP's {noreply, …} tuples returned from gen_server callbacks. The supervisor owns the lease, the heartbeat, the restart policy.
Two places we depart from Erlang are deliberate. The worker body is Scheme, not Erlang. The workers do not run in their own scheduler; the supervisor handles concurrency at the OS level. We are not reimplementing the BEAM; we are porting OTP's discipline into a language the model speaks natively. Structured events are the mailboxes between workers and surfaces. A worker that has crashed is not a process that lost its state; it is a process that left a journal entry the supervisor reads when it restarts the worker.
The actors are crude. In production the inbox is the structured journal, the sender is a worker emitting an event, the receiver is a worker waiting on a particular event kind, and the supervisor decides what to do when a recv times out. Erlang was, in places, a Lisp in C dress; Lacuna Scheme is, in places, a Lisp returning the favor.
Notes
- Hewitt, Bishop, Steiger, "A Universal Modular Actor Formalism for Artificial Intelligence," IJCAI 1973. ijcai.org/Proceedings/73/Papers/027B.pdf.
- Hoare, "Communicating Sequential Processes," CACM 21:8 (August 1978), 666–677.
Scheme with workers — the spine
The whole problem of programming would be very much easier if there were a clear separation between the part of the program that decides what to do, and the part of the program that does it.— Edsger W. Dijkstra, A Discipline of Programming, Prentice-Hall, 1976 (paraphrased).
A Lacuna worker is a small Scheme program shaped like a state machine. The shape is the same for every worker, and the regularity is the point: the supervisor knows how to drive any worker, the audit log knows how to read any worker's events, and the model knows the template it is generating into. There are five states and seven possible descriptors a state can return. That is the whole spine.
Talking to workers
How an operator writes code that drives workers. The shape is message-passing: the operator's session sends a request, the supervisor spawns or locates the worker, the worker advances through its states, the supervisor surfaces the result. The operator's vocabulary is small. (spawn 'feed-poller args) creates a worker; (tell w '(refresh)) sends it a message; (observe w) returns the stream of events the worker has emitted since some watermark. The vocabulary is closed.
The advantages of this shape: isolation (one worker's failure does not corrupt another); supervision (the restart strategy is structural, not ad hoc); replayability (the journal is the source of truth, so the worker can be rebuilt from its event stream); the structured event log (every interesting state transition is durably recorded with a canonical shape).
The disadvantages: debugging across mailboxes is harder than debugging a single thread of control; latency under load is higher than a direct call because every message round-trips through the supervisor; deadlock potential is real when two workers wait on each other. We accept the disadvantages because the advantages compound at scale and the disadvantages are bounded.
The dangers: a worker escaping its capability bound (mitigated by the tier check at GUARD, in the binding, not in user code); an unbounded mailbox (mitigated by per-worker mailbox limits); a supervisor restart-loop on a deterministic failure (mitigated by exponential backoff and an escalation to the human after a configurable threshold); a tier-misconfiguration that grants more privilege than intended (mitigated by the supervisor's audit of every grant at petition time).
The five states
- PRE. Reads cache, computes readiness. Side effects limited to
READ_LOCAL. Returns(next 'guard ctx'),(done), or(escalate kind detail). - GUARD. Validates preconditions. The tier check fires here, in the binding, not in user code. Returns
(next 'act ctx'),(after seconds 'guard ctx), or(refuse tier clause adjacent). - ACT. Performs the single side effect through a tier-bound primitive. Returns
(act verb args on-result-symbol); the supervisor invokes the primitive and feeds the result back to the named on-result state. - RESULT. Reads the value, emits a structured event, advances. Returns
(next 'done ctx')or another state symbol. - ON-ERROR. Receives
(error class detail ctx)and returns one of four canonical strategies: retry-after, degrade-to, escalate-tier, escalate-human.
The descriptor union
(next state-symbol ctx)
(done)
(done value)
(wait milliseconds)
(after signal-symbol state-symbol ctx)
(act verb args on-result-symbol)
(escalate kind detail)
(refuse tier clause adjacent-path)
The set is closed. A worker that returns anything else is itself a bug, and the supervisor halts it. We have considered an eighth descriptor several times; we have always backed out. The discipline of a small descriptor set is itself a safety property.
Safety — where the guardrails go
Four guardrails, each independent.
Typed tier checks at GUARD. The check fires inside the binding of the primitive, not inside user code. A worker cannot bypass it by writing a clever expression; the binding does not exist outside the tier-grant context. If the tier is denied, GUARD returns a (refuse tier clause adjacent) descriptor — a value, not an exception. The supervisor surfaces it; the audit log records it.
Mailbox bounds. Every worker has a maximum mailbox depth (default: 1024 messages). When the mailbox overflows, the supervisor escalates to the human rather than dropping messages. The cost of a memory blowout is contained.
Supervisor lease timeouts. Every worker holds a lease the supervisor renews on heartbeat. When the lease expires without renewal, the supervisor presumes the worker is dead and restarts it from the last journal-recorded state. A worker that hangs cannot block the fleet.
Structured refusal returns. A refusal is a value with a discriminated-union shape. It can be emitted, logged, queried, and reasoned about. The architecture treats it as first-class data, not as a control-flow oddity. This is the difference between a system that hides what it would not do, and a system that tells you, in a structured form, exactly what it would not do and why.
A small security companion
The runtime mechanism is necessary but not sufficient. The tier ladder is a hard wall; a worker that lacks EXEC_ROOT cannot reach a primitive that requires it. The audit log records every privileged call with the petition that authorized it. These are mechanism, not policy; they refuse the structurally impermissible. What the runtime mechanism does not do is judge the intent of an action that is technically permitted.
That judgment is the job of a separate narrowly-scoped LLM — a small security companion model, in the 3–7B parameter range, dedicated to reviewing privileged action proposals before execution. The name of the companion is unsettled. The architecture is: the authoring model proposes; the companion reviews; two models trained for different objectives produce a structural check that one model trained for both could not. Code review at engineering organizations works because the author and the reviewer are different humans with different perspectives. We port the same discipline.
The companion's corpus is publicly named even when the corpus itself is partly redacted for operational reasons: MITRE ATT&CK for the threat-model vocabulary, the OWASP Top 10 for web-facing exposure, the NIST SP 800 series for systematic controls, CWE for the bug-class taxonomy, and supply-chain post-mortems (xz-utils 2024, SolarWinds 2020, Log4Shell 2021, PyPI typosquatting incidents, npm event-stream 2018). The companion learns to recognize the patterns by name and to cite the relevant clause when refusing.
The interaction is simple. A privileged action proposal goes through five steps: (1) the authoring model writes the worker, declares max-tier; (2) the supervisor sends the proposal to the companion before granting any petition; (3) the companion returns approve, refuse with citation, or ask for clarification; (4) on approve, the petition surfaces to the operator with the companion's approval noted; (5) on refuse, the supervisor halts the worker; the operator can override and the override is logged with the operator's identity.
The worked example. The operator asks Lacuna to deploy a systemd unit running a metrics exporter on port 9090. The companion notices the unit exposes port 9090 with no firewall rule; the host has nftables active; the operator did not mention a firewall change. The companion refuses, citing NIST SP 800-41 §3.4 (default-deny inbound). The refusal carries a proposed amendment: add an nft rule allowing port 9090 only from the operator's LAN. The operator's surface renders the refusal in a HAL-register format the operator already reads: "EXEC_ROOT: petition deferred. The metrics exporter would open port 9090 with no firewall rule. NIST SP 800-41 §3.4 specifies default-deny inbound. Adjacent: add an nft rule restricting 9090 to 10.0.0.0/24. Proceed?"
The chapter is the architecture, not the roadmap. The companion is not in v0.1. The runtime tier mechanism is.
A worked worker, live
We will not run the full worker spine in the embedded interpreter — it depends on supervisor primitives the browser does not have. We will, however, build the shape in pure Scheme so the reader can read it. The slates below build a small worker that polls a feed every 60 seconds.
At the chosen interval — 60 seconds — the worker enters its GUARD state, checks readiness, and either proceeds to ACT or waits another interval. Try changing poll-interval to 120 and re-running. The interval propagates through the rest of the document.
The result is the ledger of transitions: the state, the descriptor it returned, in order from PRE through DONE. The ledger is what the production supervisor writes to the journal. A future reader of the journal — a human, the supervisor's audit pass, or a re-played simulation — sees exactly what the worker did, in what order, with what context.
The shared core
The Lacuna dialect shares a small kernel with a sibling environment that targets the browser surface. The two environments bind different primitives — one paints to a canvas; the other reads from disk and the network — but the language they share is the same language. The shared core is roughly one hundred and two primitives covering reader, evaluator, list operations, numeric operations, structured records, and the worker-spine descriptors. The Lacuna environment adds roughly one hundred and forty-one further bindings across the five tiers — filesystem, network, process, journal, supervisor. A worker that emits a structured event in one environment is consumed by a card in the other; the bridge is the event shape.
Python, the cousin
I came to Python not because I thought it was a better/acceptable/pragmatic Lisp, but because it was better pseudocode.— Peter Norvig, Hacker News, October 2010.22
Cousin, not killer. The Python ecosystem is the most successful general-purpose programming environment in the operating-engineer's world today, and a language that proposed to displace it would be insulting the audience. The relationship we want is the relationship Clojure has with Java — a host relationship — except not as a host, as a peer. Cousins. The libraries Python has, we use. The libraries Python lacks, we build. The boundary should be friendly enough that, in a notebook, you cannot remember which cell is which.
Bidirectional FFI
The runtime is Rust; PyO3 is the Rust–Python bridge. PyO3 in 2026 underwrites Polars, pydantic-core, and Ruff; it is a mature project, not a research prototype. From Python's side, import lacuna_scheme exposes an evaluator with shared CPython types: a Scheme number is a Python integer or float, a Scheme string is a Python string, a Scheme list is a Python list. From Scheme's side, (import-python "numpy") exposes numpy as a Scheme module: (numpy.array '(1 2 3)) returns a numpy array; the array's methods are callable as Scheme procedures.
The transition cost across the boundary is low. A Lacuna worker hands a list of records to a pandas DataFrame; pandas does the heavy aggregation; the result comes back as a record. The data does not copy except where shape changes require it. Apache Arrow buffers move between the two languages without copy where the runtime can prove non-aliasing.
Shared data formats
JSON, MessagePack, Apache Arrow buffers — the standard formats both ecosystems already speak. Arrow is the load-bearing one for numeric work: the columnar representation moves between Python's pandas/Polars and Scheme's analogous primitives without serialization. Scheme builds the workflow; Python does the heavy numeric work; the data crosses the boundary once.
Package interop
The Lacuna registry hosts both .scm native modules and shim packages wrapping pip-installable Python packages. lacs add numpy resolves to a shim that ensures the host Python has numpy and exposes it as (lacuna-python numpy). The shim is thin: it does not vendor numpy, it does not pin its version against pip's resolution, it simply names the dependency and exposes the surface. lacs lock records the pip resolution along with the Scheme dependency graph.
Jupyter kernel
A Jupyter kernel that hosts both languages in the same notebook, sharing one runtime state. Cells tagged %%scheme evaluate against the Scheme interpreter; cells tagged %%python evaluate against the embedded CPython. Bindings cross. A Python cell sees Scheme-defined variables as Python attributes on a scheme namespace object; a Scheme cell sees Python-defined variables through (python …).
REPL parity
The Scheme REPL and the Python REPL are both interactive. The Scheme REPL keeps state across expressions; so does Python's. The cycle time is comparable. A developer alternating between them — answering an LLM's tool calls in one window, exploring a dataframe in the other — does not notice the boundary.
Idiom alignment
Where there is a choice, we pick the Pythonic form. Keyword arguments are supported. The dataframe primitives mirror Polars and pandas names. A piece of Lacuna Scheme code dropped into a Python codebase should read as a sibling. The "they'll think we're together" property: side-by-side examples should require careful reading to tell which language is which.
AI-fluency boundary management
The model writes both languages. Where the model can produce the same outcome in either language, the choice goes to the language whose idioms fit the operator's reading style. The model is instructed to default to Scheme for control flow, supervised workers, and structured events; Python for numeric computation, machine learning, and the libraries we do not reimplement. The boundary is named in the system prompt, not enforced by the language.
What we do not do
We do not embed CPython into Scheme programs that do not need it. We do not pretend our standard library is a substitute for the Python scientific stack. We do not reimplement numpy, pandas, scikit-learn, or PyTorch. We are not racing the Python data-science community. We are using its work.
An illustrative worker
The production worker swaps the mocked primitives for real ones — http-get at tier READ_NET, (lacuna-python pandas) for the dataframe work, emit for the journal side. The spine stays the same. The forty-line sketch in the embedded interpreter and the four-hundred-line production worker share the same structure.
Notes
- Norvig, Hacker News comment, October 2010. news.ycombinator.com/item?id=1803815. PyO3: pyo3.rs. Polars: pola.rs.
Emacs, the home
Eight megabytes and constantly swapping.— folk acronym for EMACS, circa 1990; the joke survives because Emacs survives.
The Lacuna answer to "which editor" is Emacs. The reasons are not aesthetic. Emacs has the deepest investment of any extant editor in the Lisp-family-as-extension-language posture; Geiser is the standard Scheme-in-Emacs mode and has had two decades to settle; the Lisp-family programmer who uses Emacs already has the muscle memory we want to inherit.
Geiser day one
Geiser provides the standard experience: live REPL with autodoc, jump-to-definition, macro expansion in a side buffer, namespace-aware completion, error overlays, paredit integration. Lacuna ships a Geiser backend bundled with the runtime. M-x run-geiser RET lacuna RET brings up a live REPL connected to a local Lacuna runtime; C-c C-e evaluates the form at point; C-c C-z switches to the REPL buffer; M-. jumps to the definition under point. An Emacs user who already writes Guile, Racket, or Chicken at a Geiser REPL gets Lacuna Scheme on the same workflow.
SLY-compatible REPL hooks
SLY is the modern SLIME — the Common Lisp interactive environment. SLY's REPL protocol is well-documented; the Lacuna LSP server speaks a compatible subset, which means SLY users who want to extend their environment to Scheme can do so through familiar mechanisms.
LSP for the rest of the world
The Lacuna LSP server is written in Rust and shipped in the same crate as the runtime. Completion, hover, goto-definition, find-references, rename, code-action, formatting, tier-check diagnostics. Consumed by Emacs eglot and lsp-mode, VS Code via the Lacuna extension, Helix, Zed, Sublime, anything that speaks LSP. The LSP server is the canonical way the Lacuna runtime talks to editors; the Geiser backend is a wrapper around it for the Emacs-Scheme tradition.
Structural editing
Paredit, smartparens, lispy — three Emacs minor modes for paren-balanced editing. The LSP server emits structural-edit commands (form-level wrap, splice, replace) the model can produce and consume directly. The Lacuna formatter is paredit-aware: it never produces unbalanced output mid-edit, and the editor never has to fight the formatter.
lacuna-mode and org-babel
A small lacuna-mode major mode provides indentation, font-locking, and syntactic awareness on top of scheme-mode. ob-lacuna is the org-babel back end: a #+begin_src lacuna … #+end_src block in an Org document evaluates against the same runtime the REPL talks to. Org-babel is the most underrated piece of GNU userspace; for documenting a Lacuna deployment, an Org file with interleaved prose and runnable code is the right artifact.
Emacs Lisp is family
An (emacs-eval form) bridge: a small server inside Emacs (a Lacuna ELPA package) that accepts s-expression requests over a Unix socket, evaluates them in Emacs, and returns the result. Lacuna programs that want to drive an Emacs session — pop a buffer, set a region, open a file — go through this bridge. Emacs Lisp is not Scheme, but the two languages can talk.
A simulated Geiser session
The open table
Be conservative in what you do, be liberal in what you accept from others.— Jon Postel, RFC 760, January 1980, §3.2.
The framework is open. The dialect specification is published under CC BY-SA 4.0. The runtime is open source under GPLv3+. The standard library is LGPLv3+. Anyone can implement another runtime. The training packs that bring language models up to working competence in the dialect are published on model registry under a permissive license. The safety guarantees live in the runtime's capability model, not in any model. The language survives if Lacuna Labs does not.
The point is structural. A language whose only fluent typist is a single proprietary model is a sharecropping arrangement. We are not building one. One fluent typist exists today; the publication of the dialect specification and the shim-pack mechanism means that any other model family — cloud assist provider's deep reasoning, cloud assist's GPT, Google's cloud assist, the open base, Meta's the open base, the local the open base and reasoning lines — can be brought up to working fluency through documented procedure.
The shim pack
A shim pack has four parts.
The system prompt — paragraphs naming the dialect, the environment, the tier ladder, the worker spine, the refusal protocol. Roughly two thousand words. Sufficient for a model with strong baseline coding ability to produce competent Lacuna Scheme zero-shot on simple tasks.
The example library — 200 to 500 canonical examples covering the shared core, the worker spine, the tier-bound primitives, the refusal protocol, the cross-dialect bridge. Each example is a complete unit: prompt, expected response, brief commentary. The examples double as the documentation a human contributor reads first.
The evaluation harness — a test suite that scores models 0 to 100 with per-category breakdowns: syntactic correctness, semantic correctness, refusal recognition, tier-citation accuracy, cross-dialect transport, hygienic-macro use. Reproducible. Published. Updated with each release.
The documentation excerpt — the relevant chapters of this book formatted for inclusion in the model's context. Approximately ten thousand words. Tuned to fit comfortably in a hundred-thousand-token context window with room for the working session.
All four published under CC BY-SA 4.0. An organization that uses a shim pack owes the same license back on any derivative. The structural property we want is that the language gets better in proportion to the number of organizations using it, not in proportion to the number paying us.
The model is the variable; the language is the constant
The safety guarantees Lacuna Scheme makes are not "we have trained the model to refuse bad things." They are in the runtime's capability tiers and in the security companion's review. A model that does not refuse will be refused by the binding. A model that pretends to have a grant it does not have will be caught at the tier check. The model is the typist; the runtime is the steward. If the safety story were "trust the model," we would have to keep the model proprietary. Because the safety story is "trust the runtime," we can publish the language and let any model write it.
The licensing posture, in one paragraph
The Lacuna runtime is open source from day one — GPLv3+ for the runtime, LGPLv3+ for the standard library, CC BY-SA 4.0 for the documentation. The language model artifacts trained alongside this work — the dialect-fluent authoring model, the security-companion model — are held proprietary for approximately one year of focused documentation before being released under MIT. The principle: runtimes must be open from day one because operators have to be able to audit what they run, but model artifacts benefit from a year of documenting safe usage and failure modes before becoming widely free. After that year, the model artifacts join the runtime under permissive licensing. The asymmetry is deliberate and time-bounded.
The cultural pitch
This is the GNU posture. The IETF posture. Specs over implementations; protocols over products. The strongest analogy is TCP/IP — a protocol stack whose value compounded because no single party could capture it, and whose operators benefitted in proportion to the size of the inter-network. We are not pretending Lacuna Scheme will be TCP/IP. We are saying we want to be in that tradition. The work is a contribution to a commons, not the foundation of a moat.
Proposed implementation
A schedule is an act of belief.— Frederick P. Brooks, The Mythical Man-Month, Addison-Wesley, 1975.
This chapter is the concrete implementation proposal. The operator will edit it. The other chapters are structured for the long-term reader; this one is structured for the engineer making decisions about how to build the thing in the next eighteen months. The subsections are short and replaceable. Treat the prose as a working draft.
Runtime: Rust
The runtime is implemented in Rust. The choice is for memory safety in the evaluator, mature async I/O, capability-bound standard libraries, and a single static binary in the tens of megabytes. The candidate crate set for v0.1:
| crate | purpose | note |
|---|---|---|
libc | raw syscalls where needed | direct, no abstraction |
nix | Unix syscall wrappers | fork, exec, signals, pipes |
cap-std | capability-bound filesystem and net | the load-bearing safety crate |
tokio | async runtime | for the supervisor and network I/O |
rustyline | REPL line editor | history, completion |
pyo3 | Python interop | the cousin bridge |
tower-lsp | LSP server framework | chosen over hand-rolled |
serde + rmp-serde | JSON and MessagePack | journal format |
The crate set is the proposal, not the commitment. The architect reviews and substitutes as warranted.
Dialect baseline: R7RS-small
The dialect is R7RS-small (Shinn, Cowan, Gleckler, ratified 2013) with namespaced extensions under (lacuna …). A pure-computation library written against the baseline runs on Chibi, Gauche, Chicken, or Guile with a one-line compatibility shim. The Lacuna-specific environment bindings are the delta.
Primitive surface
The technical specification at research/lacuna-scheme/LACUNA-SCHEME-CANONICAL.md enumerates the surface. The high-level counts:
- Shared core: 102 primitives. Reader, evaluator, list operations, numeric operations, symbol/string operations, structured records, the worker-spine descriptors as data. This surface is shared with the sibling browser-environment dialect; one Rust crate (
lacuna-scheme-core) implements it; both environments depend on it. - Lacuna environment bindings: 141 symbols, across the five tiers. Filesystem (cap-std-backed), network (tokio + reqwest), process (nix), journal (the structured event log), supervisor (lease/heartbeat/restart), Python interop (pyo3). The binding count is the audit-relevant surface area; we resist its growth.
The worker spine
The worker spine is a five-state Mealy automaton with seven canonical descriptors. The states are PRE, GUARD, ACT, RESULT, ON-ERROR. The descriptors are next, done, wait, after, act, escalate, refuse. The supervisor adds a sixth state — awaiting-human — that parks the worker while a petition is reviewed by the operator. The driver loop is approximately five hundred lines of Rust (the spine itself) plus the per-worker Scheme bodies. We own the spine code; we do not adopt an external state-machine library (no XState, no statecharts) because the discipline of a small descriptor set is itself the safety property.
Capability tiers
Five tiers as binding-time annotations: READ_LOCAL, WRITE_LOCAL, READ_NET, EXEC_USER, EXEC_ROOT. Each primitive in the Lacuna environment is annotated with its required tier. The supervisor grants a worker a maximum tier at spawn time. The binding's first action is to assert the caller's grant covers the required tier. A failed assertion returns (refuse tier clause adjacent); it does not raise an exception. Tiers are values in the language; they can be inspected, compared, and logged.
Tooling, one paragraph each
Package manager (lacs). Cargo-shaped. Lockfile-driven, semver, content-addressed, monthly frozen distributions. Capability annotations on each package; an audit pass at install time. Single registry; signed releases; offline-mirror friendly.
REPL. rustyline-based. History persists. Tab completion against the loaded environment. Inline help via :help. Hot-reload via :load file.scm. Multi-line input via configurable bracket-balancing.
LSP server. Rust-implemented, tower-lsp-based. Completion, hover, goto-definition, find-references, rename, code-action, formatting, tier-check diagnostics. Streamed; cancellable.
Formatter. Opinionated, paredit-aware, one canonical style. Not configurable. Round-trip stable: format(format(x)) == format(x).
Linter. Catches unused bindings, shadowing, non-tail recursion in tail-expecting contexts, tier inconsistencies between declared max-tier and primitives invoked.
Debugger. Scheme-level breakpoints; REPL at any frame; step descriptor-by-descriptor. Talks to gdb for the Rust side through a small extension.
Profiler. Sampling. Rust and Scheme frames in the same output. Flamegraph export.
Documentation generator. Scribble-inspired, small Rust binary. HTML, PDF, man pages. The book you are reading is the prototype.
Jupyter kernel. IPyKernel-compatible. Cells tagged by dialect share a runtime state.
VS Code extension. Thin shell over the LSP. Same for Helix and Zed.
Training-pack generator. Walks the example library and the documentation, emits a corpus in JSONL.
Shim builder. Takes a target model family, emits the system-prompt + example-library + eval-harness + docs-excerpt bundle.
Observability bridge. Subscribes to the journal and ships events to OpenTelemetry, Honeycomb, Grafana.
SBOM tool. Emits SPDX or CycloneDX bills of materials.
Repository
| name | fit | typing | verdict |
|---|---|---|---|
lacs | high (cf. bash, zsh, ksh) | 4 keys | recommended |
lsx | medium | 3 keys | alternative |
glsh | strong | awkward to speak | declined |
Working choice: lacs, pronounced "lax." Binary lacs; REPL prompt lacs>; crate lacs; file extensions .scm for portable R7RS and .lacs for files that import the Lacuna environment. GitHub organization Lacuna-Labs, repository lacs.
Branches and releases
Default branch main. Semver tags. The 0.x series is pre-stable. The 1.0 release commits to backward compatibility within 1.x. Monthly cadence pre-stable; quarterly after 1.0. RFCs in lacuna-labs/lacs-rfcs as numbered Markdown documents. Contributor agreement: DCO 1.1, not a CLA.
Roadmap
| version | target | deliverables |
|---|---|---|
0.1 | Q3 2026 | REPL, runtime, R7RS-small core, worker spine reference, this book |
0.2 | Q4 2026 | package manager, formatter, linter, example library |
0.3 | Q1 2027 | LSP server, Geiser backend, VS Code extension, public repository |
0.5 | Q2 2027 | debugger, sampling profiler, doc generator, Jupyter kernel, Guix channel |
0.7 | Q3 2027 | security companion (named at design time), hardened tier checker, SBOM tool, observability bridge |
0.9 | Q4 2027 | public beta; formal-verification scaffolding |
1.0 | Q2 2028 | stable runtime; backward-compat commitment within 1.x; one verified-evaluator proof |
Open questions to decide before v0.1
- Continuations. Leaning: delimited continuations only; full
call/ccdeferred. - syntax-case or syntax-rules? Leaning:
syntax-rulesdefault;syntax-caseopt-in by v0.4. - Numeric tower. Leaning: Float64 for v0.1; bigints by v0.3; rationals and complex deferred.
- Concurrency primitive. Leaning: channels first; actors as a library.
- Bootstrappability target. Leaning: Rust runtime; core spec written with Mes-compatibility in mind. Not blocking.
- SRFI numbers or our own naming? Leaning: both. Adopt finalized SRFIs; namespace dialect extensions under
(lacuna …). - How do we evaluate model competence? Leaning: publish a Lacuna-specific benchmark suite (~200 tasks); report scores each release.
- The package manager's name.
lacsis the working choice. The alternativeslsx,lpm,lpxare noted. - Shim-pack governance. Monorepo at
github.com/Lacuna-Labs/lacuna-scheme-shimsversus per-model repos. Leaning: monorepo, easier to keep aligned. - The training-pack license. Leaning: CC BY-SA 4.0 to preserve lineage.
- The first public surface. Leaning: open the repository at v0.3, when external contributors can do useful work without studio guidance.
This list will move. We commit to publishing the updated list in each release's documentation.
Cross-references
The full technical specification lives at ~/code/lacuna-labs/research/lacuna-scheme/LACUNA-SCHEME-CANONICAL.md. It is the source of truth for the primitive surface, the worker-spine state machine, the structured event shape, and the supervisor protocol. This chapter summarises; that document specifies.
A closing
Everything should be made as simple as possible, but not simpler.— widely attributed to Einstein; the closest verified source is the 1933 Spencer Lecture at Oxford, "On the Method of Theoretical Physics."
Friend, the last slate. Below is a small meta-circular evaluator — m-eval, m-apply, lookup — written in the language it evaluates. SICP Chapter 4 is the canonical version; this one is shorter. The program it evaluates computes the factorial of 5 by way of a Y combinator, inside an interpreter written in Scheme, running inside an interpreter (Scheme engine) written in JavaScript, running inside a browser. Three layers of evaluation. They all agree on the answer.
One hundred and twenty. The book ends here.
Bibliography
Sources verified against live URLs or Wayback snapshots as of June 4, 2026. Where a URL did not resolve at verification time, the citation either appears in paraphrase or has been dropped.
Primary papers and reports
- McCarthy, John. "Programs with Common Sense." Mechanisation of Thought Processes symposium, Teddington, November 1958. Reprinted in Minsky, ed., Semantic Information Processing, MIT Press, 1968. www-formal.stanford.edu/jmc/mcc59.html. The founding statement of the Advice Taker.
- McCarthy, John. "Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I." Communications of the ACM 3:4 (April 1960), 184–195. DOI 10.1145/367177.367199. Lisp as a notation.
- McCarthy, John, et al. LISP 1.5 Programmer's Manual. MIT Press, 1962.
- Sussman, Gerald J., and Guy L. Steele Jr. Scheme: An Interpreter for Extended Lambda Calculus. MIT AI Lab Memo 349, December 1975. dspace.mit.edu/handle/1721.1/5794. The Scheme that became Scheme.
- Steele, Guy L. Jr. "Lambda: The Ultimate GOTO." MIT AI Lab Memo 443, 1977. Part of the Lambda Papers; index at research.scheme.org/lambda-papers.
- Hoare, C. A. R. "Communicating Sequential Processes." CACM 21:8 (August 1978), 666–677. DOI 10.1145/359576.359585. The CSP root of message-passing concurrency.
- Hewitt, Carl, Peter Bishop, and Richard Steiger. "A Universal Modular Actor Formalism for Artificial Intelligence." IJCAI 1973. ijcai.org/Proceedings/73/Papers/027B.pdf. The actor model.
- Dennis, Jack B., and Earl C. Van Horn. "Programming Semantics for Multiprogrammed Computations." CACM 9:3 (March 1966), 143–155. The older capability-theory primary the usual chain skips.
- Lampson, Butler W. "Protection." 5th Princeton Conference on Information Sciences and Systems, 1971; reprinted ACM SIGOPS Operating Systems Review 8:1 (January 1974), 18–24. DOI 10.1145/775265.775268.
- Saltzer, Jerome H., and Michael D. Schroeder. "The Protection of Information in Computer Systems." Proceedings of the IEEE 63:9 (September 1975), 1278–1308. The eight principles.
- Perlis, Alan J. "Epigrams on Programming." ACM SIGPLAN Notices 17:9 (September 1982), 7–13. ACM DL listing at dl.acm.org/doi/10.1145/947955.1083808. Epigrams mirrored at cs.yale.edu/homes/perlis-alan/quotes.html. The diagnosis.
- Shivers, Olin. "A Scheme Shell." MIT AI Lab TR-1635, 1994. ccs.neu.edu/home/shivers/papers/scsh.pdf.
- Kohlbecker, Eugene, Daniel P. Friedman, Matthias Felleisen, and Bruce Duba. "Hygienic Macro Expansion." LFP 1986.
- Dybvig, R. Kent, Robert Hieb, and Carl Bruggeman. "Syntactic Abstraction in Scheme." LASC 5:4 (1993), 295–326.
- Clinger, William. "Proper Tail Recursion and Space Efficiency." PLDI 1998.
- Tobin-Hochstadt, Sam, et al. "Languages as Libraries." PLDI 2011.
- Armstrong, Joe. Making Reliable Distributed Systems in the Presence of Software Errors. PhD thesis, Royal Institute of Technology, Stockholm, defended November 20, 2003. erlang.org/download/armstrong_thesis_2003.pdf. OTP, "let it crash."
- Simmons, Reid. "Structured Control for Autonomous Robots." IEEE Transactions on Robotics and Automation 10:1 (February 1994), 34–43. TCA and the layered-control lineage.
- Leroy, Xavier. "Formal Verification of a Realistic Compiler." CACM 52:7 (July 2009), 107–115. CompCert as the existence proof.
- Shinn, Cowan, Gleckler, et al. Revised7 Report on the Algorithmic Language Scheme: Small Edition, 2013. small.r7rs.org.
Books
- Abelson, Harold, and Gerald J. Sussman. Structure and Interpretation of Computer Programs. MIT Press, 1985. The canonical Scheme text; the meta-circular evaluator lives in Chapter 4.
- Steele, Guy L. Jr. Common Lisp the Language. Digital Press, 1st ed. 1984; 2nd ed. 1990.
- Norvig, Peter. Paradigms of Artificial Intelligence Programming. Morgan Kaufmann, 1992.
- Felleisen, Findler, Flatt, Krishnamurthi. How to Design Programs. MIT Press, 1st ed. 2001; 2nd ed. 2018.
- Brooks, Frederick P. The Mythical Man-Month. Addison-Wesley, 1975.
- Russell, Stuart, and Peter Norvig. Artificial Intelligence: A Modern Approach. 4th ed., Pearson, 2020. Chapter 26 surveys the structured-control literature.
Talks
- Dahl, Ryan. "Node.js," JSConf EU, Berlin, November 8, 2009. youtube.com/watch?v=ztspvPYybIY.
- Hickey, Rich. "Simple Made Easy," Strange Loop, September 19, 2011. infoq.com/presentations/Simple-Made-Easy.
- Steele, Guy L. Jr. "Growing a Language," OOPSLA 1998 keynote.
- Hoare, C. A. R. "The Emperor's Old Clothes," 1980 ACM Turing Award lecture. CACM 24:2 (February 1981), 75–83.
Online sources
- Norvig, Peter. Hacker News comment on Python and pseudocode, October 2010. news.ycombinator.com/item?id=1803815.
- Wingo, Andy. "Lessons Learned from Guile, the Ancient & Spry," February 7, 2020. wingolog.org/archives/2020/02/07/lessons-learned-from-guile-the-ancient-spry.
- Beane, Zach. Quicklisp. quicklisp.org/beta. Naggum archive at xach.com/naggum/articles.
- Symbolics article, Wikipedia. en.wikipedia.org/wiki/Symbolics. Founded April 1980, defunct May 1996.
- Node.js article, Wikipedia. en.wikipedia.org/wiki/Node.js.
- npm article, Wikipedia. en.wikipedia.org/wiki/Npm.
- Software Preservation Group, LISP collection. softwarepreservation.computerhistory.org/LISP.
Tools and runtimes
- Scheme engine, by Yutaka Hara. MIT license. github.com/Scheme engine/Scheme engine; Scheme engine.org.
- Steel, by Matt Paras. github.com/mattwparas/steel.
- cap-std (Bytecode Alliance). github.com/bytecodealliance/cap-std.
- Geiser. nongnu.org/geiser.
- GNU Mes, by Jan Nieuwenhuizen. gnu.org/software/mes; bootstrappable.org.
- PyO3. pyo3.rs.
- Polars. pola.rs.
- the base model (Apache 2.0). huggingface.co/the open base/the base model.
Glossary
- AST
- Abstract syntax tree. The structured representation of source code after parsing. In a homoiconic language the AST and the surface source are the same kind of value.
- Capability tier
- One of the five symbols
READ_LOCAL,WRITE_LOCAL,READ_NET,EXEC_USER,EXEC_ROOTthe Lacuna runtime uses to gate primitive calls. - Delimited continuation
- A first-class value representing a slice of the rest of a computation, bounded by a prompt. The
shift/resetform is one canonical interface. - Descriptor
- A tagged list a worker state procedure returns. One of seven canonical variants. The closed set is itself a safety property.
- Homoiconicity
- The property of a language in which the surface syntax of a program is also a value in the language's value space. Code is data; data is code.
- Hygienic macro
- Macro expansion in which identifiers introduced by the macro cannot collide with identifiers in the calling code. R7RS-small's
syntax-rulesis the standard hygienic mechanism. - LSP
- Language Server Protocol. The Microsoft-originated wire protocol by which editors talk to language servers for completion, hover, diagnostics, and so on. The Lacuna runtime ships an LSP server.
- Mealy automaton
- A finite-state machine whose outputs are functions of both the current state and the current input. The worker spine is a Mealy machine; the Cortex event it emits depends on the action's result, not on the state alone.
- Meta-circular evaluator
- An interpreter for a language written in the language being interpreted. The closing slate of this book is one. SICP Chapter 4 is the long form.
- OTP
- Open Telecom Platform. Erlang's library of supervisor and behavior patterns. The Lacuna supervisor protocol is OTP-shaped.
- R7RS-small
- The Revised7 Report on the Algorithmic Language Scheme, Small Edition (2013), edited by Shinn, Cowan, and Gleckler. The small Scheme of the post-schism era. Lacuna Scheme's baseline.
- REPL
- Read-Eval-Print Loop. The interactive shell of a Lisp-family language. State persists between expressions.
- S-expression
- A symbolic expression. The atomic-or-list value type at the heart of every Lisp.
- Shim pack
- A small set of artifacts — system prompt, example library, evaluation harness, documentation excerpt — that brings an arbitrary LLM up to working competence on the Lacuna dialect.
- Source-tag
- A record carried by every structured event: origin, actor, tier, host, provenance.
- Structured event
- A value of fixed shape (
kind payload at source-tag correlation-id) workers emit and surfaces consume. - Supervision tree
- A tree of supervisor processes that own worker processes. Borrowed from OTP.
- Tail call
- A function call in the tail position of another function. R7RS-small mandates that tail calls consume no stack.
Acknowledgements
- John McCarthy — for Lisp itself, and for writing the dream down in 1958.
- Gerald Sussman — for Scheme, for SICP, for the Lambda Papers.
- Guy Steele — for the same, and for thirty years of careful language design afterward.
- Olin Shivers — for scsh, and for the careful argument that the shell should be a programming language.
- Joe Armstrong — for Erlang, for OTP, and for the discipline of "let it crash."
- Robert Virding — for the same, and for keeping the BEAM moving.
- Andy Wingo — for Guile, for Hoot, and for the honest retrospectives.
- Rich Hickey — for Clojure, and for showing a Lisp could be marketed in operational terms.
- Zach Beane — for Quicklisp, which proved packaging is a Lisp's load-bearing problem; and for the Naggum archive.
- Peter Norvig — for the AI textbooks and the honesty about language choices.
- Kent Pitman — for the Common Lisp condition system.
- Erik Naggum — for the comp.lang.lisp archives, both as warning and as resource.
- Dennis Ritchie — for C and for Unix's small-tools discipline.
- Richard Stallman — for the GNU project and Emacs.
- Ryan Dahl — for Node, and for the structural argument we are echoing.
- Carl Hewitt — for the actor model.
- Tony Hoare — for CSP, and for the Turing lecture.
- Butler Lampson — for the protection literature.
- Jerome Saltzer and Michael Schroeder — for the eight principles.
- Jack Dennis and Earl Van Horn — for the 1966 paper that everyone should have cited.
Colophon
This document was set in Inter for the body, Geist Mono for code and the wordmark, and Source Serif Pro (italic) for the chapter epigraphs.
The embedded Scheme interpreter is Scheme engine 0.8.3, an R6/R7 Scheme written in JavaScript by Yutaka Hara, MIT-licensed, vendored locally at ./Scheme engine-min.js. Source: Scheme engine.org.
Composition followed the Lacuna Labs visual canon: dark canvas, grayscale structure, hard 90° corners, one accent (terminal green #7eea8e) reserved for the REPL caret, Scheme keywords, chapter numerals, and the active-link underline.
The book carries a CC BY-SA 4.0 license. Canonical text at lacuna.ai/docs/scheme/.
Reset document state — clears every define you have run and reloads the defaults.
Version v0.1, drafted June 4, 2026. Lacuna Labs imprint.