9153 字
约 30 分钟
1
第9章 Harness工程-大脑的工程化外壳

第9章 Harness工程-大脑的工程化外壳

来源:https://ai-agent-guide.xiaofuge.cn/chapters/ch22-harness.html 所属:第二篇-Agent的大脑


模型不动,只优化 Harness 就提升 10 倍——大脑的工程化体系

9.1 三个范式:Prompt → Context → Harness

2025年底,一位创业者在社交媒体发了一条动态,迅速引爆了技术圈:

🔥 一条引爆技术圈的动态

"我花了三个月调 Prompt,模型回答质量提升了 20%。然后我花了两周搭 Harness,整体任务完成率从 35% 飙到了 82%。"

点赞最高的评论只有四个字:方向错了

这条动态揭示了一个反直觉的事实:变量是 Harness,不是 Model。过去两年,行业都在追更强的模型——GPT-5、Claude 4.5、Gemini 2.5。但三组实验给行业泼了一盆冷水。

9.1.1 三组关键实验 | 实验 | 方法 | 结果 | 关键洞察 | | --- | --- | --- | --- | | Bölük (2026a) | 模型完全不动,只调整 edit-tool 格式与外围工具脚手架 | 15 个模型上最高 10 倍提升 | 工具格式 > 模型代际 | | Trivedy (2026) | 固定 GPT-5.2-Codex,重构系统提示 + 中间件上下文 + 自验证钩子 | Terminal-Bench 2.0 从 52.8% 拉到 66.5%(+13.7pp) | Harness 优化 > 模型升级(代际通常只 +2-4pp) | | Meta-Harness (Lee et al., 2026) | 自动化 Harness 优化,模型权重一个字节都没改 | 达成 76.4%,超越所有手工方案 | 自动优化 > 人工调参 | 💡 反直觉对比

模型代际升级在同一 Benchmark 上通常只能带来 2-4 个百分点的提升。而 Harness 优化可以带来 10 倍的差距。变量是 Harness,不是 Model——这就是绑定约束论

9.1.2 绑定约束论 (Binding-Constraint Thesis)

论文把这个观察命名为绑定约束论📌 绑定约束论

对于长程任务,跨可比的前沿模型,Benchmark 方差由 Harness 主导,而非模型本身。

类比:一辆赛车(模型)再快,如果赛道(Harness)坑坑洼洼、没有导航、油箱漏油,最终成绩也不会好。决定成绩方差的是赛道质量,而不是赛车品牌。

9.1.3 三层范式迁移

过去三年 LLM 应用工程经历了三次范式迁移,三者是嵌套关系,不是替代关系: 🔑 核心洞察

三个阶段是嵌套关系,不是替代关系。Harness 工程师必须懂 Context 工程,Context 工程师必须懂 Prompt 工程。但工程效益的边际产出已经从 Prompt 转移到了 Harness。 | 维度 | Prompt Engineering | Context Engineering | Harness Engineering | | --- | --- | --- | --- | | 关注点 | 怎么写指令 | 怎么管理对话上下文 | 怎么搭建整套基础设施 | | 典型操作 | 改措辞、加示例、调格式 | 设计窗口策略、注入中间件 | 选沙箱、配权限、搭可观测 | | 边际产出 | 低(调3个月只+20%) | 中(+30-50%) | 高(2周+47pp) | | 代表项目 | ChatGPT prompt 模板 | RAG、滑动窗口 | Claude Code、Codex、Cursor | | 裸模型硬伤 | 输出格式不稳定 | 无记忆、信息腐烂 | 无工作环境、不能执行代码 | ## 9.2 Harness 六大组件

裸模型有四大硬伤:无记忆、不能执行代码、知识过时、无工作环境。Harness 的六大组件逐一补救:

9.2.1 文件系统——解决"无工作环境"

裸模型没有文件系统,没有工作空间,没有项目目录。它不能创建文件、不能组织代码结构、不能管理依赖、不能运行构建工具。这意味着它无法完成任何"工程级"任务。

📁 文件系统的作用

工程不是写代码,工程是在正确的位置写正确的代码,并确保它与其他代码正确协作。文件系统提供了:

工作空间:项目目录、文件组织、依赖管理

版本控制:git 跟踪变更,回滚出错修改

持久存储:跨会话保持状态,不会每次从零开始

协作基础:多 Agent 共享同一文件系统协同工作

9.2.2 沙箱执行——解决"不能执行代码"

裸模型只能输出文本,不能真正执行代码。但 Agent 需要运行代码来验证结果——写了一段排序算法,得跑一下确认没问题。沙箱让 Agent 拥有了"动手验证"的能力。

🔒 沙箱的三层隔离

进程隔离:在子进程中运行代码,Agent 主进程不受影响

资源限制:CPU/内存/网络/时间上限,防止资源耗尽

权限限制:文件系统白名单、网络白名单、禁止危险操作

代表项目:E2B(云沙箱)、Docker 容器沙箱、Jupyter kernel

9.2.3 AGENTS.md / System Prompt——解决"知识过时"

模型的知识有截止日期——截止之后的新 API、新框架版本、新安全漏洞它都不知道。AGENTS.md 是一个无需训练就能注入知识的机制。 📝 AGENTS.md 的核心思路

把项目的规则、约定、架构说明写在一个 Markdown 文件里,每次 Agent 启动时自动加载。就像给新员工发了一份入职手册。

为什么比微调更好?

