An Agent in 100 Lines of Lisp, or How my Prof was Right - Just 25 Years Early | The Beach
Around 2000 I took an AI course at the University of Guelph. I don’t think I learned too much. We didn’t talk about neural networks, as far as I can remember. My end of term project was, I think, a pathfinding algorithm wearing an AI costume. There were certainly no discussions of transformers. No CUDA. No PyTorch. None of that existed.
But what I remember doing a lot of was coding in Lisp - a lot of Lisp in the dark University of Guelph CIS lab.
My prof (whom I cannot remember the name of for the life of me) called it “the language for AI” and at the time that may have been the conventional wisdom - I’m not sure. But I actually enjoyed writing Lisp code. To me, it was a kind of art building these elegant recursive functions. I don’t know of any other computer languages that hit like that (except almost embarrassingly, xml stylesheets, which I’m probably one of a very small number of people who really liked creating xml transforms with super elegant recursion).
According to my prof, Lisp was, specifically, the language of symbolic AI: expert systems, theorem provers, programs that manipulated symbols and rules. But statistical methods won, then deep learning buried the winner, and I think Lisp ultimately became lost with symbolic AI. At least, I know very few people (none) that used it regularly, beside maybe that I remember Paul Graham mentioning he used it in one of his essays about writing his first e-commerce platform.
Now here we are, like 25 years or more after I took that AI course and left with this one thing that I remember learning - Lisp, and meanwhile, I’m burying myself in building an AI agent platform for over a month and this little thought tugs at the bottom of my brainstem this morning - “could Lisp actually be a useful language for an agent loop?” And then I find myself distracted from what I should be doing and instead working with Claude just to see…
An agent is a recursive function#
Don’t let the power of Claude Code, OpenClaw or any other AI agent tooling fool you - strip away the frameworks and an agent loop is incredibly simple. You have a list of messages. You send it to a model. Either the model answers in words, or it asks to use a tool. If it asks, you run the tool, append the results, and do it again.
Maybe some agents implement that as a while loop with state. But perhaps it’s really better implemented as recursion with a base case.
I’m not going to get into Lisp syntax at all, and if you’ve never coded with Lisp, this will seem pretty strange, but here’s an agent loop in Lisp:
(defun agent-loop (messages)<br>(let* ((message (ref (call-model messages) "choices" 0 "message"))<br>(tool-calls (gethash "tool_calls" message)))<br>(if (and tool-calls (plusp (length tool-calls)))<br>(agent-loop (append messages<br>(list message)<br>(map 'list #'execute tool-calls)))<br>(append messages (list message)))))
That’s the whole agent, no kidding, just 8 lines of Common Lisp. Base case: the model answers, returns the history. Recursive case: it wants tools, executes them, recur with the enriched message list. No framework. No state machine. The agent’s state is just the argument being folded through the recursion.
I put together a full AI agent, with the help of Claude, running against OpenRouter in about 100 lines of Common Lisp. SBCL, two libraries (dexador for HTTP, shasht for JSON), and nothing else.
The only tool is eval#
When you build an agent you normally start bolting on tools - Agent Foundry has web search and crawling, tables and file tools, python execution tooling, etc. In fact, most of most agents are a tool catalog.
Lisp lets you cheat. Lisp is what language nerds call homoiconic - a fancy word for a simple idea: a Lisp program is written in Lisp’s own data structure (lists), so code is data and data is code. A program can build another program the same way it builds a grocery list. Which means instead of building tools, you hand the model the language itself:
(defun lisp-eval (form-string)<br>(handler-case<br>(format nil "~s" (eval (read-from-string form-string)))<br>(error (e) (format nil "ERROR: ~a" e))))
One tool. The model writes a Common Lisp form as a string. The agent reads it, evals it, and sends back whatever printed. Ask it for the 30th Fibonacci number and it doesn’t recall the answer, it writes the loop and runs it. Here’s the actual transcript:
* (agent:run "What is the 30th Fibonacci number? Compute it, don't recall it.")<br>⤷ (defun fibonacci (n)<br>(if ( FIBONACCI<br>⤷ (fibonacci 30) => 832040<br>The 30th Fibonacci number is 832040.<br>NIL<br>I did not tell it to use recursion, but in this blog post about recursive agent loops, the model reached for textbook doubly-recursive Fibonacci all on its own. It defined a function into the live image with its first eval and called it with...