> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# 构建 Agent Harness 只要90分钟？
- URL: https://www.hubwiz.com/blog/it-only-takes-90-minutes-to-build-an-agent-harness/
- Published: 2026-06-02T03:08:43.000Z
- Updated: 2026-10-13T15:36:38.000Z
- Description: 没有框架。没有 LangGraph。没有 CrewAI。90 分钟。Python、一个配置文件和一个 LLM 提供商。
- Author: admin
- Tags: AGENT, HARNESS ENGINEERING, #Import 2026-10-14 14:41

**模型不是 Agent。**

我花了两年时间构建 agent 运行时，目前正在专业构建 agentic harness。这是我得出的一个结论：模型是系统中最容易替换的部分。你可以把 ChatGPT 换成 Claude，把 Llama 换成 Qwen，harness 根本不在乎。Harness 关心的是 agent 是否触碰了不该触碰的文件、是否突破了 token 预算，或者在 12 步计划中的第 7 步静默失败且没有留下任何日志。

**Harness 才是产品。模型只是依赖项。**

所以今天我们要构建一个。90 分钟。Python、一个配置文件和一个 LLM 提供商。完成后，你将拥有一个可工作的 agent harness，它接受纯 Markdown 格式的任务，从注册表中调用工具，将危险操作放在审批提示之后，执行预算限制，将每个工具调用记录为结构化 JSONL，并在运行完成后生成运行报告。

没有框架。没有 LangGraph。没有 CrewAI。一个带护栏的 while 循环。就是这么简单地开始——然后你迭代——你构建——你让它成为你自己的。

## 1、六项职责

