The Agent Loop in 8 Lines

Jamie Beach, building an agent platform called Agent Foundry, wondered if Lisp could still serve as the substrate for AI agents. He coded an agent loop in Common Lisp that fits in 8 lines:

(defun agent-loop (messages)
  (let* ((message (ref (call-model messages) "choices" 0 "message"))
         (tool-calls (gethash "tool_calls" message)))
    (if (and tool-calls (plusp (length tool-calls)))
        (agent-loop (append messages
                            (list message)
                            (map 'list #'execute tool-calls)))
        (append messages (list message)))))

The loop is recursive. Base case: the model responds in words, return the history. Recursive case: model requests tool calls, execute them, append results, recurse. No framework, no state machine. The entire agent, including HTTP client (dexador) and JSON parser (shasht), clocks in at ~100 lines of Common Lisp running on SBCL.

One Tool to Rule Them All

Instead of building a catalog of tools, Beach gave the agent a single eval tool:

(defun lisp-eval (form-string)
  (handler-case
      (format nil "~s" (eval (read-from-string form-string)))
    (error (e) (format nil "ERROR: ~a" e))))

The model writes any Common Lisp form as a string, the agent reads and evaluates it, and returns the printed result. This is homoiconicity in action: Lisp code is Lisp data, so the model can generate programs the same way it generates text.

When asked "What is the 30th Fibonacci number? Compute it, don't recall it," the agent responded by defining a recursive Fibonacci function and calling it:

⤷ (defun fibonacci (n)
    (if (<= n 2) 1
        (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) => FIBONACCI
⤷ (fibonacci 30) => 832040

The model reached for recursion on its own, writing a textbook doubly-recursive Fibonacci.

The Agent Built Its Own Web Search

Beach pasted a Brave Search API key into the conversation. The agent, using its eval tool, defined a brave-search function into the running Lisp image:

(defun brave-search (api-key query &key (count 10))
  "Search using Brave Search API"
  (let* ((encoded-query (quri:url-encode query))
         (url (format nil "https://api.search.brave.com/res/v1/web/search?q=~a&count=~d"
                      encoded-query count))
         (response (dexador:get url
                     :headers `(("Accept" . "application/json")
                                ("X-Subscription-Token" . ,api-key)))))
    (shasht:read-json response)))

It wrote the HTTP call, parsed JSON, and started answering questions with live web results—without any pre-built search tool. The model decided it needed web search, wrote the function, and evaled it into existence. This inverts the typical agent design where the tool catalog is fixed at compile time. Here, capability is determined at runtime by conversation.

Skills as Memories

Memory is implemented in 20 lines: serialize the message list (already a list of hash tables) to a JSON file, read it back on restart.

(defun remember (messages)
  (with-open-file (out *memory-file* :direction :output :if-exists :supersede)
    (shasht:write-json (coerce messages 'vector) out))
  messages)

(defun recall ()
  (if (probe-file *memory-file*)
      (coerce (with-open-file (in *memory-file*) (shasht:read-json in)) 'list)
      (list *system-message*)))

No schema, no migrations, no vector database. The serialization format is the runtime format. But there's a twist: functions defined via defun die when the process exits. On restart, the agent re-reads its own history and evals the function definitions back into existence. Skills are stored as memories of having built them, rehydrated on demand. This is the 2026 version of what made Lisp "the language for AI"—programs that manipulate programs, now with an LLM doing the reasoning.

Practical Implications

Beach runs this inside a Docker container because eval on user input is a security nightmare. The full source, including Dockerfile, is on GitHub.

This experiment suggests that the industry's current approach—fixing tool catalogs at design time, formalized in MCP—may be overly rigid. Code interpreters were the first step; this collapses the remaining distance. The agent's skills are stored in its own transcript, and it re-evaluates them every time it needs them.

For now, this is a toy. But it points toward a future where agent capabilities are fluid, emergent from conversation rather than predefined contracts. The old symbolic AI dream of programs that write programs is alive—it just took a language model to make it practical.