Skip to main content

Agentic Engineering 基础:45 分钟速成课

8 个概念,覆盖真实使用中的 80%

前置要求:Agentic Coding 速成课 那一页讲工具:Claude Code、OpenCode、plan mode、CLAUDE.md、skills、MCP、hooks。本页讲你使用这些工具时需要的纪律。两者互补:有工具但没有纪律,会产出 vibe code;有纪律但没有工具,只是理论。

"Code is not cheap. Bad code is the most expensive it has ever been." Matt Pocock

"Vibe coding is about raising the floor for everyone in terms of what they can do in software. Agentic engineering is about preserving the quality bar of what existed before in professional software." Andrej Karpathy

行业里正在流行一种叙事:AI 是新范式,所以旧的工程规则不再适用;specifications 是新的 source code;model 是 compiler;只要程序表现正确,diff 就不重要。这个说法让人安心,但它是错的。

本章的 thesis,也是本书每一个 Digital FTE 贯穿始终的主线,正好相反。AI 时代的软件基本功比过去更重要。 原因不是情怀,而是机制。你设计的 interface,就是 agent 学习的 interface;你选择的 names,就是 agent 会复用的 names;你画出的 boundaries,就是 agent 会尊重的 boundaries。同一个 agent,在干净、测试完善的 codebase 中,产出的代码质量会比在混乱 codebase 中高出好几档。Architecture 不再只是代码的属性;它也是 agent 的输入。坏代码会产出坏 agents。好代码会让 agents 显得惊人地 competent。

本章讲的 workflow,就是让这种 competence 可以重复出现:一条七阶段 pipeline(idea → grilling → PRD → issues → implementation → review → QA),通过小而可组合的 Skills 实现,并且在 Claude CodeOpenCode 中以同样方式运行。为其中一个写好的 Skills、specs 和 architectural patterns,可以原封不动落到另一个里。方法是常量,工具是变量。

读完本章后,你将能够:

  1. 判断自己处在 vibe coding ↔ agentic engineering 光谱的什么位置,并选择与工作风险匹配的纪律。
  2. 诊断 AI coding 的六种失败模式,并对每一种应用相应的解法。
  3. 在 Claude Code 或 OpenCode 中跑完整的 grill → PRD → vertical-slice issues → AFK implementation 循环。
  4. 写一个 SKILL.md,让 agent 只在需要时加载它,而不是每一轮都烧掉 tokens。
  5. 把 codebase 从 "shallow modules" 重构为 "deep modules",让 AI feedback loops 真正起作用。
  6. 熟练使用这些工作词汇:smart zonedumb zoneclearingcompactionhandoffAFKtracer bulletdesign conceptgrillingjagged intelligence

Pipeline 总览

在进入任何理论之前,先看本章要教的操作形状。七个阶段,五个 Skills,一条流向。后面的每一节,要么解释表格中的一行,要么用代码展示它。

#Stage发生什么Input → OutputSkillSection
1Idea → Aligned conceptAgent 用苏格拉底式访谈追问你,直到双方共享设计wish → design conceptgrill-me§6.1
2Concept → Destination把对话综合成 PRDconversation → PRDto-prd§6.2
3PRD → Backlog把 PRD 拆成 vertical-slice ticketsPRD → tracer-bullet issuesto-issues§6.3
4Issue → Slice实现一个 slice,test-firstissue → reviewable difftdd§6.4
5Slices → Drained backlogAFK loop 在 sandboxes 中清空队列issues → PRs(orchestrator)§6.5
6Diff → Decision人类阅读 diff,运行 QAPR → merge or new issue(taste,不自动化)§6.6
7Codebase health, ongoing找 shallow modules;提出 deepeningscodebase → RFCimprove-codebase-architecture§7.4

阶段 1–3 是 day shift:human in the loop。阶段 4–5 是 night shift:agent 在 sandbox 中 AFK 运行。阶段 6 回到 day shift。阶段 7 每周按 cron 运行,并把新的 issues 喂回阶段 3。整条 pipeline 在 Claude Code 和 OpenCode 中完全相同。

刚接触编程?先读这个。

本章假设你已经写过代码、用过 git、运行过 test suite,并打开过 pull request。如果这些都熟悉,可以跳过这个框继续阅读。

如果还不熟,本章仍然可以作为概念地图阅读。你会获得:workflow 的形状、理解 AI-coding 对话所需的词汇、常见失败的诊断目录,以及让 agents 在真实 codebases 中表现良好的架构哲学。你暂时还不能运行示例代码;这需要先补几周编程基础。诚实路径是:先读一遍本章拿到地图,学习 prerequisites,然后回来跟着代码走。

理解概念部分所需的最低词汇:

  • Repo(repository 的缩写):项目的代码文件夹,由 git 跟踪。
  • Branch:repo 的一个平行版本,你可以在其中实验而不影响主代码。Worktree 是相关概念:磁盘上连接到某个 branch 的一份 repo 副本。
  • Commit:保存下来的一次变更快照,并带有一条简短说明。
  • Pull request (PR):提交给别人 review、再合并进主 branch 的拟议变更。也就是本章第 6 阶段里人类 review 的对象。
  • Test / test suite:检查其他代码是否正确的代码,通常自动运行。"Tests pass" 表示所有检查都通过了。
  • Sandbox(或 container):隔离环境,像一台封闭的小电脑,agent 可以在里面运行、写文件、弄坏东西,而不碰你系统的其他部分。
  • Token:语言模型处理文本的单位。平均大约是一个英文单词的 3/4。一个 100k-token context window 大约能容纳 75,000 个英文单词。
  • Terminal / shell / bash:在电脑上用文本运行命令的方式。本章中以 $ 开头的行,是你要在 terminal 里输入的命令。

1. 从 Vibe Coding 到 Agentic Engineering

两件事在很短时间内接连发生。第一件让第二件成为必要。

1.1 Software 3.0:新的计算范式

Andrej Karpathy 把 software 分成三个时代。Software 1.0 是多数工程师职业生涯中写的东西:显式代码,由 CPU 执行,处理结构化数据。Software 2.0 是 learned weights 的时代:通过整理 datasets 和训练 neural networks 来编程,而不是手写 branching logic。Software 3.0 是我们现在所处的时代:通过 prompting 编程,LLM 像一种可编程计算机,而你放进 context window 的内容,就是你撬动它的杠杆。

这些时代之间变化的是你产出的 artifact。在 1.0 中,artifact 是 executable code。在 3.0 中,artifact 越来越多地是一段写给 agent 的文本。当 OpenCode 发布 installer 时,它发布的不是 bash script;而是一段自然语言,供用户粘贴给 coding agent。agent 会读取环境、在循环中 debugging,并完成可工作的安装。installer 不再是一个传统程序;它是一个 Skill。

这个变化会泛化。写给人的文档("go to this URL, click Settings...")会变成写给 agents 的文档("give this to your coding agent and it will configure your project")。UI 不再是唯一 interface;agent 成为你构建和依赖的每个系统的第二类用户。Agent-native infrastructure(优先为 agents 设计的 APIs、docs、tooling 和 deployment pipelines)就是下一层平台。

本章讨论如何在 Software 3.0 中工作。Skills(§5)是 3.0 artifacts。PRDs 和 tickets(§6)是 3.0 artifacts。AGENTS.mdCONTEXT.md 文件(§3,Failure 2)也是 3.0 artifacts。代码本身越来越多地处在它们的下游。

1.2 Vibe Coding 提高地板;Agentic Engineering 保住天花板

Karpathy 还提出了 vibe coding:让 agent 写代码,不读 diff 就接受输出,只按程序能不能跑来判断。Vibe coding 真实、有用,并且会长期存在。它让非程序员可以在一个周末交付有用工具;它也是 Karpathy 描述自己 side project MenuGen 的方式,这个项目把餐厅菜单照片转换成带菜品渲染图的菜单。Vibe coding 提高了个人能产出软件的地板。这个地板抬升带来的经济后果很大,而且总体是好事。

但第二种纪律正在它之上出现:agentic engineering。Vibe coding 提高地板,而 agentic engineering 保住天花板:专业软件的质量标准。agent 做大部分 typing;你仍然要负责 security、data integrity、maintainability、contracts 和 user experience。Vibe coding 本身不会引入漏洞;粗心使用它的 engineer 会。打字的人变了,并不意味着标准可以下降。

Vibe codingAgentic engineering
Goal提高可构建内容的地板保住专业软件的天花板
Reviewer往往没有;看它能不能跑人类阅读 diff;再叠加 automated review
Architectureagent 输出什么就是什么engineer 设计,agent 实现
Tests可选不可谈判;TDD 位于关键路径
Codebase health接受漂移按计划 refactor;加深 modules
Failure handling"It works for me"可复现、可测试、可解释
Right settingSide projects, prototypes, throwaway toolsProduction systems, regulated work, anything multi-user

本章中的原则和 workflows,是 agentic engineering 的纪律,不是 vibe coding 的自由。当你构建一个组织会信任它处理 payroll、customer escalations 或 financial reconciliation 的 Digital FTE 时,vibe coding 就是 malpractice。你需要地板和天花板:吞吐量被提高,质量标准被保住。

平庸 agentic engineer 和优秀 agentic engineer 之间的差距,比旧时代 "10× engineer" 的差距大得多。Karpathy 说:"10× is not the speed-up you gain. People who are very good at this peak a lot more than 10× from my perspective right now." 缩小这个差距,就是本章的工作。


2. 每个 Coding Agent 继承的三个约束

coding agent 不是魔法工程师;它是一个被 harness 包起来的 model。这个组合有三个属性,会塑造我们在其上构建的每一种 workflow:有限的 attention budget、没有 persistent state,以及 jagged capability profile。

2.1 Smart Zone 和 Dumb Zone

model 预测下一个 token(一段文本,大约是一个英文单词的四分之三)时,它会权衡 context window 中已经存在的每一个其他 token。每个 token 都有有限的 attention budget:一份固定的影响力预算,用来分配给其余 token。一个包含 N 个 tokens 的窗口,大约有 N² 级别的 attention relationships 在争抢这份固定预算。

后果不可讨价还价。在 session 早期,agent 处于 smart zone:清晰、聚焦、recall 良好。随着 session 变长,每个 token 的信号被竞争者稀释。agent 会漂入 dumb zone:忘掉你一开始粘贴的 schema,发明 type file 中不存在的字段,把两个同名变量绑定错,甚至和自己前面的 reasoning 矛盾。同一个 model,同样的 parameters;只是有更多张嘴在同一个盘子里抢食。