① 微调要花钱花时间,AGENTS.md 写完即生效

② 微调的知识嵌入权重里很难更新,AGENTS.md 改了就更新了

③ 微调需要大量训练数据,AGENTS.md 只需要你项目的事实规则

④ 微调的模型不能同时服务多个项目,AGENTS.md 可以每个项目一份

# AGENTS.md 示例 — Python 项目
# 文件:AGENTS.md

# 项目规则
- 使用 Python 3.11+
- 代码风格遵循 PEP 8
- 所有函数必须有类型注解
- 测试覆盖率要求 > 80%
- 禁止使用 eval() 和 exec()

# 架构说明
- 入口:app/main.py
- 配置:config/settings.py
- 数据库模型:models/
- API 路由:routes/
- 工具函数:utils/

# 编码约定
- 变量名用 snake_case
- 类名用 PascalCase
- 常量用 UPPER_SNAKE_CASE
- 异步函数必须加 async 前缀
// AGENTS.md 示例 — TypeScript 项目
// 文件:AGENTS.md

// 项目规则
- 使用 TypeScript 5.x strict 模式
- 代码风格遵循 ESLint + Prettier
- 所有函数必须有返回类型注解
- 测试覆盖率要求 > 80%
- 禁止使用 any 类型(除非显式标注)

// 架构说明
- 入口:src/index.ts
- 配置:src/config/
- 数据模型:src/models/
- API 路由:src/routes/
- 工具函数:src/utils/

// 编码约定
- 变量名用 camelCase
- 类名用 PascalCase
- 常量用 camelCase(不用 UPPER)
- 异步函数返回 Promise
// AGENTS.md 示例 — Go 项目
// 文件:AGENTS.md

// 项目规则
- 使用 Go 1.22+
- 代码风格遵循 gofmt + go vet
- 所有公开函数必须有 godoc 注释
- 测试覆盖率要求 > 80%
- 禁止使用 init() 函数

// 架构说明
- 入口:cmd/server/main.go
- 配置:internal/config/
- 数据模型:internal/models/
- API 路由:internal/handler/
- 工具函数:pkg/utils/

// 编码约定
- 包名用小写单词(不用下划线)
- 导出标识符用 PascalCase
- 未导出标识符用 camelCase
- 错误处理必须显式检查(不用 _ 忽略)
// AGENTS.md 示例 — Java 项目
// 文件:AGENTS.md

// 项目规则
- 使用 Java 21+ (LTS)
- 代码风格遵循 Google Java Style
- 所有公开方法必须有 Javadoc
- 测试覆盖率要求 > 80%
- 禁止使用 System.out.println(用 SLF4J)

// 架构说明
- 入口:src/main/java/com/example/App.java
- 配置:src/main/resources/application.yml
- 数据模型:src/main/java/com/example/model/
- API 路由:src/main/java/com/example/controller/
- 工具函数:src/main/java/com/example/util/

// 编码约定
- 类名用 PascalCase
- 方法名用 camelCase
- 常量用 UPPER_SNAKE_CASE
- 包名全小写(不用下划线)

9.2.4 Web Search + MCP——解决"知识截止日期"

模型的知识有截止日期,截止之后的一切它统统不知道。Web Search 让 Agent 能主动搜索最新信息,MCP 让 Agent 能调用外部工具获取实时数据。 🌐 Web Search + MCP 的组合威力

Web Search:Agent 主动搜索互联网,获取最新信息(新闻、文档、API 变更)

MCP(Model Context Protocol):标准化工具接口,让 Agent 调用数据库、API、文件系统等外部资源

组合使用:先搜索到新 API 文档 → 再通过 MCP 调用实际 API 验证

这就像给一个只能看教科书的学生配了手机和实验室——教科书(训练数据)是基础,手机(Web Search)获取最新资讯,实验室(MCP)动手验证。

9.2.5 上下文工程——解决"无记忆与信息腐烂"

裸模型没有记忆——每次对话都是从零开始。上下文工程的核心是让关键信息不丢失。 | 策略 | 原理 | 适用场景 | 代表实现 | | --- | --- | --- | --- | | 滑动窗口 | 保留最近 N 条消息,丢弃更早的 | 短期对话、实时交互 | 大多数 LLM SDK 默认策略 | | 摘要压缩 | 把早期对话压缩成摘要,保留关键信息 | 长对话、多轮任务 | LangChain ConversationSummaryMemory | | 关键信息保留 | 显式标记重要信息,永远不丢弃 | 任务约束、用户偏好 | System Prompt + 变量注入 | ### 9.2.6 编排 + Hooks——解决"无法协作"

单个 Agent 能力有限,复杂任务需要多个 Agent 协作。编排引擎负责调度,Hooks 保障协同质量。

🪝 Hooks 的四种类型

pre-hook:执行前检查——参数校验、权限检���、成本预估

post-hook:执行后验证——结果校验、格式检查、日志记录

error-hook:异常时处理——重试策略、降级方案、告警通知

approval-hook:高危操作审批——暂停执行,等待人工确认

Hooks 是 Harness 的安全网——即使 Agent 决策出错,Hooks 也能在执行前拦截。

9.3 Harness 实战:构建一个最简 Agent Harness

让我们从裸模型开始,逐步添加 Harness 组件,观察每一步的性能提升。

9.3.1 Step 0:裸模型(仅 Prompt)

