← 返回主页

Building a Harness from Scratch: ~300 Lines of Python

上一篇文章我们讨论了 Agent = Model + Harness 的理论。这篇文章,我们动手写一个。

目标

用一个下午写出一个最小可用的 Agent Harness,验证以下概念:

最终产物:7 个 Python 模块,~300 行核心代码,支持通过 CLI 或代码两种方式使用。

👉 github.com/greasebig/harness-poc

架构

harness/
├── agent.py       # 核心 Agent Loop — 整个系统的心跳
├── context.py     # AGENTS.md 自动发现与系统提示构建
├── memory.py      # MEMORY.md 跨会话持久记忆
├── permissions.py # 路径白名单 + 危险命令拦截
├── hooks.py       # PreToolUse / PostToolUse 生命周期
├── providers.py   # Anthropic + OpenAI + 可扩展
└── tools.py       # read_file, write_file, list_dir, shell

核心:Agent Loop

整个系统的灵魂只有 30 行:

while not done:
    response = self.provider.chat(self.messages, self.tools.schemas())
    
    if response.stop_reason == "tool_use":
        for tc in response.tool_calls:
            # 权限检查 → 钩子 → 执行 → 结果
            result = self._execute_tool(tc.name, tc.input)
            self.messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tc.id,
                    "content": result,
                }],
            })
    else:
        final_text = response.text
        done = True

模型决定"做什么"。Harness 负责"怎么做"——权限校验、钩子触发、结果格式化。环环相扣,永不停歇。

五个设计决策

1. 上下文 ≤ 60 行

def _load_agents_md(self) -> str:
    # ...
    if len(lines) > 60:
        lines = lines[:57] + ["# ... (truncated for focus)"]
    return "\n".join(lines)

给 Agent 一张地图,不是一本百科全书。超过 60 行的 AGENTS.md 会被自动截断。

2. 每次工具调用都经过权限检查

def _execute_tool(self, name, params):
    if not self.perm.allow(name, params):
        return "[BLOCKED] Permission denied."
    self.hooks.fire("pre_tool", name=name, params=params)
    result = self.tools.execute(name, params)
    self.hooks.fire("post_tool", name=name, params=params, result=result)
    return result

Permission → Hook → Execute → Hook。没有捷径。

3. 记忆是显式的

Agent 必须主动使用 [MEMORY: ...] 标记才能写入长期记忆。非侵入式,不污染上下文。

# Agent 在回复中写:
# I notice this project uses pytest. [MEMORY: This project uses pytest for testing]

# Harness 自动提取并写入 MEMORY.md:
# - [2026-05-24 20:30] This project uses pytest for testing

4. Provider 是插件

添加新 provider 只需 30 行代码:

class MyProvider(BaseProvider):
    default_model = "my-model"
    def chat(self, messages, tools):
        # Your API call here
        return ProviderResponse(text=..., tool_calls=..., ...)

PROVIDERS["my_provider"] = MyProvider

5. 钩子是可选的

不注册钩子,系统正常运行。注册了钩子,你就有了日志、审计、验证的能力。

agent.hooks.register("pre_tool", lambda **kw: print(f"🔧 {kw['name']}"))
agent.hooks.register("post_tool", lambda **kw: print(f"✅ done"))

Demo

$ python -m harness "List files and create hello.txt"

[harness] 🤖 anthropic/claude-sonnet-4-5-20250514 — 4 tools loaded
[harness] 💾 Saved 1 memory entries to MEMORY.md

Here's what I did:
1. Listed files: AGENTS.md, README.md, harness/, examples/
2. Created hello.txt with "Hello from harness!"

三个示例

示例演示
01_basic_task.py基本任务:列文件 + 写文件
02_hooks.py钩子系统:记录每次工具调用
03_memory.py跨会话记忆:Agent 记住并回顾

为什么要做这个 POC?

上一篇文章引用了 LangChain 的实验数据:同样的模型,加上 Harness 优化后准确率提升了 26%。

这个 POC 就是那份实验的可运行版。它证明了:

  1. Harness 不复杂——核心逻辑只有 300 行 Python
  2. Harness 可扩展——新 provider、新工具、新钩子,都是插拔式
  3. Harness 有复利——每次 Agent 犯错,你加一条规则,它就变得更可靠

下一步计划:

Fork it, break it, build on it.

👉 github.com/greasebig/harness-poc