在当前 frontier models 上,不管营销页面声称 200k 还是 1M context window,coding work 的实践上限都远低于标称窗口。从业者报告大致收敛到 100k tokens 左右,作为 drift 开始显现的水线。但具体数字没有形状重要:超过标称窗口的某个比例后,你得到的不是更多能力,而是更多花钱购买的 dumb zone。更大的窗口有助于长文档 retrieval;但它不会以同样倍数延长 code 的 reasoning horizon。

Token usage:    0k ────────── 50k ────── 100k ────── 200k ────── 1M
Quality: ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░
↑ ↑
smart zone dumb zone begins

在真实 session 中,这个转折大致是什么样?大概如下:

turn  5  → you paste users.ts schema (8 fields: id, email, name, ...)
turn 9 → agent uses User.email correctly
turn 23 → agent builds a route, refers to User.id, all good
turn 47 → context is now ~80k tokens
turn 52 → agent writes user.emailAddress ← field doesn't exist
turn 55 → agent invents user.preferences ← also not in the schema
⇒ smart zone exited.
⇒ /clear, re-paste schema in a fresh session, continue.

turn 52 和 turn 9 是同一个 model,同一个 prompt。唯一改变的是 attention budget。解法不是硬撑。把每个工作单元切到能放进 smart zone 的大小;一个单元完成后,丢掉 session,开一个新的。

2.2 Memento Problem

Models 是 stateless 的。它们不会在 model provider requests 之间携带任何东西。session 内的连续性,是 harness 每一轮重新喂入 context;跨 session 的连续性,则来自某个 memory system 写入磁盘并在下一次 session 开始时重新加载。

这是一个 feature。agent 最可靠的一点是:clearing the context returns it to a known-good state。刚刚花了 40 轮漂进 dumb zone 的 agent,在 /clear 之后 5 秒钟,会带着全新的 attention budget 阅读你的 fresh prompt,并产出优秀工作。

session 膨胀后,有两种恢复方式:

  • Clearing:结束 session,启动 fresh session。彻底 reset。
  • Compaction:总结上一个 session,并用 summary seed 一个新的 session。有损。

大多数 developers 会先想 compaction,因为它感觉破坏性更小。要警惕这个直觉:compaction 会保留一部分把你带进麻烦的 dumb-zone reasoning。clearing 加上一份小而明确的 handoff artifact(PRD、ticket、AGENTS.md),能让下一个 session 每次都从同一状态开始。可预测的开始,会产出可预测的结束。

工作原则。 把 agent 当成 Memento 的主角来对待。围绕它会忘记这件事规划。让每个重要事实都存在于 environment 中(AGENTS.mdCONTEXT.md、Skill、ticket),不要存在 chat history 中。

2.3 Jagged Intelligence

前两个约束关于 agent 能注意多少。第三个约束关于它擅长什么,而且最容易让 engineers 措手不及。

LLMs 是 jagged 的。它们并不均匀聪明;会在一些 domains 上陡然很强,在另一些 domains 上停滞,而且与人类觉得任务难不难几乎没有相关性。一个 state-of-the-art model 可以 refactor 十万行 codebase,或发现 zero-day vulnerability,却在同一个 session 中告诉你步行去 50 米外的 car wash,而不是开车。两种能力之间的联系,主要取决于 labs 恰好在哪些 RL environments 上训练过。

Frontier models 在输出可验证的任务上接受了大量 reinforcement learning:答案可检查的数学题、能编译并通过 tests 的代码、formal proofs。reward signal 干净时,model 会在这些 circuits 中学得非常好。离开这些 circuits,它就依赖 pre-training intuition,而没有同等反馈去打磨。capability profile 像一片有深谷的山脉:competitive coding 和 code refactoring 是 peaks,physical-world distances 上的 common-sense planning 是 valley。

capability

│ ╱╲ ╱╲
│ ╱ ╲ ╱╲ ╱ ╲
│ ╱ ╲ ╱ ╲ ╱ ╲ ╱╲
│ ╱ ╲╱ ╲╱ ╲ ╱ ╲
│ ╱ ╲ ╱ ╲___
└────────────────────────────────────────► task
code refactor math car-wash common-sense
walking physical reasoning

jagged-intelligence 约束有四个操作含义。

第一,code 是幸运 domain。 你正在整个能力表面最深的 peaks 之一工作,不是因为 coding 天生更容易,而是因为 labs 在经济上优先训练它,而且训练得很重。把这当成好运,不要当成 model "is intelligent" 的证据。离开这个 peak,同一个 model 可能对孩子都能答对的事情自信地犯错。

第二,feedback loops 是你留在 verifiable circuits 里的方式。 Static types、automated tests、lints 和 compile errors,都是模型训练时面对的 reward signal。当 agent 运行你的 tests 并看到失败时,它处在训练中产生最强行为的反馈形状里。没有这些信号,它就回到没有校正的 pre-training intuition。这就是 Failure 3 和 tdd Skill 背后的 deeper why:tests 不只是抓 bugs;它们让 agent 留在 peak 上。

第三,你必须知道自己处在哪个 circuit。 当 agent 做出一个 junior engineer 都不会做的选择时,常常不是它「笨」,而是你把它带离了 peak,进入 labs 没有训练好的区域。"Why would you cross-reference users by email instead of by an explicit user_id?" Karpathy 问道,因为他看到自己的 agent 在 MenuGen 项目上正是这么做的。agent 在跨第三方服务的 identity modelling 上离开了最强 circuits。解法不是更好的 prompt,而是 Karpathy 介入,给出明确的 architectural guidance。

第四,从零开始时,选择 stack,让它落在 peak 里。 jagged map 在语言和 frameworks 上并不对称。Boris Cherny 很直接地解释 Claude Code 为什么用 TypeScript 和 React 构建:"It's very on distribution for the model." 如果其他约束允许,优先 mainstream choices:Python 和 TypeScript 优先于 niche languages,Postgres 优先于 exotic stores,popular frameworks 优先于 hand-rolled ones。你选择的不是一个人独写时最喜欢的技术;你选择的是你的 agent workforce 写得好的技术。长尾会追上来;在那之前,on-distribution choices 能买来几年的有效 leverage。

Animals vs. ghosts。 Karpathy 把 LLMs 描述为 ghosts,而不是 animals:它们是由数据和 reward 塑造的统计模拟,不是由进化塑造的生物智能。结果是:对 agent 大喊不会改善它;同情它不会改善它;"think step by step" 不会唤醒沉睡的 cognition。有效的是把 agent 放到 peak 上(clear context、verifiable feedback、命名良好的代码、精确 spec),然后让训练出的行为触发。把 agent psychology 当成 physics,而不是 personality。


3. AI Coding 的六种失败模式

这三个约束会产生可预测的失败。尤其有六种足够常见,可以当成一个闭合目录来处理。下面的表格是诊断;后面的段落会把每一行展开为 symptom、root cause,以及本章其余部分会编码成 Skill 的 cure。

#SymptomRoot causeCureSkillWhere
1"The agent didn't do what I wanted."你和 agent 之间没有 shared design concept写任何 asset 之前,先通过 Socratic interview 强制对齐grill-me§5, §6.1
2"The agent is way too verbose."没有 ubiquitous language;你和 agent 给同一件事起了不同名字维护一份每个 session 都加载的 domain terms 文件 CONTEXT.mdgrill-with-docs§5, §6.1
3"The code doesn't work."feedback loops 弱;agent 在盲写代码响亮的环境(types、tests、lints)+ TDD red-green-refactortdd§5, §6.4
4"We built a ball of mud."Shallow modules;agents 产出它们的速度比 humans 清理更快每天投资 module design;周期性 deepening passimprove-codebase-architecture§7
5"My brain can't keep up."你在以 5× 正常速度阅读每一行Gray-box principle:design interfaces,delegate implementations(architectural habit)§7.3
6"I'm reviewing more code than I'm building."吞吐量把瓶颈移到了 review把 review 拆成 automated + human layers;vertical slices 让 diffs 保持小automated-review(§6.5 中的 recipe;不在 upstream pack 中)§6.5, §7

Failure 1:"The agent didn't do what I wanted."

最常见的失败是未对齐。你脑中有一个清晰的 feature 画面;agent 构建了一个微妙不同的东西;你们甚至对 "done" 的含义都不一致。这是沟通问题,不是 model 问题。Frederick P. Brooks 在 The Design of Design 中给缺失的东西命名:design concept,也就是关于正在构建什么的、双方共享的临时想法。PRDs、specs 和 conversations 都是试图捕捉 design concept 的 assets;它们本身都_不是_ design concept。

Cure: 在写任何代码或正式 asset 之前,强制 design concept 稳定下来。这个技巧是 grilling:agent 用苏格拉底式方式采访你,一次解决一个决定,沿着 design tree 的每个分支向下走,并针对每个问题提出自己的 recommendation,直到双方对齐。第 5 节展示这个 Skill。

Failure 2:"The agent is way too verbose."

fresh agent 被放进你的项目时,并不知道你们的 jargon。你的 codebase 叫它们 lessons,agent 叫它们 course units。你的团队说 materialisation cascade,agent 写了一整段来描述同一个 idea。你们在互相错频,还为此燃烧 tokens。

这正是 domain-driven design 二十多年前解决的问题:ubiquitous language。项目需要一套单一共享词汇,让代码、tests、conversation 和 documentation 都从中取词。对 agents 来说,它还有第二个好处:更紧的 vocabulary 意味着更少 thinking tokens 花在展开歧义上,更多 attention 用在任务上。

Cure: 在 repo root 维护一份 CONTEXT.md,记录项目的 domain terms,并加载进每个 session。第 5 节会展示 grilling 和 CONTEXT.md 如何在同一个 Skill 里配合。

Failure 3:"The code doesn't work."

你和 agent 对齐了。你写了干净的 spec。agent 产出了代码,而代码坏了,有时很明显,有时很隐蔽。诊断几乎总是:feedback loops 太弱。agent 在盲写代码。

The Pragmatic Programmer 警告过不要 outrunning your headlights:不要承担超过反馈照亮速度的任务。Agents 经常这样做,而且比 humans 更严重,因为它们会很乐意先写一千行,再检查其中有没有一行能编译。coding agent 的有效 IQ,受它所在环境提供的 feedback 质量限制。

Cure: 让环境足够响亮:static types、type-checked imports、automated tests、fast lints、pre-commit hook,以及做视觉工作时的 browser access。然后强制执行 test-driven development,让 agent 采取小而刻意的步骤:failing test、make it pass、refactor、repeat。§5 中的 tdd Skill 编码了这套流程。

