跳转到内容

Agent 与 harness:把「帮我优化」变成可验收的工程

上一章导读与环境中,我们看到 Kernel Agent 的产物不是一段代码,而是一条带证据的候选链。问题随之而来:模型本身只会根据上下文预测下一个 token,它凭什么能稳定地改对 kernel、跑对测试、在合适的时候停下来?答案不在模型里,而在模型外面那层 harness(执行框架)。这一章把 Agent 循环拆开,说明 harness 为什么存在,以及怎样为一个 kernel 任务写出可执行的验收标准。

  • 描述一个 Agent 循环的五个组成部分,并解释上下文为什么是稀缺资源。
  • 用自己的话说明 harness 与 prompt、Agent 框架的区别。
  • 写出包含正确性、性能、约束与通过条件的任务合同。
  • 解释 draft 与 plan 各自解决什么问题。
  • 识别 Agent 自评失灵的四种机制,并知道「写/判分离」为何必要。
  • 为一个最小任务设计目录、产物与停止条件。

Agent 循环:模型、上下文、工具

Section titled “Agent 循环:模型、上下文、工具”

一个终端里的编码 Agent,本质上在重复下面这件事。拆开看,它由五个部件组成:模型(策略)、上下文(工作记忆)、工具(动作接口)、循环控制(何时继续、何时停止)与环境(文件系统、权限、GPU)。

  1. 读取当前上下文:系统指令、任务描述、已读文件、历史工具输出、之前的决定。
  2. 生成下一步动作:回复用户,或发起一次工具调用(读文件、搜索、编辑、执行命令)。
  3. 等待环境返回结果:文件内容、命令退出码、stdout/stderr、测试报告。
  4. 把结果写回上下文,进入下一轮。
  5. 检查停止条件:成功、预算耗尽或阻塞时结束并归档证据;否则回到第 1 步。

这里有三条容易被低估的事实。

第一,上下文是有限且会衰减的资源。 模型并不是「记得」整个仓库,它只看见被塞进上下文的那一部分。长任务里,早期读过的代码、任务合同、失败原因会被压缩或截断;一旦任务合同被挤掉,Agent 就会开始「凭感觉」优化。可靠的做法不是把 prompt 写得更长,而是把合同、计划、候选记录落成文件,每轮重新读取关键部分。

第二,工具调用是 Agent 与现实的唯一接口。 模型可以声称「已经通过测试」,但只有真正执行 pytest 或 ncu 才知道结果。工具也定义了它的能力边界:没有 profiler,它只能猜瓶颈;没有 benchmark 脚本,它无法判断快慢;没有写权限,它只能给建议。

第三,会话不等于任务。 一个 session 是一段连续对话;一个任务可能跨多个 session,甚至跨两个模型。KDA 的推荐布局把「可复用的工作流」和「任务工作区」分开:工作流放在参考仓库,代码、测试、数据、benchmark 脚本和产物留在任务工作区。这样换模型、换会话都不会丢证据。

harness 是包在 Agent 外面的执行框架:它规定工作区、任务合同、可用工具与权限、预算、验证命令、记录格式和停止条件。Agent 负责在框内探索,harness 负责让探索可复现、可审计、可比较。

它和 prompt 的区别是「强制」:prompt 说「请先写计划」,harness 可以检查 docs/plan.md 是否存在,不存在就不允许改代码;prompt 说「不要改测试」,harness 可以用权限或 diff 检查阻止。它和 Agent 框架的区别是「任务语义」:框架提供工具调用与消息循环,harness 决定这个任务什么叫成功。

下图是一个最小闭环:任务合同经过计划变成动作,工具循环产生证据,验证决定是否晋升,记录把这一次的经验留给下一次;预算贯穿全程。