最基础的调用——只靠 Prompt 让 LLM 回答问题。没有工具、没有记忆、没有沙箱。

# Step 0: 裸模型调用 — 只有 Prompt
import openai

client = openai.OpenAI()

def ask_bare_model(question: str) -> str:
    """纯 Prompt 调用,无工具、无记忆、无验证"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": question}]
    )
    return response.choices[0].message.content

# 测试:问一个需要实时数据的问题
answer = ask_bare_model("北京今天天气怎么样?")
print(f"裸模型回答: {answer}")
# 输出:可能基于过时知识回答,无法获取实时天气
# 任务完成率:~35%
// Step 0: 裸模型调用 — 只有 Prompt
import OpenAI from "openai";

const client = new OpenAI();

async function askBareModel(question: string): Promise {
  // 纯 Prompt 调用,无工具、无记忆、无验证
  const response = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: question }],
  });
  return response.choices[0].message.content || "";
}

// 测试:问一个需要实时数据的问题
const answer = await askBareModel("北京今天天气怎么样?");
console.log(`裸模型回答: ${answer}`);
// 输出:可能基于过时知识回答,无法获取实时天气
// 任务完成率:~35%
// Step 0: 裸模型调用 — 只有 Prompt
package main

import (
	"context"
	"fmt"
	openai "github.com/sashabaranov/go-openai"
)

func askBareModel(question string) (string, error) {
	client := openai.NewClient("your-api-key")
	resp, err := client.CreateChatCompletion(
		context.Background(),
		openai.ChatCompletionRequest{
			Model:    openai.GPT4o,
			Messages: []openai.ChatCompletionMessage{
				{Role: openai.ChatMessageRoleUser, Content: question},
			},
		},
	)
	if err != nil {
		return "", err
	}
	return resp.Choices[0].Message.Content, nil
}

// 测试:需要实时数据的问题
answer, _ := askBareModel("北京今天天气怎么样?")
fmt.Printf("裸模型回答: %s\n", answer)
// 任务完成率:~35%
// Step 0: 裸模型调用 — 只有 Prompt
import com.theokanning.openai4j.*;
import com.theokanning.openai4j.chat.*;

public class BareModel {
    static OpenAiService service = new OpenAiService("your-api-key");

    static String askBareModel(String question) {
        ChatCompletionRequest req = ChatCompletionRequest.builder()
            .model("gpt-4o")
            .messages(List.of(
                new ChatMessage("user", question)
            ))
            .build();
        ChatCompletionResult result = service.createChatCompletion(req);
        return result.getChoices().get(0).getMessage().getContent();
    }

    public static void main(String[] args) {
        String answer = askBareModel("北京今天天气怎么样?");
        System.out.println("裸模型回答: " + answer);
        // 任务完成率:~35%
    }
}

9.3.2 Step 1:加 System Prompt(知识注入)

加入 AGENTS.md 风格的 System Prompt,注入项目规则和上下文知识。

# Step 1: 加 System Prompt — AGENTS.md 知识注入
import openai

client = openai.OpenAI()

AGENTS_MD = """# 项目规则
- 你是一个天气查询助手
- 回答时必须附带温度数据
- 如果无法获取实时数据,明确告知用户
- 使用中文回答
"""

def ask_with_system_prompt(question: str) -> str:
    """加入 System Prompt,注入项目知识"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": AGENTS_MD},
            {"role": "user", "content": question}
        ]
    )
    return response.choices[0].message.content

answer = ask_with_system_prompt("北京今天天气怎么样?")
# 现在Agent会明确告知无法获取实时数据,而不是瞎编
# 任务完成率:~45%(知识更准确,但仍无法执行)
// Step 1: 加 System Prompt — AGENTS.md 知识注入
import OpenAI from "openai";

const client = new OpenAI();

const AGENTS_MD = `# 项目规则
- 你是一个天气查询助手
- 回答时必须附带温度数据
- 如果无法获取实时数据,明确告知用户
- 使用中文回答
`;

async function askWithSystemPrompt(question: string): Promise {
  const response = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [
      { role: "system", content: AGENTS_MD },
      { role: "user", content: question },
    ],
  });
  return response.choices[0].message.content || "";
}

const answer = await askWithSystemPrompt("北京今天天气怎么样?");
// 现在Agent会明确告知无法获取实时数据,而不是瞎编
// 任务完成率:~45%
// Step 1: 加 System Prompt — AGENTS.md 知识注入
package main

import (
	"context"
	"fmt"
	openai "github.com/sashabaranov/go-openai"
)

const agentsMD = `# 项目规则
- 你是一个天气查询助手
- 回答时必须附带温度数据
- 如果无法获取实时数据,明确告知用户
- 使用中文回答
`

func askWithSystemPrompt(question string) (string, error) {
	client := openai.NewClient("your-api-key")
	resp, err := client.CreateChatCompletion(
		context.Background(),
		openai.ChatCompletionRequest{
			Model: openai.GPT4o,
			Messages: []openai.ChatCompletionMessage{
				{Role: openai.ChatMessageRoleSystem, Content: agentsMD},
				{Role: openai.ChatMessageRoleUser, Content: question},
			},
		},
	)
	if err != nil {
		return "", err
	}
	return resp.Choices[0].Message.Content, nil
}
// 任务完成率:~45%
// Step 1: 加 System Prompt — AGENTS.md 知识注入
import com.theokanning.openai4j.*;
import com.theokanning.openai4j.chat.*;