Failure 4:"We built a ball of mud."

Agents 会加速一切,也包括 codebase 变得不可维护的速度。如果不干预,它们会产出 shallow modules(许多小文件暴露许多小函数,隐式依赖在它们之间穿线),因为 shallow modules 更容易一个一个生成。一个无法 navigate 自己 codebase 的 agent,会每一轮都写出更差的代码。codebase 会变成 poison loop。

John Ousterhout 在 A Philosophy of Software Design 中给出替代方案:deep modules。少量大的 modules,simple interfaces,把大量功能藏在后面。Deep modules 对 agents 来说更容易测试(test boundary 就是 interface)、更容易 reasoning(callers 不需要知道 implementation)、也更容易 delegate(你设计 interface,agent 写 implementation)。

Cure: _每天_投资 module design(Kent Beck),并周期性运行 improve-codebase-architecture,找出 shallow modules 并提出 deepenings。第 7 节深入讲这些原则。

Failure 5:"My brain can't keep up."

这是一个出人意料、也很严重的 failure mode。第一次与 agents 合作的 senior engineers,经常报告说自己更累了,而不是更轻松,尽管 shipping 了更多代码。agent 以正常速度三到五倍的节奏产出代码时,engineer 也要以_新的节奏_把_整个系统_装在脑中。没有架构纪律,cognitive load 会倍增,而不是分摊。

Cure: gray box principle。全神贯注设计 module interfaces;把 implementation delegate 给 agent;从外部通过 tests 验证 module,而不是阅读里面的每一行。你持有 architectural map;agent 填砖。§7.3 会展开这一点。

Failure 6:"I'm reviewing more code than I'm building."

这是吞吐量的另一面。agent 一旦 shipping 得很快,瓶颈就移动到 code review,而 review 工作会扩张到填满它。解法是把 review 拆成两层:高吞吐的 automated layer,抓住大部分常规问题;低吞吐的人类 layer,专注在 automated layer 无法处理的事情上。

Cure: 一个 automated-review Skill,在 fresh session 中运行,只把 diff、项目 coding standards 和 security checklist 作为输入,并在人类打开 PR 前产出结构化评论。把它作为 CI step 在 pre-merge 阶段运行;它会抓 contract regressions、missing tests、常见 security antipatterns,以及和项目 conventions 不匹配的地方。人类 reviewer 面对的是已经 pre-triaged 的 PR,attention 被释放出来,用于 taste、product fit,以及 automated layer 标出的模糊判断。Vertical slices(§6)让每个 diff 保持小;persistent review loops(§6.5.3)让 automated reviewer 可以按 schedule 运行,而不只在 merge 时运行。这些都不会消除 human review;它只是把人的 attention 挪到 judgement 不可替代的地方。

这些就是本章其余部分会依次消除的六种失败。


4. End-to-End Workflow

后面所有内容都挂在这个骨架上:先把整条 pipeline 的形状固定在脑中,再下钻到 Skills 和代码。

4.1 Day Shift / Night Shift Model

有两类工作。Human-in-the-loop 工作需要人在键盘前回答问题并做 judgement calls:alignment、design、taste、QA。AFK("away from keyboard")工作在 sandbox 中无人值守地运行,早上把 diff 交给你:implementation、refactors、test fills。

pipeline 会在两者之间交替:

每次 transition 都是一次 handoff。每次 handoff 都由一个小而持久的 artifact 介导(CONTEXT.md、PRD、ticket、diff),而不是由一个长时间运行的 session 介导。Long-running sessions 会死在 dumb zone;durable artifacts 会永远留下。 这是让后面整套方法成立的架构洞见。

4.2 "Specs-to-Code" 的边界

Specs 很有用。§6.2 中的 PRDs 是 specs。§6.3 中的 issues 是 mini-specs。CONTEXT.md 也是 spec。这里的论点不是笼统否定 specs,而是更窄:反对把 specs 当成_整个_ workflow,也就是写一份 specification,通过 agent 把它编译成代码,忽略产出的代码,如果出错,就编辑 spec 再重新编译。作为 pipeline 的一个阶段,specs 必不可少。作为一个替代其余 pipeline 的闭环,它会崩掉,原因有两个。

代码才是战场。 代码里藏着 spec 没预料到的约束:feature 必须集成的既有 module、database 实际返回的数据形状、cache 冷启动时才暴露的 bug。一个不回应这些现实的 spec,会随着每次 recompilation 离现实更远;每一轮产出的代码也会_更差_,因为 agent 继承的是越来越长的、无根建议历史。

Specs 会衰减。 三月写的 gamification-prd.md,到七月时就成了一份描述已经不存在的系统的文档:names 变了,boundaries 移动了,requirements 演化了。agent 加载这份 spec 去 "extend" 系统时,在写第一行代码之前就继承了一个 faithfulness problem。

正确模型是 §4.1 中的模型:specs 是 pipeline 某一阶段的 handoff artifacts,不是系统的 source of truth。它们指导一两个 implementation sessions,然后退休。真正持久的是 code、tests 和 CONTEXT.md

Karpathy 对 plan mode 也有同样观察:它会在 reasoning 尚未稳定前急着产出 asset,而正确做法是在写任何代码之前,"work with your agent to design a spec that is very detailed"。grilling-then-PRD-then-issues pipeline 就是这个形状:plan mode 会急着产出 asset;pipeline 先抵达 design concept,再让 asset 自然落出来。

4.3 Vertical Slices 和 Tracer Bullets

§4.1 中最重要的形状决定,是如何把 PRD 拆成 issues。诱惑是横向切分:一个 issue 做 database,一个做 API,一个做 UI。这是错的。横向切分时,agent 要到第三个 issue 落地时才得到端到端 feedback;bugs 会在 seams 处积累;任何一个 issue 都可能卡住其他 issue。

正确形状是 vertical slice,也就是 tracer bullet,来自 The Pragmatic Programmer 中的比喻:发光弹让防空炮手看到火力方向。每个 issue 都薄薄地穿过 feature 触碰的_每一层_。先打一发 tracer 看准不准,然后再全力开火,知道自己会命中。

§6.3 会在 worked example 中演示 vertical slicing 的样子,包括 slices 之间的 dependency graph 如何允许 parallel execution。现在只需要掌握概念:每个 issue 都交付一条 end-to-end path;sequencing 来自 dependencies,而不是 phases。


5. Skills as Encoded Process

每一种 cure 都需要编码成可复用、agent-loadable 的 artifact。这个 artifact 就是 Skill

Principle vs. instance。 这条 pipeline 由五个 principles 驱动:grilling、PRD-synthesis、vertical-slicing、TDD、deepening。每个 principle 当前都有某个 skill pack 中 best-in-class 的 implementation。Implementations 会演化;principles 不会。社区 Skills 的 live registry 是 skills.sh;Matt Pocock 的 pack 在 skills.sh/mattpocock,并提供下面的 worked examples。下个季度如果出现更好的 grill-me,换掉 instance;你的 pipeline 中 grilling 这个 principle 不动。这个架构 invariant 与 §7.3 在代码层面教的一样:interface 稳定,implementation 可变。

5.1 Skill 是什么,不是什么

Skill (n.):以一个单元打包的可教学能力(做好一个任务所需的 instructions 和 resources),放在 environment 中,只在相关时加载进 context window。它是 progressive disclosureharness 中的单元。

Skill 是 agent 读取_的东西;Tool 是 agent 调用_的东西。Skill 可能会说:"when the user asks for a deploy, run bash deploy.sh and verify with the gh tool":Skill 是 prose;bashgh 是 tools。

Skill 也是 on-demand 的。AGENTS.md 每一轮都会加载,并且每个 model provider request 都要付 token 成本;Skill 只在 agent 判断相关时加载。任何不需要每一轮都在 context 中的东西,都应该放进 Skill,而不是 AGENTS.md。这就是 progressive disclosure 的实际含义。

而且 Skill 是 portable 的。同一个 SKILL.md 可以不改地在 Claude Code 和 OpenCode 中运行。纪律跟着文件走;harness 可以互换。

5.2 Skills 放在哪里

两个 harnesses 都会在 session start 时扫描 well-known directories,读取每个 SKILL.md 的 YAML frontmatter,并把 names 和 descriptions 暴露给 agent。body 只有在 agent 判断 Skill 相关时才加载。

skills CLI 会把 community pack 安装到 .agents/skills/,这是跨工具标准位置。一组已安装 skills 的目录大致如下:

project/
└── .agents/
└── skills/
└── grill-me/
└── SKILL.md

同一个 SKILL.md format 可以不改地在两个 harnesses 中使用。区别在于每个 harness 扫描哪些目录,而这只会改变安装时的一个步骤。

Claude Code 2.1.141 会扫描 .claude/skills/<name>/SKILL.md(以及全局 ~/.claude/skills/)。它不会扫描 .agents/skills/skills CLI 会安装到 .agents/skills/,并且只有在该目录已经存在时,才会把安装内容 link 到 .claude/skills/。所以先创建目录,再安装:

mkdir -p .claude/skills
npx skills@latest add mattpocock/skills

安装前已经有 .claude/skills/ 时,每个 skill 都会 link 进去,Claude Code 就能发现这个 pack。(如果你先安装,之后 Claude Code 找不到 /grill-me,原因就是缺少目录:创建 .claude/skills/,再重新运行 install。)

用 plain language 请求即可 invoke 一个 Skill("grill me on this plan"),agent 会根据 frontmatter-description match 加载它。Claude Code 另外接受显式 slash invocation:输入 /grill-me 按名称加载那个 Skill。

OpenCode 会直接扫描 .agents/skills/,所以安装不需要额外步骤:

npx skills@latest add mattpocock/skills

OpenCode 还会扫描 .opencode/skills/<name>/SKILL.md(它自己的位置,优先级最高)和 .claude/skills/<name>/SKILL.md(Claude-compatible),以及全局 equivalents:~/.config/opencode/skills/~/.claude/skills/~/.agents/skills/为 Claude Code 写的 SKILL.md 可以放进这些位置中的任何一个,并且不改地运行。 OpenCode 会从当前目录向上走到 git worktree root,沿途拾取 skills;这对 monorepos 很有用,因为 sub-package 可以有自己的 skills。

用 plain language 请求即可 invoke 一个 Skill。OpenCode 会把每个 available Skill 作为 skill tool 暴露给 agent,agent 在 frontmatter description 匹配请求时调用它,所以加载是否可靠,同样取决于 description 的质量,就像 Claude Code 一样。

一个 format,两个 harnesses,不需要 translation step。install path 是唯一差异,而且只差一个 mkdir