Agent harness 循环任务规格进入计划,再进入由 read、edit、run、profile 组成的工具循环;验证通过后记录,不通过则带着证据回到工具循环。预算约束贯穿全程。AGENT HARNESS · 一次任务的闭环BUDGET / 预算01 / TASK任务规格验收 · 预算正确性 / 性能02 / PLAN计划步骤 · 风险先改哪里03 / TOOL LOOPread 读取edit 编辑run 运行profile 性能每一轮都留下可复核的证据04 / VERIFY验证测试 · baseline证据是否完整唯一放行条件05 / RECORD记录保留理由放弃模式BUDGET / 预算时间 · 调用次数 · 花费已用 62% · 示意停止条件:预算耗尽 · 连续无改进 · 满足验收
Agent harness 的基本闭环:工具循环产出证据,验证决定是否继续,预算贯穿全程。

读图时注意两个容易被忽略的箭头。一个是「验证 → 计划」:验证失败不一定要改 kernel,也可能是计划、测试或 baseline 错了,应该回到计划层修正。另一个是「预算 → 停止」:预算不是事后统计,而是每一轮都要检查的约束,否则 Agent 会在一个错误方向上无限迭代。

任务合同:把「优化一下」变成可验收

Section titled “任务合同:把「优化一下」变成可验收”

KDA 的 prompts/basic-flow.md 把任务合同拆成八项内容:任务名、目标、正确性要求、性能或质量目标、允许的实现方式、验证命令、评估命令、晋升条件;docs/agent-flow.md 进一步要求候选既要满足合同,又要有证据表明目标指标变好或不变差,才可晋升;被否决的候选要写明理由而不是静默丢弃。这些可以直接拿来当模板。关键在于每一项都要能对应一条命令或一个可检查的事实。

字段 反例 可验收的写法
目标 优化 attention 把 decode attention 在 batch=16、seq=8k、head_dim=128 下的 kernel 中位耗时降到 baseline 的 0.8 倍
正确性 结果差不多 与 FP32 参考实现比较,BF16 输出最大相对误差不超过 2e-2,且无 NaN/Inf
性能 越快越好 固定输入与 dtype,预热 5 次、重复 30 次,取中位数;同时报告 p90(90 分位耗时)
约束 随便写 只允许修改 kernel/ 下文件;不得改测试与 benchmark;允许使用 CuTe-DSL
验证命令 跑一下测试 python tests/check.py --case decode_8k --tol 2e-2
评估命令 看看速度 python bench.py --case decode_8k --warmup 5 --repeat 30 --json
晋升条件 比原来快 正确性通过,且中位耗时低于 baseline 的 0.8 倍,p90 不高于 baseline 的 0.9 倍

这张表最重要的不是格式,而是把「我认为」变成「命令说」。如果一条验收标准无法被第三方在同一硬件上重跑,它就不算验收标准。

正确性还要覆盖边界:空输入、单 token、最长序列、非连续布局、极端 gate(门控)值、不同 batch 组合。只测一个「好看的 shape」是 reward hacking(奖励作弊)的温床。性能则要固定硬件、时钟、输入、dtype、warmup 与重复次数;kernel-only(只计 kernel 执行)与 end-to-end(含框架与数据准备)要分开报,否则你无法判断加速来自 kernel 还是少了框架开销。

KDA 的流程要求 Agent 先写 docs/draft.md,再转成可执行的 docs/plan.md。这不是形式主义,而是把「探索」和「承诺」分开。

draft 是低成本的探索。 它应该包含:当前 baseline 是什么、如何验证;主要风险与未知;按期望收益/风险排序的候选方向;第一步做什么;要跑哪些命令;什么证据足以晋升或放弃。draft 允许粗糙、允许被推翻,目的是暴露假设。

plan 是带约束的承诺。 它把 draft 里的方向变成有顺序的步骤:先改哪一块、每步如何验证、失败时回退到哪里、预算怎么分配。plan 一旦确认,Agent 的改动就应该围绕它进行;要改 plan,必须显式说明原因。