public class SystemPromptAgent {
    static OpenAiService service = new OpenAiService("your-api-key");
    static String AGENTS_MD = """
# 项目规则
- 你是一个天气查询助手
- 回答时必须附带温度数据
- 如果无法获取实时数据,明确告知用户
- 使用中文回答
""";

    static String askWithSystemPrompt(String question) {
        ChatCompletionRequest req = ChatCompletionRequest.builder()
            .model("gpt-4o")
            .messages(List.of(
                new ChatMessage("system", AGENTS_MD),
                new ChatMessage("user", question)
            ))
            .build();
        return service.createChatCompletion(req)
            .getChoices().get(0).getMessage().getContent();
    }
}
// 任务完成率:~45%

9.3.3 Step 2:加工具调用(Web Search + MCP)

加入 Function Calling,让 Agent 能调用真实天气 API 获取实时数据。

# Step 2: 加工具调用 — Function Calling + 外部API
import openai, requests, json

client = openai.OpenAI()

AGENTS_MD = """你是一个天气查询助手,可以调用 get_weather 工具获取实时天气。
回答时附带温度数据,使用中文回答。"""

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "获取指定城市的实时天气",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名称"}
            },
            "required": ["city"]
        }
    }
}]

def get_weather(city: str) -> str:
    """调用外部天气API获取实时数据"""
    resp = requests.get(f"https://wttr.in/{city}?format=j1")
    data = resp.json()
    temp = data["current_condition"][0]["temp_C"]
    desc = data["current_condition"][0]["weatherDesc"][0]["value"]
    return json.dumps({"city": city, "temp": temp, "desc": desc})

def ask_with_tools(question: str) -> str:
    """带工具的Agent调用"""
    messages = [
        {"role": "system", "content": AGENTS_MD},
        {"role": "user", "content": question}
    ]
    response = client.chat.completions.create(
        model="gpt-4o", messages=messages, tools=tools
    )
    msg = response.choices[0].message
    if msg.tool_calls:
        for tc in msg.tool_calls:
            args = json.loads(tc.function.arguments)
            result = get_weather(args["city"])
            messages.append(msg)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": result
            })
        response = client.chat.completions.create(
            model="gpt-4o", messages=messages, tools=tools
        )
        return response.choices[0].message.content
    return msg.content

# 任务完成率:~65%(能获取实时数据了)
// Step 2: 加工具调用 — Function Calling + 外部API
import OpenAI from "openai";

const client = new OpenAI();

const AGENTS_MD = `你是一个天气查询助手,可以调用 get_weather 工具获取实时天气。
回答时附带温度数据,使用中文回答。`;

const tools: OpenAI.ChatCompletionTool[] = [{
  type: "function",
  function: {
    name: "get_weather",
    description: "获取指定城市的实时天气",
    parameters: {
      type: "object",
      properties: { city: { type: "string", description: "城市名称" } },
      required: ["city"],
    },
  },
}];

async function getWeather(city: string): Promise {
  const resp = await fetch(`https://wttr.in/${city}?format=j1`);
  const data = await resp.json();
  const temp = data.current_condition[0].temp_C;
  const desc = data.current_condition[0].weatherDesc[0].value;
  return JSON.stringify({ city, temp, desc });
}

async function askWithTools(question: string): Promise {
  const messages: any[] = [
    { role: "system", content: AGENTS_MD },
    { role: "user", content: question },
  ];
  const response = await client.chat.completions.create({
    model: "gpt-4o", messages, tools,
  });
  const msg = response.choices[0].message;
  if (msg.tool_calls) {
    for (const tc of msg.tool_calls) {
      const args = JSON.parse(tc.function.arguments);
      const result = await getWeather(args.city);
      messages.push(msg);
      messages.push({ role: "tool", tool_call_id: tc.id, content: result });
    }
    const r2 = await client.chat.completions.create({
      model: "gpt-4o", messages, tools,
    });
    return r2.choices[0].message.content || "";
  }
  return msg.content || "";
}
// 任务完成率:~65%
// Step 2: 加工具调用 — Function Calling + 外部API
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	openai "github.com/sashabaranov/go-openai"
)

func getWeather(city string) string {
	url := fmt.Sprintf("https://wttr.in/%s?format=j1", city)
	resp, err := http.Get(url)
	if err != nil { return fmt.Sprintf(`{"error":"%s"}`, err) }
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var data map[string]interface{}
	json.Unmarshal(body, &data)
	current := data["current_condition"].([]interface{})[0].(map[string]interface{})
	temp := current["temp_C"].(string)
	descArr := current["weatherDesc"].([]interface{})
	desc := descArr[0].(map[string]interface{})["value"].(string)
	result, _ := json.Marshal(map[string]string{"city": city, "temp": temp, "desc": desc})
	return string(result)
}
// 任务完成率:~65%(能获取实时数据了)
// Step 2: 加工具调用 — Function Calling + 外部API
import com.theokanning.openai4j.*;
import com.theokanning.openai4j.chat.*;
import java.net.http.*;
import java.net.URI;
import org.json.*;

public class ToolAgent {
    static OpenAiService service = new OpenAiService("your-api-key");

