从零搭建 Agent Harness:工程师实操指南

TL;DR

别设计"理想 harness"。Day 1 写一个 60 行的 CLAUDE.md + 一个验证 hook,然后跑真实任务,出错再加配置。所有成功的实践者都是这么做的,预先设计完美 harness 的团队反而效果最差。


一、心智模型

在碰任何配置之前,先理解这个等式:

Coding Agent = AI Model + Harness

Agent 出错时,几乎总是配置问题,不是模型问题。Harness 是操作系统,Model 是 CPU。你的工作是造 OS。

三条原则(Phil Schmid):

  1. 从简单开始 — 提供可靠的原子工具,让模型做计划。不要构建庞大的控制流。
  2. 为删除而构建 — Manus 6 个月重构 5 次,Vercel 删掉 80% 的工具反而更好。你写的 harness 代码会被删。为此设计。
  3. Harness 即数据集 — 每次执行都产生训练轨迹。竞争优势不在 prompt,在 harness 捕获的数据。

二、30-60-90 天路线图

Days 0-30:最小可用 Harness

Day 1:Bootstrap(2-4 小时)

创建以下文件结构:

your-repo/
├── CLAUDE.md                    # <60 行,手写
├── .claude/
│   ├── settings.json            # Hook 定义
│   ├── hooks/
│   │   └── verify.sh            # 验证脚本
│   ├── rules/                   # 模块化规则(可选)
│   └── skills/                  # 先空着
└── docs/
    ├── architecture.md          # 依赖流向、系统边界
    └── standards.md             # 命名、日志、错误处理

Day 1 的 CLAUDE.md

保持 60 行以内。手写。不要让 LLM 生成。

ETH Zurich 的研究发现:LLM 生成的 CLAUDE.md 损害性能且多花 20%+ token。人工编写的仅提供约 4% 改善。代码库概览和目录列表没有帮助。

markdown
# Project
[一句话描述项目]

# Architecture
Unidirectional dependency flow: Types → Config → Repo → Service → Runtime → UI
Dependencies NEVER flow backward.

# Commands
- Build: npm run build
- Test: npm run test
- Single test: npm run test -- --grep "test name"
- Lint: npm run lint
- Typecheck: npx tsc --noEmit

# Standards
- Use ES modules (import/export), not CommonJS (require)
- Structured logging (JSON format)
- All public functions have docstrings