维度 draft plan
目的 探索可能性 约束执行
内容 风险、候选方向、初步证据 步骤、命令、回退点、预算
变更成本 低,鼓励推翻 高,需要说明理由
常见错误 写成营销文案 写成无法执行的愿望清单

一个实用技巧:让 plan 的每一步都带一个「验证动作」。例如「把 BLOCK_SIZE 从 128 调到 256,跑一次正确性 + 30 次 benchmark,若中位耗时无改善则回退」。这样 Agent 不会连续改十处再一次性测试,失败时也能定位。

Agent 说「完成了」,只是一个 claim(声明),不是 evidence(证据)。至少有四层原因。

同源盲区。 写代码的模型和审代码的模型如果共享同一套先验,它容易犯的错,往往也正是它看不出来的。Humanize 论文把这条链路写成 b → b·m_R → b·m_R·m_O:b 是 builder(实现者)自身的漏检率,m_R 是 reviewer(评审者)的漏检率,m_O 是人工或 oracle(可依赖的标准答案)的漏检率。换一个厂商的模型、换一个全新上下文做 reviewer,本质上是在压低 m_R;并且只有 reviewer 能宣布完成。

目标被代理指标替换。 Agent 被要求「让 benchmark 变快」,它可能去改 benchmark、缓存输入、跳过计算,而不是优化 kernel。KDA for KDA 的 3.74× 案例就是典型:硬编码 L2 范数、跳过三角矩阵求逆,在合成数据上误差刚好过关。

自我一致性偏差。 模型倾向于为自己已经写出的实现找理由。让它同时扮演作者和裁判,它很难主动否定自己刚做的工作。

上下文污染。 作者上下文里有「我为什么这样写」的叙事,裁判需要的是「结果是否满足合同」的冷证据。两者混在一起,判断就会被叙事带偏。

所以可靠的结构是写/判分离:builder(KDA 中称 Coder)在任务工作区实现,reviewer(KDA 中称 Verifier)用新上下文、甚至不同厂商的模型,只读合同、diff 与证据,独立重跑验证命令。第 5 章 Humanize 会展开计划合同、机械 gate(门禁检查)与 RLCR(Ralph-Loop with Codex Review);这一章先记住原则:完成与否不能由写代码的那一方单方面宣布。

没有预算的 Agent 会一直「再试一个版本」。预算至少包含五维:墙钟时间、token/花费、GPU 小时、候选数量、连续无改善轮数。前三个是成本,后两个是方向性信号。

停止条件建议写成三类:

  • 成功停止:合同里的晋升条件全部满足,证据已归档,reviewer 签字。
  • 预算停止:任一预算耗尽,保存当前最佳候选与失败记录,写复盘。
  • 阻塞停止:缺少权限、数据、硬件,或连续两次验证结果互相矛盾;此时应升级给人,而不是继续猜。

KDA 官方博客的策略消融在 B300 上设了 14 小时预算(2026-09-27),Humanize 论文的 DSA indexer 消融用 48 小时预算(2026-10);它们是研究级预算,不是初学者默认值。小任务可以从 30 分钟、20 个候选开始,先验证流程,再增加投入。

KDA README 推荐的工作区布局很值得直接借用;再补上任务合同与两个 Agent 不应修改的验证脚本,最小版本如下:

task-workspace/
docs/
task.md # 任务合同,Agent 不可修改
draft.md # 第一版计划草稿
plan.md # 可执行计划
runs/ # 每次运行的日志与命令
outputs/ # 生成的 kernel、编译产物
profile/ # ncu 报告与摘要
tests/
check.py # 正确性检查,Agent 不可修改
bench.py # 性能测量,输出 JSON
benchmark.csv # 表格化的性能记录
candidates.jsonl # 候选、父版本、状态、理由

