Karpathy Skills 怎样让 Agent 写代码更稳

核验范围:本文以 multica-ai/andrej-karpathy-skillsmain 分支为主要来源,阅读了 README、CLAUDE.md、Cursor Rule 和 Claude Code Skill。仓库内容、安装入口和宿主支持情况会变化,文中不会把它们写成永久事实。仓库没有公开的对照实验数据,我也没有独立运行其行为评测。因此,本文对“是否有效”的判断是一套可以自行复现的评估方法,而不是效果承诺。

用户说:“给上传函数加一行日志。”

Agent 做完后,除了日志,还把单引号换成双引号,补了类型注解,重排了空白,又顺手调整了一个返回值。每处单看都不算荒谬,可它们都不属于需求。真正进入 code review 后,审阅者不得不逐行确认这些额外改动有没有副作用。

这类问题不是模型不会写代码,而是模型在写代码时缺少边界感。andrej-karpathy-skills 试图处理的正是这部分。它不是 Andrej Karpathy 本人发布的仓库,而是 multica-ai 把 Karpathy 对 LLM 编码失误的公开观察,整理成四条短规则,再分别放进 Claude Code、项目级 CLAUDE.md 和 Cursor Rule 中。仓库 README

这篇文章不把它当成“装上就变聪明”的万能 Skill,而是回答几个更具体的问题:它究竟约束了 Agent 的哪一步?一段自然语言规则为什么有时有用、有时没用?初学者应该怎么接入,怎样判断自己只是多了仪式感,还是确实少了返工?

初学者先建立三个概念

读这类仓库之前,最好先把几个常被混用的词分开。否则很容易把一份 Markdown 指令,当成拥有权限控制或自动测试能力的程序。

