
同一个模型放进不同产品,表现往往像换了一个人。原因不只在提示词,而在模型周围的运行时:它看得到什么、能调用什么、修改如何被检查、错误如何回流、跨会话如何记住进度。把这些模型之外的部分当成一等工程对象,就是 Agent Harness Engineering。

Agent 不是模型调用的同义词
一个可交付任务的 Agent 至少由模型、状态、工具执行、反馈回路和约束组成。模型产生候选行动;Harness 决定行动是否允许、在哪里运行、返回什么观测,以及何时停止。把它写成公式并不严谨,却足够实用:
Agent = Model + Context + Tools + Control Flow + Verification
这个视角改变了排障方式。Agent 不知道项目约定,不是先换模型,而是补一条项目规则;它误删文件,不是再说一次“请小心”,而是把破坏性命令放进权限钩子;它写完代码却没有通过测试,不是接受“已完成”,而是把测试失败作为下一轮输入。
每次失败都应留下一个棘轮
好的 Harness 不靠一份越来越长的万能规范,而是把真实失败转成最小、可验证的保护。比如曾出现“为了让 CI 通过而注释测试”的提交,就可以同时在规则、提交前检查和审查器中增加防线:
#!/usr/bin/env bash
# scripts/guard-tests.sh
set -euo pipefail
if git diff --cached -- '*.ts' '*.tsx' | grep -E '^\+.*(\.skip\(|xit\(|xdescribe\()'; then
echo '不允许以跳过测试的方式通过提交。请修复或删除无效测试。' >&2
exit 1
fi
约束应当“有来处”:每条规则对应某个故障、外部合规要求或不可变接口。没有证据的规则会稀释真正关键的规则;模型能力提升后,已无价值的脚手架也应该删除。
文件系统是持续工作的记忆体
上下文窗口不是数据库。将计划、决策、日志摘要、中间产物和验收证据写入文件,Agent 才能跨会话恢复,也能和人或其他 Agent 协作。Git 在此基础上提供了可回滚实验、变更审阅与工作区隔离。
.agent/
├── plan.md # 目标、阶段与完成条件
├── handoff.json # 下一轮恢复所需的结构化状态
├── evidence/ # 测试报告、截图、诊断结果
└── decisions.md # 被确认过的关键取舍
真正需要塞进模型上下文的只是当前阶段、相关文件指针和少量摘要;两千行日志应留在磁盘上,按需读取头尾或精确片段。这样既降低上下文噪声,也避免任务越跑越久、推理越发散。
工具既是能力,也是攻击面
工具描述、参数 schema 和返回结果都会进入模型可见范围。工具列表越长、职责越重叠,越容易误调用;接入不可信 MCP 时,工具描述本身还可能包含提示注入。设计原则是:小而清晰、最小权限、结果可校验。
type DeployInput = {
environment: 'staging' | 'production';
changeTicket: string;
approvedBy?: string;
};
function deploy(input: DeployInput) {
if (input.environment === 'production' && !input.approvedBy) {
throw new Error('生产部署需要人工审批记录');
}
return runDeployment(input);
}
同样的原则适用于 Shell:给 Agent 通用执行能力很有价值,但应运行在隔离环境,限制网络、凭据和目录范围。沙箱内预置语言运行时、Git、测试工具和无头浏览器;产物由 Harness 收集,环境在任务结束后销毁。
把验证做成反压,而非建议
“记得运行测试”是软提示;在编辑后自动运行类型检查、在提交前阻止格式错误、在推送主分支前要求审批,才是硬约束。一个高信噪比的策略是:成功保持安静,失败把可行动的错误完整回灌给 Agent。
result = run("pnpm typecheck && pnpm test")
if result.exit_code != 0:
state["feedback"] = {
"kind": "verification_failed",
"tail": result.stderr[-6000:],
"artifact": save_full_log(result),
}
return "repair"
return "review"
让生成者自评通常会偏乐观。重要任务应分出规划者、执行者与评估者:评估者读取验收契约和事实证据,独立决定是否通过;涉及外部副作用时再加入人工关口。这样系统不会因为“语气自信”就把变更视为正确。
Harness 不是一次配置完毕的模板。模型、工具和任务边界变化后,旧的控制可能变成冗余,新风险又需要新机制。持续记录失败、缩短反馈路径、删除失效规则,才会让运行时逐渐贴合真正的工程环境。
上下文策略决定长任务能走多远
模型并不是读到更多内容就会做得更好。任务进行数小时后,重复的工具输出、过期计划和无关对话会挤占注意力,造成“上下文腐化”:模型开始忽略重要约束、重复已做过的动作,或为了结束会话而过早宣布完成。Harness 的职责是主动管理信息密度。
可把信息分成三层:每轮都必须携带的任务契约与安全规则;当前阶段需要的工作集;存放在磁盘或检索系统中的长尾证据。超过阈值的日志只注入头尾与错误片段,完整文件保存到 artifact;旧对话压缩为事实性 handoff;skill 按任务匹配渐进加载,而不是启动时把所有工具说明塞进提示词。
def compact_context(transcript, artifacts):
handoff = extract_facts(transcript, fields=[
"goal", "constraints", "completed", "failed_attempts", "next_step", "open_questions"
])
path = artifacts.write_json("handoff.json", handoff)
return {
"fresh_context": ["读取 .agent/handoff.json 后继续,不要重复已完成检查。"],
"artifact": path,
}
对三天以上的任务,必要时应主动换一个全新上下文,而不是无限压缩同一会话。新的 Agent 通过交接文件、Git 状态、计划和验证证据重新上岗;这比让旧会话背着所有历史继续推理更可靠。
规则文件要短,但必须可执行
AGENTS.md 的价值不在于把团队 wiki 全搬进去,而在于提供飞行检查单:包管理器是什么、哪些命令必须执行、哪些目录不能碰、哪些变更需要审批。过长的规则会让每一条都失去显著性;空泛的“写高质量代码”更无法改变行为。
# AGENTS.md
- 使用 `pnpm`;依赖变更后运行 `pnpm lint && pnpm test`。
- 不得修改 `contracts/` 的公开 schema;需要变更时先创建设计说明。
- 禁止提交 `.env`、访问生产数据库或执行 `git push --force`。
- 修改支付、权限、迁移时,必须产出回滚步骤并等待人工审批。
规则之外的项目知识可以拆到按需加载的 skill 中。这样通用任务不会被部署细节干扰,涉及迁移或支付时才加载相应风险清单。
观测不是可选的附属品
没有轨迹,就无法区分“模型推理错了”“工具返回错了”“上下文缺失”“策略把它带错路”。每次运行至少记录:任务与版本、输入摘要、模型与参数、工具调用、权限决策、状态迁移、耗时、成本、验证结果和最终 artifact。敏感输入需脱敏,日志本身也应有访问控制。
{
"run_id": "run_01J9...",
"node": "run_tests",
"tool": "shell",
"input_digest": "pnpm test auth",
"exit_code": 1,
"duration_ms": 18234,
"next_state": "repair",
"artifact": ".agent/evidence/test-01J9.log"
}
有了轨迹才能建立评测集:挑选真实失败案例,固定输入与验收标准,比较修改 Harness 前后的成功率、人工接管率、延迟和成本。改善不是“感觉更聪明”,而是某类任务在同样约束下更少误调用、更少返工。
权限要随动作升级
读取文件、运行测试、创建分支、发送消息、部署生产系统的风险完全不同,却常被粗暴地归为“Agent 有工具权限”。更合理的是能力分层:默认只读;可逆的本地写操作允许自动执行;对外可见的写操作要求证据和审批;不可逆或高影响操作使用短期令牌、双人批准或直接禁止。
Harness 的目标不是消灭模型的不确定性,而是保证不确定性不会直接落到不可恢复的系统上。当模型的能力提升时,规则可能减少;当任务半径扩大时,新的隔离、交接和评估机制又会变得必要。运行时因此始终是活的工程系统。
FIELD NOTES / DISCUSS
文章讨论
读完后,欢迎留下你的补充、疑问或不同看法。
正在读取评论…