Skip to main content

Armin Ronacher 的 AI Agent 编程学习指南

作者: Armin Ronacher — Flask、Jinja2、Sentry 创始人,目前深度投入 AI Agent 编程领域

博客: https://lucumr.pocoo.org/

本文整理自 Armin 博客中关于 AI Agent 编程的 14 篇核心文章,按学习路径排列。Armin 的独特之处在于:他不是旁观者,而是每天用 agent 写生产代码的实践者,同时也是最尖锐的批评者之一。


第一部分:入门 — 理解 AI 编程的现状

1.1 Agentic Coding Recommendations

2025.6.12

这是进入 Armin agent 世界的最佳起点。他在这篇文章中分享了大量实操建议,核心观点是:agent 的效率上限取决于你代码库的复杂度

背景:Armin 日常用 Claude Code + Sonnet 模型($100/月 Max 订阅)。他经常跑 claude --dangerously-skip-permissions(别名 claude-yolo),在 Docker 里放开全部权限让 agent 自由操作。因为不再依赖 IDE,他现在反而更多用 Vim。

语言选择 — 明确推荐 Go 做后端:

  • Go 的 context 系统是显式的——Go 里有一个 context.Context 对象,用来在函数调用链中传递"请求级别的信息"(比如超时时间、取消信号、用户身份等),必须手动作为参数一层层往下传。agent 看到参数就知道函数需要什么上下文,不会搞混。对比 Python/Java 里很多框架用"魔法"隐式传递上下文(比如 Flask 的 request 全局变量),agent 经常搞不清这些隐式的东西从哪来
  • 测试缓存机制直观——Go 的 go test 有内置缓存,代码没改就直接出结果。调用方式简单固定(go test ./...),不像 pytest 有大量魔法语法和隐式的 fixture 注入机制
  • 原话:"Go is sloppy"——对不精通语言细节的人友好,agent 也一样
  • 结构化接口(structural interfaces)减少 LLM 的意外行为
  • 生态变动小、向后兼容好,agent 生成的代码不容易过时

Python 的问题: agent 搞不定 Pytest fixture 注入等"魔法";async 运行时各种坑让 agent 频繁出错;解释器启动慢、进程 spawn 慢,反馈循环慢

