构建你自己的 Harness
大约 4 分钟
构建你自己的 Harness
两条路:从零手写(理解原理)或 Agent SDK(生产推荐)。本篇都做。
路线一:100 行手写一个迷你 Harness(Python)
import json, anthropic
client = anthropic.Anthropic()
# ── 1. 工具注册表:schema + handler ──────────────
TOOLS = [{
"name": "read_file",
"description": "读取指定路径的文本文件内容",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
}]
def read_file(path: str) -> str:
try:
with open(path, encoding="utf-8") as f:
return f.read()[:50_000] # 截断防爆上下文
except Exception as e:
return f"ERROR: {e}" # 错误当数据回注
HANDLERS = {"read_file": read_file}
# ── 2. 系统提示:harness 的「宪法」────────────────
SYSTEM = """你是一个文件分析助手,工作目录为当前目录。
使用 read_file 工具读取文件后回答问题。
回答引用代码时注明文件路径和行号。"""
# ── 3. Agentic Loop ─────────────────────────────
def run(task: str, max_turns: int = 20):
messages = [{"role": "user", "content": task}]
for _ in range(max_turns): # 失控刹车
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096,
system=SYSTEM,
messages=messages,
tools=TOOLS,
)
# 收集本轮全部工具调用并执行
tool_results = []
for block in resp.content:
if block.type == "tool_use":
result = HANDLERS[block.name](**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
if not tool_results: # 终止:无工具调用
return resp.content[0].text
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": tool_results})
return "(达到最大轮数,强制停止)"
print(run("总结 pyproject.toml 里都管理了什么依赖"))
跑起来你就拥有了一个最小可用 harness。逐个痛点升级,就是整部 harness 演化史:
| 痛点 | 升级 | 参考篇目 |
|---|---|---|
| 长任务上下文爆炸 | 压缩 + 外部记忆 | 03 上下文工程 |
| 只有读文件不够 | 增加 write/bash/搜索工具 + 权限层 | 04 工具与 MCP |
| 串行太慢 | 子代理 + worktree 隔离 | 05 子代理 |
| 每次重复交代规矩 | 系统提示外置 + hooks/skills | 06 扩展机制 |
路线二:Claude Agent SDK(生产级)
SDK 把上面所有机制做成了现成能力——你写业务,它管运行时:
npm install @anthropic-ai/claude-agent-sdk
import { query } from "@anthropic-ai/claude-agent-sdk"
const conversation = query({
prompt: "分析 src/ 目录,找出所有没有错误处理的 async 函数,输出清单",
options: {
allowedTools: ["Read", "Grep", "Glob"], // 工具白名单
permissionMode: "default", // 权限模式
cwd: process.cwd(),
// systemPrompt: 可完全接管系统提示
},
})
for await (const message of conversation) {
if (message.type === "assistant" && message.message.content) {
for (const block of message.message.content) {
if (block.type === "text") console.log(block.text)
if (block.type === "tool_use")
console.log(`[工具] ${block.name}`, JSON.stringify(block.input))
}
}
}
SDK 给了什么(对比手写版)
✅ 完整工具集(文件/shell/搜索,且权限系统就绪)
✅ 子代理、MCP、hooks、skills 全部可用
✅ 会话持久化与恢复
✅ 流式事件(每一步工具调用都能订阅)
✅ 结构化输出约束
你的代码量集中在「编排逻辑」:
何时派子代理、结果怎么聚合、和你的业务系统怎么对接
构建实战:代码审查机器人(30 行)
import { query } from "@anthropic-ai/claude-agent-sdk"
import { execSync } from "node:child_process"
// 1. 拿到改动文件列表
const changed = execSync("git diff --name-only main")
.toString().split("\n").filter(Boolean)
// 2. 派 agent 审查(只读工具,权限收紧)
const result = await query({
prompt: `审查以下文件的改动,只报告可确认的问题,
按严重度排序,引用 file:line:\n${changed.join("\n")}`,
options: { allowedTools: ["Read", "Grep", "Bash"] },
}).then(async q => {
let text = ""
for await (const m of q)
if (m.type === "assistant")
for (const b of m.message.content)
if (b.type === "text") text += b.text
return text
})
// 3. 接入 CI:发评论 / 卡门禁
console.log(result) // 或 post 到 PR comment API
这就是「CI 里的 Claude Code」雏形——harness 即服务。
自建 Harness 的选型清单
先问:真的需要自建吗?
通用编码任务 → 直接用现成 harness(Claude Code 等)
特定业务流(客服、数据处理、内部工具)→ SDK 起
架构决策点:
1. 模型接入:多模型路由 or 单一模型
2. 工具面:内置工具 + 自有业务工具(SDK 的 MCP server 最干净)
3. 权限:哪些自动放行、哪些落人工审批(对接工单系统)
4. 上下文策略:何时压缩、外部记忆存哪(DB/文件)
5. 观测:每步工具调用落日志(排查「AI 为什么这么干」的唯一手段)
6. 评估:改动 prompt/工具描述后,怎么验证没有退化(见下篇)
常见坑
1. 系统提示写太「文艺」:模型需要精确规则,不是企业愿景
2. 工具描述偷懒:直接导致模型乱传参数/选错工具
3. 无轮数/成本上限:一个死循环烧掉一个月预算
4. 工具输出不截断:一次 dump 就吃掉半个上下文
5. 把关键约束只写在 prompt 里:该用 hook 的地方用了「请求」
本篇小结
- 100 行 = 最小 harness;每个工程痛点对应一个机制升级
- 生产用 Agent SDK:工具/权限/子代理/扩展开箱即用,你只写编排
- 观测与上限(日志、轮数、预算)在第一天就要有
下一篇:评估、安全与成本。