名词 在本文中的意思 它做不到什么
模型 根据上下文生成下一步文字、工具调用或代码的 LLM 它不会天然知道你的业务规则,也不保证每次都服从指令
Agent 模型加上读文件、改文件、运行命令等工具后的工作方式 它不等于一个能为结果负责的工程师
Harness 承载 Agent 的产品或运行环境,例如 Claude Code、Cursor、Codex 不同 harness 对规则文件、Skill 触发和 Hook 的支持不同
项目规则 与代码仓库一起保存的文字指令,例如 CLAUDE.md.cursor/rules/*.mdc 它只能影响模型的倾向,不能直接阻止一次危险操作
Skill 描述“何时做什么、怎样验证”的可复用说明 有些 Skill 由模型按需读取,有些只会在安装或显式调用后生效
护栏 能够实际拒绝、阻断或检查行为的机制,例如权限、Git Hook、测试和 CI 它通常比提示词更可靠,但覆盖面也更窄

可以先把它们看成一条链:模型负责生成,Agent 负责执行,harness 决定怎样加载规则,自动化护栏负责在结果不合格时把它挡下来。Karpathy 这套内容处在“规则”这一层。它改善默认决策方式,却不能替代后面的测试和权限控制。

图中的箭头不是“每个工具都会自动发生”的保证。比如一个项目没有测试,Agent 就没有测试可跑;一个平台没有加载 CLAUDE.md,文件写得再好也不会进入上下文。先弄清宿主行为,再讨论规则内容,才能避免复制配置后却看不到效果。

这个仓库来自哪里,解决的是什么问题

Karpathy 的原始观察很朴素:LLM 会替人做未经确认的假设,遇到困惑不主动暴露;会把代码和 API 搭得比需求复杂;也会碰自己并不理解的无关代码或注释。仓库作者把这些现象归为四种可干预的行为,并加上一条共同的验证原则。

原始问题 仓库中的原则 希望看到的外部行为
静默猜测需求 Think Before Coding 在会造成不同实现的地方,先说明假设或提问
过早抽象和堆配置 Simplicity First 先给最短的可维护实现,再为额外复杂度举证
顺手修改周边内容 Surgical Changes diff 中每一行都能解释与需求的关系
写完就宣布完成 Goal-Driven Execution 把任务转成可以检查的成功条件,并完成检查

这张表很重要,因为四条规则不是四种不同的“编程技巧”。它们是在约束 Agent 的决策顺序:先处理不确定性,再控制方案规模,接着限制修改范围,最后用反馈确认结果。把顺序倒过来,例如先大改一轮再问用户是否接受,规则就失去意义。

仓库中的文本十分短。短并不是缺点,它的目标不是教会模型所有工程知识,而是在模型容易走偏的岔路口给一个明确提示。真正需要补充的是项目自身的信息:测试命令在哪里、错误响应长什么样、哪些目录不可修改、部署是否需要审批。这些都不是通用规则能够替你猜出来的。

四条规则逐条拆开

1. Think Before Coding:先处理会改变方案的未知项

“先想再写”经常被误解成让 Agent 展示很长的推理过程。不是。对用户有用的是能影响实施的前提,而不是模型的内心独白。

假设需求是“给注册接口加校验”。至少存在这些可能:

  • 校验放在浏览器、服务端,还是两边都做。
  • 邮箱已存在时,接口返回 409400,还是项目已有的业务错误码。
  • 项目是否已经使用 Zod、Joi、Pydantic 之类的校验工具。
  • 密码规则由后端定义,还是必须与移动端共享。

其中“当前文件用单引号还是双引号”通常不影响方案,没必要问。相反,错误响应格式会影响前端、测试和 API 契约,静默选择就很容易返工。

一个合格的 Agent 回复可以很短:

1
2
我会在服务端复用现有的校验中间件,并保持当前 API 的错误结构。
这里有一个需要确认的业务选择:重复邮箱是返回 409,还是沿用项目的 400 业务错误码?

如果 Agent 能从代码、文档或测试里查出答案,就应该自己查,而不是把可检索事实抛给用户。只有需要业务取舍、且不同答案会改变实现时,提问才真正有价值。对初学者来说,这是一条很实用的分界线:先查事实,再问决策。

规则也允许 Agent 适度反驳。例如用户要求“为了一个单页表单专门造一套通用表单引擎”时,Agent 可以说明现有组件已经满足需求,提出一个更小的实现。反驳的目标不是争论,而是把成本和替代方案说清楚,最后仍让用户决定。

2. Simplicity First:简单不是少写,而是不为假想需求付费

仓库的表述是“用解决问题所需的最少代码,不做推测性的东西”。其中最容易被误读的一句是“不要为单次使用代码创建抽象”。它不是反对函数、模块或设计模式,而是要求抽象有正在发生的重复和变化作为理由。

例如现在的需求只有“计算订单的百分比折扣”,下面的函数已经把输入、计算和输出写清楚:

1
2
def calculate_discount(amount: float, percent: float) -> float:
return amount * percent / 100

此时新增 DiscountStrategy、工厂、配置解析器和多个子类,只是在为“以后也许有”的规则提前付维护成本。每多一个接口,未来读代码的人就多一个问题:它是否真的有多个实现?配置到底从哪里来?

但若系统已经有会员折扣、满减、优惠券互斥和地区税率等规则,上面的函数很快会塞进一长串条件。这个时候拆分策略或规则表可能更清楚。决定因素不是代码行数,也不是“用了设计模式就高级”,而是当前变化是否已经让简单写法难以理解或验证。

可以用四个问题检查复杂度是否合理:

  1. 这段新结构解决的是已经出现的哪个具体变化?
  2. 删除它后,当前需求是否仍能清楚实现?
  3. 项目里是否已有同类边界或约定,复用是否比新建更便宜?
  4. 这份复杂度会让测试和排错更简单,还是只是让名字更多?

如果前两个问题回答不出来,通常先不要加。所谓“没有为需求之外的场景写错误处理”也应谨慎理解:它指的是不要虚构不可能的分支,不是让程序忽略真实的网络异常、用户输入和安全风险。项目已经有的安全边界、合规要求和错误处理约定仍然要遵守,它们本身就是需求的一部分。

3. Surgical Changes:用 diff 控制变更半径

这条最容易落地,因为它有一个可见产物:Git diff。它要求 Agent 只动必须动的部分,遵循当前文件风格,不把“我看见了,可以顺便修”当作授权。

回到开头的日志任务。假设已有函数如下:

1
2
3
4
export async function upload(file: File) {
const result = await storage.save(file)
return { url: result.url }
}

需求是记录成功上传的文件名。最小改动可以是:

1
2
3
4
5
 export async function upload(file: File) {
const result = await storage.save(file)
+ logger.info({ fileName: file.name }, 'upload completed')
return { url: result.url }
}

下面这些改动即使“看上去更好”,也应该另开任务:把全文件分号统一、把 File 换成自定义 DTO、将返回值改为 UploadResult、清理老的注释、改写存储层 API。它们提高了审阅成本,也扩大了回滚范围。

这条规则并不要求机械地只改一行。若新增日志导致一个 import 变得未使用,删除它属于清理“本次改动制造的垃圾”;如果文件里本来就有未使用变量,则应该在交付说明里指出,不要擅自删掉。仓库把这个界限说得很清楚:清理自己的痕迹,不替别人补历史债。

一次改动完成后,可以请 Agent 或自己做一次 diff 审计:

1
2
3
4
5
逐个文件回答:
1. 这个文件为什么必须改?
2. 每个代码块和需求的哪个词对应?
3. 是否夹带了格式化、重命名、依赖升级或重构?
4. 如果有额外修改,能否拆成独立提交?

这比笼统要求“改得小一点”更可检查。它也特别适合初学者建立 review 习惯。你不必立刻判断所有代码是否优雅,先能识别一份 diff 有没有越权,就已经避开了很多 AI 协作中的坑。

4. Goal-Driven Execution:把命令换成可验证的完成定义

用户常给 Agent 的是命令,例如“修登录 bug”“加校验”“重构这个模块”。命令告诉它要做什么,却没有告诉它什么时候算完成。模型很容易挑一个看似合理的停止点,然后说“已完成”。

这条规则要求把命令改写成目标加检查。例如:

模糊命令 更可验证的目标
修复登录 bug 先写出能重现“锁定用户仍可登录”的测试,再让该测试通过,并运行认证测试集
给接口加校验 为非法输入写测试,确认返回既定错误格式,再让测试通过
重构支付模块 改动前后运行同一组支付契约测试,输出行为不变
增加导出功能 固定字段、权限和空数据的样例,生成文件后逐项检查

这里的关键不是“必须先写单元测试”。当任务是改文案、调整样式、迁移配置或排查线上数据时,测试未必是最佳反馈。成功条件也可以是构建通过、静态检查通过、截图与设计稿一致、API 响应符合约定,或由人检查一个明确的文件。重点是让任何人都能回答:“依据什么说已经完成?”

多步骤任务可以写成很小的计划,每一步带一个验证点:

1
2
3
4
1. 阅读现有认证测试和错误码定义 → 验证:能说明当前锁定状态的行为
2. 增加失败用例 → 验证:修复前测试失败,且失败原因符合预期
3. 最小修改认证判断 → 验证:新增测试通过
4. 运行认证相关测试与类型检查 → 验证:无回归

“循环直到验证完成”不是要求 Agent 无限重试。连续失败时,应当停下来报告已尝试的路径、错误信息和下一项需要确认的决策。没有这个退出条件,Goal-Driven Execution 可能变成无意义的工具调用循环。

四条规则如何在一次任务中协作

把四条拆开理解后,再看一个完整但不复杂的例子。需求是:“管理员导出订单 CSV 时,排除已删除订单,并记录导出数量。”

第一步:列出需要查和需要问的事情

Agent 先检查路由、权限中间件、订单查询、现有 CSV 测试和日志格式。它若发现系统已有软删除字段 deletedAt,就不该问“怎样判断删除”;若文档没有说明 CSV 是否要包含取消订单,则应指出这会影响结果,并请产品方决定。

这一步对应 Think Before Coding。重点在于把“我不知道”变成一个可以回答的问题,而不是直接选择最顺手的实现。

第二步:写出最低限度的目标

在假设“取消订单仍应导出”已经确认后,目标可以这样写:

1
2
3
4
5
6
当管理员请求导出时:
- 只输出 deletedAt 为 null 的订单;
- 保持现有 CSV 字段与排序不变;
- 成功后按项目既有日志格式记录导出数量;
- 补充或更新测试,覆盖已删除订单不会出现在 CSV 中;
- 运行订单导出相关测试。

这一步对应 Goal-Driven Execution。它同时限定了行为和验证范围,避免 Agent 改到一半又自行决定“顺便加入日期筛选和异步任务”。

第三步:选择最小实现

如果导出服务已有 findMany 调用,只需要补上 deletedAt: null 条件和一条日志。此时没必要引入新的 Export Job、队列、配置开关或“可插拔过滤器”。这些选择对应 Simplicity First。

如果当前查询已经由一个专门的 activeOrders() 方法处理,复用它反而更简单。简单从来不是“代码越少越好”,而是让当前项目的读者最容易看出行为从哪里来的。

第四步:审计改动范围和结果

检查 diff 后,理想结果只涉及导出查询、相应测试和必要 import。若自动格式化工具改了整份文件,最好把格式化从当前提交拆出去。然后运行目标测试,必要时再跑类型检查或构建。

到这里,Surgical Changes 和 Goal-Driven Execution 才真正收尾。前者检查“改了不该改的吗”,后者检查“该做的真的做了吗”。两者缺一个都不够。

它的三种分发形态,以及为什么不能混为一谈

仓库把几乎相同的准则写成了多种文件。内容相似,不代表加载时机相同。

形态 适用范围 何时可能进入模型上下文 使用时要确认什么
CLAUDE.md 单一项目 Claude Code 读取项目指令时 文件位置、项目已有规则和优先级
Cursor .mdc Rule 单一项目 Cursor 根据 Rule 元数据加载 仓库版本使用 alwaysApply: true,会常驻生效
Claude Code 插件中的 Skill 安装该插件的多个项目 平台发现并调用该 Skill 时 插件是否安装成功,Skill 是按需还是自动触发

当前仓库的 Cursor Rule 明确设置了 alwaysApply: true。这表示作者希望它在 Cursor 项目中持续发挥作用。Skill 的 front matter 则把“写代码、评审代码、重构代码”等情景写进 description,供宿主发现或匹配。至于某个具体版本的 Claude Code 会在什么时候把 Skill 正文送进上下文,应当以该产品的文档和实际日志为准,不能从一个 SKILL.md 文件名直接推断“它肯定每次自动生效”。Cursor Rule 源码 Skill 源码

仓库 README 给出 Claude Code 插件和项目级 CLAUDE.md 两条安装路线,且插件安装命令指向维护者提供的 marketplace。安装命令与仓库组织可能调整,实际使用时请以 README 的最新说明为准。安装说明

对个人项目而言,我更建议先用项目级规则。原因很实际:它和代码一起版本控制,团队成员可以 review,也能为不同仓库写出不同的测试命令和边界。等你确认同一套通用纪律确实适合多个项目,再考虑全局插件。

一个可作为起点的项目规则

不要把上游文件原封不动附加到一个已经很长的 CLAUDE.md 末尾。先阅读项目现有内容,合并冲突规则,再保留最有用的部分。下面是一个示意模板,命令和路径需要替换成你项目真实存在的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
## 编码行为

- 实施前先阅读相关代码、测试和项目约定。只有业务选择会改变实现且无法从仓库查到时,才向用户提问。
- 选择满足当前需求的最小实现。不要为单次使用场景创建配置层、抽象层或开关。
- 只修改与本任务有关的文件。保留原有风格;仅清理由本次改动造成的未使用代码。
- 任务完成前说明成功条件,并运行与改动相关的检查。

## 项目约束

- API 错误响应遵循 `src/http/errors.ts` 中的格式。
- 修改 `src/api/` 后运行 `pnpm test api`
- 不得修改 `migrations/`,除非任务明确涉及数据库迁移。
- 提交前运行 `pnpm lint``pnpm typecheck`

上半段来自通用行为原则,下半段才是项目的真实知识。后半段往往更有价值,因为它让 Agent 不必猜命令、错误格式和禁区。若项目规则里出现“必须先运行测试”,还应确保测试命令真的能在当前环境执行。写一条无法运行的规则,通常只会让模型学会解释为什么跳过它。

规则、测试、Hook 与 CI 各管什么

只写规则最容易产生一种错觉:模型看见“不要改无关内容”,就永远不会误改。实际上,这些都是概率性的行为约束。模型会遗漏、误解,或者在相互冲突的指令间选错优先级。

你想要的结果 首选机制 Karpathy 准则的作用
合并前必须通过测试 CI、分支保护 提醒 Agent 主动运行相关测试,并报告结果
提交前格式化和静态检查 Git Hook、统一脚本 让 Agent 不要把格式化噪声夹带进无关任务
不允许读取密钥或生产数据 最小权限、密钥管理、环境隔离 无法替代权限系统
API 改动必须兼容客户端 契约测试、CI 要求先定义可检查的成功条件
PR 不要夹带重构 小提交、PR 模板、人工 review 用“每行可追溯”提高自检和 review 质量
需求存在业务歧义 产品规格、人工确认 要求 Agent 在合适的时间暴露歧义

一个容易忽略的边界是:CI 能判定“测试过没过”,却未必能判定“这 30 行重命名是不是用户要求的”。反过来,Surgical Changes 能引导 Agent 避免无关改动,却无法阻止有人绕过测试。因此两者不是替代关系。可以把规则当作事前的行为提醒,把自动化当作事后的客观门禁,把人工 review 留给仍然需要判断的业务含义。

怎样评估它有没有用

仓库列出的观察指标包括:无关 diff 变少、过度复杂导致的重写减少、澄清在实现前而不是出错后发生。它们是很好的方向,但在自己的项目中最好把它们变成可记录的数据。

先准备一组小而真实的任务

不要用“写一个待办应用”这种宽泛题目测试。选 6 到 12 个你确实会遇到的小任务,覆盖不同风险:

任务 主要观察哪条规则 可检查的结果
给函数加结构化日志 Surgical Changes 是否只改必要行,是否保持现有日志格式
修复一个已知回归 Goal-Driven Execution 是否先获得失败复现,修复后是否跑了回归测试
给接口增加一个字段校验 Think Before Coding 是否主动查已有错误格式,歧义是否在编码前提出
做一处小 UI 变更 Simplicity First 是否多加状态管理、配置或不需要的组件层
修改一项配置 四条共同作用 是否识别环境差异并提供可验证的结果

最好让规则组和对照组使用相同模型、相同代码版本和尽量相同的任务描述。生成式模型有随机性,一次成功或失败都说明不了太多。每组至少重复几次,记录过程,而不是只看最终“能不能跑”。

记录四个足够实用的指标

  1. 无关变更率:不直接服务任务的改动行数,除以全部改动行数。这个数需要人工抽查,因为自动工具无法准确判断“相关”。
  2. 首次验证成功率:第一次实现后,目标测试或构建是否通过。它比最终是否修好更能反映返工。
  3. 有效澄清率:编码前提出的问题中,真正改变实现或避免返工的比例。问题越多不代表越好。
  4. 完成成本:从收到任务到交付所花的轮次、工具调用或人工等待时间。规则可能提高质量,也可能让简单任务变慢,两个都要记。

也可以把每次任务压缩成一张记录卡:

1
2
3
4
5
6
7
任务:给订单导出排除已删除记录
规则版本:项目 CLAUDE.md v3
Agent 的假设或提问:取消订单是否需要保留?已从测试中确认,不需提问
改动文件:export-orders.ts、export-orders.test.ts
验证:pnpm test orders 通过
无关改动:0 行
问题:自动格式化改动 18 行,已拆到单独提交

连续积累十几张这样的卡片,得到的信息会比“感觉更稳了”可靠得多。如果有效澄清率很低,说明 Think Before Coding 写得太宽,模型在不停提无关问题;如果无关变更率没有下降,也许需要把“查看 diff 并说明每个文件为何修改”写得更具体,或在 PR 模板中加一项检查。

常见误用,以及它们为什么会失败

把四条原则当作完整开发流程

它们只是行为底线,不包含需求分析、架构设计、迁移、发布、监控和安全评估的完整步骤。复杂功能仍需要规格、风险评审和测试计划。若把任何任务都套成“提问、写计划、测试、评审”的长流水线,一个改错别字也会被放大成流程负担。仓库本身也说明,它偏向谨慎,简单的一行改动不需要完整严谨流程。权衡说明

用“简单”当作拒绝必要工程的借口

认证、支付、数据迁移、并发控制和可观测性经常需要看似多余的代码,因为它们面对的风险真实存在。“没有为不可能的场景写错误处理”不能被曲解成“不处理异常”。正确做法是把风险来源写清楚:哪些是业务要求、哪些是现有系统边界、哪些只是未来猜想。

让 Agent 对每句话都提问

“先确认”不等于“凡事等待”。文件路径、既有命名、测试命令和项目惯例通常可以自行查证。应当提问的是产品选择、数据含义、权限边界和不可逆操作等内容。一个总是追问的 Agent 既慢,也没有真正减少不确定性。

把项目规则写成一份百科全书

规则文件越长,越容易出现重复、冲突和被忽略的关键句。通用行为只保留几条,项目特有内容写成准确的路径、命令和验收条件。详细的领域知识、接口规范和排障手册可以放进独立文档,在任务匹配时再引用或加载。

没有验证环境,却要求“所有测试必须通过”

依赖服务没启动、私有包无法安装、测试数据不存在时,Agent 不能凭空让检查通过。规则应该要求它报告执行过的命令、得到的输出、尚未完成的原因和下一步需要谁处理,而不是诱导它虚构“测试已通过”。

只看最终代码,不看过程证据

一次输出碰巧正确,不代表规则可靠;一次失败,也不代表规则没有价值。保留 Agent 的提问、计划、diff、测试命令和结果,才能知道问题发生在理解需求、选择方案、编辑范围还是验证阶段。

给初学者的落地顺序

如果你第一次给项目加这类规则,不需要从插件、全局配置和多套评测一起开始。下面的顺序足够稳:

  1. 选一个有测试命令、改动风险不高的小项目。
  2. 阅读项目现有规则,加入前文那四条短行为约束,并写清楚真实的测试命令和不可修改目录。
  3. 连续用它完成几次小任务,每次都看 diff 和验证结果。
  4. 记录哪一种错误还会重复。例如总漏跑测试,就补脚本或 CI;总改到相邻文件,就在规则中加入 diff 审计。
  5. 当同一套约束在多个项目都稳定有用时,再考虑安装为跨项目插件。

这条顺序看起来没有“直接装一个万星仓库”那么省事,但你会知道每一条规则为什么存在,也能在它制造阻力时删掉或调整。对 Agent 的约束不是越多越好,而是要对准真实失败模式。

我的结论

andrej-karpathy-skills 的价值不在于四句新颖的口号。它把四种很常见的编码失误,分别对应到假设、复杂度、变更半径和验证条件。对于刚开始用 AI 编码的人,这比“让模型认真一点”具体得多。

我会先保留 Surgical Changes 和 Goal-Driven Execution。前者可以直接用 diff 检查,后者可以直接用测试或构建检查。Think Before Coding 与 Simplicity First 也值得保留,但应该写得足够具体:什么要自行查,什么必须确认,项目里哪些复杂度是现实需要。之后再根据任务记录调整,而不是一次性塞进更多泛泛的原则。

一份好的 Agent 规则,最后应当能回答三个问题:它在阻止哪一种可观察的错误?违反时谁能发现?如果多次无效,下一步该把它改成规则、测试,还是权限门禁?能持续回答这三个问题,Skill 才会从一段漂亮的提示词变成工程流程的一部分。

参考资料