news 2026/9/28 19:00:26

Harness Engineering 实战:用 AGENTS.md 给 AI Agent 套上缰绳与护栏

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Harness Engineering 实战:用 AGENTS.md 给 AI Agent 套上缰绳与护栏

1. 为什么你的 AI Agent 总是“嘴上说做完了”

先说一个我踩过的坑。去年我让一个 Agent 帮我重构一个 TypeScript 项目的鉴权模块,它用了不到三分钟就回复“已完成重构,所有测试通过”。我打开文件一看,只改了两个 import 语句,测试根本没跑,tsconfig里的严格模式报错还挂在那里。这不是模型笨,是我没给它搭好运行环境。

这就是 Harness Engineering 要解决的问题。简单说,Harness Engineering 是一套围绕 AI Agent 构建的约束、上下文管理、反馈回路与执行控制机制,目标是让模型在明确边界内稳定、可靠、可追踪地完成复杂任务。它适合谁?适合所有把 Agent 从“能演示”推进到“能交付”的开发者,尤其是做 AI coding、自动化运维、多步骤任务编排的团队。

核心检索词先摆出来:Harness Engineering 是给 AI Agent 设计“缰绳 + 马鞍 + 跑道护栏 + 反馈镜子”的工程方法;AGENTS.md 是给 Agent 看的项目说明书;状态机负责锁定执行阶段;反馈回路负责让 Agent 在犯错后收敛。本文会交付一份可复制的 AGENTS.md 骨架、一套状态机配置片段,以及验证 Agent 行为收敛的具体操作步骤。

如果你现在手里的 Agent 经常出现这几种症状:多步骤任务做到一半就说“已完成”、长任务里忘了初始目标、自己评自己永远说“没问题”、对话一长就草草收尾,那这篇内容就是写给你的。下面从环境准备开始,一步步把缰绳套上。

2. 前置准备:TaoToken 接入与 AGENTS.md 初始化

在给 Agent 套缰绳之前,得先让它能稳定地跑起来。我实测下来,用 TaoToken 做模型接入层比较省心,它兼容 OpenAI 风格的接口,配置简单,适合作为 Harness 的模型调用底座。

2.1 获取 API Key 并配置环境变量

首先到 TaoToken 控制台创建一个 API Key。地址是https://taotoken.net/api-keys,登录后点“创建密钥”,复制生成的 key。注意不要把它硬编码到代码里,用环境变量管理。

# 在项目根目录创建 .env.local(确保已加入 .gitignore) echo 'TAOTOKEN_API_KEY=sk-你的实际key' >> .env.local echo 'TAOTOKEN_BASE_URL=https://taotoken.net/api' >> .env.local

如果你用的是 Node.js 项目,安装官方 SDK:

pnpm add openai

然后在lib/llm-client.ts里初始化客户端:

import OpenAI from "openai"; export const llmClient = new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, });

Python 项目同理:

import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], )

注意:base_url末尾不要多加/v1,TaoToken 的 API 地址已经包含了正确的路径前缀。如果你不确定,先用下面的验证请求测一下。

2.2 验证接入是否成功

在正式搭 Harness 之前,先跑一个最小请求确认链路通:

curl -s https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "回复 OK 两个字母"}], "max_tokens": 10 }'

如果返回的 JSON 里choices[0].message.content包含 “OK”,说明接入层没问题。这一步看起来简单,但很多后续的 Harness 验证失败,根源都是 API 配置写错了。

2.3 创建 AGENTS.md 骨架

接入通了之后,第一件事是写 AGENTS.md。它是给 Agent 看的项目说明书,和 README 的区别在于:README 给人看,AGENTS.md 给 Agent 看,内容更偏规则、命令和禁区。

在项目根目录创建AGENTS.md,先填入最小可用版本:

# AGENTS.md ## 项目概览 这是一个 Next.js 14 + TypeScript 全栈项目,使用 App Router。 ## 技术栈 - 框架:Next.js 14(App Router,禁止使用 Pages Router) - 语言:TypeScript(严格模式,禁止 any) - 样式:Tailwind CSS(禁止 CSS Modules) - 数据库:Prisma + PostgreSQL - 测试:Vitest + Testing Library ## 开发命令 - 安装依赖:`pnpm install` - 开发服务器:`pnpm dev` - 运行测试:`pnpm test --run` - 类型检查:`pnpm typecheck` - 代码检查:`pnpm lint` ## 架构约束 - 所有 API 路由放在 `app/api/` 下 - 业务逻辑放在 `lib/` 下 - 环境变量通过 `env.ts` 统一管理,禁止硬编码 - 禁止修改 `.env`、`secrets/`、`config/production/`、`.git/` ## 验证方式 改完代码后必须依次执行: 1. `pnpm typecheck` 2. `pnpm lint` 3. `pnpm test --run`

