07 harness

Harness

让 LLM + ReAct 在真实世界里稳定、安全、持续跑起来的那套工程脚手架。

本章聚焦  →  把模型包成能干活的系统 · 支撑 Agent 稳定运转的工程脚手架

我们已经知道了LLM 如何说话、Agent如何长出手脚、ReAct 如何让它连续干活。但把这三样直接丢进 Agent Loop 放任不管,跑不了几轮就会出事:web_search 返回几万字撑爆窗口、脚本写错却没人告诉它、上下文满了开始出错、一条 rm -rf 删掉整个工作区。

让这台机器能稳定、安全、持续地跑起来的那一整套人工规则,就是 Harness。

7.1 什么是 Harness

Harness = 人工对 Agent Loop 制定的所有约束规则的集合,是 Agent 的工程脚手架。

如果说 LLM + ReAct 是引擎,那 Harness 就是让引擎能真正上路的底盘、刹车和仪表盘。

上一章节开头我们提到的截断超长返回、把脚本报错回传、上下文过半就压缩——手段各异,却都是 Harness,也都在做同一件事:管理喂给 LLM 的 Context。所以说,Harness 要解决的问题,大多本质上是上下文问题。

图 1 · Harness 全景:把内核裹起来的那一圈规则
输入侧上下文组装与压缩
内核Agent Loop:观察 → 决策(LLM) → 行动(工具) → 再观察
工具侧before / after hook · 边界防护
循环侧用户中断 · 步数上限 · 完成判定
观测侧日志 · Trajectory Replay
内核只管“想一步、动一下”;Harness 在它四周包了输入、工具、循环、观测四圈规则。
直觉类比 · 教练车

LLM 像刚上路的学员司机,Harness 就是那套教练车装置——在关键处兜底,让新手也敢真正上路。

7.2 Hooks

Agent 真正改变世界靠的是工具,所以 Harness 最常见的着力点,就是在工具执行的前后各挂一个钩子(hook)

图 2 · 一次工具调用穿过两道钩子
LLM发起 tool_call before_tool校验 / 改参 / 放行或拦截 执行工具原始结果 after_tool截断 / 注入反馈 回传 LLM下一轮
工具的输入和输出,都要先过 Harness 这两道钩子。
web_search 返回 5 万字,after_tool 把它裁到预算内并附上“(内容过长,已截断)”,避免一次调用就超出上下文窗口(呼应第 5 章场景 1)。在 mybot 里,工具返回统一经过一层处理,见 mybot/web/agent_loop.py

7.3 工具边界与安全

工具让 Agent 能改造环境,也就意味着它能破坏环境。Harness 必须给工具划边界:

Agent 试图执行 rm -rf /before_tool 命中黑名单直接拒绝,并把拒绝原因回传给它,让它换一个安全的做法。

对应到 mybot:BashToolrestrict_to_workspace,以及 BASH_GUARD_PATH_TRAVERSAL / BASH_GUARD_OUTSIDE_WORKDIR 两个护栏开关;文件工具也都被限制在工作区内。(这里只做指向,实现细节见仓库代码。)

7.4 后处理

工具结果不只是「原样返回」,after hook 常在里面注入反馈,帮 Agent 自我修正:

截断

超长返回按 token 预算裁剪。

校验注入

写完脚本自动跑语法 / LSP 检查,有错就把错误粘进工具结果,下一轮自己改(第 5 章场景 2)。

错误引导

失败时附“分析错误再换种方式”,或“write_file 太大就改用 apply_patch”。

说到底,after hook 就是把“环境的真实反馈”翻译成 Agent 看得懂、能据此改进的下一轮输入。mybot 里就有这类提示,见 agent_loop.pytool_base.py

7.5 循环控制

while True 得有人管:什么时候停、停不下来怎么办、以及“它说做完了但其实没做完”。

这些都是给自主循环装的“安全阀”。

7.6 可观测与回放

Agent 是一个黑箱式的自主过程,出了问题得能复盘。Harness 会记录完整的 trajectory(每一轮的 thought / action / observation),从而支持:

有了可观测,Harness 才能从凭经验设规则,转向依据数据设规则。

至此,Agent = LLM + ReAct + Harness 的三块拼图补齐:LLM 会说话,工具让它长出手脚,ReAct 让它连续地干活,而 Harness 是让这一切在真实世界里稳定、安全、可控地跑起来的工程底盘。

7.7 Prompt / Context / Harness / Loop Engineering

走到这里,我们已一路穿过围绕模型的四层工程。它们常被混为一谈,其实是同一个栈的不同层级,各有各的工作单位与失效方式——理清它们,就能判断"问题该在哪一层解决"。

