@yibie: 推荐这篇让我头皮发麻的文章。一个开发者回忆起 25 年前 AI 课上的教授说"Lisp 是 AI 的语言",然后他用 100 行 Common Lisp 写了一个完整 agent——8 行递归式的 agent loop,一个唯一工具是 e…
摘要
一个开发者用100行Common Lisp构建了一个AI agent,唯一的工具是eval,模型通过递归agent loop执行代码,并通过持久化transcript恢复技能,展示了Lisp作为AI语言的独特优势。
查看缓存全文
缓存时间: 2026/07/13 07:51
推荐这篇让我头皮发麻的文章。一个开发者回忆起 25 年前 AI 课上的教授说“Lisp 是 AI 的语言“,然后他用 100 行 Common Lisp 写了一个完整 agent——8 行递归式的 agent loop,一个唯一工具是 eval(模型写 Lisp 代码,agent 当场执行),20 行的持久化记忆。最震撼的不是简洁——是那个 agent 用自己的 eval 给自己写了一个 Brave Search 函数,然后当进程死后,函数消失但 transcript 还在,下次启动时 agent 读了历史又把它 defun 回来了。作者说:“一个 agent 的能力是它对自己讲的故事。”
100 行 Lisp 的 Agent——或为什么我的教授是对的,只是早了 25 年
2000 年左右我在 Guelph 大学上了一门 AI 课。不记得学到了什么——没讲神经网络,没有 transformer,没有 CUDA。但我记得做了很多事:在黑暗的机房里写了很多 Lisp 代码。教授称之为“AI 的语言“。
25 年后,我埋头做了一个多月的 AI agent 平台。今天早上,一个念头在脑干底部拉扯我:“Lisp 能成为 agent loop 的一个有用语言吗?”
一个 agent 是一个递归函数
不要被 Claude Code、OpenClaw 或任何其他 AI agent 工具的威力骗了——剥掉框架,agent loop 极其简单。你有一个消息列表。你把它发给模型。模型要么用文本回答,要么请求使用一个工具。如果请求,你运行工具,追加结果,再来一次。
可能有些 agent 实现为带着状态的 while 循环。但也许它更适合由带基本情况的递归实现。
(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)))))
整个 agent,8 行 Common Lisp。基本情况:模型回答,返回历史。递归情况:它要工具,执行工具,用丰富的消息列表递归。不看玩笑。
没有任何框架。没有状态机。agent 的状态只是被折叠在递归中的参数。
我用 Claude 的帮助,用约 100 行 Common Lisp 组装了一个完整的 AI agent,跑在 OpenRouter 上。SBCL,两个库(dexador 用于 HTTP,shasht 用于 JSON),没有别的。
唯一的工具是 eval
当你构建 agent 时,通常开始挂载工具——网络搜索和爬虫、表格和文件工具、Python 执行工具等。事实上,大多数 agent 的大部分代码是一个工具目录。
Lisp 让你作弊。Lisp 是语言书呆子们称之为同像性的语言——一个花哨的词表达一个简单的想法:Lisp 程序是用 Lisp 自己的数据结构(列表)写的,所以代码就是数据,数据就是代码。一个程序可以以构建购物清单相同的方式构建另一个程序。
这意味着不需要构建工具——你把语言本身交给模型:
(defun lisp-eval (form-string) (handler-case (format nil “~s” (eval (read-from-string form-string))) (error (e) (format nil “ERROR: ~a” e))))
一个工具。模型把 Common Lisp 表达式作为字符串写入。agent 读取它,求值它,发回打印的任何结果。
让它找第 30 个斐波那契数——它不回忆答案,它写了一个循环然后运行它:
• (agent:run “第 30 个斐波那契数是多少?算,不要回忆。”) ⤷ (defun fibonacci (n) …) => FIBONACCI ⤷ (fibonacci 30) => 832040 第 30 个斐波那契数是 832040。
我没有告诉它用递归,但在关于递归 agent loop 的这篇博文里,模型自己伸手拿了教科书式的双递归斐波那契。它的第一个 eval 把一个函数定义到了实时镜像里,第二个 eval 调用了它。
记忆是 20 行
一旦循环工作了,我想要持久化——跨 session 存活的对话。在 Agent Foundry 里,我用 pgvector 实现了记忆,但在这个实验中,消息已经是 hash 表的列表——换句话说,它们已经是 JSON 了。记忆不过是把它写下来再读回来:
(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)))
循环本身完全没变。入口变成了一条管道:
(remember (agent-loop (append (recall) (list new-user-message))))
回忆。递归。记录。无模式。无迁移。无存储抽象。序列化格式就是运行时格式。今天告诉它你的名字,明天在新的进程里问它——它回答。
然后它写了自己的网络搜索
斐波那契的把戏很漂亮,但接下来事情变得疯狂。我把一个临时 Brave Search API 密钥粘贴进了对话——主要是想看看会发生什么。而发生了什么让我有点惊讶。
agent 用它的 eval 在实时镜像里 defun 了一个 brave-search 函数。 它检查了自己有什么,写了 HTTP 调用,自己解析了 JSON 响应——然后开始用实时网络结果回答问题。我从来没有构建过网络搜索工具。
在这里停一秒。这颠倒了我们通常对 agent 能力的思考方式。在 Agent Foundry 里,在 Claude Code 里,在我所知的每一个 agent 平台里,工具目录是在设计时固定的。某个像我一样的人决定 agent 能做什么,写工具,发布。这里的目录是开放的。模型决定自己需要什么,在自己的基质语言里写它,求值为存在。
还有一个需要一分钟才能理解的转折。 defun 本身在进程退出时就死了——函数存在运行的 Lisp 镜像里,不在磁盘上。但 transcript 持久化了。Brave Search 函数的源码,模型写它的过程,全部以纯文本形式存在 memory.json 里。所以在一个全新的 session 里,函数不存在——直到 agent 重新读自己的历史并求值它回到存在。
一个 agent 的技能——是它对自己讲的故事。
技能只是记忆
今天每一个认真的 agent 平台都把工具目录当作设计时决策。MCP 本质上是设计时假设的形式化——能力作为合约,提前协商、版本化、批准。这个实验坐在另一极:能力在运行时被决定,由 agent,回应对话需要的任何东西。
或许整个行业已经在这个连续谱上滑动了,只是没有明命名。代码解释器是第一步。Skill(SKILL.md 模式)是第二步——因为一个 skill 不过是以文本形式储存、在运行时加载的能力。这个 100 行的 Lisp agent 只是塌了剩下的距离。它的技能储存在它自己的 transcript 里。
往远了说,这基本上也是我们的运作方式。几乎没有任何事是你做的时候被设计时安装的。你学了它,存了它,现在按需重新激活它——经常带着把它写进自己的记忆,像是记住你学会骑车的那个夏天。
他没有错,他只是早了
符号 AI 输了。没有人家的专家系统在反向传播和梯度下降面前活下来了。但 Lisp 真正被构建来做的事——代码即数据,可被检视、转换并喂回自身的计算——原来是对一个 agent 到底是什么的非常好的描述。
模型现在做推理。但围绕着模型的 loop——我们实际工程化的那部分——正是 Lisp 一直擅长的那部分。
25 年是对于一个东西站得太远的时间。在某个地方,也许我那个老的 AI 教授还在另外一些东西上走在前面。
完整源码 + Dockerfile:https://github.com/jamiebeach/lisp-agent… 原文:https://thebeach.dev/posts/lisp-agent/…
#Lisp #AgentLoop #AI考古
jamiebeach/lisp-agent
Source: https://github.com/jamiebeach/lisp-agent
lisp-agent
“LISP is the language for AI.” — my professor, circa 2000
He was right. He was just 25 years early.
This is a complete AI agent in about 100 lines of Common Lisp. Recursive agent loop, one tool (the Lisp REPL itself), and persistent memory in 20 lines. It talks to any model on OpenRouter.
No framework. No state machine. No vector database. Just eval, append, and recursion.
The whole agent
(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)))))
Base case: the model answers in words. Recursive case: it asks for tools, we run them and recur with the enriched history. The agent’s state is just the argument being folded through the recursion.
The only tool is eval
Code is data, data is code. So instead of building a bunch of tools, the agent gets one tool: a live Common Lisp REPL.
(defun lisp-eval (form-string)
(handler-case
(format nil "~s" (eval (read-from-string form-string)))
(error (e) (format nil "ERROR: ~a" e))))
Ask it for the 30th Fibonacci number and it doesn’t recall the answer. It writes the loop and runs it.
Quick start (Docker)
docker build -t lisp-agent .
docker run -it --rm \
-e OPENROUTER_API_KEY=sk-or-... \
-v "$(pwd)/data:/agent/data" \
lisp-agent
You land in a live SBCL REPL with the agent loaded:
(agent:run "What is the 30th Fibonacci number? Compute it, don't recall it.")
(agent:run "My name is Jamie.")
Exit the container. Come back tomorrow.
(agent:run "What's my name?") ; it remembers
(agent:forget) ; wipe the slate
Quick start (bare metal)
export OPENROUTER_API_KEY=sk-or-...
sbcl --load agent.lisp
Dependencies (fetched automatically via Quicklisp): dexador for HTTP, shasht for JSON. That’s the whole list.
How memory works
Messages are already a list of hash tables, which is to say, already JSON in spirit. So memory is just writing the list down and reading it back:
(remember (agent-loop (append (recall) (list new-user-message))))
Recall, recur, remember. No schema, no migrations, no store abstraction. The full transcript (tool calls included) lands in memory.json, or wherever AGENT_MEMORY points.
Known limitation: it never forgets on its own, so a long-lived conversation will eventually hit the model’s context window. The natural fix is a compress step between recall and the loop, where the agent summarizes its own past. PRs welcome.
Configuration
| What | Where | Default |
|---|---|---|
| API key | OPENROUTER_API_KEY env var | required |
| Model | *model* in agent.lisp | anthropic/claude-sonnet-4.5 |
| Memory file | AGENT_MEMORY env var | memory.json |
Any OpenRouter model that supports tool calling works. Swap *model* and nothing else changes.
⚠️ Read this before you get clever
eval as a tool means the model executes arbitrary code wherever the agent runs. That is the entire point, and also the entire risk. Run it in the container, mount nothing you care about, and treat the host as off limits. This is a toy for a sandbox, not a pattern for production.
Why
Symbolic AI lost. But the thing LISP was actually built for, programs as data, computation that inspects and transforms itself, turned out to be a pretty good description of what an agent is. The models do the reasoning now. The loop around them is the part LISP was always best at.
Longer version on the blog: My Prof Was Right About LISP. He Was Just 25 Years Early.
Files
agent.lisp the agent: loop, tool, memory (~100 lines)
Dockerfile SBCL + Quicklisp + deps, drops you at a REPL
LICENSE MIT
License
MIT. Go play.
相似文章
100行Lisp代码实现一个智能体
作者回顾了25年前学习Lisp用于符号人工智能的经历,并展示了如何仅用100行Common Lisp代码实现一个现代AI智能体循环,强调递归的优雅性。
@yibie: 每个程序员都应该自己手写一个 agent。 只要 50 行代码,很有趣,而且会让你惊讶。但为了获得最大收获,请做这两件事: 第一,从零手写。打开空白文本文件,自己敲完每一行代码。不要用任何 AI,连自动补全都不要。 第二,只依赖标准库文档…
本文鼓励程序员亲手手写一个简单的AI agent(约50行代码),以加深对agent工作机制的理解,并建议从零手工编写、只依赖标准库和API文档。
@cellinlab: https://x.com/cellinlab/status/2064144608242679822
这篇文章介绍了 Loop Engineering 的概念——不再直接给 AI agent 写 prompt,而是设计一个系统(loop)来递归地让 agent 迭代工作,直到任务完成。文章详细对比了 Claude Code 和 Codex 在 automations、worktrees、skills、sub-agents 等五个构建块上的实现,认为这可能是未来与 coding agent 协作的趋势,但仍需警惕 token 成本和 AI slop 问题。
本文系统梳理了AI Agent架构与工程实践,涵盖控制流、上下文工程、工具设计、记忆、多Agent组织、评测、追踪和安全,基于OpenClaw实现展开,强调Harness(测试验证基础设施)对系统稳定性的关键作用。
本文系统梳理了AI Agent架构与工程实践,涵盖控制流、上下文工程、工具设计、记忆、多Agent组织、评测、追踪和安全,基于OpenClaw实现展开,强调Harness(测试验证基础设施)对系统稳定性的关键作用。
@yibie: 推荐这篇文章,Anthropic 的 Eugene Yan(前 Amazon/Alibaba ML 团队负责人)把他个人 AI 工作流写成了一份实操指南。不是抽象理念,是具体到你明天就能复制的方法:怎么组织目录让模型更容易检索、怎么写 C…
这是一篇推荐Eugene Yan的AI工作流指南的推文,详细介绍了如何通过组织上下文、配置CLAUDE.md、创建skills等方式高效与AI协作并实现工作复利。