# Gotchas
- IMPORTANT: Never import from @internal/* in client code
- Database migrations require `npm run db:generate` after schema changes

# Tools
Use CLI for GitHub operations:
- `gh pr create --title "..." --body "..."`
- `gh issue view 123`
Do NOT use MCP servers for things the CLI handles.

每一行都问自己:"删掉这行会不会导致 Claude 犯错?"不会就删。

Day 1 的验证 Hook

.claude/settings.json:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

.claude/hooks/verify.sh:

bash
#!/bin/bash
set -euo pipefail
cd "$CLAUDE_PROJECT_DIR"

# 并行跑类型检查和 lint
ERRORS=""
npx tsc --noEmit 2>&1 | tail -20 > /tmp/tc_out || ERRORS+="$(cat /tmp/tc_out)
"
npx eslint --quiet . 2>&1 | tail -20 > /tmp/lint_out || ERRORS+="$(cat /tmp/lint_out)
"

if [ -n "$ERRORS" ]; then
  echo -e "$ERRORS" >&2
  exit 2  # exit 2 = 让 agent 修复问题
fi

# 成功 = 沉默。不打印任何东西。
bash
chmod +x .claude/hooks/verify.sh

关键原则:成功沉默,失败才说话。HumanLayer 发现完整测试输出会淹没上下文,agent 反而失焦。

Days 2-7:观察并补规则

跑几个真实任务,记录 agent 在哪里犯错。然后:

  • 对重复错误模式添加 1-2 条 lint 规则
  • 对常用外部工具写 wrapper CLI,在 CLAUDE.md 中添加用法示例
  • 如果观察到上下文腐烂,开始用 subagent 委托模式

Days 0-30 的反模式

不要做为什么
预先设计"理想" harness你还不知道什么会失败
安装大量 MCP servers / skills "以防万一"每个都消耗上下文预算,未使用的工具降低性能
每次改动后跑 5 分钟以上的完整测试套件淹没上下文,agent 失焦
创建角色型 subagent("前端工程师")不工作。用任务型委托。
让 LLM 生成 CLAUDE.md可测量地损害性能

Days 31-60:可观测性和验证

目标:Agent 能看到自己工作的后果并自我纠正。

给 Agent 结构化的可观测性访问

写精简的 wrapper 脚本,不要给 raw log dump:

bash
#!/bin/bash
# .claude/hooks/check-errors.sh
tail -100 /var/log/app/error.log \
  | jq -r '.timestamp + " " + .level + " " + .message' 2>/dev/null \
  || echo "No recent errors"

在 CLAUDE.md 中引用:

markdown
# Observability
- Recent errors: `.claude/hooks/check-errors.sh`
- Metrics summary: `.claude/hooks/check-metrics.sh`
- Only show errors, do NOT dump full logs

关键用户旅程转为可执行验收测试

不仅是"代码编译通过",而是"用户流程是否正常工作"。四个验证组件:

  1. 用户视角 E2E 验证 — 从用户角度测试结果
  2. 关键路径 SLO — 变更成功率、缺陷逃逸率预算
  3. 真实世界验收数据集 — 真实使用模式 + 对抗性边缘情况
  4. 意图失败检测 — agent "遵循了规则但错过了产品意图"的场景

开始小规模自动 PR

先从低风险开始:修错字、升级依赖、简单重构。逐步建立信任。

原则:先实现可检测性,再授予自主权;先建立约束,再优化吞吐量。


Days 61-90:熵治理

  • 自动扫描模式漂移 — 定期检查 agent 行为是否偏离预期
  • 质量仪表板 — 按领域/层跟踪技术债务
  • 将 review 反馈编码为规则 — 如果你反复告诉 agent 同一件事,它应该是 hook 或 lint 规则,不是注释

三、Claude Code 的五个配置杠杆

杠杆 1:CLAUDE.md(全局上下文注入)

加载层次(自动加载,全部合并):

位置路径作用域
Home~/.claude/CLAUDE.md所有项目
项目根./CLAUDE.md 或 ./.claude/CLAUDE.md团队共享(git)
项目本地./CLAUDE.local.md个人,gitignore
父目录向上搜索Monorepo 支持
子目录按需加载局部上下文

Import 语法:

markdown
Git workflow: @docs/git-instructions.md
API docs: @docs/api-reference.md

黄金法则:

该放的不该放的
Claude 猜不到的 bash 命令Claude 读代码就能知道的
不同于默认的代码风格规则标准语言惯例
测试指令和首选 test runner详细 API 文档(改用链接)
架构决策和非显然行为频繁变化的信息
开发环境怪癖(需要的 env vars)文件逐一描述

杠杆 2:.claude/rules/*.md(模块化路径规则)

当 CLAUDE.md 太挤时,拆分为独立规则文件:

.claude/rules/
├── code-style.md       # 通用,始终加载
├── testing.md          # 通用
├── security.md         # 路径限定
└── api.md              # 路径限定

路径限定(YAML frontmatter):

yaml
---
paths: src/api/**/*.ts
---
# API Rules
- All endpoints must validate input with Zod
- Return consistent error shapes
- Use parameterized queries exclusively

无 paths frontmatter 的规则始终加载(等同于 CLAUDE.md 内容)。有 paths 的规则仅在 Claude 操作匹配文件时加载。

实际例子:

.claude/rules/security.md:

yaml
---
paths:
  - src/auth/**/*
  - src/payments/**/*
---
# Security-Critical Code
- Never log sensitive data (PII, tokens, passwords)
- All auth changes require human review
- Use parameterized queries exclusively

.claude/rules/migrations.md:

yaml
---
paths: prisma/migrations/**/*
---
# Migration Safety
- Always include rollback instructions
- Never drop columns without two-phase migration
- Test on production data copies first

杠杆 3:Hooks(确定性控制流)

这是你最强的控制机制。CLAUDE.md 是建议性的;hooks 是确定性的。

所有生命周期事件(部分常用):

事件触发时机可阻断?
PreToolUse工具执行前是
PostToolUse工具执行后否
StopClaude 完成响应是
SessionStart会话开始/恢复否
SubagentStopSubagent 完成是
FileChanged监视的文件变化否

Exit code 约定:

码含义效果
0成功正常继续
2阻断错误阻止操作,stderr 反馈给 Claude 修复
其他非阻断错误继续执行

四种 handler 类型:

json
// 1. Shell 命令(最常用)
{"type": "command", "command": ".claude/hooks/verify.sh", "timeout": 30}

// 2. HTTP 端点
{"type": "http", "url": "http://localhost:8080/hooks/check", "timeout": 10}

// 3. LLM 判断(yes/no 决策)
{"type": "prompt", "prompt": "Is this change safe?", "model": "fast-model"}

// 4. Agent(有工具的 LLM)
{"type": "agent", "prompt": "Verify setup", "model": "default"}

实战 Hook 模式:

阻止危险命令(PreToolUse):

bash
#!/bin/bash
# .claude/hooks/block-dangerous.sh
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)

if *"$COMMAND" == rm\ -rf** || *"$COMMAND" == *--force**; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Destructive command blocked"}}' 
  exit 0
fi
exit 0

自动格式化(PostToolUse,异步):

bash
#!/bin/bash
# .claude/hooks/auto-format.sh
INPUT=$(cat /dev/stdin)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')

if *"$FILE_PATH" == *.ts* || *"$FILE_PATH" == *.tsx*; then
  npx prettier --write "$FILE_PATH" 2>/dev/null
fi
exit 0

完成通知(Stop):

bash
#!/bin/bash
# .claude/hooks/notify-done.sh
osascript -e 'display notification "Claude finished" with title "Claude Code"' 2>/dev/null \
  || echo -e '\a'  # fallback: terminal bell

完整 settings.json 示例:

json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(npx jest *)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(git status)"
    ],
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(npm publish *)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.sh",
            "timeout": 10
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify.sh",
            "timeout": 30
          },
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-format.sh",
            "async": true
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify-done.sh"
          }
        ]
      }
    ]
  }
}

杠杆 4:Skills(渐进式知识披露)

目的:按需加载指令和工具,而非全部塞进 system prompt。

目录结构:

.claude/skills/
├── fix-issue/
│   └── SKILL.md
├── deploy-preview/
│   ├── SKILL.md
│   └── scripts/
│       └── validate.sh
└── api-conventions/
    └── SKILL.md

SKILL.md 格式:

yaml
---
name: fix-issue
description: Fix a GitHub issue end-to-end
disable-model-invocation: true
---
Analyze and fix GitHub issue: $ARGUMENTS

1. `gh issue view $ARGUMENTS` to get details
2. Search codebase for relevant files
3. Implement fix
4. Write and run tests
5. Ensure lint and typecheck pass
6. Create commit with descriptive message
7. Push and create PR

用 /fix-issue 1234 调用。

关键 frontmatter 字段:

字段作用
name显示名 + /slash-command
descriptionClaude 用来决定何时自动加载
disable-model-invocation: true只能用户调用,Claude 不会自动触发
user-invocable: false只有 Claude 能调用,用户看不到
allowed-tools激活时自动授权的工具
context: fork在隔离 subagent 中运行
paths仅在匹配文件上工作时加载

动态上下文注入:

yaml
---
name: pr-summary
context: fork
agent: Explore
---
## PR Context
- Diff: !`gh pr diff`
- Comments: !`gh pr view --comments`

Summarize this PR...

!`command` 语法在发送给 Claude 前执行 shell 命令。

什么该放 Skills vs CLAUDE.md:

放 CLAUDE.md放 Skills
每个任务都需要的通用规则特定工作流(部署、修 issue、迁移)
项目架构和命名约定偶尔需要的领域知识
常用命令速查复杂多步流程

杠杆 5:Sub-Agents(上下文隔离)

目的:防止上下文腐烂。父 agent 只看到 subagent 的 prompt 和最终结果——中间过程全部隔离。

Chroma 的研究:上下文越长性能越差,尤其是当前问题与累积上下文语义相似度低时。

有效的委托模式(任务型):

"找到 processPayment 的定义,返回文件路径和行号"
"分析代码库中所有使用已废弃 oldAuth 的地方"
"追踪 /users 端点到数据库的数据流"

无效的委托模式(角色型):

"你是前端工程师"  ← 不工作
"你是后端工程师"  ← 不工作

成本优化:贵模型(Opus)做父 agent 规划编排,便宜模型(Sonnet/Haiku)做 subagent 离散任务。


四、自主权分级

不是所有任务都需要同等程度的人工审查:

级别场景合并前要求
完全自主修错字、加测试、依赖升级CI + 自动审查
轻度审查已有模式内的功能开发CI + 自动审查 + 人类速览(<5 min)
完整审查新端点、数据模型变更CI + 自动审查 + 人类深入审查
人类主导Schema 迁移、基础设施、安全关键人类编写/共写,agent 辅助

五、什么有效 vs 什么无效

有效

  1. 从简单开始,出错再加配置 — 最核心的原则
  2. 成功沉默,失败才说话 — 吞掉成功输出,只暴露错误
  3. 将有效配置通过 git 共享给团队 — .claude/settings.json + .claude/rules/ 提交到仓库
  4. 优化迭代速度而非首次成功率 — 让 agent 试错 + hook 纠正,比预防所有可能的失败更快
  5. 渐进式能力暴露 — 从全功能开始,逐步缩减到 agent 实际需要的
  6. 无情简化 — Vercel 删掉 80% 的工具,更少步骤、更少 token、更快响应

无效

  1. 预先设计理想 harness — 你无法预测什么会失败
  2. 预防性安装大量 skills/MCP — 每个都消耗上下文预算
  3. 每次改动跑完整测试套件(5+ 分钟) — 淹没上下文
  4. 微调 subagent 工具权限 — 造成 "tool thrash"
  5. LLM 生成的 CLAUDE.md — 可测量地更差
  6. 角色型 subagent — 不工作
  7. 过度工程化控制流 — 复杂管道随模型进步而过时

六、Day 1 检查清单

□ 创建 CLAUDE.md(<60 行,手写,通用规则)
□ 创建 docs/architecture.md(依赖流向图)
□ 创建 .claude/settings.json(一个验证 hook)
□ 创建 .claude/hooks/verify.sh(typecheck + lint,成功沉默)
□ chmod +x .claude/hooks/verify.sh
□ 跑一个真实任务,观察哪里失败
□ 不要加 MCP servers、skills、sub-agents

Week 1:

□ 添加 1-2 条针对观察到的失败模式的 lint 规则
□ 为外部工具写 wrapper CLI
□ 在 CLAUDE.md 中添加 CLI 用法示例
□ 如有上下文腐烂,创建首个 subagent 委托模式

Month 1:

□ 为复杂多步工作流创建 1-2 个 skills
□ 添加可观测性 hooks(结构化日志访问)
□ 为关键用户旅程构建首个验收测试
□ 审查并删除没有积极改善输出的一切
□ 将有效配置通过 git 共享给团队

七、快速参考:用哪个机制

需求用
每个会话都需要的项目上下文CLAUDE.md
路径限定的指令.claude/rules/*.md + paths
必须每次发生的确定性动作Hooks
按需加载的领域知识Skills
可复用的任务工作流(/command)Skills + disable-model-invocation: true
工具权限控制settings.json permissions
团队共享配置.claude/settings.json(提交到 git)
个人覆盖.claude/settings.local.json 或 CLAUDE.local.md

参考来源

Comments (0)

No comments yet

imfht
April 6, 2026