    static String getWeather(String city) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create("https://wttr.in/" + city + "?format=j1"))
            .build();
        HttpResponse resp = client.send(req,
            HttpResponse.BodyHandlers.ofString());
        JSONObject data = new JSONObject(resp.body());
        JSONObject current = data.getJSONArray("current_condition")
            .getJSONObject(0);
        String temp = current.getString("temp_C");
        String desc = current.getJSONArray("weatherDesc")
            .getJSONObject(0).getString("value");
        return new JSONObject()
            .put("city", city).put("temp", temp).put("desc", desc)
            .toString();
    }
}
// 任务完成率:~65%(能获取实时数据了)

9.3.4 Step 3:加沙箱验证 + Hooks(完整 Harness)

加入沙箱执行验证结果、Hooks 拦截危险操作——这就是一个完整的 Harness。

# Step 3: 完整 Harness — 沙箱 + Hooks + 工具 + System Prompt
import openai, requests, json, subprocess, sys

client = openai.OpenAI()

# === AGENTS.md (知识注入) ===
AGENTS_MD = """你是一个天气查询助手。
规则:1) 必须调用 get_weather 获取实时数据  2) 回答附带温度
3) 不允许访问用户私人数据  4) 使用中文回答"""

# === 工具定义 (MCP) ===
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "获取指定城市的实时天气",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    }
}]

# === 工具实现 ===
def get_weather(city: str) -> str:
    resp = requests.get(f"https://wttr.in/{city}?format=j1")
    data = resp.json()
    return json.dumps({
        "city": city,
        "temp": data["current_condition"][0]["temp_C"],
        "desc": data["current_condition"][0]["weatherDesc"][0]["value"]
    })

# === Hooks (安全网) ===
def pre_hook(tool_name: str, args: dict) -> dict:
    """执行前检查:权限校验 + 参数过滤"""
    if tool_name == "get_weather" and "私人" in args.get("city", ""):
        return {"blocked": True, "reason": "禁止查询私人地址"}
    return {"blocked": False}

def post_hook(result: str) -> str:
    """执行后验证:格式校验"""
    try:
        data = json.loads(result)
        if "temp" not in data:
            return json.dumps({"error": "返回数据缺少温度字段"})
    except json.JSONDecodeError:
        return json.dumps({"error": "返回数据格式错误"})
    return result

# === 沙箱验证 (代码自验证) ===
def sandbox_verify(result: str) -> bool:
    """���沙箱中验证结果合理性"""
    data = json.loads(result)
    temp = int(data.get("temp", 0))
    return -50  str:
    messages = [
        {"role": "system", "content": AGENTS_MD},
        {"role": "user", "content": question}
    ]
    for _ in range(max_turns):
        response = client.chat.completions.create(
            model="gpt-4o", messages=messages, tools=tools
        )
        msg = response.choices[0].message
        if msg.tool_calls:
            for tc in msg.tool_calls:
                args = json.loads(tc.function.arguments)
                # pre-hook 检查
                hook_result = pre_hook(tc.function.name, args)
                if hook_result.get("blocked"):
                    messages.append(msg)
                    messages.append({"role": "tool",
                        "tool_call_id": tc.id,
                        "content": json.dumps(hook_result)})
                    continue
                # 执行工具
                result = get_weather(args["city"])
                # post-hook 验证
                result = post_hook(result)
                # sandbox 验证
                if not sandbox_verify(result):
                    result = json.dumps({"error": "数据不合理"})
                messages.append(msg)
                messages.append({"role": "tool",
                    "tool_call_id": tc.id, "content": result})
        else:
            return msg.content
    return "达到最大轮次限制"

# 任务完成率:~82%(完整 Harness)
answer = harness_agent("北京今天天气怎么样?")
print(answer)
// Step 3: 完整 Harness — 沙箱 + Hooks + 工具 + System Prompt
import OpenAI from "openai";

const client = new OpenAI();

// === AGENTS.md (知识注入) ===
const AGENTS_MD = `你是一个天气查询助手。
规则:1) 必须调用 get_weather 获取实时数据  2) 回答附带温度
3) 不允许访问用户私人数据  4) 使用中文回答`;

// === 工具定义 ===
const tools: OpenAI.ChatCompletionTool[] = [{
  type: "function",
  function: {
    name: "get_weather",
    description: "获取指定城市的实时天气",
    parameters: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
    },
  },
}];

// === 工具实现 ===
async function getWeather(city: string): Promise {
  const resp = await fetch(`https://wttr.in/${city}?format=j1`);
  const data = await resp.json();
  return JSON.stringify({
    city,
    temp: data.current_condition[0].temp_C,
    desc: data.current_condition[0].weatherDesc[0].value,
  });
}

// === Hooks ===
function preHook(toolName: string, args: Record) {
  if (toolName === "get_weather" && args.city?.includes("私人"))
    return { blocked: true, reason: "禁止查询私人地址" };
  return { blocked: false };
}

function postHook(result: string): string {
  try {
    const data = JSON.parse(result);
    if (!data.temp) return JSON.stringify({ error: "缺少温度字段" });
  } catch { return JSON.stringify({ error: "格式错误" }); }
  return result;
}

