16 Aug 2026
Planet Lisp
Joe Marshall: SDK for jrm-code-project.com
A few of you have noticed the OpenAPI spec floating around the site lately. Rather than watching everyone write the same HTTP boilerplate from scratch to talk to the server, I went ahead and bundled up a set of official client bindings.
If you want to programmatically hit the pastebin or mess with the other endpoints, the jrm-code-client repository is live.
Right now, it includes complete SDKs for:
- Common Lisp (obviously)
- Emacs Lisp (naturally)
- Python (seriously?)
- Go (ugh)
They all handle the JWT authentication handshake natively and deserialize the JSON responses into proper language-specific structs/objects. You can stop raw-dogging it with curl (unless that's your thing).
Source is up on GitHub: jrm-code-project/jrm-code-client
Play nice with the rate limits.
16 Aug 2026 12:29pm GMT
15 Aug 2026
Planet Lisp
Joe Marshall: OpenAPI Access to jrm-code-project.com
It's a web site! It's a service! jrm-code-project.com has an OpenAPI specification and you can use it to generate client code in your favorite programming language (which is Lisp, right?). The OpenAPI specification is available at https://jrm-code-project.com/openapi.yaml. There are the following endpoints:
GET /api/v1/ping- Returns a simple "pong" response to test connectivity and verify your authentication tier.POST /api/v1/echo- Accepts a JSON payload and returns the same payload in the response. For testing your client.POST /api/v1/auth/token- Exchange your long-lived programmatic API key for a short-lived JWT Bearer token to authenticate secure requests.GET /api/v1/pastes- Retrieve a paste's content by its ID (Publicly readable, no auth required).POST /api/v1/pastes- Create a new code snippet paste (Requires JWT).DELETE /api/v1/pastes- Delete a specific paste you own (Requires JWT).GET /api/v1/user/pastes- List all non-expired pastes associated with your authenticated account (Requires JWT).POST /api/v1/chef- Programmatic access to The Chef. Submit your raw Lisp code to be mercilessly roasted. (Requires JWT and ax-goog-api-keyheader with your Gemini API key).
I invite you to explore the API and see what you can build with it. If you have any questions or feedback, please don't hesitate to reach out to me at eval.apply@gmail.com.
15 Aug 2026 9:50pm GMT
Scott L. Burson: Teaser: CL-Torch!
I have been working for the past few months on CL-Torch, a Common Lisp equivalent of PyTorch. Like PyTorch, CL-Torch calls LibTorch - the C++ library that does most of the numerics - via FFI.
It's nowhere near done, but I need to set it aside for a few months to work on something else, so I thought I would publish what I have and let people play with it.
Claw
Claw (Common Lisp Auto-Wrap; no relation to OpenClaw, which it preceded by years) is Pavel Korolev's FFI wrapper generator; it includes IFFI, his Intricate Foreign Function Interface, which deals with C++ overloading. This is what I have used to create CL-Torch.
I had initially looked at SWIG, which had had Common Lisp support until its 4.0 release, but on closer examination it didn't look like a good choice; the Common Lisp generation apparently never worked well. I then tried C2FFI, and in retrospect, maybe I could have gotten it to work, but it doesn't have any explicit C++ support; I would have wound up with C++ "mangled names" (encoded function names including namespace and parameter type information) in the CL-Torch sources. Poking around a little more, I found these three blog posts by Pavel introducing Claw. I also noticed that Pavel already had a Claw-Torch project; although he hadn't gotten very far on it and it was years out of date, it was still the best starting point I had found.
But I didn't realize what I was getting into. Pavel describes Claw as "BETA quality", emphasizing that it isn't ready for general use, but actually I think even this description is too generous; in the state I found it in, I would describe it as alpha, and early alpha at that. I spent several weeks fixing and improving it so that it could handle LibTorch, which in fairness, is pretty much a torture test for an FFI generator - it uses features of C++ I didn't even know existed. One of the most problematic was constructor inheritance. Did you know constructors could be inherited? I didn't either, but the feature went in in C++11. Anyway, LibClang, which Claw uses to analyse the C++ code it's wrapping, doesn't expose inherited constructors in a convenient way; the information is there, but you have to dig it out. So I had to learn a bunch about the internals of Claw, including libresect, the C library that interfaces directly with LibClang. This knowledge eventually came in quite handy, though, as I made more fixes and changes to Claw, ultimately dropping 14 PRs on Pavel. - So far, he hasn't merged any of them, and I don't know whether he's going to, so for CL-Torch, if you want to regenerate the FFI bindings, you'll need to use my forks of Claw and its subprojects cl-resect and libresect.
One significant improvement I made to Claw was to add exception handling. Exceptions thrown by LibTorch code are caught and automatically translated to Lisp errors.
At the time Pavel wrote Claw, passing structs by value required libffi, which, he noted in a blog post, is quite slow. So Claw passes all structs by pointer. I see that efficient passing of structs by value has been recently added to SBCL, but I don't think it's worth modifying Claw to use it, as that would change how the generated wrappers have to be called, and thus wouldn't be portable.
What might be worth doing, eventually, is making IFFI allocate temporary objects on the stack; it currently doesn't. But for CL-Torch, the benefit is almost certainly going to be undetectable; LibTorch calls, in normal use, spend the vast majority of their time doing tensor arithmetic; allocating and freeing small objects is negligible by comparison.
Anyway, my overall impression of Claw is that, with my improvements, it works pretty well. If you have another C++ library you'd like to call from CL, I think you should give it a try. It needs quite a bit more documentation, but if you look at what I've done for CL-Torch, that will give you some clues. Beyond that, you'll have to do what I did: read the source 😸
Status
For the purpose of a project like CL-Torch, LibTorch has two major pieces. One is the tensor arithmetic library ATen (with its lower-level component C10). This library has over a thousand operations, although many of these are variants of one another; for instance, many operations have both functional and in-place versions, the latter updating one of its argument tensors rather than allocating a new one. The C++ and Python APIs for these functions are auto-generated from a description file, aten/src/ATen/native/native_functions.yaml.
I have written a generator that produces CL versions of these APIs from the YAML descriptions. It's not finished - there are cases it doesn't yet handle - but it's currently succeeding on 645 of the 1089 candidate functions, so there is a significant amount of working functionality here. (In some cases, not all features of the function are supported yet.) If you just want to do a bunch of tensor arithmetic, there may be enough here to do what you want. It's not heavily tested, but there are enough tests to reasonably assure me that the code generation is being done correctly, at least in most cases.
The second major piece of LibTorch is the high-level neural net API. Here CL-Torch is less far along, but this is also a much easier part to work on. (I think. I haven't tested any of the code I've written for this part.) So if you want to add CL-Torch code for some of this part of the API, I think you should be able to do that. (You could even try using an LLM for this - I haven't, yet.) One thing you should know, if you want to work on that, is that there are two levels within this part of LibTorch: the torch::nn::functional:: code is the slightly lower level, that implements the operations of neural-net layers but without keeping state, and in particular, without maintaining trainable parameters. I have started hand-translating these (they're mostly quite simple) in Code/torch-functional.lisp.
The higher level is the module API, which I have just barely started in Code/torch-modules.lisp. This API implements parameters and training. To actually train a network, you'll also need an optimizer; I haven't started on these.
15 Aug 2026 1:13am GMT
14 Aug 2026
Planet Lisp
Joe Marshall: Pics or it Didn't Happen
An anonymous reader said it out loud: "Alright, it's a simple website... can we see its sources though?"
I started going through the sources and parameterizing the secrets so that there weren't any hard-coded sensitive strings. It's a royal pain because the secrets then have to be injected via environment variables, which means reconfiguring the server on the host and the development environment on the local machine, and let's face it, no one is going to actually run the server, they just want to see what the vibe coded lisp looks like. So I punted and did this instead.
jrm-code-public is a copy of the website repository with the secrets redacted. IT won't run as a standalone web site without some development work. (Although I bet you could sic a high-end model on it have it massage the code into a running state.) I'm releasing it as a snopshot of the source code so you can see the kind of code that the LLM has written for the web site. As you can see, it is a little bit more complex than your standard static web site.
The Lisp code isn't bad for machine generated. There is a lot to critique, sure, but a lot is pretty good, too. I've seen worse code in production.
As usual, I put this under an MIT license, so feel free to use any or all of it in your own projects. You could even use this as the skeleton for nibe coding your own site.
14 Aug 2026 10:45am GMT
13 Aug 2026
Planet Lisp
Joe Marshall: A Web Site in Vibe Coded Common Lisp
I believe that vibe coding is the future. This is crazy because last year I was a skeptic. Last year LLMs couldn't write large Lisp programs. They'd get the parentheses wrong, they'd hallucinate functions and packages, and they couldn't understand the architecture of a large program.
This is all in the past.
A SOTA frontier LLM absoulely can write large lisp programs. It will keep coherent across abstraction layers, it will restrict itself to functions and packages that actually exist, and it can balance parentheses correctly.
I put my money where my mouth is. jrm-code-project.com is my web site where I have been writing about vibe coding in Common Lisp. The site is written in 100% Common Lisp and it is 100% vibe coded. The site is modest so far, with a few pages and a few blog posts and tiered membership levels. It isn't pretty; neither I nor my LLM is a graphic designer. As a pedagogic exercise I added a Lisp pastebin to the site. I invite people to create a free account and try it out. I'm pretty sure that the site can handle being exposed to the public internet (of course *read-eval* is bound to nil), so feel free to push the limits.
13 Aug 2026 10:34am GMT
09 Aug 2026
Planet Lisp
Joe Marshall: llambda.lisp on linux
A reader named Madhu sent me a patch for running llambda.lisp under linux. This patch uses mmap to pull the weights into the lisp address space outside the heap.
In addition, he tried to use a hugging face model that needed some default values, so he added them.
He reports that he was able to get the model to do inference on his linux box with about an hour of hacking. I have incorporated his patches and pushed the update to GitHub.
09 Aug 2026 10:33pm GMT
06 Aug 2026
Planet Lisp
Joe Marshall: Why vibe code in Lisp?
Why Target Common Lisp for Code Generation?
I've been asked twice now: if the generated code doesn't matter-if the AI is doing the heavy lifting of writing the syntax-why do I vibe code in Common Lisp?
Why not target Python, TypeScript, or Java? These are mainstream languages with massive training sets. The models can generate code in them with a high degree of statistical accuracy. So why do I choose to target a niche language like Common Lisp for code generation?
There are a lot of reasons, and they all come down to the same age-old question. Why use Lisp when you could use a more popular language? The answer is that language popularity is a poor proxy for utility and expressiveness. The Lisp community has long known this - it is why we chose Lisp in the first place. Selecting for popularity is what middle managers do to ensure that they can always find a warm body to maintain the code. It is not what elite hackers do.
- The Baseline of Expertise First, I have been programming in Common Lisp for decades. I know it intimately. Vibe coding requires a human architect to supervise the machine. When I look at the code generated by the model, I can tell in a fraction of a second whether it is any good, or if the model is hallucinating a dead-end. You cannot successfully orchestrate an AI in a language you don't deeply understand.
- Abstraction over Implementation Most modern languages force you to describe exactly how a machine should shuffle bits around. Lisp was designed as a language for expressing high-level abstractions rather than expressing tedious implementation details. When I prompt the AI, I want it generating architectural logic, not fighting with boilerplate just to manage basic state.
- Designed for the Elite Let's be honest: Lisp is a language designed by and for elite hackers, not for the masses. It doesn't hold your hand, and it doesn't pander to lowest-common-denominator programming bootcamp patterns. When you use it as a target language, you are operating in an environment built for maximum expressiveness.
- Homoiconicity and the AST This is perhaps the biggest technical advantage. Lisp is homoiconic-the code is structured as the data it manipulates. When an LLM generates Python or Java, it has to predict surface syntax: whitespace, brackets, semicolons, and rigid class structures. When an LLM generates Lisp, it is operating directly at the level of the Abstract Syntax Tree (AST). It is predicting pure structure. Removing the syntactic friction is a massive advantage for AI code generation.
- Macros as Context Compression In vibe coding, the LLM's context window is your most precious resource. Lisp's macro system allows for a highly effective form of context compression. Instead of the AI repeatedly generating verbose boilerplate, you can hide that boilerplate behind a macro. The AI learns the macro, uses it, and saves thousands of tokens, allowing you to maintain massive architectures within the model's memory constraints.
- Introspection in the REPL I do not operate the LLM in a sterile text editor. I operate it from within a Lisp REPL. This allows the LLM to introspect the program while it is under development. If we need to know the state of a specific object or function, the model can query the live environment. You are not writing dead text; you are conversing with a living system.
- Superior Error Handling When the AI writes bad code (and it will), Lisp's condition system provides superior error handling and debugging facilities. Instead of a hard crash that requires a full reboot, the error is caught, and the LLM can analyze the stack trace and debug the generated code interactively, right at the point of failure.
- No Ab Initio Restarts Using the REPL means you don't have to start your program ab initio (from the beginning) every time you want to test a change. In a compiled, mainstream language, a one-line AI fix requires a full rebuild and state reset. In Lisp, you just redefine the specific function and immediately test it in the REPL while the rest of the application's state remains perfectly intact. The iteration speed is unmatched.
You don't give an elite hacker a code monkey language. I want my AI to be an elite hacker, not simply a code monkey. If I expect my AI to work at an elite level, I should give it elite tools, not a code monkey language.
06 Aug 2026 8:36pm GMT
04 Aug 2026
Planet Lisp
Joe Marshall: RFC 6238 in Common Lisp
I wanted to implement 2FA as per RFC 6238. This is the Time-based One-Time Password (TOTP) algorithm that is used by Google Authenticator and other 2FA apps. This was originally `vibe coded`. The vibe coding got me 80% of the way there, and I made a manual pass to turn it into a more functional style.
Feel free to use this under an MIT license.
;;; -*- mode: lisp; coding: utf-8-unix; -*-
;;; RFC 6238: TOTP (Time-Based One-Time Password Algorithm) implementation in Common Lisp
;;; This implementation provides functions to generate a
;;; base32-encoded secret, create a QR code URI for authenticator
;;; apps, and verify TOTP codes based on the current time. It
;;; adheres to the specifications outlined in RFC 6238 and RFC 4226.
;;; Dependencies: cl-base32, ironclad
(in-package "TOTP")
(defun generate-secret (&optional (length 10))
(cl-base32:bytes-to-base32 (ironclad:random-data length)))
(defun generate-qr-uri (secret email &key (issuer "JRM-Code"))
(format nil "otpauth://totp/~A:~A?secret=~A&issuer=~A" issuer email secret issuer))
(defun pack-time (time-step)
"Converts an integer time-step into an 8-byte, big-endian array as required by RFC 4226 (HOTP).
Used to construct the message payload for the HMAC-SHA1 operation."
(let ((arr (make-array 8 :element-type '(unsigned-byte 8))))
(dotimes (i 8 arr)
(setf (aref arr (- 7 i)) (ldb (byte 8 (* i 8)) time-step)))))
(defun universal-time->unix-time (universal-time)
(- universal-time 2208988800))
(defun universal-time->time-step (universal-time)
(floor (universal-time->unix-time universal-time) 30))
(defun mac->hash (mac)
"Extracts a 6-digit TOTP code from a 20-byte HMAC-SHA1 result using dynamic truncation (RFC 4226).
Takes the lower 4 bits of the final byte as an offset, extracts a 31-bit slice starting at that offset,
and returns the value modulo 1,000,000 to produce the final 6-digit integer."
(let ((offset (logand (aref mac 19) #x0F)))
(mod (logand #x7FFFFFFF
(logior (ash (aref mac offset) 24)
(ash (aref mac (+ offset 1)) 16)
(ash (aref mac (+ offset 2)) 8)
(aref mac (+ offset 3))))
1000000)))
(defun mac->hash-string (mac)
(format nil "~6,'0D" (mac->hash mac)))
(defun generate-hash-string (secret-bytes time-step-bytes)
"Performs the HMAC-SHA1 cryptographic operation using the decoded secret and the packed time-step,
then dynamically truncates and formats the resulting MAC into a zero-padded 6-digit string."
(let ((hmac (ironclad:make-mac :hmac secret-bytes :sha1)))
(ironclad:update-mac hmac time-step-bytes)
(mac->hash-string (ironclad:produce-mac hmac))))
(defun verify-totp (secret user-code &key (time (get-universal-time)) (window 1))
"Verifies a user-provided 6-digit TOTP code against the base32 secret.
Defaults to the current universal time. The :window keyword determines the allowable drift in 30-second steps
(e.g., a window of 1 checks the previous, current, and next 30-second intervals).
Returns T if the code matches within the window, otherwise NIL."
(let ((secret-bytes (cl-base32:base32-to-bytes secret))
(user-string (format nil "~6,'0D" (parse-integer (string user-code) :junk-allowed t)))
(current-step (universal-time->time-step time)))
(do ((step (- current-step window) (1+ step))
(limit (+ current-step window)))
((or (string= (generate-hash-string secret-bytes (pack-time step)) user-string)
(> step limit))
(not (> step limit))))))
Get it at http://github.com/jrm-code-project/totp/
04 Aug 2026 12:14pm GMT
03 Aug 2026
Planet Lisp
Joe Marshall: Lisp-p
I needed a function that could tell whether a string was a valid Common Lisp program. In theory, you could just call read on the string and see if it throws an error, but I don't want to throw random text at read. It could contain a reader macro or something nasty. It also would intern a ton of random symbols into the current package. I wanted a function that would act mostly like the reader, but not CONS any data or intern any symbols.
So I vibe coded a function that does just that. It implements the reader algorithm as a state machine but does not actually read any data. The state machine tracks the list and string delimeters and tokenizes the string, but it discards the tokens and does not intern any symbols. It just checks that the state machine is in `top level' state at the end of the string. If it returns NIL, the string is definitely going to cause an error if you try to read it. If it returns T, it does not guarantee that the string represents a valid Common Lisp program, but rather that it is not obvious that the reader will throw an immediate error.
A curious edge case is that of an unpunctuated string. The words in the string will read as a simple sequence of symbols, which is perfectly valid.
The code is in lisp-p on GitHub. You call the function lisp-p with a string or a stream and it will return T or NIL.
03 Aug 2026 12:10am GMT
27 Jul 2026
Planet Lisp
Joe Marshall: Vibe Coding Reconsidered
A year ago, you couldn't vibe code in Lisp. Even the SOTA models had trouble balancing parentheses, and they'd hallucinate packages and symbols that didn't exist. A year makes a big difference in this field, and the latest models are capable of vibe coding moderately sized programs in syntactically correct Lisp.
I have been experimenting with vibe coding in Common Lisp and I'm hooked. It is a blast. It is like having on hand a talented undergraduate who just took a Lisp course. If you give him small enough, focused tasks, he will churn out passable code. If you give him a good chunk of legacy code, he will churn out more code in the legacy style. The models are not good enough to do a full rewrite of a large codebase, but they are good enough to handle a small library with supervision.
I find myself accepting a large amount of code with just a glance-if it passes the Lisp reader, compiles, and the tests pass, I accept it. Unlike the code of a year ago, the generated code these days is far less buggy, and the models are pretty good at debugging their own code. I'll do spot checks on the code, but I don't bother reading it line by line unless I see something odd. If the model generates code in a style I don't like, I'll ask it to rewrite the code to be more to my liking.
But frankly, you don't need to read the code at all. If there is a good test suite, the model will generate code that passes tests. If the code is functionally correct, it doesn't matter if the code is pretty. In one way, it doesn't matter if the code is easy for a human to read and maintain because we ask the model to maintain it. We treat the code as a black box and we constrain it to pass the tests. (We accept machine code largely unread.)
Failure Modes
By far the most common failure mode is the model getting the number of closing parentheses wrong. The tail end of a block of code is usually a bunch of closing parentheses, and the model will be tokenizing them in groups of 2 or 3. But the likelihood of the "))" token isn't very much different from the likelihood of the ")))" token, so the model will sometimes grab the wrong one.
Depending on the model and the agent, when it tries to recover from the ensuing read error, it will re-compute the tokens in the output. It sometimes will thrash as it tries to balance parentheses, adding and removing them from various places in the code. (Sort of like a noob Lisp programmer.) Some models are more susceptible to this than others. I have found that the solution here is to pause the agent and manually fix the parentheses when the agent starts to thrash.
Vibe Coding Workflow
I've been using Copilot CLI and Gemini CLI to vibe code in Common Lisp. I start with a blank project directory and create an .asd file that loads the packages.lisp file and the main file for the project (which can start out as a "hello world"). Basically, make a minimal project that you can load with ASDF or Quicklisp.
The models can work at moderate levels of abstraction, but they do better if there is existing code supporting the abstraction level, and this suggests a `bottom-up` approach to the problem rather than a `stratified` design. But the models are actually quite capable of starting at a moderate level of abstraction right from the get-go.
So starting with a minimal project, I boot up the model and ask it to write the first things needed for the project-some data structures, some utilities, a few tests. The very simple stuff that is easy for the model to do ab initio. Then I ask the model to write a minimal main function that will implement the basic functionality of the project-a command loop, a server, what-have-you-with stubs for everything. Once a framework is in place, the models are easily able to extend it.
The agents will get into a loop of adding code, adding tests, and running all the tests. They will debug any test failures and only consider a task to be complete when all the tests pass.
The model does not write great code, and you will accumulate technical debt if you accept it as is. But the model can write code that works and passes the tests. It is a good idea to pause during development and simply ask the model to find the technical debt in the code, enumerate it, and rank it in order of importance. Then you ask the model to address each item in turn and the model will clean up the code. After a couple of iterations of cleanup, the code will look no worse than what I've seen in many professional codebases.
There are sort of two modes that you operate in: one is to modify the existing code (e.g. refactor) without disturbing the functionality; the other is to extend the functionality without disturbing the core operation. It is important to spend enough time refactoring and cleaning up. But the model is good at generating potential refactorings, and it is not good at knowing when to call it quits. It will happily churn away at your code making it `better' and doing more and more trivial refactorings. If you give the model one particular refactoring task and tell it to do just that one, it will do a good job.
Refactoring is satisfying in a certain way, but adding features gives you more instant gratification. The models are good at adding features and extending existing code, especially if the feature shares any similarity with existing code.
For more complex features and refactorings, tell the model that you want a 'plan' for the feature or refactoring. The model will come up with a multi-step plan, broken down into a series of tasks. The tasks in the plan are generally small enough to be handled by the model itself.
The models are good enough to maintain a codebase, so once you have a project up and running, the model will generally choose file names and a directory structure that is appropriate to put in the .asd file. If you get the model started with a test suite, it will extend the tests as it extends functionality, or you can ask it to add specific tests.
I have found that building a project by vibe coding it is an extremely rapid way to prototype. The model can churn out `obvious' code much faster than I can and it frees me up to think about the higher level design issues. I can build in a weekend what would have taken me a month before.
27 Jul 2026 7:39pm GMT