AI Coding 底层心智
18

AI Coding 的底层工作模型

很多程序员第一次认真用 AI Coding,都会高估一件事:以为 Agent 真的“理解了整个项目”。

它没有。

更准确的说法是:Agent 在一组指令、有限上下文和可用工具之间循环工作。它能读文件、改文件、执行命令、观察输出,再根据新的输出继续调整。它看起来像在理解项目,本质上是在不断把“可见证据”转成下一步行动。

这个差别会影响你的用法。

如果你把 AI 当成一个已经熟悉项目的同事,就会自然地给它一句模糊任务:“帮我修一下订单提交失败”。它会开始搜索、猜测、修改、运行测试,最后给你一句“已完成”。如果你把它看成一个基于上下文和工具反馈工作的开发 Agent,你就会先关心另外几个问题:

  • 它实际读了哪些文件?
  • 它有没有看到真实入口?
  • 它的计划是否扩大了改动范围?
  • 它跑的命令能证明什么,不能证明什么?
  • 它遇到失败后,是基于证据收敛,还是在继续试错?

AI Coding 的稳定性,不来自更会写 prompt,而来自你能不能看懂这个工作循环,并把关键证据钉住。

Agent 工作边界:模型、工具、仓库和证据

Agent 不是一个“会写代码的模型”

单独看模型,它只是根据输入生成输出。放到 AI Coding 场景里,事情变成了一个更复杂的系统:

  • 有一层指令:系统规则、个人规则、项目规则、当前任务要求。
  • 有一块上下文:用户粘贴的需求、Agent 读取的文件、搜索结果、历史对话。
  • 有一组工具:文件读取、文件编辑、Shell、测试命令、浏览器、MCP 服务、IDE 能力。
  • 有一个反馈通道:命令输出、测试失败、lint 报错、浏览器截图、Git diff。

真正参与开发的不是“模型”一个东西,而是模型加工具形成的工作循环。

OpenAI Codex 官方文档对 Codex CLI 的描述也很直接:它是在本机选定目录里读代码、改代码、运行代码的 coding agent。Codex 的最佳实践也强调几个方向:给对任务上下文、复杂任务先计划、把可复用规则放进 AGENTS.md、用配置和外部系统约束 Agent 行为。

这些建议背后的共同点不是“提示词更精致”,而是把 Agent 工作时依赖的输入、工具和反馈变得更可控。

程序员需要先接受一个事实:Agent 的代码能力很强,但它没有天然的项目记忆。它没有读过的文件,对它来说就是不存在;它没有跑过的命令,对它来说就不是证据;它没有被明确约束的边界,很容易被当前上下文里最显眼的线索带偏。

一个真实任务在 Agent 眼里是什么样

拿一个常见需求举例:

用户在订单页点击提交后,偶尔会提示“状态异常”,但订单其实已经创建成功。
请修复这个问题。

人看到这句话,会自然联想到订单状态、接口幂等、前端重复提交、支付回调、后端事务、历史兼容逻辑。

Agent 不会自动拥有这些背景。它要先把任务转成一组可搜索线索:

rg -n "状态异常|submitOrder|createOrder|checkout|order status" src test
rg --files | rg "order|checkout|payment|test|spec"
git status --short

这些命令不是为了展示命令行技巧,而是为了回答三个基础问题:

  1. 相关入口在哪里。
  2. 有没有现成测试或规格文件。
  3. 工作区是否已经有未提交改动。

如果这一步跳过,Agent 很可能只改到最先搜索出来的前端提示文案。它看到了“状态异常”,就修改错误提示;看到了 submitOrder,就加一个重试;看到了测试失败,就让测试通过。每一步都像在工作,但问题可能根本不在这里。

Agent 的行动路径高度依赖它先看到什么。

所以,程序员不能只问“它会不会写代码”,还要问“它是从哪里开始看的”。

Agent 的工作循环

一个可控的 AI Coding 过程,通常会经过六个阶段。

Agent 证据驱动工作循环

第一步是任务合同。你给它的不只是需求描述,还包括不能改什么、完成后怎么证明、是否允许新增依赖、是否允许跨模块修改。