5.3 SKILL.md 的 Anatomy

SKILL.md 有两部分:YAML frontmatter(harness 扫描的 metadata)和 markdown body(agent 在 load 时阅读的 instructions)。

Matt Pocock pack 中 stars 最多的 Skill,grill-me,完整内容如下:body 只有七行。

---
name: grill-me
description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
---

Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.

Ask the questions one at a time.

If a question can be answered by exploring the codebase, explore the codebase instead.

这就是整个 Skill,而 grill-me 是一个拥有数万 GitHub stars 的 pack 中最常用的 skill。三点观察可以泛化:

  1. Skills 不需要很长才有影响力。 这个 Skill 本质上就是三句话,却改变了 planning conversation。只有当长度值得时,才增加长度。
  2. frontmatter 在做真正的工作。 harness 展示给 agent 的是 description,不是 body,所以 description 必须足够具体,让 agent 在正确时刻加载它。"Use when user wants to stress-test a plan, get grilled on their design, or mentions 'grill me'" 远好于 "for grilling."
  3. body 用第二人称直接对 agent 说话,语气就像你对 junior collaborator 说话。"Interview me relentlessly." "Ask the questions one at a time." 直接、陈述、没有犹豫。

更复杂的 Skill(to-prdto-issuestddimprove-codebase-architecture)也是同样形状,只是加上 numbered steps、template 和指向其他 Skills 的 pointers。原则不变:encode the process; do not encode the answer.

5.4 The Five Daily Principles(以及当下最好的 Skills)

五个 principles 与 §4.1 中 pipeline 的阶段一一对应。每个 principle 当前都有一个 best-in-class implementation:今天就可以安装的 SKILL.md。下表引用最常用的 pack(Matt Pocock 的 pack,位于 skills.sh/mattpocock)。每个 Skill name 都链接到 canonical SKILL.md;body 都很短,值得阅读。

StageSkill它做什么
Idea → Aligned design conceptgrill-me苏格拉底式访谈,直到达成 alignment。
Aligned concept → Destination docto-prd把 conversation 综合成 PRD,包含 user stories、implementation decisions 和会修改的 modules 列表。
PRD → Backlog of issuesto-issues把 PRD 拆成 vertical-slice tickets,并标出明确 blocking relationships。
Issue → Implemented slicetdd一次一个 slice,执行 Red–green–refactor。
Codebase health, ongoingimprove-codebase-architecture找出 shallow modules;提出 deepenings;打开一个 RFC issue。

在运行这些 Skill 之前。 Matt 的 pack 预期每个 repo 先运行一次 bootstrap step:setup-matt-pocock-skills。它会 scaffold repo 的 issue-tracker config,并把一个 ## Agent skills block 加入你的 AGENTS.md / CLAUDE.md 中,同时建立 docs/agents/ 目录。engineering skills 会读取这套 scaffolding(to-prd / to-issues 如果存在 docs/adr/,也会使用它),所以安装 pack 后、第一次调用 to-issuestdd 前,先运行 setup 一次。

每个 Skill 的 frontmatter description:,就是 harness 在 session start 扫描并决定向 agent 展示什么时看的那一行。这个 description 决定 agent 是否会在正确时刻加载 Skill,所以它承载真正的重量。grill-me 的完整 SKILL.md 已在 §5.3 原样出现;其他几个 Skill 做什么如下(从已安装 skills 中 paraphrase,不是逐字引用):

  • to-prd 把当前 conversation 变成 PRD,并发布到项目的 issue tracker。它不会重新采访你;它综合已经在 context 中的内容。
  • to-issues 把 plan、spec 或 PRD 拆成项目 issue tracker 上可独立领取的 issues,按 vertical slices 切分,带显式 blocking relationships,并把每个 issue 标为 agent 可领取。
  • tdd 运行严格的 red-green-refactor loop 来构建 feature 或修复 bug:先写一个 failing test,只写足够通过的代码,refactor,repeat;tests 落在 module interfaces 上,而不是 internal helpers 上。
  • improve-codebase-architecture 在 codebase 中找 deepening opportunities,参考 CONTEXT.md 中的 domain language 和 docs/adr/ 中的 decisions,并且只提出建议,不修改代码。

想看 exact frontmatter 的读者,可以 cat 已安装的 SKILL.md files,或者打开上面的 linked sources;上面的 wording 是忠实 summary,不是 quote。注意 summaries 明确写出、读者也能直接看到的一点:to-prdto-issues 都会写入你的 issue tracker,而不只是写一个 local file。

三个属性可以从这五个 Skill 泛化出来:

  1. description 在做加载工作。 它必须足够具体,让 agent 认出_什么时候_加载 Skill,而不只是知道 Skill 关于什么。"Use when..." clauses 和明确 negative scope,就是这种 specificity 的位置。
  2. Skills 会说清自己的 boundaries。 to-prd 不会再次采访;improve-codebase-architecture 不会修改 codebase。这些 negative clauses 让 Skills 可以组合,而不会互相踩脚。
  3. Skills 会说清自己的 pairings。 tdd 隐含地与它实现的 issue 配对;to-issues 与它拆分的 PRD 配对。pipeline 是一条 Skills chain,每个 Skill 都 hand off 给下一个。
Skill loading depends on your model's instruction-following

这条 pipeline 的_架构_(Skills、vertical slices、deep modules、sandboxes)与 model 无关。但它的_操作可靠性_不是。frontier-class instruction-follower(Claude Sonnet/Opus、GPT-5-class、Gemini 2.5 Pro)能根据 description match 加载正确 Skill、按顺序执行 multi-step Skill body,并在 grilling interview 达成 alignment 时 self-terminate。economy 或 local model(deepseek-chat、Haiku-class、Llama-70B、多数 local models)上,这些行为会退化:Skills 错过 trigger,multi-step sequencing 滑掉,literal-output contracts(§6.5 中的 NO_MORE_TASKS signal)被破坏。§2.3 中的 recall 在这里也是解法:在 weaker model 上,scaffold harder。显式按名称 invoke Skills,不要依赖 description-matching;让 Skill bodies 短而 declarative;说明 model must not do 什么,而不只是 should do 什么。

Matt Pocock 的 pack 中第六个 Skill,会闭合 Failure 2(verbose agent / no shared vocabulary):grill-with-docs。它与 grill-me 一样是 Socratic interview,但它_还会_在 conversation 中 decisions crystallise 时,inline 更新 CONTEXT.mddocs/adr/ Architecture Decision Records。在 Matt 的 Software Fundamentals Matter More Than Ever 演讲中,这最初是一个独立的 "ubiquitous language skill",会扫描 codebase 并写 domain glossary;后来它被折叠进 grilling skill 本身,因为术语最好在 decision 被做出的那一刻解决,而不是事后单独跑一遍。greenfield design conversations 中没有 project context 时,用 grill-me;要使用 grill-with-docs,前提是 repo 已经有你希望保持 current 的 CONTEXT.md 和 ADRs。

先构建自己的 Skills,再考虑别人的 pack。 最好的 Skill,是捕捉_你们团队_流程的 Skill。fork mattpocock/skills 是很好的起点。owning the stack、能读懂你加载的每一个 Skill,才会在出问题时给你 observability。


6. Pipeline in Practice

本节用一个 worked example 端到端跑完整 workflow:给课程平台添加 gamification service。同一个例子会用 Python 做一次,也会用 TypeScript 做一次;每一步都能在 Claude Code 和 OpenCode 中运行。重点不是例子本身,而是_形状_。一旦内化这个形状,语言和 harness 都只是细节。

6.1 Stage 1:Grilling the Idea

stakeholder 发来一条 Slack message:

"Our retention numbers aren't great. Students sign up, do a few lessons, then drop off. I'd love to add some gamification to the platform: points, streaks, levels."

这不是 spec;这是 wish。把它交给 agent 并说 "build it",你会得到一个不符合 stakeholder 想法的东西,因为你们两个其实都还不知道他们真正想要什么。invoke grill-me

$ claude
> /grill-me
> Read the Slack message in client-brief.md and grill me on adding gamification.
$ opencode
> Use the grill-me skill. Read client-brief.md and grill me on adding gamification.

agent 会读取 brief,探索 codebase 来理解既有 models(LessonQuizEnrollment),然后一次一个问题地开始提问,并为每个问题给出 recommended answer:

Q1. What earns points? My recommendation: lesson completion + quiz pass.
Skip video-watch events (too noisy, gameable).

Q2. Are points retroactive for existing lesson_progress records?
My recommendation: yes, backfill once on migration, then live going forward.

Q3. Streak granularity? My recommendation: daily, with a 1-day grace period.

Q4. Level thresholds: linear, quadratic, or hand-tuned?
My recommendation: quadratic, capped at level 50.
...

sessions 可能在十五个问题到一百个问题之间结束。agent 报告没有 unresolved decisions、你们拥有稳定的 design concept 时,session 才结束。不写 code,不写 PRD。 grilling 的输出是 chat history;chat history 是被显式化的 design concept。

6.2 Stage 2:From Conversation to PRD

design concept 稳定后,invoke to-prd。这个 Skill 不会再次采访你;它会把你已经说过的内容综合成 Product Requirements Document。

> /to-prd
> Use the to-prd skill on this conversation.

输出是一份遵循固定 template 的 markdown document:

# PRD: Course Platform Gamification

## Problem Statement

Students drop off after a handful of lessons. Retention metrics
indicate completion rates ... [synthesised from the brief]

## Solution

Add a points/streaks/levels gamification layer ...

## User Stories

1. As a student, I earn 10 points when I complete a lesson.
2. As a student, I earn 25 points when I pass a quiz.
3. As a student, I see my current streak on the dashboard.
4. As a student, I see my level on my profile.
5. As an admin, I can see aggregate engagement metrics.
... [12-20 more, each independently verifiable]

## Modules Touched

- NEW: gamification_service (deep module, owns points + streaks + levels)
- MODIFIED: lesson_progress_service (emits events on completion)
- MODIFIED: dashboard route (reads from gamification_service)
- NEW DB: point_events table, streak_state table

## Implementation Decisions

- Level formula: floor(sqrt(total_points / 50))
- Streak grace: 1 missed day allowed
- Backfill: one-time job at deploy

## Out of Scope

- Leaderboards (separate PRD)
- Push notifications (separate PRD)