这个骨架覆盖了三类关键信息:WHAT(项目是什么)、HOW(怎么开发)、RULES(什么不能碰)。先别追求完美,把这三类写清楚,Agent 的行为就会明显收敛。

3. 可复制配置:状态机 + 硬约束 + 反馈回路

AGENTS.md 解决的是“读得懂”的问题,但光靠文档约束不够。模型会忘、会忽略、会在上下文压力下跳过规则。所以关键流程必须有硬约束,写进执行层,让 Agent 绕不过去。

3.1 状态机锁定执行阶段

复杂任务不要一口气跑到底,显式分阶段:research → plan → execute → verify。每个阶段允许的工具不同,状态迁移必须合法。

创建harness/state-machine.ts:

export enum AgentPhase { RESEARCH = "research", PLAN = "plan", EXECUTE = "execute", VERIFY = "verify", } const ALLOWED_TRANSITIONS: Record<AgentPhase, AgentPhase[]> = { [AgentPhase.RESEARCH]: [AgentPhase.PLAN], [AgentPhase.PLAN]: [AgentPhase.EXECUTE, AgentPhase.RESEARCH], [AgentPhase.EXECUTE]: [AgentPhase.VERIFY], [AgentPhase.VERIFY]: [AgentPhase.EXECUTE, AgentPhase.PLAN], }; const PHASE_PERMISSIONS: Record<AgentPhase, string[]> = { [AgentPhase.RESEARCH]: ["read_file", "search_code", "list_files"], [AgentPhase.PLAN]: ["read_file", "create_plan"], [AgentPhase.EXECUTE]: ["read_file", "write_file", "run_command"], [AgentPhase.VERIFY]: ["run_tests", "run_lint", "run_typecheck"], }; export class PhaseStateMachine { private current: AgentPhase = AgentPhase.RESEARCH; canTransition(to: AgentPhase): boolean { return ALLOWED_TRANSITIONS[this.current].includes(to); } transition(to: AgentPhase): void { if (!this.canTransition(to)) { throw new Error( `非法状态迁移:${this.current} → ${to}。允许的目标:${ALLOWED_TRANSITIONS[this.current].join(", ")}` ); } this.current = to; } canUseTool(tool: string): boolean { return PHASE_PERMISSIONS[this.current].includes(tool); } get phase(): AgentPhase { return this.current; } }

这个状态机的价值在于:Agent 在 research 阶段想直接改代码,会被canUseTool("write_file")拦下;想从 research 跳到 execute,会被transition抛错。它不一定知道状态机代码长什么样,但会真实感受到哪些动作被允许、哪些被拒绝。

3.2 防“嘴上完成”的完成度校验

Agent 最大的问题不是不会做,而是会提前宣布胜利。所以需要在执行层检查:你说完成了,有没有真实工具调用记录?

创建harness/completion-validator.ts:

interface ToolCallLog { hasCalls: boolean; hasFileWrites: boolean; hasTestRuns: boolean; calls: Array<{ tool: string; success: boolean }>; } export class CompletionValidator { validate(agentOutput: { claimsDone: boolean }, log: ToolCallLog) { if (agentOutput.claimsDone && !log.hasCalls) { return { action: "retry" as const, reason: "你声称任务已完成,但没有工具调用记录。请实际执行操作。", }; } if (log.hasFileWrites && !log.hasTestRuns) { return { action: "run_tests" as const, reason: "检测到文件修改但未运行测试。请先执行 pnpm test --run。", }; } return { action: "accept" as const }; } }

3.3 循环失败检测

Agent 还容易反复撞墙:同一个动作失败两三次了还在重试。加一个熔断器思路的检测器:

export class LoopDetector { private failureLog: Array<{ action: string; error: string }> = []; constructor(private maxRetries = 3) {} recordFailure(action: string, error: string) { this.failureLog.push({ action, error }); } shouldBreak(proposedAction: string): boolean { const sameFailures = this.failureLog.filter( (f) => f.action === proposedAction ); return sameFailures.length >= this.maxRetries; } }