第二步是上下文获取。Agent 通过搜索和读文件建立当前任务视野。这里最重要的不是“读了很多文件”,而是读到了入口、调用链、测试、配置和约束。

第三步是计划。计划不是文档仪式,而是在写代码前暴露它的判断。它准备改哪些文件,为什么改这些文件,预计跑什么命令,都应该先说出来。

第四步是小范围编辑。真正的工作边界不是它口头说“我只改一点”,而是 Git diff。一次改动越大,审查成本越高,问题归因越难。

第五步是验证命令。测试、lint、类型检查、构建、浏览器验证、接口调用,都是把代码行为变成证据的手段。命令通过不等于业务完全正确,但命令没跑就更谈不上完成。

第六步是观察和复盘。Agent 要解释命令输出、失败原因、剩余风险,并决定是否继续。很多失控发生在这里:测试失败后没有分类,直接继续改,最后把原本的小问题改成大 diff。

这六步可以很轻,也可以很重。按钮文案这种低风险任务,可以两三步完成。涉及状态机、权限、金额、数据迁移的任务,就必须把每一步都写清楚。

指令层:Agent 首先会被什么约束

Agent 开始工作前,会先读到一组指令。不同工具的文件名和加载规则不完全一样,例如 Codex 使用 AGENTS.md 提供项目指导,Claude Code 常见的是 CLAUDE.md,GitHub Copilot 也有 repository instructions 和自定义指令能力。

这里不要把重点放在文件名上。重点是指令分层。

一份有效的项目规则,应该回答这些问题:

# Project Instructions

## Commands

- Install: `npm install`
- Test: `npm test`
- Lint: `npm run lint`

## Work Rules

- Do not edit generated files under `dist/`.
- Do not add runtime dependencies without approval.
- Keep bug fixes scoped to the failing behavior.
- Before changing backend behavior, read the related tests first.

## Verification

- For behavior changes, run the focused test first.
- If broader shared code changed, run the full test suite.
- Report command output and remaining risk.

这类规则的价值不是“提醒 AI 要认真”。它会改变 Agent 的默认行动路径。

如果规则里写清楚 dist/ 不能改,Agent 在发现构建产物报错时就不应该直接编辑构建产物。如果规则里写清楚新增依赖要先确认,它遇到一个解析问题时就不能顺手安装包。如果规则里写清楚共享代码改动要跑全量测试,它就不能只跑一个局部用例后宣布完成。

项目规则不需要长。长而空的规则反而容易污染上下文。好规则通常来自真实事故:AI 改错过哪里,就把能预防复发的规则写下来。

上下文层:搜索不是理解,读到关键路径才算开始

Agent 最容易犯的错之一,是把相似文件当成关键文件。

比如一个仓库里同时有:

src/order/submit.ts
src/order/submit.legacy.ts
src/demo/order-submit.ts
test/order-submit.test.ts
docs/order-submit.md

如果任务是修线上订单提交,demo 和 docs 可能只是参考;legacy 可能只服务旧接口;真正入口可能在路由、service 或 controller 里。Agent 只读到一个文件并不能说明它找到了入口。

让 Agent 建立上下文时,可以直接要求它输出一份读取记录:

先不要改代码。

请完成上下文侦察:
1. 列出你实际读取过的文件路径。
2. 标出入口文件、核心实现、测试文件和配置文件。
3. 说明哪些文件看起来相关但暂时不应该改。
4. 如果无法确认真实入口,停止并提问。

这个要求有两个作用。

第一,它迫使 Agent 暴露自己的证据。你能看到它是否只读了表层文件。

第二,它让你在实现前纠偏。实现前纠偏很便宜,写完代码再纠偏就会变成清理 diff。

配套代码仓库 DavidDingXu/learn-ai-coding 的工具 module 里有一个轻量代码地图命令,可以在接手陌生仓库时先扫一遍结构:

cd learn-ai-coding/ai-coding-workflow-kit
node src/cli.js codemap --root .

它不会替代 rg 和人工判断,也不会解析完整 AST。它的价值是把文档、配置、测试入口先列出来,给 Agent 和人一个共同起点。