批准 PRD 前要读什么。 Skim for drift, don't proofread. 你和 agent 已经通过 grilling session 共享了 design concept,而 agent 很擅长 summarisation;逐行阅读属于 dumb-zone work。把注意力放在 summarisation 可能 drift 的四个地方:user stories(有没有被丢掉或发明?)、modules touched(boundary 是否仍然匹配你们讨论的内容?)、implementation decisions(是否匹配 grilling 中做出的 calls?)、out of scope(boundary 是否 creep?)。两分钟 focused skimming 几乎能抓住所有失败;完整阅读整份文档抓到的是同一批失败,却要花十倍 attention。

6.3 Stage 3:From PRD to Vertical-Slice Issues

PRD 描述 destination。下一个 Skill 描述 journey:如何把 PRD 拆成可独立领取的 issues,按 vertical slices 切分,并明确它们之间的 blocking relationships。

运行 to-issues。针对 gamification PRD,它会产出一个小 Kanban board:

┌────────────────────────────────────────────────────────────┐
│ Issue #1 - Award points for lesson completion (E2E) │
│ blocked by: nothing. Type: AFK. │
│ Touches: schema, service, lesson route, dashboard widget │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Issue #2 - Award points for quiz pass (E2E) │
│ blocked by: #1. Type: AFK. │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Issue #3 - Streak counter (E2E) │
│ blocked by: #1. Type: AFK. │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Issue #4 - Level threshold + UI badge │
│ blocked by: #2. Type: AFK. │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Issue #5 - Retroactive backfill of historical lessons │
│ blocked by: #1. Type: human-in-the-loop. │
└────────────────────────────────────────────────────────────┘

几个属性不是偶然的:

  • Issue #1 会交付一个 working slice。 如果团队只 merge #1 就停止,平台也会拥有一个 functioning(虽然 minimal)的 gamification feature。横向切分下,"phase 1" 只会产出一张什么都做不了的 database table。
  • DAG 允许 parallelism。 #1 merged 之后,#2 和 #3 可以在 parallel sessions、parallel branches 上同时运行。两个 AFK agents,早上两个 PRs。
  • #5 被标为 human-in-the-loop,而不是 AFK。Backfills 会触碰 historical data;每一步都需要人看着。Type field 会告诉 §6.5 中的 AFK loop 跳过它。

6.4 Stage 4:Implementation:TDD on One Slice

选队列顶部未被 blocked 的 issue:Issue #1。invoke tdd。这个 Skill 强制严格的 red–green–refactor:写_一个_ failing test,看它 fail;写_刚好足够_让它 pass 的代码,看它 pass;在所有 tests 仍然 green 的情况下 refactor;repeat。

为什么一定是 TDD?两个原因。

  1. 它强制小步前进。 没有 TDD 时,agent 会产出六个文件的代码,然后事后在周围补一层 test。这些 tests 往往会 cheat;它们测试 implementation,而不是 behaviour。有 TDD 时,test 在 implementation 存在之前先写,所以它不能被塑造成刚好适配 agent 写出来的东西。
  2. 它每分钟提供 feedback。 每次 test pass 都是 checkpoint。如果 agent drift,下一个 failing test 会在它产出一百行垃圾之前抓住它。

下面是 Issue #1 在两种语言中的 slice:一个 deep GamificationService module,small interface、wide implementation,以及 focused test file。tdd Skill 假设 test runner 已经可用:开始前先安装一个,Python slice 用 pip install pytest,TypeScript slice 用 npm install -D vitest,否则第一个 red step 会因为缺少 runner 而 fail,而不是因为缺少 implementation。

这里重要的是什么。 下面的例子展示了两点,不读语法也能看出来:

  1. service 有一个极小的 public interface:只有两个 methods(award_lesson_completiontotal_points)。其他所有东西都藏在 class 内部。Callers 不能触碰 internals。
  2. test 只调用这两个 methods。 test 不戳 internal helpers。它检查 caller 会看到的 behaviour("after three completions, the total is 30"),而不是 service 如何计算它。

这个形状(small interface、wide implementation、tests at the boundary)就是 §7 所说的 deep module。Python 和 TypeScript 版本逐行等价。

# gamification/service.py - the deep module's interface

from dataclasses import dataclass
from datetime import datetime
from typing import Protocol


@dataclass(frozen=True)
class PointAward:
student_id: str
points: int
reason: str
awarded_at: datetime


class PointEventStore(Protocol):
def append(self, award: PointAward) -> None: ...
def total_for_student(self, student_id: str) -> int: ...


class GamificationService:
"""Awards and totals points. Streaks and levels live here too,
but in the same module, so the interface stays small."""

LESSON_COMPLETION_POINTS = 10

def __init__(self, store: PointEventStore, clock=datetime.utcnow) -> None:
self._store = store
self._clock = clock

def award_lesson_completion(self, student_id: str) -> PointAward:
award = PointAward(
student_id=student_id,
points=self.LESSON_COMPLETION_POINTS,
reason="lesson_completion",
awarded_at=self._clock(),
)
self._store.append(award)
return award

def total_points(self, student_id: str) -> int:
return self._store.total_for_student(student_id)
# gamification/test_service.py - written FIRST

from datetime import datetime
from gamification.service import GamificationService, PointAward


class InMemoryStore:
def __init__(self) -> None:
self._events: list[PointAward] = []

def append(self, award: PointAward) -> None:
self._events.append(award)

def total_for_student(self, student_id: str) -> int:
return sum(a.points for a in self._events if a.student_id == student_id)


def test_lesson_completion_awards_ten_points():
store = InMemoryStore()
fixed_clock = lambda: datetime(2026, 5, 10, 12, 0, 0)
svc = GamificationService(store, clock=fixed_clock)

award = svc.award_lesson_completion("student-42")

assert award.points == 10
assert award.reason == "lesson_completion"
assert svc.total_points("student-42") == 10


def test_multiple_completions_accumulate():
svc = GamificationService(InMemoryStore())
for _ in range(3):
svc.award_lesson_completion("student-42")
assert svc.total_points("student-42") == 30

这就是 deep module 在工作:一个 two-method public interface(awardLessonCompletiontotalPoints)覆盖一个可以增长到数千行的 implementation。为了证明这个 claim,而不是只是宣称它,下面看 Issue #3(streak counter)落地时会发生什么。

这里重要的是什么。 盯住 public interface,而不是代码行数。这个 slice 之前,service 有两个 methods(awardLessonCompletiontotalPoints)。这个 slice 之后,它有三个(原来的两个加上 currentStreak)。implementation 明显增长了,包括 streak store、activity log 和 date helper,但_这些都没有泄露出去_。Callers 只看到一个 new method。Existing callers 什么都不用改。Existing tests 仍然 green。new test 只调用 new method。这就是 "deep" 在实践中的含义:behaviour 增长,surface 几乎不动。

# gamification/service.py - interface gains ONE method, nothing else changes

class GamificationService:
LESSON_COMPLETION_POINTS = 10

def __init__(self, store, streaks=None, clock=datetime.utcnow):
self._store = store
self._streaks = streaks or InMemoryStreakStore() # internal detail
self._clock = clock

def award_lesson_completion(self, student_id: str) -> PointAward:
# unchanged signature; internally also updates streak state
award = PointAward(...)
self._store.append(award)
self._streaks.record_activity(student_id, self._clock().date())
return award

def total_points(self, student_id: str) -> int: # unchanged
return self._store.total_for_student(student_id)

def current_streak(self, student_id: str) -> int: # NEW - only addition
return self._streaks.streak_length(student_id, today=self._clock().date())
# gamification/test_service.py - existing tests untouched; ONE new test added

def test_streak_grows_with_consecutive_daily_completions():
days = [date(2026, 5, 8), date(2026, 5, 9), date(2026, 5, 10)]
clock = iter(datetime.combine(d, time()) for d in days)
svc = GamificationService(InMemoryStore(), clock=lambda: next(clock))

for _ in days:
svc.award_lesson_completion("student-42")

assert svc.current_streak("student-42") == 3

发生了三件事,它们都是 healthy deep module 的诊断信号:

  • interface 只增加了一个 method,而不是五个。 shallow alternative 会暴露 recordActivitystreakLengthstreakStoresetActivityCalendar:internal mechanics 泄露到 boundary。deep version 只给 callers 需要的东西(currentStreak),除此之外什么也不给。
  • Existing tests 没有变化。 它们钉住的 behaviour 仍然成立;test file 只是 additive。这就是 testing at the interface 带来的好处。
  • new behaviour 在同一 boundary 上得到_一个_ test。 streak store、activity log 和 date helper 不被直接测试;它们通过 currentStreak 的 contract 间接测试,而这才是正确层级。

