메뉴
HN
Hacker News 21일 전

100줄의 Lisp로 구현하는 AI 에이전트

IMP
7/10
핵심 요약

현대의 복잡한 AI 에이전트 프레임워크를 벗어던지면, 에이전트의 본질은 단순히 LLM 모델을 호출하고 도구를 실행하는 '재귀 함수'에 불과합니다. 개발자는 오랜 역사를 가진 Lisp 언어의 동질성(Homoiconicity, 코드가 곧 데이터)을 활용하여 단 100줄의 코드로 복잡한 프레임크 없이 완벽하게 작동하는 에이전트를 구현할 수 있음을 증명합니다. 이는 에이전트 개발의 실질적인 진입 장벽이 얼마나 낮은지를 보여주는 중요한 통찰입니다.

번역된 본문

2000년경, 저는 구엘프 대학교(University of Guelph)에서 AI 강의를 수강했습니다. 제가 많은 것을 배웠다고는 생각하지 않습니다. 기억하는 한, 우리는 신경망(Neural Networks)에 대해 다루지 않았습니다. 제 학기 말 프로젝트는 아마도 AI 탈을 쓴 길 찾기 알고리즘이었을 겁니다. 트랜스포머(Transformers)에 대한 논의는 당연히 없었습니다. CUDA도, PyTorch도 없었습니다. 그런 것들은 존재하지도 않았으니까요. 하지만 제가 기억하는 것은 어두운 구엘프 대학교 CIS 실에서 Lisp로 코딩을 정말 많이 했다는 것입니다. 저는 평생 이름도 기억할 수 없는 당시 교수님께서 그것을 "AI를 위한 언어"라고 불렀고, 당시에는 그것이 상식이었을 수도 있습니다 - 확실치 않습니다. 하지만 저는 실제로 Lisp 코드를 작성하는 것을 즐겼습니다. 저에게 이 우아한 재귀 함수들을 구축하는 것은 일종의 예술이었습니다. 저는 그런 느낌을 주는 다른 컴퓨터 언어를 알지 못합니다 (아마 매우 우스꽝스럽겠지만, 초우아한 재귀로 XML 변환을 만드는 것을 정말 좋아했던 극소수의 사람 중 한 명인 제가 좋아했던 XML 스타일시트는 예외입니다).

교수님에 따르면, Lisp는 구체적으로 기호 AI(Symbolic AI)의 언어였습니다: 전문가 시스템, 정리 증명기, 기호와 규칙을 조작하는 프로그램들 말입니다. 하지만 결국 통계적 방법이 승리했고, 딥러닝이 그 승자를 묻어버렸으며, 저는 Lisp가 궁극적으로 기호 AI와 함께 잊혀졌다고 생각합니다. 적어도 폴 그레이엄(Paul Graham)이 자신의 첫 전자상거래 플랫폼을 작성하는 것에 대한 에세이 중 하나에서 사용했다고 언급한 것을 기억하는 것 외에는, 저는 그것을 정기적으로 사용하는 사람을 거의 (아예) 모릅니다.

이제 저희는 제가 그 AI 과정을 수강하고 배웠다고 기억하는 단 한 가지, Lisp를 떠난 지 25년 이상이 지났습니다. 그리고 그동안 저는 한 달 넘게 AI 에이전트 플랫폼을 구축하는 데 매몰되어 있었고, 오늘 아침 제 뇌간 밑바닥을 잡아당기는 작은 생각이 떠올랐습니다. "Lisp가 실제로 에이전트 루프에 유용한 언어가 될 수 있을까?" 그리고 저는 제가 해야 할 일에서 주의를 돌려 그저 확인해 보기 위해 Claude와 함께 작업하기 시작했습니다...

에이전트는 재귀 함수입니다

Claude Code, OpenClaw 또는 다른 AI 에이전트 도구들의 힘에 속지 마세요. 프레임워크를 걷어내면 에이전트 루프는 믿을 수 없을 정도로 단순합니다.

메시지 목록이 있습니다. 이를 모델로 보냅니다. 모델은 단어로 대답하거나 도구 사용을 요청합니다. 요청하면 도구를 실행하고, 결과를 추가한 다음, 다시 반복합니다. 일부 에이전트는 이를 상태(state)를 가진 while 루프로 구현할 수 있습니다. 하지만 아마도 기저 상태(Base case)를 가진 재귀로 구현하는 것이 실제로 더 나을 것입니다.

저는 Lisp 구문에 전혀 깊이 들어가지 않을 것입니다. 만약 여러분이 Lisp로 코딩해 본 적이 없다면 이것은 꽤 이상하게 보일 것이지만, 여기 Lisp로 작성된 에이전트 루프가 있습니다:

(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)))))

농담이 아니라 전체 에이전트는 고작 8줄의 Common Lisp입니다.

기저 상태: 모델이 답변하고 기록을 반환합니다. 재귀 상태: 도구를 원하고, 실행하고, 강화된 메시지 목록으로 재귀합니다. 프레임워크도 없습니다. 상태 머신도 없습니다. 에이전트의 상태는 재귀를 통해 접힌(Fold) 인자일 뿐입니다.