在真实项目里,我更建议把它当作“第一层索引”:先知道仓库有哪些明显入口,再让 Agent 针对具体任务搜索调用链。

计划层:先暴露判断,再允许实现

很多人不让 AI 写计划,是担心它浪费时间。这个担心有一半是对的:如果计划只是泛泛写“分析、实现、测试”,确实没价值。

有价值的计划应该短,但必须具体。

## Plan

1. Read `src/order/submit.ts` and `test/order-submit.test.ts`.
   Reason: confirm current submit flow and existing behavior coverage.

2. Add or adjust one failing test for paid order status rollback.
   Reason: lock the bug before changing implementation.

3. Change only the status transition guard in `src/order/submit.ts`.
   Reason: keep the fix scoped to the observed failure.

4. Run `npm test -- order-submit`.
   Reason: verify the targeted behavior before wider checks.

看计划时,重点看五个信号:

  • 它有没有把读文件和写代码分开。
  • 它有没有说明为什么改这些文件。
  • 它有没有提到测试或可观察验证。
  • 它有没有混入“顺手重构”。
  • 它有没有扩大到不相关模块。

如果计划里出现“优化整体结构”“统一抽象”“顺便清理旧逻辑”,就要停一下。不是这些事情永远不能做,而是它们不应该混在一个 bug fix 里。

AI Coding 最容易产生的问题,不是代码写不出来,而是它在一个任务里做了太多事。计划阶段就是防止这个问题的第一道门。

编辑层:真正的边界是 diff

Agent 说“我只做了小改动”没有意义。真正的边界看 diff。

每次实现后,至少要看两类信息:

git diff --stat
git diff

git diff --stat 看改动范围,git diff 看实际语义。很多问题只看测试是看不出来的。

例如,测试通过但 diff 里出现这些内容,就要谨慎:

  • bug fix 顺手改了公共工具函数。
  • 前端交互问题顺手改了后端接口。
  • 为了让测试通过删除了断言。
  • 修改了锁文件却没有说明新增依赖。
  • 改动了格式化大文件,遮住真实逻辑变化。
  • 把错误吞掉了,页面不报错但业务状态也没处理。

这里有一个很实用的交互方式:

只执行计划第 2 步。

约束:
- 不改计划外文件。
- 不新增依赖。
- 不做重构。
- 如果必须扩大范围,先停止并说明原因。

完成后输出:
- 修改了哪些文件。
- 每个文件为什么必须改。
- 还没有跑的验证命令。

这段话的重点不是措辞,而是把 Agent 的自由度压到当前步骤里。

如果它真的发现必须扩大范围,例如测试入口依赖一个公共状态枚举,而枚举定义有缺陷,那也可以扩。但必须先停下来说明。这个停顿很关键,它让人重新判断风险。

验证层:命令输出是证据,不是结论

Agent 很喜欢说“测试通过”。程序员不能只看这句话。

你要看它跑了什么命令,在什么目录跑的,输出是什么,命令和本次改动的关系是什么。

验证报告要写到这个粒度:

## Verification

- Command: `npm test -- order-submit`
- Directory: `learn-ai-coding/checkout-service`
- Result: passed, 8 tests
- Why this command matters: covers submit flow and paid-order rollback guard

## Not verified

- Real payment provider callback was not exercised locally.
- Existing integration test suite was not run because it requires staging credentials.

这里的“不确定”不是缺点,反而是专业表现。真实项目里很多东西无法在本地完全验证,例如第三方支付回调、生产数据分布、异步任务延迟、浏览器兼容性。Agent 如果把这些都说成“已完成”,风险更大。

配套工程的工具 module 里的验证记录校验命令,就是为了让完成报告不只停留在口头上:

cd learn-ai-coding/ai-coding-workflow-kit
node src/cli.js verify-ledger --file examples/verification-ledger.pass.md

这份记录至少要求有 baseline、change summary、verification commands、evidence 和 residual risk。它不能证明业务一定正确,但能防止最糟糕的情况:没有命令、没有证据、没有风险说明,却说完成了。

反馈层:测试失败以后,先分类再修改