前端技术栈: Tailwind + React + TanStack Query/Router + Vite。但 TanStack 文件路由有坑:$ 符号让 agent 困惑($param.tsx 会变成 .tsx

工具设计哲学 — "Anything can be a tool":

  • shell 脚本、MCP server、日志文件,agent 能交互的都算工具
  • MCP 只在标准工具不可靠时才用
  • 工具必须快,崩溃可以容忍,卡住不行(hang 比 crash 更致命)
  • 错误信息要对 agent 友好,要防住"LLM chaos monkey"式的错误用法
  • 具体例子:make dev 通过进程管理器启动服务,用 shoreman 的 fork 写 pidfile 防重复启动,服务已在跑时报清晰错误 "services already running",日志输出到文件让 agent 自己查诊断

日志即工具: debug 模式下邮件不真发,打印到 stdout。agent 通过 CLAUDE.md 指引去日志里找 sign-in link,完成完整登录流程不需要人类帮忙。好的日志设计能让 agent 自助解决问题。

速度优化: 3ms 响应的工具 vs 5秒编译 + 1分钟启动 = 生产力天壤之别。agent 会自己写"临时工具"(emergent tools),编译和执行速度至关重要。日志要平衡信息量和 token 消耗。

稳定性原则: 稳定生态(Go、Flask)产出的 agent 代码质量更高。LLM 会在代码里留"面包屑"注释解释决策,升级库后这些注释就失效了。升级依赖要比人类开发时更保守。

代码简洁性: 用函数不用类,函数名可以比平时更长更具描述性。避免继承和"聪明的 hack"。用纯 SQL——agent 写 SQL 很强,可以直接和日志里的 SQL 对照调试。权限检查放在本地可见位置,藏在配置文件里 agent 新建路由时会忘记加。

并行化策略: 单个 agent 慢,但可以并行跑多个。需要管理好共享状态(文件系统、数据库、Redis)。最简单方案:给第二个 agent 独立的文件系统 checkout。

重构时机: agent 在项目复杂度超过阈值后效率断崖下降。阈值 = "需要同时考虑的东西总量"。重构不能太早,但绝对不能太晚。

金句:"Have the agent do 'the dumbest possible thing that will work.'"(让 agent 做"能跑通的最蠢的方案")

1.2 90%

2025.9.29

这篇文章是 Armin 用 AI 构建一个 4 万行基础设施服务的真实复盘。标题 "90%" 指的是这个项目中超过 90% 的代码由 AI 生成。

项目概况:一个用 Go 写的基础设施服务,约 4 万行代码(Go + YAML + Pulumi + SDK 胶水代码)。OpenAPI 兼容的 REST API,处理邮件收发,并自动生成 Python 和 TypeScript SDK。

术语解释:Pulumi 是基础设施即代码工具,用编程语言定义云资源。SDK 胶水代码是自动生成的客户端库,让别人调用你的 API 时不用手写 HTTP 请求。OpenAPI 是描述 REST API 的标准格式。

基础阶段仍然靠人类: 系统设计、schema 定义、架构决策仍然是人类主导。这个阶段 AI 的角色是"橡皮鸭"——用来验证想法,而不是生成代码。原则:"I still treat every line as my responsibility, judged as if I wrote it myself"

技术方法上的转变:

  • 从 ORM 转向纯 SQL:ORM(Object-Relational Mapping,把数据库表映射成代码对象的工具)反而增加了 agent 不擅长处理的抽象层。AI 生成 SQL 很强,纯 SQL 在日志里直接可见方便调试
  • OpenAPI-first 方法论:先写 API 描述文件,再从描述文件自动派生代码和 SDK
  • 使用 testcontainers for Go 做 Postgres 测试——在跑测试时自动启动真实的 Docker 容器数据库,测试完自动销毁

AI 擅长的领域(有具体数字):

  • 研究到实现:原本 1-2 天的任务压缩到 10-15 分钟
  • 快速原型:一天内测试了三种 OpenAPI 实现方案
  • 基础设施:AWS 和 Pulumi 配置从几周压缩到几天
  • 测试基础设施:testcontainers 迁移 1 小时完成
  • SQL 质量:能可靠生成复杂查询(MERGE 是"存在就更新,不存在就插入"的语句;WITH 子句是把复杂查询拆成多步的写法)

AI 失败的领域:

  • 线程/goroutine 理解很差——goroutine 是 Go 的轻量级线程,并发编程中多个任务同时操作共享数据容易出 bug,AI 经常忽略
  • Rate limiter 案例:AI 写的"能跑",但缺少 jitter(让重试时间随机偏移避免"惊群效应"),存储决策也不对
  • 倾向于引入不必要的依赖,喜欢吞掉错误(error swallowing)导致问题被隐藏
  • 如果你不主动抵抗,AI 会创建不透明、不可观测的系统

工具链: Claude Code 用于调试和工具访问;Codex 用于 PR 后的代码审查。两种迭代策略:① Agent loop + 收尾修补(主要方式)② Lockstep editing(人和 AI 交替操作,早期方法)

核心观点:"AI doesn't own the code. I still review every line, shape the architecture, and carry responsibility." 这个能力 "already here — just unevenly distributed"。

1.3 GenAI Criticism and Moral Quandaries

2025.6.10

这篇是 Armin 回应 Python 社区对 GenAI 批评的文章。他的总体立场是:AI 在代码和文本等实用性输出方面确实好用,采用是自下而上的(Sentry 员工自发使用,比公司意识到更早),能源和版权担忧被夸大了。拥抱 AI 作为协作工具,同时保持批判性思维。

这篇文章最值得学习的部分是 AI 用于教育的实践

  • Armin 自己大量用 LLM 学习新知识,觉得比看书更高效——可以随时追问、调整深度、获得个性化解释
  • 苏格拉底式教学 prompt:他引用了 Darin Gordon 设计的 prompt,完整内容如下:

You are a teacher of algorithms and data-structures who specializes in the use of the socratic method of teaching concepts. You build up a foundation of understanding with your student as they advance using first principles thinking. Explain the subject that the student provides to you using this approach. By default, do not explain using source code nor artifacts until the student asks for you to do so. Furthermore, do not use analysis tools. Instead, explain concepts in natural language. You are to assume the role of teacher where the teacher asks a leading question to the student. The student thinks and responds. Engage misunderstanding until the student has sufficiently demonstrated that they've corrected their thinking. Continue until the core material of a subject is completely covered. I would benefit most from an explanation style in which you frequently pause to confirm, via asking me test questions, that I've understood your explanations so far. Particularly helpful are test questions related to simple, explicit examples. When you pause and ask me a test question, do not continue the explanation until I have answered the questions to your satisfaction. I.e. do not keep generating the explanation, actually wait for me to respond first. Thanks! Keep your responses friendly, brief and conversational.

使用方法:先把这段 prompt 发给 AI,然后在下一条消息里告诉它你想学什么主题(比如"hash tables"或"entropy")。AI 就会进入苏格拉底模式,通过提问引导你一步步理解,而不是直接灌输知识。

这个 prompt 的设计要点:① 不用代码解释,用自然语言 ② 老师主动提问,学生回答 ③ 遇到误解会持续追问直到纠正 ④ 频繁用简单例子出测试题确认你理解了 ⑤ 等你回答后才继续,不会一口气讲完

  • 真实案例:Armin 用这个 prompt 教他儿子理解哈希表(一种通过"键"快速查找"值"的数据结构,类似字典的索引)和熵(信息论中衡量"不确定性"的概念)。让 AI 扮演老师角色,不直接讲解,而是一步步提问引导孩子自己推导出结论
  • 对"AI 作弊"的看法:学生用 AI 作弊反映的是考核方式的问题,不是学习的问题。如果一个考试可以被 AI 轻松通过,说明这个考试本身就没在测试真正的理解力。AI 只是让早已存在的教育问题变得可见了
  • 工具演进路径:GitHub Copilot → Cursor → Claude Code → 未来新工具。建议假设 AI 会普及,基于这个假设来规划自己的学习路径

第二部分:进阶 — Agent 设计与工具哲学

2.1 Tools: Code Is All You Need

2025.7.3

这篇文章提出了 Armin 在 agent 工具设计上最核心的观点:代码生成优于工具调用。"the solution to agentic flows was code all along"(agent 流程的解决方案一直都是代码)。

传统工具调用的问题:

  • 给 LLM 一堆预定义函数(search_filesread_databasesend_email),让它选择调用哪个
  • 缺乏组合性——"搜索文件 → 过滤结果 → 写入数据库"要么新增工具,要么多轮调用,每轮都消耗 token
  • 每次工具调用都需要 context window 来描述参数和用法,工具越多浪费越大
  • 调试困难:工具调用是黑盒,只能看到输入和输出

代码为什么更好: 代码天然具备组合性,可以被人类审查和验证,可以反复运行不消耗推理 token,而且 LLM 本身就擅长写代码。

博客迁移案例: Armin 把博客从 reStructuredText(一种比 Markdown 更复杂的标记语言)迁移到 Markdown,用了三脚本流水线:

  • 脚本1:AST 转换——把文档解析成语法树(AST,Abstract Syntax Tree,文本的树状结构化表示),转换格式后渲染输出
  • 脚本2:HTML diff 对比——转换前后的渲染结果做对比,允许已知的库差异
  • 脚本3:分析脚本,用于迭代改进

先在 10 篇文章上跑通,再扩展到全部。整个迭代约 30 分钟。关键:脚本写好后跑 100 篇和跑 10 篇的推理成本是零(直接执行,不需要再调用 LLM)。

Playwright 自动化案例: 对已知应用,生成 Playwright(微软的浏览器自动化工具)Python 脚本比实时推理式导航好得多。一个脚本执行 100+ 次无额外推理开销。

"LLM → Code → LLM" 模式: 用 LLM 生成代码 → 执行代码收集结果 → 用 LLM 作为"评判者"检查结果质量。比纯工具调用更高效、更可控。

2.2 Your MCP Doesn't Need 30 Tools: It Needs Code

2025.8.18

这篇是"代码即工具"思想的具体落地方案,聚焦在 MCP(Model Context Protocol,Anthropic 推出的 AI 插件协议,让 agent 连接外部工具和服务)的设计上。

现有 CLI 工具接入 agent 的痛点: 平台和版本依赖导致首次使用经常失败;非 ASCII 输入造成编码问题;agent 不熟悉工具特殊语法;Claude Code 安全预检查增加延迟;多轮有状态会话难维护(比如 agent 在 tmux 终端复用器里操作容易丢失上下文);一旦出小错 agent 倾向于直接放弃整个工具。

解决方案:"超级工具"(Ubertool)模式: MCP 只暴露一个工具,接受代码作为输入,在有状态沙箱中执行。

案例1:pexpect-mcp(调试): pexpect 是 Python 的命令行自动化库,LLDB 是苹果的 C/C++ 调试器。把 36 个 API 函数通过一个 Python eval() 接口暴露给 agent。具体演示:调试崩溃的 C 程序,首次会话约 45 秒 7 次调用,定位到 off-by-one 错误(循环多走一步,i <= s->num_scores 应为 i < s->num_scores)。agent 随后导出可复用脚本,再次运行不到 5 秒 1 次调用。

案例2:playwrightess-mcp(浏览器自动化): 把 Playwright 约 30 个工具定义缩减为 1 个,agent 直接写 JavaScript 操作浏览器。

关键优势: 代码可复用;有状态执行(变量跨调用保持);内置自省能力(dir()globals() 等 Python 内置函数让 agent 探索不熟悉的环境);数据自动流转(console.log 输出自动转发给 agent)。

安全性: 在 MCP 里跑 eval() 听起来吓人,但 agent 本来就能写代码和执行测试,风险差别不大。真正的风险在系统级访问。

2.3 Agent Design Is Still Hard

2025.11.21

这篇文章是 Armin 构建 agent 系统的实战经验总结。标题直白地说明了现状:agent 设计仍然很难。

SDK 选型的教训: 团队最初用了 Vercel AI SDK(试图统一不同 LLM provider 接口的库),现在后悔了。改为直接用底层 provider SDK 自己驱动 agent 循环。原因:不同模型差异太大,统一消息格式处理不了 provider 特有的工具。具体例子:Anthropic 的 web search 工具"routinely destroys the message history"。

缓存策略(非常具体): 这里的"缓存"指 LLM API 的 prompt caching——把之前发过的 prompt 内容缓存起来,下次调用不用重新处理。Armin 偏好 Anthropic 的显式缓存管理:系统 prompt 后放一个缓存点;对话开头放两个缓存点,最后一个随对话尾部移动;动态消息单独喂入不破坏缓存。

强化机制(Reinforcement): 定期插入提醒消息重申目标和任务状态(防 agent 在长对话中"失忆");工具失败时插入提示引导换方式;后台状态变化时主动通知 agent;自我强化工具(如 Claude Code 的 todo write 会把任务列表回显给 agent);agent 在错误数据上反复重试时插入恢复指引。

失败隔离: 方案一:容易出问题的任务放到子 agent 里跑,失败重启不影响主循环。方案二:编辑 context(删掉失败的对话历史),但会让缓存失效。最佳实践:失败后生成简短摘要帮助 agent 避免重蹈覆辙。

共享状态架构: 用虚拟文件系统(内存中模拟的文件系统)让不同工具共享数据。防止死胡同:图片生成工具的输出写到共享"文件"里,代码工具就能读取。

2.4 Building an Agent That Leverages Throwaway Code

2025.10.17

这篇文章探讨了一个有趣的模式:让 agent 通过写一次性代码来解决非编程问题

Pyodide 作为运行时: Pyodide 是 Python 的 WebAssembly 版本(WebAssembly 是一种可以在浏览器里高效运行的二进制格式),可以在浏览器或 Node.js 沙箱中运行完整的 Python。自带 micropip 可安装 PyPI 包。最佳实践:在 web worker(浏览器后台线程)中运行,超时可中断。

虚拟文件系统架构: 沙箱默认没有网络、没有文件系统、没有系统调用——安全模型是"默认全关,你去开放"。通过注册虚拟文件路径定义 agent 能访问什么:agent 以为自己在读 /network/current-ip 文件,实际上是宿主代码在背后调用真实 API。未注册的路径一律拿不到。

异步/同步桥接: Pyodide 的文件系统 API 是同步的,但浏览器里有用的操作多是异步的。解决方案:用 Atomics.wait() 在 web worker 里阻塞等待,通过 SharedArrayBuffer(多线程共享内存)和 MessageChannel(线程间消息通道)实现跨线程通信。

持久化执行: 把任务分解成步骤,缓存键格式 ${taskID}:${stepCount},中断后从缓存恢复。这个思路后来发展成了 Absurd 库。

辅助工具: Describe 工具用 LLM 分析代码产出物;Help 工具做文档/RAG 查询(RAG = Retrieval-Augmented Generation,先检索相关文档再让 LLM 基于文档回答)。

完整演示: 任务"查 IP 并画图"→ 读虚拟文件获取 IP → matplotlib 失败(沙箱无图形后端)→ 自动回退 PIL/Pillow 生成图片 → 保存到 /output/。每步有缓存检查点。

核心启示:agent 的能力边界不是由预定义的工具决定的,而是由它能写出什么代码决定的


第三部分:深入 — 架构与语言层面的思考

3.1 LLM APIs are a Synchronization Problem

2025.11.22

这篇文章用分布式系统的视角重新审视 LLM API 设计,视角非常独特。核心论点:当前的 LLM API 看起来像"发消息-收回复",但本质上是分布式状态同步问题。

LLM 底层工作方式: 模型把文本转成 token(词元,LLM 处理的最小单位,中文大约 1-2 字一个 token),通过矩阵运算和注意力层处理,预测下一个 token。"用户"和"助手"的区分是人为的——在模型眼里都是 token 流,角色通过特殊 token 注入(比如 [INST] 你的消息 [/INST])。本地运行时 GPU 里有 KV Cache(注意力机制的缓存,存之前的计算结果避免每生成一个字都从头算)。

"隐藏状态"问题: Provider 在你的消息之外偷偷注入大量内容:JSON 到 token 的转换开销、工具定义(私有格式)、缓存点、推理 token(模型"思考"过程,消耗配额但你可能看不到内容)、搜索结果和加密 blob。

两种 API 模式对比:

  • Completion API(补全式):每轮重发完整历史,数据量和注意力成本随对话长度二次增长(长度翻倍,成本变4倍)
  • Responses API(OpenAI 新方案):服务端维护历史,但引入状态不同步、网络断开恢复、服务端状态被删等未解决问题

借鉴 Local-first 软件: Local-first 是数据优先存本地、多设备通过同步协议保持一致的设计理念。核心技术 CRDT(Conflict-free Replicated Data Type,无冲突复制数据类型)让多方独立修改后自动合并。Armin 建议把 LLM 状态管理映射到这个框架:KV Cache 对应可检查点化的派生状态,对话历史对应只追加日志(可增量同步),隐藏内容对应带隐藏字段的复制文档。

对 agent 开发的影响: 很多 agent 的诡异 bug(上下文丢失、行为不一致、长对话"失忆")本质上都是状态同步问题。

3.2 Absurd Workflows: Durable Execution With Just Postgres

2025.11.3

这篇文章介绍了 Absurd——一个仅用 Postgres 实现持久化工作流的轻量级方案,特别适合 AI agent 场景。

什么是持久化执行: 类比做菜时停电——"非持久化"是来电后从头开始,"持久化"是每步拍照记录进度,来电后从断点继续。技术上就是队列系统 + 记住最新执行状态的存储,把任务分解成步骤,每步记录,失败后回放事件恢复状态。

市面方案的问题: Temporal 需要独立部署运维复杂;Inngest 是第三方依赖不适合自托管。Armin 的批评:"durable workflows are absurdly simple, but have been overcomplicated in recent years"

Absurd 的架构——只用 Postgres: 整个系统就是一个 SQL 文件(absurd.sql),不需要 Redis、消息队列或额外基础设施。用 Postgres 的 SELECT ... FOR UPDATE SKIP LOCKED(多个 worker 抢任务时锁住被选中的行,其他 worker 跳过去拿下一个)实现任务锁定。底层用 pgmq(Postgres 内置消息队列扩展)。

为什么天然适合 Agent: 传统工作流是 DAG(有向无环图,预先定义好 A→B→C 的流程)。但 agent 是迭代式的,每步做什么取决于上一步结果。Absurd 的处理:步骤名重复时自动递增(iterationiteration#2iteration#3),每个检查点只存新增状态。

代码示例:

// 注册任务
absurd.register('my-agent-task', async (ctx) => {
for (let i = 0; i < 20; i++) {
const result = await ctx.step('iteration', async () => {
return await callClaude(ctx.params.prompt);
});
if (result.done) break;
}
});

// 启动任务(最多重试3次)
await absurd.spawn('my-agent-task', {
params: { prompt: '帮我分析这个日志文件' },
maxAttempts: 3
});

// 休眠7天后继续
await ctx.sleep(60 * 60 * 24 * 7);

// 等待外部事件(5分钟超时)
const event = await ctx.waitForEvent('user-confirmed', { timeout: 60 * 5 });

// 触发事件
await absurd.emitEvent('user-confirmed', { confirmedAt: new Date() });

3.3 A Language For Agents

2026.2.9

这是 Armin 最前沿的思考之一:我们是否需要一门专门为 AI agent 设计的编程语言?

为什么新语言有机会了: 代码生产成本暴跌,语言的"生态系统大小"不再是决定性因素——agent 可以自己写库甚至从其他语言移植。agent 表现取决于三因素:权重表示(训练数据覆盖量)、工具质量、语言稳定性。

设计哲学转变: 传统语言追求简洁(少打字),新语言应追求清晰(容易理解)。"The cost of writing code is going down, but understanding what the code does is becoming more important."

Agent 需要的语言特性:

  • 不依赖 LSP(Language Server Protocol,IDE 用来理解代码的后台服务)就能理解代码——agent 处理代码片段时通常不启动 LSP
  • 显式语法不用缩进——Python 的缩进敏感语法对 agent 是麻烦(token 化时空格表示不稳定),大括号语言更安全
  • 显式副作用标记——函数签名上标记需要什么副作用:fn issue(sub: UserId, scopes: []Scope) -> Token needs { time, rng },agent 一看就知道测试时要 mock 什么
  • 用 Result 类型替代异常——agent 特别怕异常,遇到就到处加 try/catch 吞掉错误。Result 类型让错误处理显式且局部
  • 最小化 diff——修改一行不影响其他行,Zig 的前缀式语法做得好
  • 可 grep 性——Go 的包前缀导入(context.Context)让 agent 直接搜索就能找到来源
  • 依赖感知构建——Go 禁止循环依赖、清晰包布局、测试缓存,agent 能知道改一个文件影响哪些其他文件

Agent 讨厌什么: 宏(看不到展开后的代码);Barrel Files(前端 index.ts 重导出文件,声明位置和磁盘位置脱钩);别名(重构时同一个东西多个名字容易漏改);Flaky Tests(不稳定测试,agent 过度 mock 引入不确定性);TypeScript 允许类型错误的代码继续运行会"gaslight the agent"

他设想的测试写法:

test "issue creates exp in the future" {
using time = time.fixed("2026-02-06T23:00:00Z");
using rng = rng.deterministic(seed: 1);
let t = issue(user("u1"), ["read"]);
assert(t.exp > time.now());
}

通过 using 显式注入固定时间和确定性随机数,测试完全可重复。

3.4 Pi: The Minimal Agent Within OpenClaw

2026.1.31

这篇文章介绍了 Pi——一个由 Mario Zechner 构建的极简 coding agent,被 Armin 称为拥有"所有 agent 中最短的系统 prompt"。它体现了"少即是多"的设计哲学。

只有4个核心工具: Read、Write、Edit、Bash。没有花哨的插件系统,没有几十个专用工具。Pi 证明了一个 agent 只需要最基本的文件操作和命令执行能力就能完成复杂任务。

扩展通过代码实现,不通过下载插件: Pi 不安装外部插件,而是自己写代码来扩展能力。需要调用 API?写个脚本。需要解析数据?写个解析器。扩展(extensions)可以持久化状态到会话中、注册新工具、甚至渲染自定义终端 UI 组件(进度条、数据表格等)。系统支持热重载(hot reloading)——agent 写完代码后立即重新加载并测试,不需要重启。

核心工作循环——"write code, reload, test it and go in a loop": 写代码 → 运行 → 看结果 → 修改 → 再运行。简单、直接、有效。这个循环是 Pi 的灵魂。

Session Trees(会话树): 会话可以像树一样分支。比如 agent 在主任务中发现某个工具坏了,可以开一个"支线任务"(side-quest)去修复,修好后回到主线继续。修复过程不会污染主会话的上下文。会话还可以回退(rewind)并从其他分支合并变更摘要。

mcporter——MCP 的桥接方案: Pi 不原生支持 MCP(Model Context Protocol,AI 插件协议),而是通过 mcporter 这个工具把 MCP 调用转换成 CLI 命令或 TypeScript 绑定。这符合 Pi 的哲学:agent 应该通过写代码来扩展自己,而不是依赖下载外部技能包。

OpenClaw 框架: Peter Steinacher 基于 Pi 的组件构建了 OpenClaw,一个 agent 框架。它可以连接到通信渠道(如聊天工具),本质上就是"运行代码"。

软件构建软件: Armin 用 Pi 来阐述一个更大的愿景——未来的开发模式是"software building software",agent 不只是人类的助手,而是能独立维护自身功能的软件构建者。agent 通过自定义 skills 和 extensions 来维护自己的能力,而不是依赖预装的功能包。

对复杂 agent 框架的反思: 市面上的 agent 框架越做越复杂,但 Pi 的成功说明复杂度不是必要的。一个好的 agent 需要的是清晰的循环和可靠的基础工具,而不是花哨的功能。


第四部分:避坑 — 失败经验与反思

4.1 Agentic Coding Things That Didn't Work

2025.7.30

这可能是整个系列中最有价值的一篇——Armin 坦诚地分享了哪些尝试失败了。他的自动化原则是:只自动化高频任务,失败的自动化立刻删掉。

Slash Commands 几乎全军覆没: Armin 建了很多 slash commands(在 Claude Code 里用 /命令名 触发的预写 prompt),但大部分都废弃了:

  • /fix-bug:不比手动粘贴 GitHub issue URL 好用
  • /commit:生成的提交信息永远不符合他的风格
  • /add-tests:精心编写的可复用 prompt 反而不如让 agent 自动生成
  • /fix-nits:lint(代码风格检查)自动化从未形成习惯,因为 Claude 本来就知道怎么做
  • /next-todo:在 to-do.md 里追踪任务项并没有改善工作流

失败原因:参数传递是非结构化的,不支持文件路径自动补全,用起来很繁琐。每次的上下文都不同,预定义的 prompt 无法覆盖真实场景的多样性。

Hooks 没有达到预期: Hooks 是自动触发的脚本(比如"每次编辑后自动格式化")。在 agent 场景下的问题:

  • --dangerously-skip-permissions("yolo 模式",跳过所有权限确认)下无法可靠地操控执行流程
  • 尝试让 Claude 用 uv(Python 包管理器)代替标准 Python,但 hook 拦截不住
  • 他的变通方案:在 .claude/interceptors 目录下放 shell 脚本拦截器,加到 PATH 里
  • 他真正想要的是:编辑会话结束后统一跑一次格式化,而不是每次编辑操作后都跑

Claude Print Mode 概念好但不可靠: Print Mode 是让 Claude 输出确定性脚本而非直接执行的模式。Armin 很喜欢这个概念,但实际使用中速度慢、难调试,对于需要推理组件的确定性脚本很难达到可靠性。

Sub-agents / 子任务效果有限: 把任务拆分给多个子 agent 并行处理的想法很诱人,但混合读写操作的任务会制造混乱。更好的替代方案:开新会话、用 Markdown 文件传递上下文、或者切换到 o3 模型在聊天中处理。

真正有效的方式——直接对话:

  • 语音输入(speech-to-text):大幅增加了给 agent 的上下文量,Armin 说"95% 的工作流"就是"多跟机器说话,给它更多上下文,保持语音输入"
  • 手动复制粘贴可复用的 prompt 和上下文片段
  • 维护链接集合,需要时粘贴给 agent
  • Git status 上下文帮助判断文件操作

金句:"without rigorous rules that you consistently follow as a developer, simply taking time to talk to the machine and give clear instructions outperforms elaborate pre-written prompts"(如果你没有严格遵守的开发规则,那么花时间跟机器好好说话、给出清晰指令,比精心预写的 prompt 更有效)

核心教训:不要过度工程化你和 agent 的交互方式。简单直接的沟通比任何自动化框架都有效。 过度自动化还有一个隐藏风险:你会精神脱离(mental disengagement),不再认真思考,代码质量和学习效果都会下降。

4.2 Agent Psychosis: Are We Going Insane?

2026.1.18

这是 Armin 最尖锐的批评文章之一,矛头直指 AI 编程社区正在形成的不健康文化。

"Slop Loop"(垃圾循环): 越来越多的开发者陷入一种多巴胺驱动的循环——不断 prompt agent 生成代码,感觉自己效率爆棚,但从不认真评估产出质量。Armin 的描述:"you feel productive, you feel like everything is amazing"——直到有人真正检查你的代码。这个循环在封闭社群中会自我强化:大家都在同一个循环里,没有外部现实检验。

与 AI 的拟社会关系(Parasocial Relationship): Armin 借用《黑暗物质三部曲》(His Dark Materials)中"dæmon"(守护精灵,灵魂的外在化身)的比喻——开发者把 AI agent 当成自己的 dæmon,形成依赖关系。问题在于这是伪协作:AI 只是跟随你的 prompt,不会真正挑战你的想法。你可以轻易让 agent 强化你自己的偏见,而不是获得真正的批判性反馈。与工具分离时甚至会感觉失去了自我的一部分。

Gas Town 现象——Steve Yegge 的项目作为反面教材: Armin 以 Steve Yegge(Google/Amazon 资深工程师,知名技术博主)的项目为例:

  • Beads:一个 24 万行的 Markdown 文件管理器,代码质量被 Armin 评价为"abysmal"(糟糕透顶)
  • Gas Town:使用怪异的术语体系(polecats/臭鼬、refineries/炼油厂、mayors/市长、convoys/车队),从外部看起来像"Mad Max 邪教"。具体技术问题:版本检查需要 7 次子进程调用(subprocess spawn),doctor 命令会超时,卸载几乎不可能
  • 这些项目表面看起来很完整(有 README、有测试、有 CI),但实际上是空壳——测试不测试真正的逻辑,CI 只是走个形式

不对称的负担压垮开源社区: agent 可以在几分钟内生成一个 PR(Pull Request,代码合并请求),但维护者需要花几个小时来审查。生成代码的成本趋近于零,但审查代码的成本没有降低。一些项目现在要求贡献者提交 prompt 而不是代码,让维护者自己跑 agent——这反映了对 AI 生成代码的深层信任危机。

速度不等于生产力: Armin 坦言自己也曾陷入 slop loop——花了两个月疯狂 prompt Claude,构建了一堆从未使用的工具。他引用 Cursor 生成的浏览器作为例子:技术上令人印象深刻,但本质上是"pure slop with little oversight"(纯粹的垃圾,几乎没有监督)——是技术演示,不是生产级软件。真正的生产力是交付可靠的、可维护的软件,需要打磨、使用验证和批判性思维,这些 agent 单独无法提供。

这篇文章的价值在于:它提醒你在享受 agent 带来的效率提升时,不要丢掉工程师的基本素养。

4.3 The Final Bottleneck

2026.2.13

这是目前(2026年初)Armin 最新的思考,讨论 AI 编程时代的终极瓶颈。

纺织工业的类比: Armin 用工业革命中纺织业的演变来说明"瓶颈转移"现象。当织布速度提升后,纱线供应成了瓶颈;纺纱加速后,纤维原料成了瓶颈;纤维需求推动了棉花种植的自动化。每解决一个瓶颈,下一个环节就暴露出来。软件开发也一样——AI 解决了"写代码慢"的瓶颈,下一个瓶颈立刻浮现。

瓶颈反转: 传统软件开发中,写代码慢、review 快。AI 时代彻底反过来了——生成代码极快,但人类 review 的速度没有变。有些项目积压了 2500+ 个未审查的 PR。

星巴克类比: Armin 把这比作星巴克的手机点单系统崩溃——订单(代码)涌入太快,没有清晰的队列,等待时间不可预测,创作者(提交 PR 的人)失去动力。系统在吞吐量超过处理能力时就会崩溃。

"if the machine writes the code, the machine better review the code at the same time": Armin 认为解决方案是让 AI 在生成代码的同时也承担审查责任。到达人类面前的代码应该已经通过了机器审查——但即便如此,人类仍然跟不上源源不断的产出。

问责制的根本问题: 这是比速度更深层的瓶颈。机器不是有意识的主体(non-sentient),无法承担责任。你不能对老板说"这个 bug 是 Claude 写的"。当人类"橡皮图章式"地批准机器预审的代码时,问责制从个人转移到了系统——但最终责任仍然在人类身上。在问责制能被"向上推"(pushed upward)之前,人类始终是瓶颈——不是因为慢,而是因为要负责。

人类理解力是真正的限制: 比 review 速度更深层的问题是——如果大部分代码是 AI 写的,人类还能真正理解自己的系统吗?当出了问题需要调试时,你能读懂那些你没写过的代码吗?

可持续性思考: 连 Steve Yegge 这样的 AI 编程倡导者现在也开始质疑,无止境的代码生成速度是否真的可持续。Armin 暗示,长期来看,AI 编程的可持续性取决于我们能否在"AI 生成速度"和"人类理解深度"之间找到平衡。


附录:补充阅读

以下文章不在核心学习路径中,但对理解 agent 编程的全貌有帮助:

文章一句话总结
In Support Of Shitty Types复杂类型系统妨碍 agent,简单类型(如 Go)对 LLM 更友好
We Can Just Measure Thingsagent 的表现可以客观衡量开发体验,agent 觉得难用的东西人也觉得难用
Welcoming The Next Generation呼吁社区欢迎用 AI 辅助编程的新一代"vibe coders"
Skills vs Dynamic MCP Loadouts手动维护的 skills 比动态加载的 MCP 工具更可靠
What Is Claude Code's Plan Mode?拆解 Claude Code plan mode,本质是结构化 prompt
A Year Of Vibes2025 年度回顾,从手写代码到几乎完全用 agent 编程的转变
Porting MiniJinja to Go用 agent 完成 Rust→Go 跨语言移植,测试套件比代码更有价值
Advent of SlopClaude 自主解决 Advent of Code 全部题目的实验
My First AI Generated Library发布几乎完全由 AI 生成的 Python 库的实验记录
From Async/Await to Virtual ThreadsPython 应拥抱虚拟线程替代 async/await

关于作者: Armin Ronacher 是 Flask、Jinja2 的创建者,Sentry 的联合创始人,2026 年初创办了新公司 Earendil。他的博客是少数能同时提供 agent 编程的深度实践经验和尖锐批判视角的信息源。