Skip to content
检测中

Harness 是什么:从聊天框到能干活的 Agent

给 LLM 一个终端,它就成了 agent 吗?不。差的不是模型,是承载它的那一层壳——harness。本篇定义 harness 的三组件,划清它与 chatbot / SDK / framework 的边界,并讲清从「问答」到「循环执行」的范式迁移。


目录

  1. 引言:终端不等于 agent
  2. 1.1 重新定义 Harness
  3. 1.2 三组件为何不可分割
  4. 1.3 边界光谱:Chatbot 到 Autonomous Agent
  5. 1.4 范式迁移:问答到循环执行
  6. 1.5 一个最小 harness
  7. 1.6 harness 成熟度阶梯
  8. 反模式小结
  9. 迁移清单
  10. 下一步

引言:终端不等于 agent

一个常见错觉:把 Claude / GPT 接上 bash,或者给它开个文件系统,它就「是」 agent 了。于是有人反复追问「哪个模型最适合做 agent」「GPT-5 出来是不是 agent 就成了」,却从没人问「我给它搭的那个壳,够格吗」。

答案往往是不够。

差的不是模型。同样的模型,丢进一个对话网页,它是聊天框;接进一个有工具总线、有上下文预算、有会话恢复的容器里,它能连跑二十轮修掉一个 CI 失败。模型没变,壳变了。这层壳,就是 harness。

本系列要讲的就是这层壳的工程。它和站内 Loop Engineering 系列是互补的两条主线:Loop 讲「发动机怎么转」(循环内在动力学),Harness 讲「把发动机装上车能跑」(底盘、悬挂、仪表盘、油路)。本篇先把「壳」的定义钉死。


1.1 重新定义 Harness

Harness(承载壳):包裹在 LLM 之外,让它从「一次性应答器」变成「能持续干活」的那一层工程框架。

一个最小可用的 harness,由三块组件构成,缺一不可:

        ┌─────────────────────────────────────────────┐
        │            ② 上下文预算 Context Budget        │
        │   在有限 token 窗口里,编排系统提示/技能/      │
        │   文件/记忆的注入与裁剪                       │
        └────────────────┬────────────────────────────┘
                         │ 喂进去 / 拿出来
        ┌────────────────▼────────────────────────────┐
        │            ① Loop 容器 Loop Container         │
        │   感知 → 思考 → 行动 → 反馈,一圈圈转         │
        └────────────────┬────────────────────────────┘
                         │ 行动 / 观察
        ┌────────────────▼────────────────────────────┐
        │            ③ 工具总线 Tool Bus                │
        │   把读文件 / 跑命令 / 调 API 安全接进 LLM     │
        └─────────────────────────────────────────────┘

① Loop 容器:把「感知环境 → 思考下一步 → 行动(调工具) → 收到反馈」这个循环跑起来的运行时。它决定 agent「怎么转、转多久」。这一块的内在动力学归 Loop Engineering 管,harness 只负责把它承住:给它一个进程、给它终止条件、给它恢复点。

② 上下文预算:LLM 的窗口是有限的。harness 必须在每个回合决定——这次请求里塞系统提示、塞哪些文件、塞几条记忆、塞多少对话历史、什么时候该压缩或裁剪。这是 agent 越跑越久却不崩的前提。本系列第 3 篇专门展开。

③ 工具总线:把「读文件、写文件、跑 shell、查 DB、建 PR、调外部 API」这些能力,以 LLM 可调用的形式(通常遵循 tool calling 协议)接进来,并管好校验、并发、权限、超时、错误回灌。没有它,模型只能空谈。第 2 篇展开。

一句话:Loop 决定 agent 怎么想,Harness 决定 agent 被什么托着跑。


1.2 三组件为何不可分割

这三块不是「锦上添花」,而是互相支撑的承重墙。去掉任何一块,agent 都会退化成一个远不如它名字响亮的东西。

缺失组件退化成症状
缺 Loop 容器问答机(Q&A machine)一问一答,无状态。问完即忘,不会主动接着干。等于 ChatGPT 网页版
缺工具总线嘴炮(talker)能规划、能讲方案,但碰不到真实文件和命令。产出的全是「你应该这样改」,不动手
缺上下文预算健忘症患者(amnesiac)跑几轮就忘掉最初的约束,重复犯错,窗口塞爆后行为漂移,长任务必崩

三者耦合很紧:Loop 容器每个回合都要从上下文预算取输入、把工具执行结果回写进上下文;工具总线产出的观察(observation)是下一轮思考的原料。任何一个接口设计不好,整套都跟着跛。

这也解释了为什么「换个更强的模型」往往治不好一个烂 agent——烂在壳上,不在发动机上。


1.3 边界光谱:Chatbot 到 Autonomous Agent