测试失败是 AI Coding 里最容易失控的节点。

很多 Agent 会看到失败就继续改。改一次不行再改一次,最后测试可能绿了,但真实问题已经被绕过去。

失败后先分类。

失败类型 典型信号 应该怎么处理
改前就失败 baseline 没跑,或同一命令在改前已失败 先记录基线,不把旧失败算成本次修复目标
本次引入失败 失败测试和 diff 直接相关 回到最小改动,优先修实现或测试预期
命令环境失败 缺依赖、缺服务、缺密钥、端口占用 不改业务代码,先处理环境或说明无法本地验证
规格冲突 测试期望和需求描述冲突 停下来让人确认业务规则
覆盖不足 命令通过,但关键路径没被覆盖 补验证路径,不要把通过当成完成

这张表在真实工作里很值钱。它能阻止 Agent 把环境问题改成业务代码,把历史失败算成自己修复,把规格冲突硬编码进实现。

你可以直接把它转成提示:

测试失败后不要直接修改代码。

请先分类:
- 这是改前已存在失败,还是本次引入失败?
- 是实现问题、测试预期问题、环境问题,还是需求不清?
- 你需要查看哪些文件或输出才能确认?

分类完成前,不要继续编辑文件。

这种提示比“仔细一点”有效得多,因为它改变了 Agent 的下一步动作。

四种最常见的偏航原因

第一种是上下文缺口。

Agent 只读到 controller,没有读 service;只读到前端组件,没有读 API 契约;只读到新测试,没有读历史兼容用例。它会基于残缺上下文做出看似合理的修改。

解决办法不是一次性塞更多文件,而是让它解释每个文件和任务的关系。上下文要精,不是要多。

第二种是指令冲突。

用户说“尽快修好”,项目规则说“不能改公共接口”,历史对话里又出现“可以重构一下”。Agent 可能会选择当前最显眼的指令。处理这种情况,要把本次任务的硬约束写在任务开头,例如“这次只修 bug,不重构,不改接口”。

第三种是验证太弱。

只跑类型检查,不能证明业务路径正确;只跑单元测试,不能证明页面交互正确;只看页面不报错,不能证明接口状态正确。验证必须和风险匹配。

第四种是长任务积累错误结论。

一次对话太长后,Agent 可能会把早期猜测当成事实。尤其是排障任务,前几轮假设如果没被及时淘汰,会一直影响后续判断。遇到这种情况,最好让它重新整理证据:

请停止修改,重新整理当前事实:
1. 已确认的事实有哪些?
2. 已排除的假设有哪些?
3. 仍未验证的假设有哪些?
4. 下一步最小验证动作是什么?

这个动作不是浪费时间。它是在清理错误上下文。

一套可以直接复用的交互协议

下面这套协议适合中等风险以上的任务。它不是固定格式,你可以按项目改短。

第一步,建立上下文:

先不要改代码。

请围绕这个任务做上下文侦察:
- 用搜索找到可能相关的入口、实现、测试和配置。
- 列出实际读取过的文件。
- 标记你认为必须改和不应该改的文件。
- 如果真实入口不确定,先提问。

第二步,给计划:

请给出最小实现计划。

要求:
- 每一步只对应一个可审查 diff。
- 说明预计修改文件和验证命令。
- 不允许新增依赖、重构或改接口,除非先说明必要性。

第三步,执行一小步:

只执行计划的第一步。

完成后不要继续下一步,先输出:
- git diff --stat 的结果摘要。
- 修改文件和修改原因。
- 需要我确认的风险。

第四步,验证:

运行和本次改动最相关的验证命令。

输出:
- 命令、目录、结果。
- 这个命令覆盖了什么。
- 没覆盖什么。
- 如果失败,先分类,不要直接继续改。

第五步,收口:

请给出完成报告:
- 改了什么。
- 为什么这样改。
- 跑了哪些验证。
- 仍然存在什么风险。
- 哪些规则应该写回项目说明或工作流。

这套协议的核心是把 Agent 的每次行动都绑定到证据。它不要求每次都写长文,但要求每次都有可复查结果。

用配套工具把证据留在仓库里