下一个 slice(Issue #4,level threshold)遵循同样 pattern:增加一个 method,existing tests 不动,在 boundary 上增加一个 new behaviour test。

6.5 Stage 5:AFK Loop

backlog 中有五个 issues,并且已经安装 tdd Skill。你不想坐在键盘前看 agent 慢慢磨完它们。你想让五发 tracer bullets 并行穿过系统,去吃晚饭,明早 review 五个 PRs。

AFK loop 是一个 shell script:收集未被 blocked 的 AFK issues,用清晰 prompt 交给 agent,在 sandboxed container 中运行,重复直到 queue 为空。下面有两个 implementations:一个 minimal bash version(两个 harness 都能用),以及一个 structured TypeScript orchestrator,可以并行运行 slices。

6.5.1 Minimal AFK loop(bash)

这里重要的是什么。 script 在 loop 中做五件事,直到没有任务为止:(1) 从文件夹读取所有 open issues;(2) 读取 recent commit history;(3) 把两者和清晰 prompt 一起交给 agent;(4) agent 选择一个 issue 并实现它;(5) 检查 queue 是否为空,如果为空就停止。任何一步都不需要人坐在键盘前。script 启动后会自己往前走。

#!/usr/bin/env bash
# ralph.sh - the simplest AFK loop. Works with either harness.
# Loops over /issues/*.md, picks the highest-priority AFK issue,
# implements it inside a sandbox, commits, repeats until done.
set -euo pipefail # bash safety: exit on any error, undefined var, or failed pipe

PROMPT_FILE="${1:-prompts/implement.md}"
ISSUES_DIR="${2:-issues}"

# Two env vars carry the harness difference. AGENT_CMD is the binary;
# AGENT_PERM_FLAG is its skip-approvals flag, which is NOT the same
# string in both harnesses (see the tool-tabs below). Everything else
# in this script is byte-identical across Claude Code and OpenCode.
CMD="${AGENT_CMD:-claude}"
PERM_FLAG="${AGENT_PERM_FLAG:---permission-mode acceptEdits}"

while :; do
ISSUES=$(cat "$ISSUES_DIR"/*.md 2>/dev/null || true)
COMMITS=$(git log --oneline -5)

PROMPT=$(cat "$PROMPT_FILE")

RESULT=$($CMD $PERM_FLAG <<EOF
$PROMPT

## Open issues
$ISSUES

## Recent commits
$COMMITS
EOF
)

# Exit only on a line that is *exactly* the sentinel, so the loop
# does not stop if the agent merely quotes the token in prose.
if echo "$RESULT" | grep -qx "NO_MORE_TASKS"; then
echo "queue drained - exiting"
break
fi
done
<!-- prompts/implement.md - fed to the agent on every iteration -->

You are operating AFK on the gamification project.

1. From the open issues, pick the highest-priority issue whose
`Type:` is `AFK` and whose blockers are all closed.
If none, reply with a line containing only `NO_MORE_TASKS` and stop.
2. Read the PRD it references.
3. Use the `tdd` skill to implement one vertical slice.
4. Run the project feedback loops (typecheck, tests, lint).
Do not commit if any fail.
5. Commit referencing the issue number and close the issue.

Skills、prompt 和 issues 在两个 harnesses 中都是 byte-identical。区别在 harness binary 以及它的 skip-approvals flag:Claude Code 用 --permission-mode acceptEdits,OpenCode 用 --dangerously-skip-permissions 达到相同效果。下面两个 env vars 承载这个差异;stdin 上的 heredoc 对两者都适用。

AGENT_CMD="claude" \
AGENT_PERM_FLAG="--permission-mode acceptEdits" ./ralph.sh
AGENT_CMD="opencode run" \
AGENT_PERM_FLAG="--dangerously-skip-permissions" ./ralph.sh

6.5.2 Parallel AFK orchestrator(TypeScript)

bash version 会 sequentially 运行 slices。等你信任这个 loop 之后,下一个 leverage point 是 parallel execution:挑出所有 unblocked issues,为每个 issue 启动一个 sandboxed worktree,并发运行,然后 merge。下面的 orchestrator sketch 了这个 pattern;Claude Code 和 OpenCode ecosystems 中都有 dedicated sandboxing libraries,可以做 production-grade implementations。

这里重要的是什么。 三个 ideas;其他都是 plumbing:

  1. Parallel,不是 sequential。 orchestrator 不是做 slice 1,再做 slice 2,再做 slice 3;它会同时做三个,每个都在自己的 isolated workspace 中。到早上,你有三个 pull requests,而不是一个。
  2. 每次 parallel run 都是 sandboxed。 "sandboxed worktree" 是 codebase 的一份单独副本(git worktree 是 git 内置的多份 checkout 方式),运行在一个不会伤害你 laptop 的 container 里。如果 agent 做错事,blast radius 是一个 worktree。
  3. reviewer 是 fresh session 中的另一个 agent。 一个不同的 agent,用不同(更便宜)的 model,只看 diff,并拿它与项目 coding standards 对照。让写代码的同一个 chat 做 review,就是在 dumb zone 中 review。

代码本身是中等复杂度的 Node.js script;Promise.all 那一行就是 parallelism 发生的地方。

// orchestrator.ts - parallel AFK loop with sandboxed worktrees
import { spawn } from "node:child_process";
import { readdir, readFile } from "node:fs/promises";

interface Issue {
id: string; // e.g. "issue-001"
title: string;
type: "AFK" | "human-in-the-loop";
blockedBy: string[]; // ids of blocking issues
closed: boolean;
}

const HARNESS = process.env.AGENT_CMD ?? "claude"; // "claude" or "opencode run"

async function loadIssues(dir: string): Promise<Issue[]> {
const files = await readdir(dir);
return Promise.all(
files.map(async (f) => {
const raw = await readFile(`${dir}/${f}`, "utf8");
return parseIssue(f, raw); // omitted for brevity
}),
);
}

function unblocked(issues: Issue[]): Issue[] {
const closed = new Set(issues.filter((i) => i.closed).map((i) => i.id));
return issues.filter(
(i) =>
!i.closed && i.type === "AFK" && i.blockedBy.every((b) => closed.has(b)),
);
}

function runInSandbox(issue: Issue): Promise<{ ok: boolean; branch: string }> {
return new Promise((resolve) => {
const branch = `afk/${issue.id}`;
// 1. create a git worktree on a fresh branch
// 2. start a docker container with that worktree mounted r/w
// 3. run the harness inside, with the implement.md prompt
const proc = spawn("scripts/run-sandbox.sh", [HARNESS, branch, issue.id], {
stdio: "inherit",
});
proc.on("exit", (code) => resolve({ ok: code === 0, branch }));
});
}

async function main() {
let issues = await loadIssues("./issues");

while (true) {
const ready = unblocked(issues);
if (ready.length === 0) {
console.log("backlog drained or fully blocked - exiting");
break;
}

// run all unblocked issues in parallel, one sandbox each
const results = await Promise.all(ready.map(runInSandbox));

// automated review on each successful branch BEFORE merge
// (in a fresh session - smart-zone reviewer)
for (const r of results.filter((r) => r.ok)) {
await reviewBranch(r.branch);
}

// reload issues from disk; agents may have closed some and opened others
issues = await loadIssues("./issues");
}
}

async function reviewBranch(branch: string): Promise<void> {
// spawn a *separate* agent session, smaller model, with the
// diff and the coding-standards skill as input. Open a comment
// on the PR. Do NOT auto-merge.
}

main();

orchestrator 中嵌入了三条 principles,它们比代码更重要:

  1. Sandboxes 是 mandatory。 AFK 配上 --permission-mode bypassPermissions 但没有 sandbox,就是 repositories 被毁掉的方式。每个 slice 都有 fresh container、fresh worktree、没有 production credentials、除必要范围外没有 network egress。
  2. reviewer 是 separate agent。 与 implementer 同一个 session 的 reviewer,是在 dumb zone 中 review。fresh session 中的 reviewer,只拿 diff 和 standards,会更清楚地看工作。review 用 smaller model 也可以(往往更挑剔);implementation 用 larger one。
  3. loop 每次 iteration 都从 disk 重新加载 issues。 当 QA 在 §6.6 中生成 new issues 时,它们会自动出现在 queue 中。

6.5.3 Persistent Loops 和 Ambient Agents

上面的 loops 针对一次 backlog 运行。启动、清空 queue、停止。下一步演进,是让它们持续运行。

Boris Cherny 所说的 loop,是用 cron 每分钟、每五分钟或每三十分钟针对一个小型 standing job 调用一次 agent。每次 invocation 都是 fresh session,所以每次都从 smart zone 开始,永远不会积累 dumb-zone drift。agent 不会一直活着;job 一直活着,每个 tick 都出生一个新的 agent 来处理它。

一个项目上的工作 loops 可能包括:

  • PR janitor:重新运行 flaky CI,rebase 到 main,修复 reviewers 留下的 typo 和 lint comments。
  • CI healer:当 flaky test 开始 intermittent fail 时,调查并修复它。
  • feedback clusterer:每三十分钟拉取 incoming user feedback,按 theme 分组,并向 Slack 发布 summary。

这些不是 tools。它们是 ambient agents:一支 persistent、low-intensity 的 AI workforce,在项目旁边运行,处理过去会吞掉 engineering hours 的 background tax,比如 PR janitorial work、CI hygiene、ticket triage、dependency upkeep、log digestion 和 monitoring summaries。单个任务并不值得完整 AFK run;合在一起却会消耗真实时间。把它们作为 loops 运行,它们就会从 engineer 的一天里消失。

minimal persistent loop 就是一行 cron 加一个 prompt file:

这里重要的是什么。 cron job 会按 schedule 运行命令:比如_每周二上午 9 点_,或者_每 30 分钟_。五个字符 */30 * * * * 的意思是_"every 30 minutes, every hour, every day"crontab.guru 可以解码任何 schedule)。下面这一行告诉操作系统:"每半小时,进入我的 project folder,并运行一次 PR-janitor agent。"_ 每个 tick 都是一个 fresh agent session,持续到处理完需要 attention 的 PRs,然后退出。job 永久存在;agents 都是 disposable。

# crontab -e
# every 30 minutes, run the PR-janitor agent in the project
*/30 * * * * cd /home/me/project && \
AGENT_CMD="claude" ./scripts/run-once.sh prompts/pr-janitor.md
<!-- prompts/pr-janitor.md -->

You are the PR janitor for this project.

1. List my open PRs (`gh pr list --author @me`). # gh = GitHub's CLI
2. For each PR:
- If CI failed on a known-flaky test, retrigger only that job.
- If the PR has merge conflicts with main, attempt a clean rebase.
If the rebase is non-trivial, leave a comment and stop.
- If a reviewer left a typo / lint comment, fix it and push.
3. Commit only changes you can explain in one sentence.
4. Do nothing else. Output a one-line summary.

更重的 pattern 是 routine:同一个 loop 在 server-side 执行,而不是从你 laptop 的 cron 执行,所以它不会受睡眠、重启和旅行影响。server-side scheduled-agent features 正在各类 coding-agent products 中出现;把 local-cron version 当成 development form,把 server-side version 当成 production form。prompt 相同,只有 scheduler 改变。

两个 design rules 约束 persistent loops:

  • 每个 tick 都是 fresh session。 除了写入 environment 的东西(PRs、CI logs、小 status file),tick 之间没有状态幸存。loop 是故意 stateless;prompt 承载 role。
  • 每个 loop 只有一个 job。 同时做 PR-janitor work、CI healing 和 feedback clustering 的 loop,会退化成三件事都做不好的 session。一个 loop 对应一个 role,就像一个 Skill 对应一个 role。

AFK pattern 现在已经 end-to-end:§6.5.1 sequentially 运行一个 slice;§6.5.2 parallel 运行多个 slices;§6.5.3 让 workforce 按项目自身生成的节奏 indefinitely 运行。每一步都增加 throughput,而不需要往 team 中加人:这就是 Digital FTE workforce 的 operational shape。

6.6 Stage 6:Human Review 和 QA

loop 运行后的第二天早上,你会有 N 个 pull requests。读 diffs,不要读 agent 对 diffs 的 summary。summary 是 agent 对自己做了什么的说法;diff 才是它实际做了什么。在 production scale 下,两者经常会有微妙但重要的差异。

一个 concrete example,来自 §6.4 的 gamification slice。agent 的 PR summary 说:"Added points for lesson completion. Tests pass. Dashboard widget shows current total." diff 也基本这么说,只是 QA pass 发现:在任何 lesson completed 之前打开 dashboard,会因为 TypeError: Cannot read property 'awarded_at' of null 而 crash。agent 在 service 中处理了 empty-state(让 0total_points 返回),但 React widget 假设存在 last_award_at timestamp。一个 null check 就能修复;但_agent 的 tests 没有覆盖 empty-state UI render_,因为 slice 的 user story 隐含假设至少已有一个 award。这个 observation 会作为新 issue 回到 backlog("add empty-state to dashboard widget; cover with a test"),blocked by nothing,type AFK。PR merge;night shift 明天处理这个 new issue。这个 loop,也就是 human 找到 gap、ticket 回到 queue、agent AFK 修复,正是 pipeline 自我改进的方式。

QA 会产出 pipeline 中最有价值的 artifact:new issues。发现的每个 bug、每个 UX concern、每个原始 PRD 漏掉的 edge case,都会变成 Kanban board 上的新 ticket,并带上合适的 blocking relationships。board 永远不会真正清空;它会持续产出 slices。

这也是 taste 所在的阶段。自动化 QA 是一种值得抵抗的诱惑:agent review agent 的 UI,会得出一种不属于任何具体人的 opinion,结果就是温和、派生、没有粗糙边缘的 slop,正是 unsupervised AI output 的特征。人类决定 "this padding is wrong""this label is too long",是不可约简的一步。agent 以五倍正常速度 shipping;你的工作是确保它以五倍正常速度 shipping your taste,而不是随便谁的。


7. AI-Friendly Codebases 的 Architecture Principles

workflow 和 codebase 不可分割:architecture 越干净,agent 在其中表现越好。Architecture 不再只是目的本身;它也是你 AI workforce 的输入。

7.1 Deep Modules over Shallow Modules

当一个 module interface 很小、后面藏着大量 behaviour 时,它就是 deep;当 interface 和 implementation 大小差不多时,它就是 shallow

对 agent 来说,这个差异是决定性的。在 shallow codebase 中,agent 要追踪许多小文件之间的大量 pairwise dependencies;每个 token 的 signal-to-noise 会下降;tests 会横跨 module boundaries 扩散,因为没有一个 boundary 包含足够行为,值得单独测试。在 deep codebase 中,agent 读取一个 interface 并信任 boundary。tests 放在 interface 上。behaviour 可以在内部添加,而不扰动 callers,也不需要重新测试 callers。

为了把差异变具体,下面是 GamificationServiceshallow version 可能长什么样:也就是一个没有 architectural guidance 的 agent 往往会写出的同一 feature。

这里重要的是什么。 数一数每个 block 中_导出的 items 数量_。shallow version 暴露九个 top-level functions,callers 必须记住以正确 order 和 combination 调用。deep version 在一个 class 上暴露三个 methods;幕后需要发生的事情都在幕后发生。要避免的 bug 是:在 shallow version 中,caller 可以忘记调用 validateAntiCheat,并静默破坏系统。在 deep version 中,caller 根本够不到 validateAntiCheat;它藏在 awardLessonCompletion 内部,并会自动调用。把正确的东西藏起来,就是 deep module 的全部工作。

// gamification/index.ts - SHALLOW: the interface IS the implementation
export function awardPoints(studentId: string, reason: string, n: number): void;
export function totalPoints(studentId: string): number;
export function recordStreakActivity(studentId: string, day: Date): void;
export function streakLength(studentId: string, today: Date): number;
export function computeLevel(totalPoints: number): number;
export function validateAntiCheat(
studentId: string,
event: PointEvent,
): boolean;
export function backfillHistorical(studentId: string, since: Date): void;
export function pointsForLessonCompletion(): number;
export function pointsForQuizPass(): number;
// ... + the data classes each function depends on

九个 top-level functions,每个都能从任何地方调用,每个都暗中依赖其他函数(awardPoints 必须调用 validateAntiCheat;dashboard 对一次 lesson completion 必须调用 awardPoints and recordStreakActivity and computeLevel;任何 caller 忘记其中一个,系统都会静默漂出一致性)。

与 §6.4 中的 deep version 对比:

// gamification/service.ts - DEEP: small interface, large hidden body
export class GamificationService {
awardLessonCompletion(studentId: string): PointAward; // does ALL of the above internally
totalPoints(studentId: string): number;
currentStreak(studentId: string): number;
// streak recording, anti-cheat, level calc, point amounts → all hidden
}

三个 methods。内部仍然存在同样九个 concerns,但它们_不是 interface_。callers 不会忘记调用 validateAntiCheat,因为 callers 根本不能调用它。tests 落在三个 methods 上,不是九个。新的 behaviour(recordStreak、level threshold、backfill)可以加在内部,不改变 contract:这正是 §6.4 所演示的属性。

Heuristic。 如果 IDE 的 Outline view 中某个 module 的内容比它的 public interface 长很多,这个 module 多半是 shallow。把它加深。

7.2 Test at the Interface

这是 §7.1 的 corollary。Tests 应该落在 module interfaces 上,而不是 internal functions 上。 对 internal function 写 test,会钉死 implementation;即使外部可见 behaviour 正确,refactor internals 也会破坏 test。对 interface 写 test,钉住的是 behaviour;只要 contract 保持,internals 可以自由变化。

这正是 tdd Skill 默认强制的东西:tests target interface;agent 在 green steps 之间 refactor internals;suite 用很小的 surface area 提供完整 coverage。

7.3 Design the Interface, Delegate the Implementation

这是 senior engineer 与 agents 合作时最重要的习惯。

你决定 module 暴露什么:contract、names、invariants。这些决定影响每个 caller;它们塑造 architecture;它们需要 taste 和 whole system in mind。

agent 决定 contract 如何被满足:internal data structures、helper placement、operation order。这些只影响一个 module 内部;错误可恢复;不需要 architectural map。

这就是 gray box principle。从外部看,module 被完全指定:interface 可见,internals 按设计不可见。从内部看,agent 可以自由完成优秀 implementation,只受 interface contract 约束。senior engineer 能在脑中持有百万行 codebase 的 architectural map,因为 map 只包含 interfaces。

这让 Failure 5 中的 brain-saturation problem 变得可处理。你不能阅读 agent 写出的每一行;那条路通向 burnout。你_可以_把 module map 保持在脑中,并仔细阅读每一次 interface change。interfaces 的 change-set 很小;modules 内部的 change-set 很大。把 attention 集中在小集合上,才会 scale。

7.4 improve-codebase-architecture Skill

Codebases 会随时间漂向 shallow,尤其是里面有 agents 时。修复方式是周期性的 deepening pass。

即使 Karpathy 在 frontier 上使用最新 models 工作,也很直白地描述这种体验:"Sometimes I get a little bit of a heart attack because the code is very bloaty and there's a lot of copy paste, and awkward abstractions that are brittle. It works, but it's just really gross." 这不是 deep model 失败;这是 model 在 "does the code run" 这个 verifiable circuit 中表现良好,却没有对应的 "is the code well-designed" reward。deepening pass 提供了 labs 没有提供的 reward。

---
name: improve-codebase-architecture
description: Find shallow-module candidates in the codebase and propose deepenings. Run weekly, or after a burst of feature work.
---

You are an architecture reviewer. Walk the codebase and find places
where understanding one concept requires bouncing between many small
files; where pure functions have been extracted only for testability,
not behaviour; where modules are tightly coupled at the seams.

Surface a numbered list of deepening candidates. For each, briefly:

- which existing files would collapse into the new deep module
- what the new interface would be (3-5 method signatures, no more)
- what behaviour would move inside, freeing callers from knowing it

Do NOT make changes. Open a markdown RFC describing the highest-value
candidate as an issue, blocked by nothing, type AFK.

每周运行一次会产出一个 deepening RFC。它进入与 feature work 相同的 Kanban board。它通过同一套 TDD-on-vertical-slices loop 实现。codebase 按 schedule 变健康,而不是靠偶然。


8. Working Vocabulary

精确词汇会加速推理。完整参考是 Dictionary of AI Coding;下面这个子集,是读写本书其余部分所需的最低集合。

TermMeaning
Model参数本身。Stateless。做 next-token prediction,除此之外什么也不做。
Harnessmodel 周围把它变成 agent 的一切:tools、system prompt、context-window management、permissions。Claude Code is a harness; OpenCode is a harness.
Agentmodel + harness,在 context window 中带着 tools 运行。也就是你实际对话的对象。
Context window每次 request 中 model 能看到的固定大小 byte view。有限。它是 model 感知任何东西的唯一表面。
Smart zone / dumb zonesession 早期 attention 清晰的区域 / session 后期 attention 被竞争 tokens 稀释的区域。
Hallucination自信但错误的输出。Factuality hallucinations 来自 parametric knowledge 缺口;faithfulness hallucinations 来自 dumb zone drift。两者修法不同。
Clearing结束 session 并启动 fresh session。hard reset。让 agent 回到已知状态。
Compaction在 memory 中 summarise session,并用 summary seed 一个新 session。有损;会保留部分 dumb-zone reasoning。
Handoff通过 artifact(PRD、ticket、CONTEXT.md)把 context 从一个 session 转移到另一个 session。
AFK"Away from keyboard." 用户启动一个 session,让它在 sandbox 中无人值守地运行。
SkillSKILL.md 文件打包的可教学能力。按需加载。progressive disclosure 的单元。
Tracer bullet / vertical slice一条 issue,端到端穿过系统每一层,交付一条很薄的 path。
Deep moduleinterface 小、内部 implementation 大的 module。让 AI codebases 可 scale 的形状。
Design conceptuser 和 agent 共同持有的、关于正在构建什么的临时 shared idea。不是 asset。
Grilling形成 design concept 的技巧:agent 用苏格拉底式方式采访 user,一次解决一个 decision。
Vibe coding不经过 human review 就接受 agent code。它不同于 "low-quality coding";这个词命名的是 review stance,而不是 output。
Agentic engineering在 production work 中使用 agents,同时保住 professional software quality bar 的纪律。它是 vibe coding 的 opposite stance:floor raised, ceiling held。
Jagged intelligenceLLM capability 的经验事实:在 labs 用 verifiable RL 训练过的任务(math、code)上陡然很强,在这些 circuits 之外停滞。能 refactor 100k lines 的 agent,也可能告诉你步行去 50 m 外的 car wash。
On distribution某件事在 model training data 中被充分代表,因此 model 能胜任地处理它。fresh start 时,选择 model 已经强的 stacks。
Loop / Routinepersistent ambient agent:fresh session 按 schedule 被调用(local cron;server-side "routine"),执行一个小型 standing job。每个 tick 都是 stateless;role 持久存在于 prompt 中。

一个熟练 coder 应该能毫不犹豫地使用这些词。"I'm going to clear, then run tdd on the next unblocked vertical slice""that's a faithfulness hallucination; the docs are still in context, it just stopped reading them around turn forty" 这类句子,会把模糊讨论和真正能推进工作的讨论区分开。


9. Practical Drills

三个练习,按顺序做。每个需要三十分钟到两小时。

Drill 1:Install and run grill-me on a real idea. 选一个你一直拖着没 scope 的 feature。按照 §5.2,在 clean repo 中安装 skill pack(Claude Code 读者:先 mkdir -p .claude/skills,再 npx skills@latest add mattpocock/skills)。打开 Claude Code(或 OpenCode),invoke /grill-me,回答问题直到 agent 停止。不要走 shortcut。数一数问题数量。记下哪些 decisions 是你自己不会主动浮现的。

"good" 是什么样。 针对 non-trivial feature 的 grilling session,通常会有 15–40 questions,耗时 30–90 minutes,然后 agent 才报告 alignment。少于大约 10 questions,通常说明 idea 太小,或者你回答得太慷慨;超过 60,通常说明 agent 在 fishing,所以打断它,并要求它每个 question 都 commit to a recommendation。结束时,你应能 paraphrase 至少三个一开始没有考虑到、后来浮现出来的 decisions。如果做不到,那就是 survey,不是 grilling。一个有用的 diagnostic ratio:大约 one in five questions 应该 surfacing 一个你没有 pre-resolved 的 decision。

Drill 2:Write a vertical slice as a tracer bullet. 从你的 codebase 中选一个 unfinished feature。写一条 single user story,追踪最小 possible end-to-end path。用 tdd Skill 实现它。注意 slice 有多短。注意 integration bugs 比 horizontal slicing 早出现多少。

"good" 是什么样。 slice 在一个 session 内落地,并且同一个 PR 中包含 test、implementation 和 reviewable diff。如果做不到,slice 太厚;拆开。你在 slice during 过程中遇到的 integration friction,就是这个练习的价值;把它 capture 成 new issues,不要扩大当前 slice 去吸收它。

Drill 3:Deepen a module. 在你熟悉的 codebase 上运行 improve-codebase-architecture。选择 highest-value candidate。先_不要_实现;在纸上 sketch new interface(3–5 method signatures,不要更多)。比较 new interface 和 old one 的 surface area(会 collapse 的 files 中 public symbols 的总和)。这个 ratio 是 codebase 变 shallow 的 concrete measure。

"good" 是什么样。 真正的 deepening 通常会 collapse several small modules(大约 5 到 15 个)到一个 deep module 中,并且 public-symbol ratio(old : new)大约是 3:1 or higher。如果 ratio 接近 1:1,这个 candidate 其实并不 shallow;换一个。

每日工作的一张短 checklist:

  • 今天开始 session 前,我 /clear 了吗?
  • 我是否对任何 non-trivial change 使用了 grill-me
  • 我的 issues 是 vertical slices,而不是 horizontal phases 吗?
  • 每个 implementation slice 都通过 tdd 吗?
  • AFK runs 是否在 sandbox 中?
  • reviewer 是否是与 implementer 分开的 separate session?
  • 我读的是 diff,而不是 summary 吗?

10. Closing:The Strategic Programmer

请带走这幅图。

你的 agent 是优秀的战术程序员:一个地面 sergeant,可以在任何语言、任何 framework、深夜里拿下任何 well-specified hill,并在早上带回一个 working slice。你不需要教它如何写 function 或 test。harness、model 和 tools 已经解决了这些。

这个 sergeant 不能做的是决定_哪座 hill_。它不能告诉你正在构建的系统是不是 business 需要的系统。它不能告诉你即将请求的第三个 module 是否应该作为 separate module 存在,还是应该折叠进一个 existing deep module。它不能告诉你所请求的代码违反了一个尚未写下来的 domain constraint。它不能在数月数年里持续记住系统的 architectural map;它没有数月数年;它只有当前 session 和磁盘上的几个文件。

sergeant 之上的所有事情,都是 strategic programmer 的 role,也就是你的 role。与 stakeholder 对齐。形成 design concept。选择 slice。设计 interface。阅读 diff。持有 map。每天投资系统设计,就像 Kent Beck 三十年前写给 humans 的原则一样,而现在它适用于未来十年构建软件的人类 engineers 与 Digital FTEs 组成的 hybrid workforce。

strategic programmer 的 tools,本章已经描述。pipeline(§4)。六种 failures(§3)及其 cures。编码这些 cures 的 Skills(§5)。让 agent 表现良好的 architecture(§7)。让你能 reasoning 这一切的 vocabulary(§8)。无论 Claude Code 还是 OpenCode,纪律相同。无论 Python 还是 TypeScript,纪律相同。无论五年后出现什么 model 和 harness,纪律仍然相同。

本章开头那种「AI 会取代 software fundamentals」的叙事是错的,因为它混淆了_谁在写代码_和_好代码长什么样_。作者变了;标准没有变。对 humans 好的 codebases,也对 agents 好。对 humans 坏的 codebases,也对 agents 坏,而且更坏,因为 agents 会放大这种坏。

读旧书。The Pragmatic Programmer. A Philosophy of Software Design. Domain-Driven Design. Extreme Programming Explained. The Design of Design. 每一页都早于这项技术,但现在比当初更尖锐地适用。它们教 strategic programmer 如何在 sergeant 到不了的时间尺度上思考。

Karpathy 有一句话值得带走:"You can outsource your thinking, but you can't outsource your understanding." agent 会做 typing、searching、boilerplate、API-detail recall、tedious refactor。它也会越来越多地做 thinking:生成 options、权衡它们、draft solutions、run experiments。真正唯一属于你的,是 understanding:为什么构建这个系统、它为了什么、谁依赖它、它绝不能做什么。Understanding 让你能指挥 agent。没有它,agent 没有 destination;没有 destination 的 fast agent,只是让你用昂贵方式迷路。

Boris Cherny 的 corollary 是:当 coding 被解决,domain knowledge 成为 bottleneck 时,最适合写软件的人,是最理解 domain 的人,而不一定是历史上写软件的人。最好的 accounting software 作者,是一个真正优秀的 accountant。历史类比是 printing press:Gutenberg 之前,阅读是一个由少数 literate minority 从事的 specialist trade;他的 press 出现后几十年内,printed output 爆炸;之后几个世纪里,literacy 成为广泛 majority skill,同时不再作为 profession 存在。软件现在也开始同样的 arc。一代人之后,building software 会成为每个 domain 中 professionals 的日常能力(会计写自己的 ledgers,医生写自己的 clinical workflows,律师写自己的 contract analysers,教师写自己的 curriculum tools),而我们称为 "engineer" 的 role 会变得更窄、更深:设计 substrate,让其余 workforce 在上面构建的人。

这就是本书关心的 workforce shape。后续章节中你会 manufacture 的 Digital FTE,是 domain expert 的 tool:由 agentic engineer 构建,但由 accountant、underwriter、analyst、case manager 这些 owns the work 的人 specify、govern 和使用。本章的 principles 和 workflows,是让这些 Digital FTEs 足够可信、值得交给他们 ownership 的 contract。Pipeline、Skills、deep modules、persistent loops、sandboxes、smart-zone discipline、jagged-intelligence awareness:全部服务于一种软件,让 domain expert 不读一行代码也能依赖它。这就是 agentic engineering 与它服务的人之间的 contract。

这就是工作。这就是本章。


Further Reading

  • Matt Pocock, Software Fundamentals Matter More Than Ever:本章 thesis 所依据的 keynote。
  • Matt Pocock, Full Walkthrough: Workflow for AI Coding:§4 和 §5 中 pipeline 的两小时 live walkthrough。
  • Matt Pocock, 5 Claude Code Skills I Use Every Single Day:daily-Skills reference。
  • Matt Pocock, Dictionary of AI Coding:canonical glossary;§8 的来源。
  • Matt Pocock, Skills for Real Engineers:全章使用的 installable skill pack。
  • Andrej Karpathy, From Vibe Coding to Agentic Engineering:命名这套 discipline,阐明 Software 1.0/2.0/3.0 framing,并引入 jagged intelligence 和 §1、§2 使用的 animals vs. ghosts lens。
  • Boris Cherny (Anthropic), Why Coding Is Solved, and What Comes Next:Claude Code 的 creator,谈他的 personal workflow、stack choice 的 "on-distribution" argument、persistent loops and routines,以及 §1.2、§2.3、§6.5.3、§10 中使用的 printing-press analogy。
  • John Ousterhout, A Philosophy of Software Design:deep modules、shallow modules。
  • David Thomas & Andrew Hunt, The Pragmatic Programmer:tracer bullets、headlights。
  • Eric Evans, Domain-Driven Design:ubiquitous language。
  • Kent Beck, Extreme Programming Explained:invest in the design every day。
  • Frederick P. Brooks, The Design of Design:design tree、design concept。

Companion Skills(本章)

本章的 pipeline 贯穿 Matt Pocock pack 中的六个 Skills,下面都给出 direct reading 链接:

  • grill-me:产出 design concept 的 Socratic interview。
  • grill-with-docs:grilling,同时 inline 写 CONTEXT.md 和 ADRs(来自 §3 Failure 2 的 "ubiquitous language" lineage)。
  • to-prd:把 conversation 综合成 PRD。
  • to-issues:把 PRD 拆成 tracer-bullet tickets。
  • tdd:red-green-refactor,一次一个 slice。
  • improve-codebase-architecture:找 shallow modules,提出 deepenings,打开 RFC。

一次性 bootstrap setup-matt-pocock-skills,应在每个 repo 中最先运行,用来 scaffold issue-tracker config 和 engineering skills 所依赖的 docs/agents/ layout。

Matt 的 pack 总共包含 fourteen skillsfull repo)。除了 seven-stage pipeline 和 setup-matt-pocock-skills,它还包括 diagnose(disciplined bug debugging)、triage(state-machine ticket triage)、zoom-out(broader-context reframing)、prototype(throwaway design prototypes)、write-a-skill(创建新 skills 的 meta-Skill)、handoff(§4.1 中 session-to-session handoff artifact discipline),以及 caveman(terse-prompt mode)。它们位于 seven-stage pipeline 之外,但可以与之组合,并且每一个都能在 Claude Code 和 OpenCode 中相同运行。Agent Factory Skillpack reference 和其他 book-specific Skills,请见 Part 5: Building OpenClaw Apps

Flashcards 学习辅助

知识检查

快速完成一次门槛式自测,检查你刚刚走过的核心思想。

Checking access...