// === 沙箱验证 ===
function sandboxVerify(result: string): boolean {
  const data = JSON.parse(result);
  const temp = Number(data.temp || 0);
  return temp > -50 && temp  -50 && temp  -50 && temp  Cursor 英文原生

• **模型选择**:Trae 多模型切换 = Cursor 多模型

• **价格**:Trae 免费 > Cursor $20/月

• **生态**:Cursor 插件生态 > Trae(基于 VS Code 兼容插件)

• **上下文工程**:Cursor @codebase > Trae Builder 模式(Cursor 更精准)

### 9.4.7 WaLiCode — 小傅哥的开源 AI IDE

🟠 WaLiCode — [walicode.xiaofuge.cn](https://walicode.xiaofuge.cn)

  WaLiCode 是一款**非常强的 AI IDE 工具**,由知名技术博主小傅哥(付政委)开发,基于 Tauri 2 + React 19 + Rust 技术栈。与商业 IDE 不同,WaLiCode 有自己独特的优势——**SSH DevOps 模式**和**全栈开源架构**。

**WaLiCode 的核心优势:**
- **SSH DevOps 模式**:支持通过 SSH 直接连接远程服务器执行命令、管理文件、部署应用。这是绝大多数 AI IDE 不具备的能力,对于运维场景和远程开发至关��要
- **多协议适配层**:同时支持 Anthropic、OpenAI、Ollama、Responses API 四种协议,模型切换零成本
- **五层权限递进**:Bash AST 分析 → 规则引擎 → AI 安全审判 → 用户审批 → 熔断器,企业级安全保障
- **子代理系统**:Explore(探索)、Verification(验证)、General(通用)三类型子代理,复杂任务自动分解
- **工具懒加载**:核心工具 + SSH 工具 + 调试工具 + 部署工具分类加载,启动快、内存省
- **Rust 后端**:用 Rust 实现的 SSH 客户端(russh)、LSP 轻量实现、AST 符号解析,性能高、内存安全
- **全开源**:前端 React + 后端 Rust 全部开源,可私有化部署、可定制扩展

**WaLiCode 独特价值**:

WaLiCode 最大的差异化是**SSH DevOps 能力**——你可以让 Agent 通过 SSH 连到生产服务器,执行诊断命令、查看日志、重启服务、部署应用。这在企业运维场景中极为重要,而 Claude Code、Cursor、Trae 等产品都不具备这一能力。

此外,WaLiCode 的**多协议适配层**让企业在 OpenAI/Anthropic/Ollama 之间切换零成本,配合国产模型(DeepSeek/Qwen/GLM)可实现完全自主可控的 AI 编程环境。

### 9.4.8 Manus — 通用 Agent 的破圈者

🟣 Manus — 2026 年初爆火的通用 Agent

  Manus 是 2026 年初由中国团队发布的**通用 AI Agent**,上线 48 小时内邀请码被炒到数千元,引发全网讨论。与 Coding Agent 不同,Manus 定位是**"数字员工"**——能处理报表分析、市场调研、旅行规划、PPT 制作等各类白领工作。

**Manus 的 Harness 设计特点:**
- **虚拟机沙箱**:每个任务在独立虚拟机中执行,配备浏览器 + 终端 + 文件系统 + Python 环境
- **异步执行**:用户提交任务后可关闭浏览器,Manus 在云端自主完成,完成后通知用户
- **多模态输出**:可生成 Excel、PPT、PDF、网页等各种格式的工作成果
- **自主规划**:Agent 自主分解任务、上网搜索、编写代码、生成报告,全程无需人工干预
- **可复现性**:每个任务的执行过程完全记录,可回放、可分享、可复用

### 9.4.9 Windsurf(Codeium)— 企业级 AI IDE

🔷 Windsurf — Codeium 的企业级 AI IDE

  Windsurf 是 Codeium 推出的 AI IDE,核心特点是 **Flow 模式**——Agent 不仅写代码,还理解你的开发意图,主动建议下一步操作。适合企业级团队协作场景。

**Windsurf 核心特点:**
- ** Cascade 功能**:类似 Cursor 的 Composer,支持多文件编辑和项目级重构
- **企业安全**:SOC 2 Type II 合规,代码不上传云端,支持私有化部署
- **多模型支持**:Claude、GPT、自研 Codeium 模型可切换
- **上下文理解**:自动索引项目代码,精准定位相关文件

### 9.4.10 Cline — VSCode Agent 插件

🟡 Cline — VSCode 中的自主 Agent 插件

  Cline 是一个**开源的 VSCode 插件**,将 VSCode 变成 Agent 工作环境。它的核心价值是**零成本门槛**——不需要换 IDE,在现有 VSCode 中即可获得 Agent 能力。

**Cline 核心特点:**
- **开源免费**:MIT 协议,社区活跃,可自行扩展
- **终端集成**:Agent 可直接在 VSCode 终端中执行命令
- **文件编辑**:支持多文件创建、编辑、Diff 预览
- **多模型**:支持 OpenAI、Anthropic、Ollama、OpenRouter 等
- ** MCP 支持**:可连接 MCP Server 扩展工具能力

### 9.4.6 代表项目对比 | 项目 | 核心 Harness 组件 | 交互模式 | 适用场景 | 成本 | | --- | --- | --- | --- | --- | | **Claude Code** | 参数约束 + 文件系统 + CLAUDE.md | 终端 CLI / headless | 代码编写、调试、重构 | 按 Token 计费 | | **Codex** | 云沙箱 + 异步任务 + 自验证 | 异步提交+查看 | 独立编码任务、CI/CD | 按任务计费 | | **Cursor** | 上下文工程 + 编辑器集成 | IDE 内交互 | 日常开发辅助 | 订阅制 | | **Devin** | 全沙箱 + 自主规划 + 自纠错 | 任务提交+监控 | 复杂长程任务 | $10-50/任务 | | **Harness Inc.** | CI/CD + 安全 + 成本 + Feature Flags | 平台 Dashboard | 企业软件交付 | 企业订阅 | | **Trae** | Builder 模式 + 多模型 + 中文优化 | IDE 内交互 | 国内开发、中文编程 | 免费 | | **WaLiCode** | SSH DevOps + 多协议适配 + 五层权限 + 子代理 | IDE + SSH 远程 | 运维 Agent、远程开发、私有化 | 开源免费 | | **Manus** | 虚拟机沙箱 + 异步执行 + 多模态输出 | 任务提交+通知 | 通用白领任务 | 按任务计费 | | **Windsurf** | Cascade + 企业安全 + 上下文索引 | IDE 内交互 | 企业团队协作 | 订阅制 | | **Cline** | VSCode 插件 + 终端集成 + MCP | VSCode 内交互 | 零成本 Agent 入门 | 开源免费 | ## 9.5 Harness 设计陷阱与最佳实践

Harness 不是"越多越好",过度约束和约束不足都会出问题。

### 9.5.1 三大设计陷阱

陷阱 1:过度约束 — 限制太多 Agent 无法完成任务

  想象一个被层层束缚的人——手脚都被绳子绑着,虽然绝对安全,但什么也干不了。

  典型症状:① Agent 总是说"我没有权限执行这个操作";② max-turns 设得太低,复杂任务还没做完就被强制终止;③ 工具白名单太窄,连最基本的文件读写都不允许。

  **解法**:按任务复杂度分级授权。简单任务严格约束,复杂任务适度放开。Claude Code 的 --allowedTools 就是这种思路——你可以精确控制哪些工具可用。

陷阱 2:约束不足 — 不加限制 Agent 可能做危险操作

  想象一个没有任何交通规则的城市——虽然出行自由,但车祸频发。

  典型症状:① Agent 删除了重要文件;② Agent 泄露了 API Key;③ Agent 无限循环消耗大量 Token;④ Agent 执行了 rm -rf / 这样的危险命令。

  **解法**:至少要有三层防护——工具白名单(声明式权限)、运行时拦截(动态检查参数)、审计日志(事后追溯)。详见第22章 Agent 安全与防护。

陷阱 3:组件缺失 — 缺了关键组件导致系统性失败

  六大组件缺了任何一个,都会导致特定类型的失败:

  ① **缺文件系统** → Agent 无法持久化工作成果,每次从零开始

  ② **缺沙箱** → Agent 不能验证代码结果,只能"纸上谈兵"

  ③ **缺 AGENTS.md** → Agent 不知道项目规则,按旧知识行事

  ④ **缺 Web Search** → Agent 无法获取最新信息,回答过时

  ⑤ **缺上下文工程** → Agent 长对话时丢失关键信息

  ⑥ **缺 Hooks** → Agent 协作时无法互相检查质量

### 9.5.2 Harness 设计最佳实践清单 | 原则 | 具体做法 | 避免的问题 | | --- | --- | --- | | **最小权限** | 工具白名单只列必需工具,高危操作需审批 | 误操作、数据泄露 | | **最大轮次限制** | 设置 max-turns(一般 10-20),防止无限循环 | Token 耗尽、死循环 | | **沙箱隔离** | 代码执行在沙箱中,资源受限、网络受限 | 恶意代码、资源耗尽 | | **知识注入** | AGENTS.md 写明项目规则、架构说明、编码约定 | 知识过时、违反约定 | | **上下文管理** | 滑动窗口 + 摘要压缩 + 关键信息保留 | 信息腐烂、忘记约束 | | **可观测性** | 全链路日志:每步输入/输出/耗时/Token | 无法调试、无法回溯 | | **自验证** | 工具执行结果在沙箱中自动验证合理性 | 幻觉、错误结果 | | **渐进授权** | 简单任务严约束,复杂任务宽约束 | 过度约束无法完成任务 | **✅ 一句话总结**

  Harness 的设计目标不是"绝对安全"(那是过度约束),而是**"安全边界内的最大自主性"**——让 Agent 在安全范围内尽量自由地完成任务。就像高速公路——有护栏(安全边界),但车可以自由开到 120km/h(最大自主性)。
**📋 八股总结 — 面试高频考点**

Q1: 什么是 Harness Engineering?和 Prompt Engineering 有什么区别?

      **Harness Engineering**:为 AI Agent 搭建模型之外的整套基础设施——文件系统、沙箱、知识注入、工具协议、上下文管理、编排 Hooks。核心观点是"变量是 Harness,不是 Model"。

      **与 Prompt Engineering 的区别**:Prompt Engineering 关注怎么写指令(一次性对话艺术),Harness Engineering 关注怎么搭基础设施(让 Agent 能真正干活)。两者是**嵌套关系**而非替代——Harness 包含 Context,Context 包含 Prompt,但边际产出已从 Prompt 转移到 Harness。

      面试要点:不要说"取代 Prompt",要说"嵌套包含 Prompt,但工程效益的边际产出已转移"。

Q2: 绑定约束论是什么?有哪些实验证据?

      **绑定约束论**:对于长程任务,跨可比的前沿模型,Benchmark 方差由 Harness 主导,而非模型本身。类比:赛车再快,赛道坑坑洼洼成绩也不会好。

      **三组实验证据**:

      ① Bölük (2026a):模型不动,只调 edit-tool 格式与脚手架 → 最高 10 倍提升

      ② Trivedy (2026):固定 GPT-5.2,重构系统提示 + 中间件 + 自验证 → +13.7pp

      ③ Meta-Harness (Lee et al., 2026):自动化 Harness 优化 → 76.4%,超越所有手工方案,模型权重一个字节没改

      面试要点:对比数据——模型代际升级通常只 +2-4pp,Harness 优化可以 +10pp 以上甚至 10 倍差距。变量是 Harness,不是 Model。

Q3: Harness 六大组件分别解决什么问题?

      ① **文件系统** → 解决"无工作环境":Agent 不能只在空中编代码,必须有项目目录、文件组织、版本控制

      ② **沙箱执行** → 解决"不能执行代码":Agent 写了代码得能跑,跑完能验证结果对不对

      ③ **AGENTS.md** → 解决"知识过时":无需训练就能注入项目规则、架构说明、编码约定

      ④ **Web Search + MCP** → 解决"知识截止日期":Agent 能搜索最新信息、调用实时 API

      ⑤ **上下文工程** → 解决"无记忆与信息腐烂":滑动窗口 + 摘要压缩 + 关键信息保留

      ⑥ **编排 + Hooks** → 解决"无法协作":多 Agent 调度 + 执行前后检查(pre/post/error/approval hooks)

Q4: 为什么 AGENTS.md 比训练微调更适合 Agent 知识注入?

      四个优势:

      ① **即写即生效**:微调要花钱花时间训练,AGENTS.md 写完立刻生效

      ② **随时可更新**:微调的知识嵌入权重里很难修改,AGENTS.md 改了文件就更新了

      ③ **无需训练数据**:微调需要大量训练样本,AGENTS.md 只需要你项目的事实规则

      ④ **多项目并行**:微调的模型不能同时服务多个项目(知识混在一起),AGENTS.md 可以每个项目一份

      面试要点:AGENTS.md 解决的是**知识时效性**问题,不是知识深度问题。对于需要深度专业知识的场景,微调仍然有用——两者互补,不是互斥。

Q5: 上下文工程的三大策略是什么?

      ① **滑动窗口**:保留最近 N 条消息,丢弃更早的。简单粗暴但有效,适合短期对话。大多数 LLM SDK 的默认策略。

      ② **摘要压缩**:把早期对话压缩成摘要,保留关键信息但大幅减少 Token。适合长对话、多轮任务。LangChain 的 ConversationSummaryMemory 就是这种实现。

      ③ **关键信息保留**:显式标记重要信息(任务���束、用户偏好、安全规则),永远不丢弃。通过 System Prompt + 变量注入实现。

      面试要点:三种策略**组合使用**效果最好——滑动窗口管近期、摘要管远期、关键信息永不丢。单独用任何一种都有缺陷。

Q6: Claude Code 的 Harness 设计思路是什么?

      Claude Code 是 Harness Engineering 的**教科书级实现**,核心参数体现完整的安全架构:

      ① **--allowedTools**:工具白名单,限制可用工具 → 权限控制

      ② **--max-turns**:最大循环轮次限制 → 资源限制

      ③ **--model**:指定使用哪个模型 → 模型选择

      ④ **--verbose**:详细日志 → 可观测性

      ⑤ **headless 模式**:无交互纯命令行 → CI/CD 集成

      ⑥ **CLAUDE.md**:项目级知识注入 → AGENTS.md

      面试要点:Claude Code 证明了"headless + allowedTools + max-turns"就是最小可行 Harness 安全架构。不需要 GUI 交互,不需要人工审批,靠参数约束就实现了安全运行。

Q7: 设计一个安全的 Agent Harness 架构需要考虑什么?

      核心原则:**安全边界内的最大自主性**——不是"绝对安全"(那是过度约束),而是让 Agent 在安全范围内尽量自由地完成任务。

      必须考虑的七个要素:

      ① **最小权限**:工具白名单只列必需工具,高危操作需审批

      ② **最大轮次限制**:max-turns 防止无限循环和 Token 耗尽

      ③ **沙箱隔离**:代码执行在沙箱中,资源受限、网络受限

      ④ **知识注入**:AGENTS.md 写明项目规则和编码约定

      ⑤ **上下文管理**:滑动窗口 + 摘要 + 关键信息保留

      ⑥ **可观测性**:全链路日志,每步记录输入/输出/耗时/Token

      ⑦ **渐进授权**:简单任务严约束,复杂任务宽约束

Q8: Harness Engineering 的未来方向是什么?

      三个方向:

      ① **自动化 Harness 优化**:Meta-Harness 论文证明了自动优化超越人工调参。未来会有"Harness 优化器"——像编译器优化代码一样自动优化 Agent 的 Harness 配置。

      ② **Harness 标准化**:MCP 已经在标准化工具接口,未来 AGENTS.md、沙箱配置、Hooks 定义都会有统一标准,就像 Docker 标准化了容器格式。

      ③ **Harness 即产品**:Claude Code、Codex、Cursor 这些产品本质上是在卖 Harness。未来会有更多"Harness as a Service"——用户选模型,平台提供 Harness。

      面试要点:类比"编译器优化"——Harness Engineering 正在从手工调参走向自动化优化,就像 C 编译器从手工优化走向 -O3 自动优化一样。
第9章 Harness工程-大脑的工程化外壳
http://www.clxhxhhr.top/posts/712/
作者
clxstart
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。