上一篇文章我们讨论了 Agent = Model + Harness 的理论。这篇文章,我们动手写一个。
目标
用一个下午写出一个最小可用的 Agent Harness,验证以下概念:
- 多 provider 支持(Claude / GPT-4 / 可扩展)
- Agent Loop(query → stream → tool-call → loop)
- AGENTS.md 自动上下文注入
- MEMORY.md 跨会话持久记忆
- 权限边界(路径白名单、危险命令拦截)
- 生命周期钩子(PreToolUse / PostToolUse)
最终产物: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 就是那份实验的可运行版。它证明了:
- Harness 不复杂——核心逻辑只有 300 行 Python
- Harness 可扩展——新 provider、新工具、新钩子,都是插拔式
- Harness 有复利——每次 Agent 犯错,你加一条规则,它就变得更可靠
下一步计划:
- 添加 self-verification loop(自动运行 lint + test)
- 支持子 Agent 派发(context firewall)
- MCP (Model Context Protocol) 集成
Fork it, break it, build on it.