3.4 反馈回路:执行与评审分离

生成和评估最好分开,因为 Agent 自己评自己容易放水。最小版本是自动跑 typecheck + lint + test,失败就打回:

export class FeedbackLoop { async runVerification(changedFiles: string[]) { const results = await Promise.all([ this.runCommand("pnpm typecheck"), this.runCommand("pnpm lint"), this.runCommand("pnpm test --run"), ]); const passed = results.every((r) => r.success); return { passed, details: results.map((r, i) => ({ check: ["typecheck", "lint", "test"][i], passed: r.success, output: r.stdout.slice(-500), errors: r.success ? null : r.stderr.slice(-500), })), }; } private async runCommand(cmd: string) { // 实际实现用 child_process.exec return { success: true, stdout: "", stderr: "" }; } }

更进一步,可以让一个 Agent 执行、另一个 Agent 审查。哪怕只是换一个独立会话来审查,效果通常都比“自己写自己夸”好。

4. 验证请求:确认 Agent 行为真的收敛了

配置写完了,怎么知道它真的生效了?不能只看代码,要跑真实任务验证。

4.1 构造一个会触发约束的测试任务

给 Agent 发一个任务:“修复src/login.tsx中的登录 bug”。正常流程应该是:

  1. Harness 启动新会话,加载 AGENTS.md,初始化状态为 RESEARCH
  2. Agent 请求读取login.tsx,Harness 检查 RESEARCH 阶段允许read_file,放行
  3. Agent 想直接改代码,Harness 检查 RESEARCH 阶段不允许write_file,拒绝
  4. Agent 请求进入 PLAN 阶段,Harness 校验状态迁移合法,允许
  5. Agent 生成修复计划,Harness 允许create_plan
  6. Agent 请求进入 EXECUTE,Harness 允许状态迁移
  7. Agent 修改login.tsx,Harness 记录文件修改事件
  8. Harness 自动触发验证守卫,强制进入 VERIFY,运行 typecheck / lint / test
  9. 若全部通过,任务完成;否则把错误反馈给 Agent 重试

4.2 用日志验证状态迁移

在 Harness 里加一行日志,记录每次状态迁移和工具调用:

stateMachine.transition(AgentPhase.PLAN); console.log(`[HARNESS] phase=${stateMachine.phase} tool=create_plan allowed=${stateMachine.canUseTool("create_plan")}`);

跑完任务后检查日志,应该看到类似输出:

[HARNESS] phase=research tool=read_file allowed=true [HARNESS] phase=research tool=write_file allowed=false [HARNESS] phase=plan tool=create_plan allowed=true [HARNESS] phase=execute tool=write_file allowed=true [HARNESS] phase=verify tool=run_tests allowed=true

如果看到phase=research tool=write_file allowed=true,说明状态机没生效,回去检查PHASE_PERMISSIONS配置。

4.3 验证完成度校验是否拦截

故意让 Agent 在没跑测试的情况下声称完成,观察 CompletionValidator 是否返回run_tests动作。如果它直接 accept 了,说明hasTestRuns的检测逻辑没接上工具调用日志。

4.4 验证循环失败检测

构造一个必然失败的命令(比如pnpm test --run在一个有语法错误的文件上),让 Agent 连续重试三次,观察 LoopDetector 是否在第四次时返回shouldBreak=true。

5. 本篇常见错排查

5.1 API 返回 401 或 403

最常见的原因是 API Key 没读到。检查.env.local是否被正确加载,Node.js 项目里process.env.TAOTOKEN_API_KEY是否有值。如果你用的是 Next.js,确保.env.local在项目根目录,且没有在next.config.js里覆盖环境变量。

另一个原因是 baseURL 写错了。正确写法是https://taotoken.net/api,不要加/v1,也不要加尾部斜杠。

5.2 状态机抛“非法状态迁移”

检查ALLOWED_TRANSITIONS里是否漏了某条路径。比如从 VERIFY 回到 EXECUTE 是允许的,但如果你只写了VERIFY: [PLAN],Agent 修复失败后就无法重试执行。建议把常见回退路径都列上。

5.3 Agent 仍然跳过测试直接宣布完成

说明 CompletionValidator 没有接入工具调用日志。检查ToolCallLog的hasTestRuns字段是否在每次run_command后被更新。如果日志是异步写入的,确保校验前已经 flush。

5.4 AGENTS.md 内容太长导致 Agent 忽略关键规则

全量灌输不如渐进披露。AGENTS.md 只保留最关键规则,详细设计放到docs/下,让 Agent 按需查阅。如果是 monorepo,可以在子目录放独立的 AGENTS.md:

项目根目录/AGENTS.md ├── packages/web/AGENTS.md └── packages/api/AGENTS.md

5.5 长任务上下文越来越脏

不要让一个会话死扛到底。当上下文利用率超过 60% 时,生成交接文档,开新会话继续:

if (agent.contextUtilization > 0.6) { const handoff = await agent.generateHandoff( "总结:1.已完成 2.当前进度 3.下一步 4.注意事项" ); agent = agent.freshSession(); agent.loadContext(handoff); }

5.6 反馈回路跑不起来

检查pnpm typecheck、pnpm lint、pnpm test --run这三个命令是否在项目里真实可用。如果某个命令不存在,FeedbackLoop 会一直返回失败,Agent 会陷入无限重试。先在终端手动跑一遍确认。

6. 让 Agent 从“能演示”走到“能交付”

Harness Engineering 的核心工作方式是一个持续改进飞轮:Agent 犯错 → 归因 → 补文档 / 补约束 / 补验证 / 补权限 → 记录经验 → 下次同类问题更少。

维护一份.harness/lessons-learned.md,每次 Agent 出错后记录根因和修复方式:

# .harness/lessons-learned.md ## 2026-03-20: Prisma 迁移必须在测试前执行 - 问题:修改 schema 后没跑 migrate - 修复:在验证步骤中加入 migrate 检查 - 状态:已纳入硬约束 ## 2026-03-18: 不要在 middleware 中直接 throw - 问题:导致页面白屏 - 修复:补充框架约束说明 - 状态:已写入 AGENTS.md

如果你需要长期跑编码任务或 Agent 编排,可以了解 TaoToken 的 Coding Plan,地址是https://taotoken.net/coding-plan。如果只是想先验证模型对话效果,用模型对话入口https://taotoken.net/chat快速测试。接入文档在https://taotoken.net/doc,API Keys 管理在https://taotoken.net/api-keys。

今天就能开始做的五件事:写一个最小版 AGENTS.md,十分钟;加一条硬约束“改完代码必须跑测试”,三十分钟;把执行和评审拆开,每次多五分钟;记录 Agent 的典型错误,持续做;让人审计划而不是盯每一行代码。做完这五步,你的 Agent 稳定性会有肉眼可见的提升。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 19:00:26

多传感器复合装备测试效率提升:从时间同步到自动化平台

多传感器复合装备这几年几乎成了各行业测试场里的标配&#xff0c;光、雷、热、惯导一上架子&#xff0c;硬件堆得漂亮&#xff0c;可真正动手测的人都是一肚子苦水。尤其是“多传感器复合装备测试”这个热词背后&#xff0c;真正让人头疼的不是传感器本身&#xff0c;而是测试…

作者头像 李华
网站建设 2026/9/28 19:00:18

位移传感器故障排查手册:常见故障、原因与现场处理方法

干设备维护这些年&#xff0c;位移传感器可以说是出镜率最高的故障源之一。只要是跟位置、行程、厚度、振动沾边的自动控制&#xff0c;几乎都躲不开它。现场一报警、精度对不上、输出乱跳&#xff0c;很多人第一反应就是“传感器坏了”&#xff0c;但真换上去才发现问题还在&a…

作者头像 李华
网站建设 2026/9/28 18:59:08

Windows 下构建 arm64 deb 安装包:三大误区与完整流程

干过这类事的朋友应该能理解&#xff0c;接到“在 Windows 上打一个 arm64 的 deb 安装包”这种需求的时候&#xff0c;第一反应多半是有点懵的。我这次的任务&#xff0c;是给一台跑 Debian 系统的 ARM 架构设备发布一个命令行小工具&#xff0c;但我的开发机是一台 Windows 笔…

作者头像 李华
网站建设 2026/9/28 18:59:00

OpenHarmony I2C驱动开发实战:从协议原理到排障优化

1. I2C 总线到底是个什么东西1.1 从两根线说起&#xff1a;I2C 的物理层本质I2C 这玩意儿&#xff0c;全称叫 Inter-Integrated Circuit&#xff0c;中文一般叫“集成电路总线”。名字听着挺唬人&#xff0c;但说白了它就是两根线&#xff1a;一根 SCL&#xff08;串行时钟线&a…

作者头像 李华