harness 不是凭空冒出来的概念,它处在一条光谱上。把容易混淆的五类东西从左到右排开,每一级都比上一级多了点东西:

级别定义比上级多了什么例子
① Chatbot无状态单轮对话 UIChatGPT 网页闲聊
② SDK封装 API 调用的库程序化调用、流式、消息拼装OpenAI SDK、Anthropic SDK
③ Framework给 agent 开发提供脚手架抽象层、编排原语、约定LangChain、LlamaIndex
④ Harness承载 agent 实际跑起来的壳三组件齐全:Loop + 工具总线 + 上下文预算,外加会话生命周期、沙箱、权限pi、Claude Code、Cursor、Aider
⑤ Autonomous Agent长期自治、跨会话调度调度层、状态持久化、验证链、kill switch、多 loop 协调Devin、CI Sweeper(见 Loop L3

关键区分在 ③→④:framework 给你积木,你自己拼;harness 是已经拼好、能直接装上发动机跑的整车。framework 不替你管 token 预算、不替你做 bash 沙箱、不替你处理 worktree 冲突——这些都是 harness 的活。

而 ④→⑤ 的跨越,正是 Loop Engineering 系列的主题:harness 是「单次会话的设置」,autonomous agent 是「harness + 调度 + 状态 + 验证链」。本系列聚焦 ④,但要时刻记住它往上还能长。


1.4 范式迁移:问答到循环执行

理解 harness,本质是理解一次范式切换。做 chatbot 和做 harness,工程预设完全不同。

维度问答范式(Chatbot)循环执行范式(Harness)
状态无状态有状态,跨回合保持
交互单次 request-response多轮循环,可能数十上百轮
副作用无(只产文本)有(改文件、跑命令、发请求)
错误处理用户重问一句harness 必须捕获、重试、降级
中断恢复不存在session/run 可 fork、可 resume
关注点这条回答好不好这一整段执行稳不稳、可不可查、可不可恢复
验收人看一眼测试 / 验证器 / 门禁

这张表才是 harness 工程的真正考卷。绝大多数「我的 agent 不好用」的抱怨,根因都落在右侧某一栏:要么没管状态、要么没管副作用、要么断电就丢、要么出错了不知道怎么收。模型再强,也回答不了「上一轮我改到哪了」——这得 harness 替它记。

一个粗暴但好用的判据:如果你的系统断电重启后会「忘了自己在干什么」,它就还不是 harness,只是个套了壳的问答机。

换句话说,harness 工程的第一道分水岭,就是「你的 agent 有没有一段可以被打断、被观察、被接回去的执行轨迹」。问答范式里没有轨迹,只有一问一答的孤点;循环执行范式里轨迹是头等公民,谁都能 fork、谁都能 replay。


1.5 一个最小 harness

剥到骨头,harness 也就是「一个 while 循环 + 工具分发 + 上下文追加」。下面这段约 40 行 TypeScript 是伪代码,省略了 schema 校验、错误重试、token 计量、流式处理,只为展示三组件如何咬合:

typescript
// 伪代码:仅示意三组件咬合,非生产可用
type Msg = { role: "system" | "user" | "assistant" | "tool"; content: string };

// ③ 工具总线:把能力注册成 LLM 可调用的 schema
const tools = {
  read_file: { schema: { path: "string" }, run: (a: any) => readFileSync(a.path, "utf8") },
  write_file:{ schema: { path: "string", content: "string" }, run:(a:any)=>writeFileSync(a.path,a.content) },
  run_bash:  { schema: { cmd: "string" }, run: (a: any) => execSync(a.cmd).toString() },
};

async function harness(task: string) {
  // ② 上下文预算:初始窗口 = 系统提示 + 用户任务
  const context: Msg[] = [
    { role: "system", content: "你是一个编码 agent。按需调工具完成任务。" },
    { role: "user", content: task },
  ];

  // ① Loop 容器:感知 → 思考 → 行动 → 反馈,转圈
  for (let step = 0; step < 50; step++) {           // 终止条件之一:步数上限
    // 思考:把当前上下文喂给 LLM
    const reply = await llm.chat({ messages: context, tools: schemas(tools) });

    context.push({ role: "assistant", content: reply.text });

    if (!reply.tool_calls?.length) break;            // 模型没再调工具 → 视为完成

    // 行动 + 反馈:分发每个工具调用,结果回灌进上下文
    for (const call of reply.tool_calls) {
      const result = await tools[call.name].run(call.args);
      context.push({ role: "tool", content: result });   // 工具观察成为下一轮原料
    }
    // ② 真实 harness 在这里还要做:超长则裁剪/压缩上下文
  }
}

三组件在这段里一目了然:for 循环是 Loop 容器,tools 字典是工具总线,context 数组 + 末尾注释那行裁剪是上下文预算。真实 harness(pi 等)在这三块上各自加了几千行工程,但骨架就是这个。

对照 pi:上面这套用 pi SDK 写就是 createAgentSession 起一个会话、session.prompt 驱动循环、用 registerTool + Typebox 注册工具、session.subscribe 监听工具事件。pi 把「裁剪/压缩/持久化」藏进了运行时,你只需要关心任务本身。本系列第 2 篇会用真实 API 重写这段。


1.6 harness 成熟度阶梯

不是所有 harness 都一样重。按「三组件的完整度」排个阶梯,能帮你定位手里的工具处在哪一级,还差什么:

级别名称Loop工具上下文典型形态对应 Loop 系列概念
L0裸 API 调用❌(单次)手拼 messages直接调 SDK 一次
L1有工具的 agent✅ while简单追加1.5 的伪代码Loop L1(产出报告)
L2会话持久化有裁剪/压缩pi、Claude Code 基础态Loop L2(可恢复执行)
L3自治 harness✅ + 调度✅ + 权限/沙箱✅ + 记忆分层pi + subagent + worktree + intercomLoop L3(无人值守)

这里和 Loop Engineering 系列的 L1/L2/L3 不是同一个轴:Loop 的 L 级讲「循环跑多自治」(报告 → 提议 → 自主合并),harness 的 L 级讲「壳有多完整」(单次 → 有工具 → 可恢复 → 自治)。两者交叉:一个 harness L3 的壳,可以只跑 Loop L1 的任务(壳很强、任务很保守);反过来,想跑 Loop L3 的无人值守,必须有 harness L3 的壳托着——否则没有 worktree 隔离、没有 kill switch、没有会话恢复,Loop L3 一启动就是事故。

记住一个方向:先升级 harness,再升级 loop。 壳没搭好就去追自治,等于给一辆没刹车的车踩油门。


反模式小结

三种最常见的「以为在做 agent,其实没做对」:

反模式症状病根解药
把 harness 当聊天 UI系统就是个套了皮的对话框,断电即忘缺 Loop 容器 + 缺上下文预算,停留在光谱 ①补三组件,先做到「能跨回合保持状态」
把工具当炫技注册了一堆工具却没管 schema 校验、权限、超时只搭了工具总线的「注册」半截,没搭「治理」半截工具必须有 schema、有权限模型、有错误回灌(第 2、8 篇)
忽视上下文预算短任务好用,长任务必崩,且崩得莫名其妙把「窗口无限」当默认,从不裁剪压缩把 token 预算当一等公民,第 3 篇展开

一句话总括:harness 的工程量,不在「让它能动」,而在「让它动得久、动得稳、动得可查、动得可停」。 能动是 L0,后四者才是 harness 的本体——而每一种「不可」,背后都对应着本系列后续一篇要解的工程问题。


迁移清单

本篇的定义层是抽象的,下表把三组件 + 成熟度映射到主流工具,方便对号入座:

pi 的做法Claude CodeCursorAider通用 harness
createAgentSession 起 Loop 容器内置 agent loopComposer/Agent 内核Coder.run() 主循环自建 while 循环 + LLM SDK
registerTool + Typebox 注册工具.claude 工具/MCP内置 edit/terminalCoder 注册 commandstool calling 协议 + schema 校验
运行时裁剪/压缩上下文自动 compaction隐式 long-contextrepo map + token 预算显式检索 + 摘要 + 窗口策略
session fork/resume(L2)--resume / --continue会话历史.aider.chat.history.md自建状态序列化 + 恢复点
subagent + worktree + intercom(L3)Task tool + 权限(较弱)(无)自建调度 + 验证链 + kill switch

横向看:越往左,三组件越内置、越省心;越往右,越要你自己拼。 pi 的位置偏右——它把积木给你、组合权留给你,这正是本系列为什么拿它当落地载体:拼的过程看得见,工程量藏不住。


下一步

本篇把「harness 是什么」钉死了:三组件(Loop 容器 / 工具总线 / 上下文预算)、五级光谱、问答到循环执行的范式迁移、L0→L3 成熟度阶梯。但三组件里,工具总线是 agent 真正「长出手脚」的那块,也是最容易做歪的一块——schema 校验、并发与中断、MCP 接入、权限边界,每一项都决定 agent 是靠谱还是闯祸。

下一篇我们钻进工具总线,从 tool calling 协议讲到 pi 的 registerTool 真实写法,再用 1.5 那段伪代码做对照,把它重写成生产级形态。

→ 下一篇:Harness Engineering · 工具系统与工具总线

系列第 1 篇 / 共 10 篇

Released under the ISC License.