저는 Claude의 도움을 받아 OpenRouter와 연동되는 약 100줄의 완전한 Common Lisp AI 에이전트를 만들었습니다. SBCL, 두 개의 라이브러리 (HTTP용 dexador, JSON용 shasht), 그리고 그 외에는 아무것도 없습니다.

유일한 도구는 eval입니다

에이전트를 구축할 때 일반적으로 도구를 죄다 덧붙이는 것으로 시작합니다 - Agent Foundry에는 웹 검색 및 크롤링, 테이블 및 파일 도구, 파이썬 실행 도구 등이 있습니다. 사실, 대부분의 에이전트에서 대부분을 차지하는 것은 도구 카탈로그입니다.

Lisp는 이런 부분에서 속일 수 있는 방법을 제공합니다. Lisp는 언어 광들이 동질성(Homoiconic)이라고 부르는 것입니다 - 이는 단순한 아이디어를 나타내는 화려한 단어입니다. Lisp 프로그램은 Lisp 자체의 데이터 구조(리스트)로 작성되기 때문에 코드가 곧 데이터이고 데이터가 곧 코드입니다. 프로그램은 객체를 조립하는 동일한 방식으로 또 다른 프로그램을 구축할 수 있습니다.

원문 보기
원문 보기 (영어)
Around 2000 I took an AI course at the University of Guelph . I don&rsquo;t think I learned too much. We didn&rsquo;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 &ldquo;the language for AI&rdquo; and at the time that may have been the conventional wisdom - I&rsquo;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&rsquo;t know of any other computer languages that hit like that (except almost embarrassingly, xml stylesheets, which I&rsquo;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&rsquo;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 - &ldquo;could Lisp actually be a useful language for an agent loop?&rdquo; And then I find myself distracted from what I should be doing and instead working with Claude just to see&hellip; An agent is a recursive function # Don&rsquo;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&rsquo;s really better implemented as recursion with a base case. I&rsquo;m not going to get into Lisp syntax at all, and if you&rsquo;ve never coded with Lisp, this will seem pretty strange, but here&rsquo;s an agent loop in Lisp: (defun agent-loop (messages) ( let* ((message (ref (call-model messages) &#34;choices&#34; 0 &#34;message&#34; )) (tool-calls ( gethash &#34;tool_calls&#34; message))) ( if (and tool-calls ( plusp ( length tool-calls))) (agent-loop ( append messages ( list message) ( map 'list #' execute tool-calls))) ( append messages ( list message))))) That&rsquo;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&rsquo;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&rsquo;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) (handler-case ( format nil &#34;~s&#34; ( eval ( read-from-string form-string))) ( error (e) ( format nil &#34;ERROR: ~a&#34; 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&rsquo;t recall the answer, it writes the loop and runs it. Here&rsquo;s the actual transcript: * (agent:run &#34;What is the 30th Fibonacci number? Compute it, don't recall it.&#34;) ⤷ (defun fibonacci (n) (if (<= n 2) 1 (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) => FIBONACCI ⤷ (fibonacci 30) => 832040 The 30th Fibonacci number is 832040. NIL 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 its second. Keep that move in mind, it comes back later in a bigger way. This is the 2026 version of what made Lisp &ldquo;the language for AI&rdquo; in the first place. The old dream was programs that manipulate programs. We finally have that. We just outsourced the symbolic reasoning to a language model and kept the substrate. Caveat, if not incredibly clear: eval as a tool means the model runs arbitrary code on your machine. This is a toy for a sandbox. I haven&rsquo;t run this outside of a local docker container. Memory is 20 lines # Once the loop worked I wanted persistence. Conversations that survive across sessions. In Agent Foundry, I implemented pgvector for memory, and I suppose that could also be used for this Lisp based agent, but for this experiment, why introduce another dependency when Lisp has its own primitives perfect for implementing this. The messages are already a list of hash tables. Which is to say, they&rsquo;re already JSON in spirit. So memory is nothing more than writing the list down and reading it back: (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*))) The loop itself didn&rsquo;t change at all. The entry point became a pipeline: (remember (agent-loop ( append (recall) ( list new-user-message)))) Recall, recur, remember. No schema. No migrations. No store abstraction. The serialization format is the runtime format. Tell it your name today, ask tomorrow in a fresh process, it answers. It never forgets, which is also its flaw. Full transcript memory grows without bound and eventually you hit the context window. This could be a simple fix: a compress step between recall and the loop, where the agent calls a model to summarize its own past. An agent recursing on its own history. Beautiful. Then it built its own web search # The Fibonacci trick was really neat to watch - the agent writing throwaway math and running it. Then I decided to give it a little more rope. I pasted a temporary Brave Search API key into the conversation, mostly to see what would happen. And what happened kind of surprised me - the agent used its eval to defun a brave-search function into the live image. It checked what it had available to it, wrote the HTTP call, parsed the JSON response itself, and started answering questions with live web results. I never built a web search tool. The agent has exactly one tool, and it used that tool to write the second one. ⤷ (defun brave-search (api-key query &key (count 10)) &#34;Search using Brave Search API&#34; (let* ((encoded-query (quri:url-encode query)) (url (format nil &#34;https://api.search.brave.com/res/v1/web/search?q=~a&count=~d&#34; encoded-query count)) (response (dexador:get url :headers `((&#34;Accept&#34; .