![](https://substackcdn.com/image/fetch/$s_!xqWW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad0eb9db-9dff-4e3e-82cf-ed73d907301d_2048x1152.png)

Harness 恰好有*六项*职责。其他一切都是模型的问题。

**任务接入。** Agent 应该做什么？可以使用什么工具？什么被禁止？什么时候算完成？*这是合约，不是提示。*

区别很重要——提示是建议，合约是执行。

**工具注册表。** 哪些工具存在，如何调用，以及它们携带什么风险等级。Agent 不知道的工具就不存在。标记为高风险的工具会获得审批门。注册表是 agent 的整个世界——外面的一切都是墙。

**策略层。** 什么需要人工审批才能执行。什么被直接阻止。Agent 被允许在哪里写文件。这就是防止 agent 在凌晨2点给你同事发邮件的部分。

**预算层。** 运行可以进行多少次 LLM 调用。多少次工具调用。多少秒的实际时间。当任何限制达到时，运行终止。没有商量余地。

**日志记录。** 每个工具调用都会产生一条结构化的 JSONL 记录：时间戳、工具名称、参数、结果、是否经过审批。每次 LLM 调用也会被记录。如果你不能重放运行，你就没有 agent。你只有一个关于 agent 的故事。

**报告层。** 运行结束后——无论是完成、被预算终止还是崩溃——harness 会生成一份 Markdown 报告。发生了什么，使用了什么工具，什么文件被修改了，什么失败了，以及 agent 认为下次应该记住什么。

## 2、架构

![](https://substackcdn.com/image/fetch/$s_!HpaP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb39ce44-0e37-467c-90f0-363cbbbfdd1a_2048x1152.png)

Runner 读取任务，连同工具定义一起交给 LLM，然后循环。LLM 请求工具调用。策略门检查每一个。执行器运行已批准的调用。结果返回给 LLM。循环继续，直到 LLM 说完成或预算终止它。

整个东西就是一个 while 循环。下面没有 DAG，没有图引擎，没有框架。

**你需要什么**

- Python 3.11+
- 一个 LLM 提供商密钥（OpenAI、Anthropic、OpenRouter——或者如果你想本地运行用 Ollama，不需要密钥）
- 一个 Brave Search API 密钥（免费层，每月 2,000 次查询，无需信用卡）
- 30 分钟用于设置，60 分钟用于测试

三个依赖：

- litellm（通过一个接口与任何 LLM 提供商通信），
- pyyaml（读取 yaml 配置文件），
- python-dotenv（读取 env 文件）。

就这些。

```
git clone https://github.com/anthony-maio/agent-harness-90m.git
cd agent-harness-90m
pip install -e .
cp .env.example .env

```

打开 .env 并添加你的密钥：

```
OPENAI_API_KEY=sk-...
BRAVE_API_KEY=BSA…

```

Brave 密钥需要 30 秒获取。前往 [brave.com/search/api](https://brave.com/search/api?ref=hubwiz.com)，注册，从仪表板获取密钥。免费计划给你每月 2,000 次查询——足够测试和轻度日常使用。如果你跳过这一步，搜索工具会返回一个占位响应，harness 仍然可以运行，但 agent 无法真正搜索任何东西。

如果你在本地运行 Ollama，不需要 LLM 密钥——只需修改 harness.yaml 中的模型配置：

```
model:
  provider: "ollama"
  name: "llama3.2"

```

## 3、任务合约

这是区分 harness 和脚本的部分。任务不是提示，而是合约。

```
# Task

Find three recent developments in small-model reasoning and write a short brief summarizing 
what changed.

# Allowed tools

- web_search
- read_file
- write_file
- summarize

# Cannot do
- send_email
- delete_file
- run_shell

# Done means

- workspace/brief.md exists
- every claim has a source URL
- workspace/brief.md is under 800 words

```

四个部分。做什么。什么工具可用。什么被禁止。什么时候算完成。

"Done means" 部分是最重要的。没有明确的完成标准，agent 就自己决定什么时候完成——它会在任务变难的瞬间决定完成了。上次我跳过了这个，agent 在一次搜索调用和一个三句总结后就宣布自己完成了。我现在把完成标准视为承重结构。

解析器是 40 行 Python 代码。它按 # 标题分割并提取项目列表。没有 frontmatter，没有 YAML-in-Markdown。纯文本，你可以在任何编辑器中阅读。

## 4、工具注册表

每个工具都是 tools/ 目录中的一个 Python 文件，包含四个东西：名称、描述、参数的 JSON schema 和一个 execute() 函数。

```
# tools/read_file.py

NAME = "read_file"
DESCRIPTION = "Read the contents of a file."
PARAMETERS = {
    "type": "object",
    "properties": {
        "path": {"type": "string", "description": "Relative path to the file"}
    },
    "required": ["path"],
}

def execute(path: str) -> str:
    target = Path("workspace") / path
    if not target.exists():
        return f"File not found: {path}"
    return target.read_text(encoding="utf-8")

```

注册表在启动时自动发现工具模块。在 tools/ 中放一个新的 .py 文件，添加到 harness.yaml，agent 在下次运行时就可以使用它。没有注册样板代码。没有装饰器。

![](https://substackcdn.com/image/fetch/$s_!Wa66!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffd266dc-927d-42e8-9309-cca44d55fe87_2048x1152.png)

配置将工具与风险等级关联：

```
tools:
  read_file:
    description: "Read contents of a file"
    risk: low
  write_file:
    description: "Write content to a file in the workspace"
    risk: medium
  web_search:
    description: "Search the web for current information"
    risk: low

```

风险等级是将工具注册表连接到策略门的桥梁。low 意味着可以自由使用。medium 意味着策略层决定。high 意味着总是需要询问。

## 5、策略门

策略门位于 LLM 的工具调用请求和实际执行之间。每个调用都经过它。

```
policy:
  require_approval:
    - write_file
  block:
    - send_email
    - delete_file
    - run_shell
  workspace_root: "./workspace"

```

三条规则。需要人工同意的工具。直接阻止的工具。文件操作无法逃脱的工作区边界。

当 agent 尝试调用 write\_file 时，harness 会停下来：

> \--- APPROVAL REQUIRED ---

> Tool: write\_file

> Arguments: {"path": "brief.md", "content": "..."}

---

> Approve? \[y/N\]

你可以看到它想写什么。在哪里。为什么。然后你决定。在你同意之前，agent 不会触碰文件系统。

策略门还执行路径限制。agent 可以写到 ./workspace/brief.md。但不能写到 ../../.env。这个检查发生在策略层——一个执行点，而不是分散在每个工具内部。

如果你在运行批处理任务并且信任任务，--yes 会自动批准所有操作。但默认是先询问。我认为这是正确的默认值。

## 6、日志记录

每个工具调用都会产生一条 JSONL 记录：

```
{
  "timestamp": "2026-05-18T14:32:07",
  "task_id": "20260518_143200",
  "type": "tool_call",
  "tool": "web_search",
  "arguments": {"query": "small model reasoning 2026"},
  "result_summary": "[web_search stub] Query: ...",
  "approved": true,
  "error": null
}

```

每次 LLM 调用也会被单独记录：哪一步，提示是什么样的，模型回应了什么，它请求了哪些工具调用。

日志很无聊，直到 agent 做了一些奇怪的事情。然后它们就是你唯一关心的东西。

两个文件。logs/tool\_calls.jsonl 和 logs/llm\_calls.jsonl。都是追加写入的。默认都被 .gitignore。

## 7、预算执行

```
budget:
  max_llm_calls: 25
  max_tool_calls: 50
  max_wall_seconds: 300
  warn_at_percentage: 80

```

25 次 LLM 调用。50 次工具调用。5 分钟实际时间。当任何限制达到 80% 时，harness 会警告你。达到 100% 时，它会终止运行。

两周前，我自己的 harness 中的一个循环因为评估器不断要求更多分页结果而命中了 200K token 上下文限制，没有任何东西阻止它。75 个内存事件已经在上下文中——对于两周的综合任务来说已经绰绰有余了。评估器把彻底性和详尽性搞混了。

没有 token 预算，没有分页上限，没有能够否决失控评估器的护栏。这就是这一层填补的空白。

你不需要提前捕获所有失败模式。你需要一个硬性停止，在 harness 吃掉你的上下文窗口或 API 预算之前触发。预算跟踪器是 50 行 Python 代码——它计算调用次数，检查实际时间，并返回一个带有 exhausted 布尔值的状态。Runner 在每次 LLM 调用之前检查它。

## 8、运行报告

![](https://substackcdn.com/image/fetch/$s_!5o15!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abf3951-29ad-433b-b171-4eba6ed114b0_2048x1152.png)

运行结束后——无论是完成、被预算终止还是崩溃——harness 会写一份 Markdown 报告：

```
# Run Report: 20260518_143200

**Task:** Find three recent developments in small-model reasoning...
**Completed:** 2026-05-18 14:35:47

## Budget

| Metric | Used |
|--------|------|
| LLM calls | 8/25 |
| Tool calls | 12/50 |
| Wall time | 167.3/300 |

## Tools used

- `web_search` -- called 3 time(s)
- `write_file` -- called 2 time(s)
- `summarize` -- called 1 time(s)

## Approvals requested

- `write_file` -- approved

## Failures

None

```

报告就是收据。有人问"agent 做了什么？"——你把报告交给他们。如果他们需要更多细节，你把 JSONL 日志交给他们。如果他们想重放运行，日志中有每个调用的精确参数和结果。

## 9、记忆

Harness 有一个简单的记忆层。每次运行前，memory.md 的内容被加载到系统提示中作为上下文。这是读取侧——LLM 看到之前运行留下来的东西。

写入侧是可选的。系统提示告诉 agent 在最终答案中以 MEMORY: 为前缀标记任何值得记住的内容。运行结束后，harness 提取这些行并让你逐条批准后才追加到文件中。

记忆是仅追加的、人工审批的、纯文本的。你可以随时阅读、编辑或删除条目。它是一个你可以在记事本中打开的 Markdown 文件。

这个问题的更难版本——我目前正在做的——是让 harness 自己决定什么值得跨会话记住。基于惊奇度的写入：harness 评估一条信息的惊奇程度，只有通过阈值时才持久化。但那是 v2 的问题。对于这个脚手架，人工审批就是门，而且它有效。

## 10、你的第一次运行

从仓库根目录运行一切。Harness 根据你调用它的位置解析路径。

先用试运行开始——它解析任务、验证配置，并显示会发生什么而不调用任何 API：

*python agent.py run tasks/example\_research.md --dry-run*

看起来没问题后，真正运行：

*python agent.py run tasks/example\_research.md*

```
============================================================

  Agent Harness -- Run 20260518_143200

============================================================

  Task:   Find three recent developments in small-model reasoning...

  Source: tasks/example_research.md

  Tools:  web_search, read_file, write_file, summarize

============================================================

  Step 1...

    web_search -> OK

  Step 2...

    web_search -> OK

  Step 3...

    summarize -> OK

  Step 4...

    write_file -> OK (approved)

  [done] Agent finished at step 5.

============================================================

  Run complete. Report: reports/run_20260518_143200.md

  Budget: {'llm_calls': '5/25', 'tool_calls': '4/50', 'wall_seconds': '23.4/300'}

============================================================

```

5 次 LLM 调用。4 次工具调用。23 秒。工作区里有一份简报，reports 文件夹里有一份报告，以及每个调用的完整日志。

## 11、三件会浪费你时间的事

**从多 agent 编排开始。** 一个有良好日志的 agent 胜过五个即兴发挥的 agent。我在专业构建的 harness 上就遇到了这个问题——我有一个 Producer/Critic 循环，Critic 本应评估输出质量。结果它开始协调工具执行，在上下文窗口被填满的同时要求更多 API 分页。215,000 tokens 对 200K 限制。硬崩溃，无法恢复。Critic 的权威比 Coordinator 还大，而没有人有终止开关。从一个 agent 开始。让它可观测。当你有来自日志的证据——表明一个 agent 做不到时——再添加编排。

**给模型所有工具。** 工具列表是一个攻击面。你添加的每个工具都是模型可能误用、循环调用或以你没想过的方式链式组合的工具。从 read\_file、write\_file 和一个搜索工具开始。当任务真正需要时再添加更多——而不是当它似乎可能有用时。

**跳过审批门因为"只是本地的"。** 本地错误仍然会删除本地文件。我是当一个我正在测试的 agent 决定通过删除它认为是临时文件来"清理"工作区时学到的。它们不是。策略门每次风险操作只需要你按一个键。这是一个好交易。

## 12、结束语

脚手架是有意极简的。

**更多工具。** 脚手架自带五个：read\_file、write\_file、list\_files、web\_search（Brave）和 summarize。对于实际工作，你会添加一个沙箱化的 run\_python 工具、一个 fetch\_url 工具、一个 query\_database 工具。每一个都有一个风险等级和一个策略条目。

**任务模板。** 构建一个你定期运行的任务库：每周摘要、竞品扫描、代码审查总结。

**模型路由。** 在规划步骤使用次前沿模型，在最终输出使用前沿模型。我正在为工作中的 harness 目标是 60-80% 的回合在更便宜的层级上——分派时分类器是一个轻量级启发式方法，不是第二个模型。litellm 让路由变成一个配置修改。

**调度。** 用 cron 任务包装 CLI。Harness 被设计为可以用 --yes 在无人值守的情况下运行受信任的任务。

**代码仓库：**[github.com/anthony-maio/agent-harness-90m](https://github.com/anthony-maio/agent-harness-90m?ref=hubwiz.com)

克隆它。换上你的 API 密钥。运行示例任务。然后为你真正需要完成的事情写一个任务，在它完成时打开报告。

Harness 并不光鲜。

生产系统主要是在危险能力周围的无聊边界。

模型获得荣誉。

Harness 做实际的工作。

---

原文链接：[Build Your First Agent Harness in 90 Minutes](https://anthonymaio.substack.com/p/build-your-first-agent-harness-in)

汇智网翻译整理，转载请标明出处

> **这是「Agent / MCP」专题的一部分。**完整地图见：[Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/)