关键的直觉是:每一层都包裹住前一层。Loop 里跑 Harness,Harness 的每一步组装 Context,Context 里装着 Prompt,而最中心是那次模型调用。模型本身是可复用的"商品",围着它搭的那一圈圈,才是真正的工程。

图 · 四层工程逐层包裹:模型是商品,外面那一圈才是工程
④ Loop engineering 系统 · the system 决定要不要再跑一次:目标 + 成功标准、迭代/预算上限、进度检测、自主多轮 ③ Harness engineering 机器 · the machine 把模型接到可执行环境:工具定义、输出解析、失败重试、子代理路由、结果校验 ② Context engineering 窗口 · the window 这一轮塞给模型什么:检索、记忆、历史、工具返回——都在有限窗口里权衡 ① Prompt engineering 措辞 · the words 单次调用怎么说:角色、指令、示例、输出格式 模型调用 · the commodity 每一层都包裹住前一层 —— 模型是商品,围着它的那一圈才是工程。
由内向外:Prompt(怎么说)→ Context(塞什么)→ Harness(怎么接进真实环境)→ Loop(要不要再来一轮)。它们不互相替代,而是层层相包的同一个栈。

把每一层拆开看内部,它们各自的流水线是这样的(每往外一层,视野就"拉远"一圈):

图 · 四层工程的内部流水线:每往外一层,视野拉远一圈
1
Prompt engineering措辞 · The message
输入 Inputs
角色 Role指令 Instructions示例 Examples格式 Format
1组装 Compose
PROMPT"你是资深工程师…""依据这些文档…""用 JSON 回复"——拼成的一次输入
2发送 Send
LLM推理 Inference
3生成 Generate
回复 Response原始输出 Raw output
↻ 4 精修:调整最弱的那一味
工作单位:一次输入
2
Context engineering记忆 · The memory
输入 Inputs
查询 Query文档 Docs记忆 Memory工具返回 Tool outputs历史 Prior turns
1收集 Gather
策展 Curator选取、压缩、丢弃
2整理 Curate
上下文窗口 Context windowchunk · chunk · ≈压缩 · 丢弃 · 有限预算
3喂入 Feed
LLM推理 Inference
回复 Response或工具调用
↻ 4 更新工作集:本轮输出成为下一步的上下文
工作单位:窗口里留下什么
3
Harness engineering机器 · The machine
只画核心循环;省略状态、护栏、错误处理
GATHER 收集
上下文区Curator
Prompt 区Prompt
1行动 Act
LLM推理 Inference
调用 →
工具 Tools执行、抓取
子代理 Sub-agents专才 Specialists
2校验 Verify
校验器 Verifier测试、评判
最终回复通过,交付用户
↻ 3 重试:带更新后的上下文重走 收集→行动→校验
工作单位:整台机器(含 Gather 内的 prompt + context)
4
Loop engineering系统 · The system
包裹上面那台机器;决定要不要再跑一次
目标 + 成功标准事先定义,不靠感觉
1运行 Run
HARNESS收集 → 行动 → 校验,跑一趟 · 它可能自称"完成"——但还没被验证
2检查 Check
迭代上限硬顶 Hard cap
预算 & 时间Token / $ / 秒
无进展同样的调用与参数
完成检查测试通过
3判定:是触发了刹车,还是真的完成了?
最终回复 Final Response是:完成检查确认已完成
↻ 4 还没完成:context += 结果 → 重复
工作单位:一整段运行 the run
每一层都包裹住前一层 —— 模型是商品,围着它的那一圈才是工程。
四张流水线并排看:Prompt 精修一次输入 → Context 决定窗口里留什么 → Harness 把模型接上工具与校验、跑通一台机器 → Loop 在外面判断"刹车还是完成、要不要再来一趟"。改编自 DailyDoseofDS。
它在解决什么 工作单位 典型失效
① Prompt
措辞
单次调用里,把任务对模型讲清楚(角色、指令、示例、输出格式、CoT) 一次模型调用 措辞不当 → 推理跑偏、格式不对
② Context
窗口
这一轮该把哪些信息放进有限窗口(检索、重排、记忆、历史压缩) 一轮交互 信息丢失 / 关键内容被埋在中间 → 准确率下降
③ Harness
机器
把模型接到可执行环境:工具、解析、重试、子代理、校验 一次完整执行 工具报错、解析失败、边界情况没兜住
④ Loop
系统
无人介入下自主多轮:定义何时继续、何时停、何时算完成 一整段自主运行 死循环、过早收工、识别不出"已完成"

回看整份讲义:第 2 章练的是 Prompt,第 5 章讲的是 Context,本章 Harness 正是第三层,而 7.5 的循环控制已经踩到了最外面的 Loop。排查问题时先定位到层,往往比盲目改 prompt 更快。