AI Coding 的瓶颈不在“写代码”
AI Coding 已经能把“写代码”这件事提速很多,但不少研发仍然会觉得更累。原因并不矛盾:代码生成变快以后,后面的验证、测试、排障、沟通和评审没有同步提速,人反而会被更多变更淹没。
软件交付不是只有编码。一个需求从想法到上线,通常会经历这些环节:
flowchart LR
A[需求理解] --> B[编码实现]
B --> C[本地验证]
C --> D[自动化测试]
D --> E[Code Review]
E --> F[部署发布]
F --> G[灰度观察]
G --> H[线上排障与反馈]
如果 AI 只加速了 B[编码实现],它带来的结果可能是:
- 代码改动更多;
- Review 压力更大;
- 测试范围更难判断;
- 出错后排障成本更高;
- 需求方与研发之间的确认次数没有减少。
一个常见的时间分布可以这样理解:
| 环节 | 典型占比 | AI Coding 当前常覆盖程度 | 瓶颈 |
|---|---|---|---|
| 编码 | 30% | 高 | 生成速度已明显变快 |
| 验证、QA、测试 | 40% | 中低 | 需要真实反馈与稳定工具 |
| 部署、发布、灰度 | 20% | 低 | 涉及环境、权限、风险控制 |
| 排障、沟通、Code Review | 10% | 中低 | 需要结构化证据和上下文 |
这就是局部最优陷阱:AI 在编辑器里飞快地产生代码,但交付链路的总耗时没有按比例下降。真正需要改造的不是“让模型再多写一点代码”,而是让 AI Agent(智能体)能跨过编码阶段,进入测试、验证、排障和交付报告这些环节。
Harness Engineering 要解决的正是这个问题。
Harness Engineering 是什么
Harness 原意是“马具”或“控制装置”。放到 AI Agent 语境里,它指的是一套让模型可控工作的工程系统:给模型稳定的上下文、可调用的工具、清晰的反馈和明确的执行边界。
过去工程师主要写代码;在 AI Agent 参与开发后,工程师还要设计“让 Agent 工作的环境”。
flowchart TB
U[工程师] --> A[定义目标与边界]
U --> B[设计上下文]
U --> C[提供工具]
U --> D[建立反馈闭环]
A --> Agent[AI Agent]
B --> Agent
C --> Agent
D --> Agent
Agent --> Code[修改代码]
Agent --> Test[运行测试]
Agent --> Debug[分析错误]
Agent --> Report[产出交付报告]
Harness Engineering 的核心不是换一个更强的编辑器,也不是堆更多 Prompt,而是把研发环境改造成 Agent 能理解、能操作、能自我纠错的系统。
可以把它拆成四件事:
| 方向 | 要解决的问题 | 典型手段 |
|---|---|---|
| 上下文工程 | 模型应该知道什么、不该被什么干扰 | Prompt 布局、AGENTS.md、Compaction、Snapshot |
| 工具工程 | 模型怎样影响真实环境 | MCP、CLI、API、LSP、测试工具 |
| 反馈工程 | 模型怎样知道自己做错了 | 结构化错误、测试报告、行号、日志摘要 |
| 执行治理 | 模型什么时候自主,什么时候问人 | Spec、Plan、AskUser、Rollback |
一句话概括:Harness Engineering 是为 Agent 打造“工作室”,而不是只给它一个“大脑”。
大模型的物理限制:慢、笨、幻觉从哪里来
很多 AI Coding 产品表现出“慢、笨、会编”的问题,背后并不全是产品缺陷,而是大语言模型(LLM,Large Language Model)的推理机制决定了它会遇到这些约束。
自回归:为什么模型一个 Token 一个 Token 输出
主流大语言模型基于 Transformer 架构,推理时按自回归方式生成内容。它不是一次性写完整段答案,而是不断预测下一个 Token。
可以用这个公式表示:
P(x1, x2, ..., xT) = ∏ P(xt | x1, x2, ..., xt-1)
也就是说,第 t 个 Token 的生成依赖前面所有 Token。
flowchart LR
A[已有上下文] --> B[预测下一个 Token]
B --> C[把新 Token 追加到上下文]
C --> D[继续预测下一个 Token]
D --> C
这解释了两个现象:
- 输出天然是流式的,因为模型必须一步一步往后生成。
- 上下文越长,模型在生成前要处理的历史信息越多。
AI Coding 往往需要把项目说明、文件内容、工具定义、对话历史和错误日志都塞给模型,Prefill 阶段的开销会很明显。
Prefill 与 Decode:首 Token 慢在哪里
一次推理可以粗略分成两个阶段:
| 阶段 | 做什么 | 主要成本 |
|---|---|---|
| Prefill | 处理输入 Prompt,计算已有上下文的中间状态 | 和输入长度强相关 |
| Decode | 逐个生成新 Token | 和输出长度强相关 |
如果 Prompt 很长,模型还没开始输出前就要做大量计算,所以 TTFT(Time To First Token,首 Token 时间)会变长。Coding Agent 把几万 Token 的上下文丢进去时,慢通常不是网络问题,而是 Prefill 本身就很贵。
KV Cache:为什么追加便宜,中间修改昂贵
Transformer 的注意力机制可以简化成三类向量:
- Q(Query):当前要问什么;
- K(Key):历史信息的索引或标签;
- V(Value):历史信息的内容。
标准 Attention 形式是:
Attention(Q, K, V) = softmax(QK^T / √d) V
如果每生成一个 Token 都重新计算所有历史 Token 的 K/V,成本会非常高。KV Cache(Key-Value Cache,键值缓存)会把已经计算过的 K/V 存下来,后续生成时直接复用。
flowchart LR
A[稳定前缀 Prompt] --> B[计算 KV]
B --> C[(KV Cache)]
C --> D[追加新 Token]
D --> E[只计算新增部分]
KV Cache 带来一条非常重要的工程规律:
| 操作 | 对缓存的影响 | 成本 |
|---|---|---|
| 在末尾追加内容 | 旧 KV 可复用 | 低 |
| 修改中间内容 | 修改点后的 KV 失效 | 高 |
| 频繁重排 Prompt | 前缀不稳定,缓存难命中 | 高 |
所以,上下文工程的第一原则是:保护前缀稳定性。
稳定前缀越长,缓存复用越多;频繁把重要内容插在 Prompt 中间,或者每轮都重排大段上下文,会让缓存收益大幅下降。
Prompt Cache:上下文治理直接影响成本
Prompt Cache(提示词缓存)可以把重复 Prompt 的 KV 缓存下来。命中缓存后,模型不用重新处理完整前缀,速度和成本都会改善。
以常见商业模型的定价方式来看,Cache Read 的价格可能只有普通输入的一小部分。即使具体数字随模型变化,基本规律不会变:
| 输入类型 | 说明 | 成本特征 |
|---|---|---|
| Base Input | 全量处理输入 | 成本最高 |
| Cache Write | 写入可复用前缀 | 首次有额外成本 |
| Cache Read | 读取已缓存前缀 | 成本显著降低 |
这也是为什么 Agent 产品会强调 Prompt 布局、会话压缩、规则文件和工具定义的稳定性。它们表面上是产品体验设计,本质上是在顺应模型推理的计算规律。
ReAct:Agent 为什么会形成循环
AI Agent 不是简单的一问一答。更常见的执行模式来自 ReAct:Reasoning and Acting,即“推理 + 行动”。
一个 Agent 循环通常长这样:
flowchart TD
T[Thought<br/>分析当前状态] --> A[Action<br/>调用工具]
A --> O[Observation<br/>获得结果]
O --> T
在 Coding 场景里,这个循环可能是:
Thought: 需要查看报错文件
Action: read_file("src/index.ts")
Observation: 返回文件内容
Thought: 发现类型定义不匹配,需要修改
Action: write_file("src/index.ts", ...)
Observation: 写入成功
Thought: 需要运行测试确认
Action: bash("pnpm test")
Observation: 返回测试失败日志
ReAct 让模型能接触真实世界:读文件、写文件、跑命令、查数据库、调 API。问题也随之出现:每一轮 Thought、Action、Observation 都会追加到上下文里。任务跑得越久,上下文越膨胀,模型越容易被中间过程、试错日志和冗余输出干扰。
flowchart LR
R1[第 1 轮<br/>少量上下文] --> R5[第 5 轮<br/>文件与日志增加]
R5 --> R20[第 20 轮<br/>大量工具返回]
R20 --> R40[第 40 轮<br/>注意力被稀释]
长任务失败经常不是模型完全不会,而是上下文里噪音太多,关键事实被埋住了。
Prompt 的五层结构
当用户在 AI Coding 产品里输入一句需求时,真正送进模型的不是这一句话,而是一整套组合后的 Prompt。
典型结构可以分成五层:
flowchart TB
A[System<br/>系统规则与 Agent 身份]
B[AGENTS.md<br/>项目约定]
C[项目快照<br/>目录、选区、相关文件]
D[会话历史<br/>对话、工具调用、摘要]
E[工具定义<br/>名称、描述、JSON Schema]
F[用户当前请求]
A --> B --> C --> D --> E --> F --> M[模型推理]
每一层的作用不同:
| 层级 | 内容 | 作用 | 稳定性建议 |
|---|---|---|---|
| System | 模型服务商规则、Agent 身份、行为边界 | 定义最高优先级约束 | 尽量稳定 |
| AGENTS.md | 技术栈、代码风格、安全要求、团队流程 | 给项目建立“宪法” | 高稳定 |
| 项目快照 | 当前目录、选中文本、相关文件片段 | 提供任务现场 | 按需变化 |
| 会话历史 | 对话、工具调用、错误、总结 | 保留推理过程 | 定期压缩 |
| 工具定义 | MCP 工具、CLI、API Schema | 说明可执行动作 | 保持结构稳定 |
| 用户请求 | 当前要做的任务 | 驱动本轮执行 | 每轮变化 |
上下文不是越多越好,而是要把重要信息放在正确位置。稳定、长期有效、所有任务都要遵守的内容应该靠前;临时信息和工具返回应该可压缩、可丢弃、可恢复。
AGENTS.md:项目给 Agent 的“宪法”
AGENTS.md 适合放那些长期有效、每次任务都应该遵守的规则。它不适合放一次性需求,也不适合放频繁变化的临时计划。
一个可用的 AGENTS.md 可以这样写:
# AGENTS.md
## Tech Stack
- Package manager: pnpm
- Framework: React + TypeScript
- Test runner: Vitest
- E2E: Playwright
- Lint: ESLint + TypeScript strict mode
## Code Style
- Prefer small pure functions.
- Do not introduce new dependencies without asking.
- Keep public API backward compatible unless the spec says otherwise.
- Use existing error handling utilities in `src/shared/errors.ts`.
## Safety Boundaries
- Never modify generated files under `src/generated`.
- Never run destructive database commands.
- Before changing build scripts, explain the reason and ask for approval.
## Verification
After code changes, run:
```bash
pnpm lint
pnpm test
If UI behavior changes, add or update an E2E test.
这类文件有三个价值:
1. Agent 每次进入项目都能读到统一规则;
2. 团队可以 Review 它,避免 Prompt 只存在个人工具里;
3. 规则文件是稳定前缀的一部分,有利于 Prompt Cache 命中。
## Compaction:把长对话压缩成结构化快照
ReAct 长任务会产生大量中间信息。把所有历史都留在上下文里,会让模型越来越慢,也越来越容易被噪音干扰。更好的方式是主动 Compaction:在关键节点把当前状态压缩成结构化文件,然后清理对话历史。
```mermaid
flowchart LR
A[长对话与工具日志] --> B[阶段性总结]
B --> C[写入 Snapshot 文件]
C --> D[清理会话历史]
D --> E[后续任务 read_file 恢复状态]
例如迁移任务可以沉淀成:
# docs/state/user-module-migration.md
## Goal
Move user profile logic from `legacy/user` to `modules/user`.
## Current Status
- Completed:
- Created `modules/user/profile-service.ts`
- Added tests for `getUserProfile`
- Replaced imports in `src/pages/profile.tsx`
- Not completed:
- `src/pages/settings.tsx` still imports legacy API
- E2E coverage is missing for profile editing
## Key Decisions
- Keep the old API wrapper until all pages are migrated.
- Do not change response shape in this phase.
## Verification Commands
```bash
pnpm test user
pnpm lint
Known Errors
settings.test.tsfails because mock data still useslegacyUser.
这样做相当于把记忆外包给文件系统。Agent 不需要携带完整聊天记录,只要读取这个快照,就能恢复关键状态。
适合写入 Snapshot 的内容包括:
| 内容 | 是否适合 | 原因 |
|---|---|---|
| 阶段目标 | 适合 | 后续执行需要对齐方向 |
| 已完成变更 | 适合 | 防止重复工作 |
| 关键决策 | 适合 | 防止 Agent 重新争论 |
| 验证命令 | 适合 | 形成闭环 |
| 全量终端日志 | 不适合 | 噪音大,占用上下文 |
| 临时猜测 | 不适合 | 容易误导后续任务 |
## Agent 可读性:基础设施要从“给人看”转向“给 Agent 读”
传统研发基础设施面向人设计,强调 GUI、图表、日志和交互体验。但 Agent 不擅长像人一样从复杂界面里提炼语义。对 Agent 友好的系统应该提供结构化、稳定、低噪音的接口。
| 传统设计 | Agent 友好设计 | 原因 |
|---|---|---|
| 酷炫 GUI | JSON、Markdown、API | Agent 能直接解析 |
| 海量日志 | 分层摘要 + 关键错误 | 降低上下文噪音 |
| 鼠标点击 | CLI 或自然语言动作 | 可复现、可自动化 |
| 截图排查 | 结构化诊断报告 | 减少视觉推理成本 |
| 模糊错误 | 文件、行号、类型、建议 | 支持自动修复 |
工具不是给人用的 UI,而是给 Agent 用的 API。一个好工具要满足三个标准:
1. **快**:秒级返回,避免长时间等待让任务链路变脆。
2. **结构化**:输出 JSON 或 Markdown,不要让 Agent 从杂乱文本里猜。
3. **有痛觉反馈**:错误要精确到文件、行号、类型和修复方向。
一个更适合 Agent 的错误输出示例:
```json
{
"status": "failed",
"tool": "typecheck",
"errors": [
{
"file": "src/user/profile.ts",
"line": 42,
"column": 18,
"code": "TS2322",
"message": "Type 'string | undefined' is not assignable to type 'string'.",
"suggestion": "Add a fallback value or narrow the type before assignment."
}
]
}
比起一整屏终端输出,这种结构化结果更容易进入 ReAct 闭环。
MCP、Skill、Sub Agent:三种能力分工
MCP(Model Context Protocol,模型上下文协议)、Skill、Sub Agent 经常一起出现,但它们解决的问题不同。
| 机制 | 定位 | 适合放什么 | 上下文成本 | 典型场景 |
|---|---|---|---|---|
| MCP | 原子工具 | GitHub、Slack、数据库、Kubernetes、内部 API | 工具定义常驻 | 让 Agent 能操作外部系统 |
| Skill | 可执行 SOP(Standard Operating Procedure,标准作业流程) | CI Debug、发布检查、迁移流程 | 按需加载 | 固化成熟流程 |
| Sub Agent | 独立执行单元 | 长任务子问题、并行探索 | 上下文隔离 | 多模块排查、代码库探索 |
MCP:让 Agent 获得动作能力
MCP 的核心价值是把外部工具暴露给模型。它提供的最好是小而清晰的原子动作,例如:
{
"name": "get_pull_request",
"description": "Get pull request metadata and changed files.",
"input_schema": {
"type": "object",
"properties": {
"repo": { "type": "string" },
"pr_number": { "type": "number" }
},
"required": ["repo", "pr_number"]
}
}
工具描述要短、准、无歧义。描述越像“产品说明书”,越容易浪费 Token;描述越像“函数签名 + 使用边界”,越适合 Agent。
Skill:把成熟打法固化下来
Skill 更像一个可执行知识包。它可以包含说明、脚本、示例和检查清单。
skills/
ci-debug/
skill.md
run.sh
examples/
typescript-error.md
flaky-test.md
skill.md 可以写成:
# CI Debug Skill
Use this skill when a CI job fails.
## Steps
1. Fetch the latest CI logs.
2. Classify the failure:
- typecheck
- unit test
- e2e test
- build
3. Extract the first actionable error.
4. Propose a minimal fix.
5. Run the related verification command.
## Output
Return a Markdown report with:
- failure type
- root cause
- changed files
- verification result
Skill 的意义是减少模型每次从零探索,让团队经验变成 Agent 可执行的流程。
Sub Agent:用上下文隔离解决长任务熵增
单个 Agent 跑长任务时,上下文会越来越乱。Sub Agent 的做法是把任务拆出去,每个子 Agent 在独立上下文里完成一部分,只把摘要返回给主 Agent。
flowchart TB
O[Orchestrator<br/>主 Agent] --> A[Worker A<br/>探索模块 A]
O --> B[Worker B<br/>探索模块 B]
O --> C[Worker C<br/>探索模块 C]
A --> SA[结构化摘要 A]
B --> SB[结构化摘要 B]
C --> SC[结构化摘要 C]
SA --> O
SB --> O
SC --> O
O --> R[汇总计划与执行]
关键点是“只交换摘要”,不要把所有子任务日志都塞回主会话。结构化通信比堆上下文更重要。
LSP:Agent 编码质量的最低保障线
LSP(Language Server Protocol,语言服务器协议)是 Agent 编码工具链里非常关键的一环。人类开发者在编辑器里能看到红线、类型错误、跳转定义和引用关系;如果 Agent 只能读文件文本,它就感知不到这些 IDE 级反馈。
接入 LSP 后,Agent 可以获得:
- 类型错误;
- 语法诊断;
- 定义跳转;
- 引用查找;
- 自动补全;
- 重命名影响范围。
这能形成自动化闭环:
flowchart LR
A[Agent 编辑代码] --> B[LSP 诊断]
B --> C{是否有错误}
C -- 有 --> D[定位文件和行号]
D --> E[最小修改]
E --> B
C -- 无 --> F[运行测试]
F --> G{测试通过}
G -- 否 --> H[读取失败报告]
H --> E
G -- 是 --> I[生成交付摘要]
没有 LSP 和测试闭环时,Agent 容易在错误上继续叠加错误,直到上下文爆炸。LSP 的价值不是“让模型更聪明”,而是让模型能及时知道自己错在哪里。
Spec 与 Plan:先约束目标,再执行任务
Agent 最容易浪费轮次的地方,是在需求不明确时自行猜测。长任务应该先写 Spec,再写 Plan。
Spec 回答“做成什么样”,Plan 回答“怎么做”。
| 文档 | 解决的问题 | 应包含内容 |
|---|---|---|
| Spec | What | 输入输出、功能边界、副作用、成功标准 |
| Plan | How | 步骤拆解、检查点、回退策略、验证命令 |
一个简单 Spec 示例:
# Spec: Unified Slash Menu
## Goal
Merge command list and skill list into one `/` triggered menu.
## Behavior
- Typing `/` opens a unified menu.
- Menu items include built-in commands, custom commands, and skills.
- Typing `/skillName` selects the corresponding skill.
- Typing `$` must not open any dropdown.
## Success Criteria
- Unit tests cover filtering and selection.
- TUI interaction test covers:
- open menu
- search skill
- select skill
- verify input value
Plan 可以这样写:
# Plan
## Step 1: Locate menu data source
Find current command and skill providers.
## Step 2: Introduce unified item model
Create:
```ts
type SlashMenuItem =
| { type: "builtin"; name: string; description: string }
| { type: "command"; name: string; description: string }
| { type: "skill"; name: string; description: string };
Step 3: Update trigger logic
/opens unified menu.$no longer opens dropdown.
Step 4: Add tests
- filtering
- selection
- trigger behavior
Rollback
If TUI behavior regresses, revert menu trigger changes only.
Spec 和 Plan 的价值是减少无目的试错。需求存在多条路径时,Agent 应该在第一轮询问,而不是猜错后跑二十轮。
## 人机协作:什么时候自主,什么时候求助
Harness Engineering 不是让 Agent 永远自主,也不是让它频繁打断人。关键是给它明确边界。
| 情况 | Agent 应该怎么做 |
|---|---|
| Spec 明确、工具齐全、可自动验证 | 自主执行 |
| 失败可回退、影响范围局部 | 自主尝试修复 |
| 需求有多种解释 | 先问人 |
| 缺少环境信息 | 先问人或请求权限 |
| 涉及不可逆操作 | 必须确认 |
| 需要改变公共 API 或架构方向 | 必须确认 |
一个好的 AskUser 问题应该具体到决策点:
```text
当前有两种实现路径:
A. 在现有 SlashMenu 里合并 skill 数据源,改动小,但菜单模型会继续耦合 UI。
B. 新建 SlashMenuItem 模型和 provider,改动稍大,但后续可扩展更多 item 类型。
请选择 A 或 B。若无偏好,将采用 B,因为 Spec 提到后续可能扩展更多触发项。
坏问题通常是泛泛而谈:
你希望我怎么做?
Agent 要问的是“关键分岔点”,不是把任务重新丢回给人。
从 Review 代码转向 Review 交付产物
AI 生成代码后,如果人仍然逐行 Review 所有改动,注意力并没有真正释放。更合理的方式是让 Agent 产出可审计的交付包,人主要 Review 结果、证据和风险。
交付包可以包含:
| 内容 | 目的 |
|---|---|
| 变更范围 | 知道改了哪些模块 |
| 行为变化 | 知道用户能感知到什么 |
| 测试覆盖 | 知道关键路径是否验证 |
| 失败与回退 | 知道风险是否可控 |
| 截图或录屏 | 验证 UI/TUI 行为 |
| 性能与兼容性说明 | 防止非功能性退化 |
一个 Agent 交付报告模板:
# Delivery Report
## Requirement
Unify skill list and command list into one `/` dropdown menu.
## Changed Files
- `src/menu/slash-menu.ts`
- `src/menu/providers/skill-provider.ts`
- `src/menu/providers/command-provider.ts`
- `tests/slash-menu.test.ts`
## Behavior Before
- `/` opens command menu.
- Skills use a separate trigger.
- `$` opens a dropdown.
## Behavior After
- `/` opens unified menu.
- Built-in commands, custom commands, and skills are mixed.
- Selecting a skill inserts `/skillName`.
- `$` does not open a dropdown.
## Verification
```bash
pnpm test slash-menu
pnpm lint
pnpm cli-test slash-menu
Result
- Unit tests: passed
- TUI test: passed
- Lint: passed
Risks
- Menu item ranking may need product review.
人不再只看“代码长什么样”,而是看“需求有没有被正确交付”。当然,架构质量仍然要治理,不能只看测试绿不绿。这个问题可以通过架构规则、公共 API 约束、变更预算和质量指标来补齐。
## 端到端闭环:让 Agent 自己测试自己
如果 Agent 只会改代码,不会验证结果,它仍然需要人当“AI 调试器”。端到端 Harness 应该让 Agent 完成从需求到验收报告的闭环。
```mermaid
sequenceDiagram
participant H as 人
participant A as Agent
participant C as Codebase
participant S as Sandbox
participant T as Test Tools
participant R as Report
H->>A: 输入需求与验收标准
A->>C: 定位并修改代码
A->>S: 启动隔离环境
A->>T: 运行 lint/unit/e2e/cli-test
T-->>A: 返回结构化结果
A->>C: 根据失败信息修复
A->>T: 再次验证
A->>R: 生成交付报告
R-->>H: 人审阅结果与风险
在 CLI(Command Line Interface,命令行界面)或 TUI(Terminal User Interface,终端用户界面)产品里,可以用 tmux 创建沙盒会话,让 Agent 像用户一样操作终端界面,再把关键步骤、截图、输出和测试结果沉淀为报告。
示例流程:
# 1. 启动沙盒
tmux new-session -d -s agent-test "pnpm dev"
# 2. 执行 CLI 测试技能
pnpm cli-test slash-menu
# 3. 导出测试报告
pnpm cli-test report --format markdown > reports/slash-menu.md
重点不是某个命令本身,而是让验证成为 Agent 的默认动作。编码结束不代表任务结束,通过测试、生成证据、说明风险才算交付完成。
AI Native 工具:不要强迫 Agent 模拟人类
Web 自动化里常见做法是让 Agent 获取 DOM(Document Object Model,文档对象模型)、读取截图、计算坐标、模拟点击。这个路径能跑,但 Token 消耗大,稳定性也容易受页面结构影响。
传统路径大致是:
flowchart LR
A[获取 DOM 树] --> B[解析页面结构]
B --> C[结合截图判断元素]
C --> D[计算目标位置]
D --> E[模拟点击或输入]
E --> F[观察结果]
AI Native 的思路是把复杂操作封装进工具层,让 Agent 用自然语言表达意图:
点击登录按钮
验证购物车里有 3 件商品
在搜索框输入 headphones
工具负责定位、执行、重试和验证,Agent 不需要把 DOM 和截图全部塞进上下文。
以 Midscene.js 这类工具为例,接口可以更接近 Agent 的自然指令:
await ai.action("点击登录按钮");
await ai.action("在用户名输入框输入 alice@example.com");
await ai.action("在密码输入框输入 password123");
await ai.action("点击提交");
await ai.assert("页面显示欢迎回来");
这种设计的关键收益是:
| 方式 | Agent 需要处理什么 | 问题 |
|---|---|---|
| 模拟人类操作 | DOM、截图、坐标、点击路径 | 上下文重、脆弱 |
| AI Native 工具 | 自然语言目标 | Token 少,工具层自愈 |
好的 Harness 会把环境复杂度下沉到工具层,而不是把所有细节都暴露给模型。
维护性不能只靠测试通过
AI 一次生成几十个文件变更时,只看单测是否通过是不够的。测试能证明某些行为没坏,但不能完全证明架构方向正确、性能没有退化、复杂度没有失控。
需要给 Agent 更多工程约束:
| 风险 | Harness 约束 |
|---|---|
| 文件过大 | 设置文件长度阈值,超过必须拆分或解释 |
| 架构漂移 | 在 AGENTS.md 写清模块边界 |
| 公共 API 破坏 | Spec 中列出兼容性要求 |
| 性能退化 | 增加 benchmark 或性能检查 |
| 内存增长 | 增加资源监控脚本 |
| 测试覆盖不足 | 要求交付报告列出覆盖与未覆盖路径 |
例如可以把架构约束写进规则文件:
## Architecture Boundaries
- `src/core` must not import from `src/ui`.
- `src/shared` must not depend on product-specific modules.
- New public APIs require a compatibility note in the delivery report.
- Files over 500 lines require an explanation and a split plan.
也可以把质量门禁做成脚本:
pnpm lint
pnpm test
pnpm test:e2e
pnpm check:cycles
pnpm check:bundle-size
pnpm benchmark:critical-path
Agent 不是不需要架构治理,而是架构治理要变成它能读取、能执行、能反馈的规则。
一套可落地的 Harness Engineering 清单
搭建 AI Coding Harness 可以从小范围开始,不需要一次性改造全部研发体系。
1. 固化项目规则
- 添加
AGENTS.md; - 写清技术栈、代码风格、安全边界;
- 写清验证命令;
- 写清架构边界。
2. 治理上下文
- 长任务使用 Snapshot 文件;
- 阶段性清理会话历史;
- 不把全量日志长期留在上下文;
- 保持稳定前缀,减少 Prompt 重排。
3. 工具结构化
- CLI 输出 JSON 或 Markdown;
- 错误包含文件、行号、类型、建议;
- 避免让 Agent 解析截图和杂乱日志;
- MCP 工具保持小而清晰。
4. 建立验证闭环
- 接入 LSP;
- 默认运行 lint、typecheck、unit test;
- UI/TUI 变更运行端到端测试;
- 失败后让 Agent 根据结构化结果修复。
5. 用 Spec 和 Plan 控制执行
- 不明确的需求先写 Spec;
- 长任务先产出 Plan;
- 涉及不可逆操作必须 AskUser;
- 每个阶段都有 Checkpoint 和 Rollback。
6. 引入 Sub Agent
- 超长任务拆给多个子 Agent;
- 每个子 Agent 独立上下文;
- 只返回结构化摘要;
- 主 Agent 汇总决策,不合并噪音。
核心结论
AI Coding 的下一阶段不是“更快补全代码”,而是“稳定完成长任务”。要做到这一点,工程师需要从单纯写代码,转向设计 Agent 的工作环境。
几个原则最重要:
- 大模型按自回归生成,长上下文会带来真实计算成本。
- KV Cache 决定了追加便宜、中间修改昂贵,稳定前缀要尽量保护。
- Prompt Cache 让上下文布局直接影响速度和成本。
- ReAct 让 Agent 能行动,但也会带来上下文膨胀。
- AGENTS.md、Snapshot、Compaction 是上下文治理的基础。
- 工具要快、准、结构化,还要能给出明确错误反馈。
- MCP 提供原子动作,Skill 固化流程,Sub Agent 隔离长任务。
- LSP、测试和交付报告让 Agent 从“写完代码”走向“完成交付”。
Harness Engineering 的目标不是让人退出研发,而是把人的注意力从低层次调试中释放出来,转向目标定义、边界设计、架构判断和结果审计。