很多 AI Coding 经验只停留在聊天记录里。下一次换任务、换工具、换对话,就要重新解释。

高频产物应该留在仓库里,下一次任务可以直接读取和复用。

配套工程的工具 module 目前提供四个小工具:

cd learn-ai-coding/ai-coding-workflow-kit

# 生成轻量代码地图
node src/cli.js codemap --root .

# 生成任务上下文包
node src/cli.js task-context \
  --title "修复订单状态回退问题" \
  --files "src/order.ts,test/order.test.ts"

# 生成只读上下文包
node src/cli.js context-pack \
  --title "检查 CLI 合同" \
  --files "src/cli.js,test/cli.test.js" \
  --done "npm test"

# 校验验证记录
node src/cli.js verify-ledger --file examples/verification-ledger.pass.md

这四个工具对应 Agent 工作循环里的四个证据点:

  • codemap 让上下文起点可见。
  • task-context 让任务目标、文件和约束可见。
  • context-pack 把任务上下文、代码地图和验证交接组合成只读上下文包。
  • verify-ledger 让完成证据和剩余风险可见。

它们不是平台,不是框架,也不需要进生产依赖。它们的价值是给真实项目提供可复制的工作形态:任务开始前先留下上下文,任务结束后留下验证证据。

什么时候可以放轻流程

不是所有任务都需要完整协议。

这些任务可以放轻:

  • 改一处明显文案。
  • 调整独立样式,不影响状态逻辑。
  • 补一条文档说明。
  • 改测试夹具里的拼写错误。
  • 在已有模式下添加同类字段。

但只要出现下面任意一种信号,就不要直接让 Agent 实现:

  • 涉及金额、权限、状态机、数据迁移。
  • 涉及多个模块或多个仓库。
  • 没有现成测试。
  • 线上 bug 原因还不清楚。
  • 需要判断历史兼容逻辑。
  • Agent 提议新增依赖或重构。
  • 测试失败需要解释。

流程轻重由风险决定。低风险任务追求速度,高风险任务追求可控。

常见问题

Agent 已经会自己搜索文件,还需要我要求读取记录吗?

需要。自动搜索只能说明它会找文件,不能说明它找对了。读取记录让你看到它的判断路径。很多错误在“它读了什么”这个层面就能发现。

我能不能直接把整个仓库塞给 AI?

一般不值得。上下文越多,噪音越多,旧约束和无关实现也会进入判断。先用搜索和代码地图找到候选路径,再按任务选择关键文件。

命令都跑通过了,还要看 diff 吗?

要。测试覆盖不了所有语义。AI 可能通过删除断言、吞异常、改无关逻辑让命令变绿。diff 是你判断工程边界的核心材料。

Agent 说环境缺依赖,能让它直接安装吗?

先看项目规则。生产项目里,安装依赖可能改变锁文件、运行时体积和安全面。让它说明缺什么、为什么缺、是否已有替代方案,再决定。

什么时候需要开新对话?

当对话里已经积累了很多被推翻的假设,或者任务从排障切到实现、从实现切到重构时,开新对话通常更干净。新对话要带上已确认事实、已排除假设和当前 diff,不要只带最终结论。

把这个模型迁移到自己的项目

你不需要一次搭完整体系。先从四件事做起。

第一,写一份短的项目规则文件。只放每次都要遵守的命令、目录、限制和完成判断,不放口号。

第二,给陌生模块生成一份代码地图。至少知道入口、测试、配置、生成文件和禁止修改目录。

第三,每个中等风险任务开始前,写一个任务上下文包。目标、相关文件、不能改的边界、预期验证命令,都放进去。

第四,任务结束后留下验证记录。哪怕只有三行,也要写清命令、结果和没覆盖的风险。

做完这四件事,Agent 就不再只是“会聊天写代码”的工具,而会进入一个可观察、可审查、可复用的工程流程。

接下来要处理任务授权问题:哪些任务可以让 Agent 主导,哪些任务只能让它辅助,哪些任务必须人先判断?这需要一张任务风险分级表。

可复查资料

搜索全部 42 篇文章。

查看配图