AI 编码工具真正难的地方,不是让大语言模型生成一段代码,而是让它在真实工程里安全、连续、可回滚地工作。
真实工程不是一段孤立的函数。它有目录结构、历史代码、依赖管理、测试脚本、构建命令、代码规范、权限边界和团队约定。AI 如果只根据用户一句话直接写代码,很容易遇到几类问题:
- 不知道项目真实结构,生成不存在的文件路径;
- 忽略已有抽象,重复造轮子;
- 修改范围过大,难以 review;
- 执行危险命令,比如删除文件、覆盖配置、访问敏感路径;
- 没有运行测试,错误直到合并后才暴露;
- 上下文越聊越长,模型开始遗忘关键约束。
Harness Engineering(驾驭工程)要解决的就是这个问题:给 AI 编码 Agent 套上一组工程化缰绳,让模型能调用工具、理解项目、修改代码、验证结果,同时把风险限制在可控范围内。
Claude Code 这类 AI 编码工具可以看作一个典型样本。它的核心不是单次代码补全,而是一个围绕“大语言模型 + 工具系统 + 上下文管理 + 权限控制”的循环执行框架。
AI 编码 Agent 和普通聊天机器人的区别
普通聊天机器人主要处理文本输入和文本输出。AI 编码 Agent 面对的是一个动态工作区:它需要读文件、搜索代码、执行命令、生成补丁、观察报错,再根据结果继续调整。
两者的差异可以这样理解:
| 能力维度 | 普通聊天机器人 | AI 编码 Agent |
|---|---|---|
| 输入 | 用户消息 | 用户目标、代码仓库、文件内容、命令输出、历史操作 |
| 输出 | 文本回答 | 解释、计划、代码补丁、命令调用、测试结果 |
| 状态 | 对话上下文 | 对话上下文 + 工作区状态 + 工具执行结果 |
| 风险 | 回答不准确 | 改坏代码、泄露密钥、执行危险命令 |
| 关键机制 | 提示词设计 | 上下文工程、工具路由、权限控制、执行闭环 |
AI 编码 Agent 的目标不是“一次回答正确”,而是通过多轮观察和修正,逐步把工程状态推进到目标状态。
整体架构:模型只负责推理,Agent 负责驾驭环境
一个可用的 AI 编码 Agent 通常由几层组成:
flowchart LR
U[用户目标] --> UI[CLI / IDE / Web 入口]
UI --> O[Agent Orchestrator<br/>调度器]
O --> C[Context Builder<br/>上下文构造器]
C --> R[(代码仓库)]
C --> M[Memory / Rules<br/>项目规则与历史摘要]
O --> L[LLM<br/>大语言模型]
L --> O
O --> T[Tool Router<br/>工具路由器]
T --> P[Permission Gate<br/>权限网关]
P --> F[文件工具<br/>read/write/edit]
P --> S[搜索工具<br/>grep/index]
P --> SH[命令工具<br/>shell/test/build]
P --> G[Git 工具<br/>diff/status]
F --> R
S --> R
SH --> W[工作区环境]
G --> R
F --> O
S --> O
SH --> O
G --> O
这里最重要的设计原则是:大语言模型不直接操作系统和文件,所有外部动作都必须经过 Agent 的工具层。
模型可以提出“读取某个文件”“搜索某个符号”“运行测试”“修改某段代码”的意图,但真正执行这些动作的是工具系统。工具系统再经过权限网关判断是否允许执行、是否需要用户确认、是否要截断输出、是否要记录审计日志。
这层隔离非常关键。没有工具层,模型就只能聊天;没有权限层,模型就可能变成不受控的脚本执行器。
核心循环:感知、计划、行动、观察
AI 编码 Agent 的工作方式接近一个循环:
flowchart TD
A[接收用户目标] --> B[收集上下文]
B --> C[生成计划]
C --> D{需要工具吗}
D -- 是 --> E[调用工具]
E --> F[获得观察结果]
F --> G{目标完成了吗}
G -- 否 --> B
G -- 是 --> H[给出变更说明和验证结果]
D -- 否 --> H
这个循环可以拆成四个动作:
- 感知:读取代码、搜索符号、查看配置、理解错误输出。
- 计划:决定改哪些文件、按什么顺序改、要运行哪些验证命令。
- 行动:通过工具执行文件编辑、命令运行、代码搜索。
- 观察:把工具返回结果放回上下文,让模型继续判断下一步。
一个典型修复流程如下:
sequenceDiagram
participant User as 用户
participant Agent as Agent 调度器
participant Ctx as 上下文构造器
participant LLM as 大语言模型
participant Tool as 工具系统
participant Repo as 代码仓库
User->>Agent: 修复登录接口 500 错误
Agent->>Ctx: 收集路由、控制器、日志、测试信息
Ctx->>Repo: 读取相关文件
Repo-->>Ctx: 返回代码片段
Ctx-->>Agent: 组装上下文
Agent->>LLM: 请求分析和计划
LLM-->>Agent: 需要查看认证中间件
Agent->>Tool: 搜索 auth middleware
Tool->>Repo: grep / index search
Repo-->>Tool: 返回匹配结果
Tool-->>Agent: 返回观察结果
Agent->>LLM: 继续推理
LLM-->>Agent: 生成补丁和测试命令
Agent->>Tool: 应用补丁、运行测试
Tool-->>Agent: 返回 diff 和测试结果
Agent-->>User: 汇报修改点、验证结果、剩余风险
真正的工程能力来自这个闭环,而不是来自某一次模型输出。
上下文工程:给模型刚好够用的信息
大语言模型的上下文窗口有限,即使窗口很大,也不应该把整个仓库塞进去。上下文越杂,模型越容易被无关内容干扰;上下文越少,又会缺少关键约束。
上下文工程的目标是:把完成任务必需的信息放进去,把无关信息挡在外面。
上下文通常包含什么
| 上下文类型 | 作用 | 示例 |
|---|---|---|
| 用户目标 | 明确任务边界 | “把支付回调改成幂等处理” |
| 项目规则 | 约束代码风格和工具链 | 代码规范、测试命令、目录约定 |
| 相关文件 | 提供真实实现 | controller、service、repository、test |
| 搜索结果 | 定位符号和调用关系 | 函数定义、引用位置、配置项 |
| 命令输出 | 反馈真实环境状态 | 测试失败日志、构建错误 |
| 已做修改 | 防止重复操作 | 当前 diff、已编辑文件列表 |
| 安全限制 | 限制危险行为 | 禁止访问密钥、禁止删除目录 |
一个粗糙的 Agent 会把“用户问题 + 全仓库代码”直接发给模型。一个工程化 Agent 会先判断任务类型,再逐步加载上下文。
渐进式加载比一次性全量加载更稳
可以把上下文加载分成几层:
flowchart TD
A[用户目标] --> B[读取项目规则]
B --> C[生成初步搜索关键词]
C --> D[搜索文件和符号]
D --> E[读取最相关文件片段]
E --> F[让模型制定修改计划]
F --> G{信息够吗}
G -- 不够 --> H[继续定向读取]
H --> F
G -- 够 --> I[生成补丁或调用工具]
这种方式有几个好处:
- 模型先理解任务,再决定要看什么;
- 上下文不会被无关文件污染;
- 每次工具调用都有明确目的;
- 读文件和改文件之间有可审计的路径。
一个上下文构造器的简化实现
下面的 TypeScript 伪代码展示了上下文构造器的基本思路。重点不是具体 API,而是“先筛选,再组装,再控制预算”。
type UserTask = {
goal: string;
repoPath: string;
};
type ContextItem = {
kind: "rule" | "file" | "search" | "diff" | "command";
title: string;
content: string;
priority: number;
};
type ContextBudget = {
maxTokens: number;
};
async function buildContext(task: UserTask, budget: ContextBudget): Promise<string> {
const items: ContextItem[] = [];
const rules = await readProjectRules(task.repoPath);
items.push({
kind: "rule",
title: "Project Rules",
content: rules,
priority: 100,
});
const keywords = extractSearchKeywords(task.goal);
const matches = await searchRepository(task.repoPath, keywords);
items.push({
kind: "search",
title: "Search Results",
content: formatSearchResults(matches),
priority: 80,
});
const candidateFiles = rankFilesByRelevance(matches, task.goal);
for (const file of candidateFiles.slice(0, 8)) {
const snippet = await readRelevantSnippet(file, task.goal);
items.push({
kind: "file",
title: file.path,
content: snippet,
priority: file.score,
});
}
const currentDiff = await gitDiff(task.repoPath);
if (currentDiff.trim()) {
items.push({
kind: "diff",
title: "Current Diff",
content: currentDiff,
priority: 90,
});
}
return packByPriority(items, budget.maxTokens);
}
这里有几个细节值得关注:
readProjectRules通常读取类似CLAUDE.md、AGENTS.md、README.md、代码规范文档一类的项目规则文件;searchRepository不应该只做文件名搜索,还要支持符号、字符串、配置项搜索;readRelevantSnippet最好读取函数级或类级片段,而不是整份大文件;packByPriority要处理 token 预算,优先保留任务目标、规则、相关代码和当前 diff。
工具系统:把模型意图变成受控动作
AI 编码 Agent 的工具不是普通函数调用,而是一组受控能力。每个工具都应该有清晰的输入、输出、权限要求和错误格式。
一个文件读取工具可以这样描述:
{
"name": "read_file",
"description": "读取工作区内的文本文件",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "相对于工作区根目录的文件路径"
},
"start_line": {
"type": "number",
"description": "起始行号,从 1 开始"
},
"end_line": {
"type": "number",
"description": "结束行号"
}
},
"required": ["path"]
},
"permission": "read_workspace"
}
一个命令执行工具需要更严格:
{
"name": "run_command",
"description": "在工作区内执行命令,默认不允许访问网络和工作区外路径",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string"
},
"timeout_ms": {
"type": "number"
},
"reason": {
"type": "string",
"description": "说明为什么需要执行该命令"
}
},
"required": ["command", "reason"]
},
"permission": "execute_command"
}
reason 字段不是装饰,它能帮助权限层和用户判断命令是否合理。例如运行 npm test 和运行 rm -rf . 都是 shell 命令,但风险完全不同。
权限体系:让 AI 能做事,但不能越界
AI 编码 Agent 必须有权限边界。权限设计太宽,风险不可控;权限设计太窄,Agent 会退化成只能给建议的聊天工具。
一种实用做法是把权限拆成几个级别:
| 权限级别 | 允许行为 | 典型工具 | 是否需要确认 |
|---|---|---|---|
| 只读 | 读取文件、搜索代码、查看 git 状态 | read_file、grep、git status | 通常不需要 |
| 工作区写入 | 修改工作区内文件、应用补丁 | edit_file、apply_patch | 可按配置决定 |
| 安全命令 | 执行测试、格式化、类型检查 | npm test、go test、cargo fmt | 通常不需要或首次确认 |
| 高风险命令 | 删除文件、安装依赖、修改环境 | rm、pip install、npm install | 需要确认 |
| 网络访问 | 调用外部接口、下载依赖 | curl、包管理器 | 需要确认 |
| 敏感信息 | 读取密钥、环境变量、凭据 | .env、SSH key | 默认禁止 |
权限判断可以做成一个独立流程:
flowchart TD
A[模型请求调用工具] --> B[解析工具类型和参数]
B --> C{路径在工作区内吗}
C -- 否 --> X[拒绝执行]
C -- 是 --> D{是否命中敏感文件}
D -- 是 --> X
D -- 否 --> E{命令是否高风险}
E -- 是 --> F[请求用户确认]
F --> G{用户允许吗}
G -- 否 --> X
G -- 是 --> H[执行工具]
E -- 否 --> H
H --> I[记录日志并返回结果]
权限系统至少要覆盖四类风险。
1. 路径越界
文件工具必须限制在工作区根目录内。即使模型传入 ../../.ssh/id_rsa,工具层也应该规范化路径后拒绝。
import path from "node:path";
function assertInsideWorkspace(workspaceRoot: string, inputPath: string) {
const resolved = path.resolve(workspaceRoot, inputPath);
const root = path.resolve(workspaceRoot);
if (!resolved.startsWith(root + path.sep) && resolved !== root) {
throw new Error(`Path escapes workspace: ${inputPath}`);
}
return resolved;
}
2. 敏感文件访问
.env、私钥、云厂商凭据、生产配置等文件不应该默认暴露给模型。即使某个任务看起来需要环境变量,也应该先让用户手动确认,或者只提供经过脱敏的键名。
const blockedPatterns = [
/\.env(\..*)?$/,
/id_rsa$/,
/id_ed25519$/,
/credentials\.json$/,
/kubeconfig$/,
/\.npmrc$/,
];
function isSensitivePath(filePath: string) {
return blockedPatterns.some((pattern) => pattern.test(filePath));
}
3. 危险命令
命令不能只按字符串黑名单处理,因为 shell 组合方式太多。更稳的做法是分层控制:
- 默认工作目录固定在 workspace;
- 默认禁用网络;
- 默认禁用交互式命令;
- 限制超时时间;
- 对删除、移动、权限修改、包安装等行为要求确认;
- 对输出做截断和脱敏。
| 命令类型 | 示例 | 处理方式 |
|---|---|---|
| 测试 | npm test、pytest、go test ./... | 可允许 |
| 静态检查 | npm run lint、tsc --noEmit | 可允许 |
| 格式化 | prettier --write .、cargo fmt | 可允许,但最好展示影响范围 |
| 删除 | rm -rf、git clean -fdx | 必须确认 |
| 安装依赖 | npm install、pip install | 需要确认 |
| 网络请求 | curl、wget | 需要确认或禁用 |
| 权限修改 | chmod、chown | 需要确认 |
4. 输出泄露
命令输出也可能包含敏感信息。Agent 返回给模型前,可以做一层脱敏处理:
function redactSecrets(output: string) {
return output
.replace(/AKIA[0-9A-Z]{16}/g, "[REDACTED_AWS_KEY]")
.replace(/ghp_[A-Za-z0-9_]{30,}/g, "[REDACTED_GITHUB_TOKEN]")
.replace(/-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]+?-----END [A-Z ]+PRIVATE KEY-----/g, "[REDACTED_PRIVATE_KEY]");
}
脱敏不能替代权限控制,但可以降低日志和模型上下文里的泄露风险。
代码修改:优先生成小补丁,而不是整文件重写
AI 编码 Agent 修改代码时,最怕“大面积重写”。重写越多,review 成本越高,也越容易引入无关变化。
更稳的方式是让模型生成结构化补丁:
diff --git a/src/auth/session.ts b/src/auth/session.ts
index 7a1c9a2..3b2f8c1 100644
--- a/src/auth/session.ts
+++ b/src/auth/session.ts
@@ -18,7 +18,11 @@ export async function getSession(userId: string) {
const cached = await cache.get(cacheKey);
if (cached) return JSON.parse(cached);
- const session = await db.session.findFirst({ where: { userId } });
+ const session = await db.session.findFirst({
+ where: { userId, revokedAt: null },
+ orderBy: { createdAt: "desc" },
+ });
+
if (!session) return null;
await cache.set(cacheKey, JSON.stringify(session), 300);
补丁式修改有几个优势:
- 变更范围清楚;
- 容易做冲突检测;
- 可以在应用前展示给用户;
- 可以通过 git diff 审计;
- 失败后更容易回滚。
一个安全的修改流程通常是:
flowchart TD
A[生成修改计划] --> B[读取目标文件]
B --> C[生成最小补丁]
C --> D[应用补丁]
D --> E[查看 git diff]
E --> F[运行格式化 / 测试]
F --> G{验证通过吗}
G -- 是 --> H[汇报变更和验证结果]
G -- 否 --> I[读取错误输出]
I --> J[生成修复补丁]
J --> D
这里的关键不是让模型“相信自己写对了”,而是让测试、类型检查和构建命令提供外部反馈。
规划机制:任务越复杂,越需要显式计划
简单任务可以直接改,例如修正拼写、补一个类型、改一处配置。复杂任务应该先拆计划,否则模型很容易在多个文件之间跳来跳去,最后留下半成品。
一个可执行计划应该满足三点:
- 每一步都有明确产物;
- 每一步都能被观察或验证;
- 中间失败时可以停下来,不会把工程状态推到不可控位置。
例如“给订单模块增加取消接口”可以拆成:
| 步骤 | 动作 | 验证方式 |
|---|---|---|
| 定位模块 | 搜索订单 controller、service、路由 | 找到现有创建、查询接口 |
| 理解约定 | 读取相邻接口实现和测试 | 确认错误码、权限、返回格式 |
| 修改业务逻辑 | 增加取消方法,处理状态流转 | 单元测试覆盖成功和失败分支 |
| 暴露接口 | 增加路由和请求校验 | 接口测试或集成测试 |
| 更新文档 | 补充 API 示例 | 文档 diff 可检查 |
计划不是为了写得漂亮,而是为了降低失控概率。Agent 每完成一步,就能根据真实反馈更新后续动作。
项目规则:把团队约定显式交给 Agent
很多 AI 编码错误来自“默认假设”。例如模型不知道项目使用 pnpm 还是 npm,不知道测试命令是什么,不知道异常应该抛业务错误还是 HTTP 错误,也不知道团队禁止直接访问数据库。
项目可以用一个规则文件告诉 Agent:
# Agent Instructions
## Project
- Runtime: Node.js 20
- Package manager: pnpm
- Main framework: Fastify
- ORM: Prisma
## Commands
- Install: pnpm install
- Type check: pnpm typecheck
- Test: pnpm test
- Lint: pnpm lint
## Coding Rules
- Do not access Prisma client directly in controllers.
- Put business logic in `src/modules/*/service.ts`.
- Use `AppError` for business errors.
- Add or update tests for behavior changes.
## Safety
- Do not read `.env*` files.
- Do not run database migration commands unless explicitly requested.
这类规则文件越具体,Agent 越容易遵守。空泛的“写高质量代码”没有什么帮助,明确的目录、命令、禁止事项和错误处理约定才有用。
记忆系统:长期规则和短期任务要分开
AI 编码 Agent 需要记住信息,但不能把所有历史都塞进上下文。可以把记忆分成三类:
| 记忆类型 | 生命周期 | 示例 | 使用方式 |
|---|---|---|---|
| 项目规则 | 长期 | 技术栈、测试命令、目录规范 | 每次任务加载 |
| 任务状态 | 短期 | 当前计划、已修改文件、失败测试 | 当前任务内维护 |
| 历史摘要 | 中期 | 最近一次重构的背景、未完成事项 | 按相关性检索 |
长期规则应该人工维护,短期任务状态由 Agent 自动维护,历史摘要要谨慎使用。历史信息如果不准确,会比没有记忆更危险。
一种简单的任务状态结构如下:
{
"task_id": "fix-login-500",
"goal": "修复登录接口 500 错误",
"plan": [
{
"step": "定位登录接口实现",
"status": "done"
},
{
"step": "修复 session 查询逻辑",
"status": "done"
},
{
"step": "运行认证模块测试",
"status": "running"
}
],
"touched_files": [
"src/auth/session.ts",
"src/auth/session.test.ts"
],
"last_observation": "1 个测试失败:should reject revoked session"
}
有了任务状态,Agent 不需要靠模型“记忆力”维持流程,调度器可以把关键状态稳定地送回上下文。
反馈系统:测试、类型检查和 lint 是 Agent 的眼睛
AI 编码 Agent 不应该只给出“已完成”的文字说明。更可信的输出应该包含:
- 修改了哪些文件;
- 每个文件改了什么;
- 运行了哪些命令;
- 命令结果是什么;
- 哪些验证没有运行,原因是什么;
- 还有哪些风险需要人工确认。
验证链路可以按成本从低到高排列:
flowchart LR
A[格式检查] --> B[类型检查]
B --> C[单元测试]
C --> D[集成测试]
D --> E[构建]
E --> F[端到端测试]
不同项目的验证命令不同,但思路一样:先运行便宜、反馈快的检查,再运行成本高的检查。Agent 如果修改了一个 TypeScript 函数,通常先跑 tsc --noEmit 或相关单测;如果修改了构建配置,再跑完整构建更合适。
示例命令可以写进项目规则:
pnpm typecheck
pnpm test src/auth/session.test.ts
pnpm lint
如果测试失败,失败输出应该回到上下文,进入下一轮修复,而不是直接把错误甩给用户。
适合和不适合交给 AI 编码 Agent 的任务
AI 编码 Agent 很适合处理上下文明确、验证手段清楚、修改范围可控的任务;如果需求模糊、业务决策重、缺少测试,风险会明显上升。
| 任务类型 | 适合程度 | 原因 |
|---|---|---|
| 修复类型错误 | 高 | 错误位置明确,验证反馈直接 |
| 补单元测试 | 高 | 可参考已有测试风格,结果可运行 |
| 小范围重构 | 中高 | 需要控制 diff,适合分步执行 |
| 接口参数调整 | 中高 | 影响面可通过搜索和测试验证 |
| 大规模架构迁移 | 中 | 需要人工拆分阶段,不能一次交给 Agent |
| 业务规则设计 | 中低 | 需要产品和领域判断 |
| 生产事故处置 | 低 | 时间压力大,权限敏感,需要人工主导 |
| 涉密配置修改 | 低 | 容易触碰凭据和环境风险 |
更稳的使用方式是把大任务拆成多个小任务,让 Agent 在每个小任务里完成“理解上下文、修改代码、运行验证、汇报 diff”的闭环。
生产化实践:可观测、可回滚、可评估
把 AI 编码 Agent 用在真实工程里,除了模型能力,还要补齐工程系统。
1. 记录每次工具调用
每次工具调用都应该记录:
{
"timestamp": "2026-06-07T10:30:00+08:00",
"tool": "run_command",
"input": {
"command": "pnpm test src/auth/session.test.ts",
"reason": "验证 session 查询逻辑修改"
},
"permission": "safe_command",
"status": "success",
"duration_ms": 3821,
"output_preview": "12 passed"
}
日志能用于排查问题,也能用于评估 Agent 行为是否合理。需要注意的是,日志也要脱敏,不能把密钥、用户数据、生产配置写进去。
2. 每次修改都能回滚
最简单的回滚基础是 Git。Agent 在修改前可以检查工作区状态:
git status --short
如果工作区已经有用户未提交修改,Agent 应该避免覆盖,或者只修改明确允许的文件。修改后再展示 diff:
git diff -- src/auth/session.ts src/auth/session.test.ts
对于自动化程度更高的环境,可以在每个任务开始时创建临时分支或 patch 文件,让回滚路径更清楚。
3. 建立任务评估集
想知道 AI 编码 Agent 是否稳定,不能只看演示效果。可以准备一组固定任务作为评估集:
[
{
"name": "fix-null-session",
"goal": "修复 session 为空时登录接口返回 500 的问题",
"expected_files": [
"src/auth/session.ts",
"src/auth/session.test.ts"
],
"validation": [
"pnpm test src/auth/session.test.ts",
"pnpm typecheck"
]
},
{
"name": "add-order-cancel-api",
"goal": "为订单模块增加取消接口,只有待支付订单可以取消",
"expected_files": [
"src/modules/order/controller.ts",
"src/modules/order/service.ts",
"src/modules/order/service.test.ts"
],
"validation": [
"pnpm test src/modules/order",
"pnpm lint"
]
}
]
评估时不要只看最终代码能否运行,也要看过程:
- 是否读取了正确文件;
- 是否遵守权限;
- 是否产生无关修改;
- 是否运行了必要测试;
- 是否能从测试失败中恢复;
- 是否清楚说明未验证部分。
4. 对失败要有降级策略
Agent 可能会遇到很多失败场景:
| 失败场景 | 合理处理 |
|---|---|
| 找不到相关文件 | 说明已搜索路径和关键词,请用户补充入口 |
| 测试命令不存在 | 查看项目文档或 package scripts,不要乱猜 |
| 补丁应用失败 | 重新读取文件后生成更小补丁 |
| 上下文预算不足 | 摘要低优先级内容,保留目标、规则、相关代码 |
| 权限被拒绝 | 解释需要该权限的原因,等待用户决定 |
| 多次修复仍失败 | 停止自动修改,汇报当前状态和失败日志 |
失败不可怕,怕的是 Agent 在失败后继续盲目修改。生产环境里的 Agent 应该有停止条件,例如同一个测试连续失败三次,就暂停并交给用户判断。
一个最小可用 AI 编码 Agent 需要哪些模块
如果从零实现一个简化版 AI 编码 Agent,不需要一开始就做复杂 IDE 集成。一个最小闭环至少包含这些模块:
flowchart TD
A[任务输入] --> B[项目规则读取]
B --> C[代码搜索]
C --> D[文件读取]
D --> E[LLM 推理]
E --> F{工具调用?}
F -- 读取/搜索 --> C
F -- 修改文件 --> G[补丁应用]
F -- 执行命令 --> H[权限检查后运行]
G --> I[git diff]
H --> J[命令输出]
I --> E
J --> E
F -- 无需工具 --> K[结果汇报]
模块优先级可以这样排:
| 优先级 | 模块 | 原因 |
|---|---|---|
| P0 | 文件读取、搜索、补丁应用 | 没有这些能力就无法操作真实代码 |
| P0 | 权限检查 | 防止越界读写和危险命令 |
| P0 | diff 展示 | 让用户知道改了什么 |
| P1 | 测试命令执行 | 形成验证闭环 |
| P1 | 项目规则文件 | 降低模型猜测 |
| P1 | 工具调用日志 | 方便排查和审计 |
| P2 | 代码索引 | 提高大仓库定位效率 |
| P2 | 历史记忆 | 支持跨任务连续工作 |
| P2 | 评估集 | 衡量长期质量 |
核心思路很朴素:先让 Agent 安全地读,再让它小心地改,再让它用测试验证。
Harness Engineering 的关键取舍
驾驭工程不是让模型“自由发挥”,而是设计一个系统,让模型的推理能力在工程约束里发挥作用。几个取舍尤其重要:
| 取舍 | 不推荐 | 更稳的做法 |
|---|---|---|
| 上下文 | 全仓库塞给模型 | 按任务渐进式加载 |
| 修改方式 | 整文件重写 | 生成最小补丁 |
| 权限 | 默认允许所有命令 | 按工具和风险分级 |
| 验证 | 只相信模型解释 | 运行测试、类型检查、构建 |
| 记忆 | 保存所有历史 | 区分项目规则、任务状态、历史摘要 |
| 失败处理 | 继续盲改 | 达到阈值后暂停并汇报 |
AI 编码 Agent 的能力边界,本质上由两部分共同决定:模型能推理到哪里,工程系统能约束和验证到哪里。模型越强,越需要清晰的工具和权限;工程系统越完善,模型输出才越容易落到真实代码里。
Harness Engineering 的价值就在这里:它把“让 AI 写代码”变成一套可运行、可审计、可回滚、可持续改进的工程流程。