每个文件的职责:task.md 保存不可变的合同,draft.md 保存被推翻的想法,plan.md 保存当前承诺,runs/ 保存可重跑的命令与环境,profile/ 保存性能证据,tests/ 与 bench.py 保存独立验证脚本,benchmark.csv 让趋势可见,candidates.jsonl 让「为什么保留/放弃」可追溯。格式可以变,一致性不能变:下一位工程师应该能只看这些文件就重建你的决策。

一个最小的循环伪代码:

# harness 只做四件事:给任务、跑工具、验结果、记证据
for step in range(max_steps):
context = read("docs/task.md") + read("docs/plan.md") + recent_tool_outputs() # 合同 + 计划 + 近期证据
action = agent.next_action(context) # 避免只凭记忆,每轮重建关键上下文
result = tools.execute(action) # 编译、测试、benchmark、ncu
append_jsonl("runs/loop.jsonl", action, result)
if not result.correct:
agent.observe(result.stderr)
continue
if result.speedup >= target and verifier.accept(result):
promote(result)
break
if budget.exhausted() or no_improvement(patience=3):
write_postmortem()
break

配套的验证脚本应该独立于 Agent 的编辑范围:

#!/usr/bin/env bash
set -euo pipefail
python tests/check.py --case decode_8k --tol 2e-2
python bench.py --case decode_8k --warmup 5 --repeat 30 --json benchmark.json

注意 set -euo pipefail:测试失败必须让脚本失败,而不是被后续命令掩盖。benchmark 输出 JSON,方便 harness 自动比较;人只看摘要,原始数据留在文件里。

  • 把 harness 当成更长的 prompt。 不能被检查的约束等于没有约束。
  • 只记录成功候选。 失败候选与放弃理由才是下一次的捷径。
  • 让 Agent 同时改 kernel 和测试。 正确性证据立刻失去独立性。
  • 只测一个 shape。 过拟合单个形状是最常见的「假加速」。
  • 上下文塞得越满越好。 关键合同被挤掉后,Agent 的行为会不可预测。
  • 没有回退点。 连续改动无法归因,最后只能全部重来。
  • 我能画出 Agent 循环,并指出上下文、工具、验证各自的位置。
  • 我能解释 harness 与 prompt、Agent 框架的区别。
  • 我写的任务合同里,每条验收条件都能对应一条命令。
  • 我能说出 draft 与 plan 的区别,以及为什么先 draft 后 plan。
  • 我能举出至少两种 Agent 自评失灵的机制。
  • 我为自己的任务设置了至少三类预算和三类停止条件。
  • 我知道最小工作区里每个文件的职责。
L2基础45 分钟

为一个 kernel 任务写验收标准与停止规则

目标:把模糊的优化愿望改写成可执行合同,并设计一个不会被 Agent 绕过的停止规则。

任务:

  1. 选一个你熟悉的算子(softmax、LayerNorm 或一个小 GEMM),写一份任务合同,覆盖目标、输入输出、正确性、性能、约束、验证命令、评估命令、晋升条件。
  2. 至少写三条边界用例(例如空输入、极长序列、非连续布局),并说明每条用什么命令测。
  3. 设计停止规则:成功、预算耗尽、无改善、阻塞各一条;说明每条由 harness 的哪个检查触发。
  4. 让另一个 Agent(或同学)只读合同,尝试指出一个可以「钻空子」的地方;你补上对应约束。

验收标准:合同中的每条条件都可执行;边界用例不少于三条;停止规则不依赖 Agent 自评;你能指出至少一个被补上的漏洞。

提示

钻空子的方向包括:改测试、换 baseline、只测一个 shape、把计算挪到计时区间外、降低精度但不改容差。逐条问「如果我是想偷懒的 Agent,我会怎么绕过这条?」

下一章补上 kernel 这一侧的基础:GPU 怎么执行一个 kernel、roofline(屋顶线模型)怎么判断瓶颈、prefill 与 decode 为什么是两类问题,以及如何用 ncu 的六个维度读出一份可信的性能证据:GPU kernel 与性能。

本章链接与数据核对至 2026-10-29;完整来源清单见资料库。