目录

[Project] 3. Harness-Everything — AI 自主代码改进 Harness

全局视图

diagram

LLM 是大脑,Harness 是手脚,项目代码是被改的对象。LLM 从来没有直接碰过文件系统——它只说"我想做什么",你的代码去执行。

本质:三句话说清楚

  1. LLM 是发动机:把项目代码扔给大语言模型,让它分析、提改进、写代码。核心就是一个 while 循环不停问 LLM"还能怎么改"。
  2. 工具是手脚:LLM 不能直接读写文件,所以通过 Anthropic 的 tool_use 协议告诉你的代码"我想读这个文件"/“我想改这行代码”,你的代码去执行,再把结果喂回来。只留一个 bash 工具也能跑。
  3. 重启是关键:Python 模块加载后就固化在内存里。LLM 改了自己的 .py 文件,运行中的进程还是用旧代码。必须重启进程,改进才生效。所以有了 push → tag → CI 部署 → 重启 的闭环。

从最简单到完整系统:每一层解决一个问题

最简版本(概念上)

1
2
3
4
while True:
    代码 = 读取项目所有文件()
    回复 = LLM("这是代码,帮我改进:" + 代码)
    写回文件(回复)

这就能跑。但会遇到一堆问题。下面每一层都是为了解决上一层的问题

diagram

第 1 层:代码太多,上下文放不下

问题:项目一大,所有文件塞不进 LLM 的上下文窗口。

解法:不再一次性塞所有代码,而是给 LLM 工具让它自己选择看什么。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
LLM: "我想看 harness/core/llm.py"
你的代码: open("harness/core/llm.py").read() → 返回文件内容
LLM: "我想搜索哪些文件引用了 _check_path"
你的代码: grep -r "_check_path" → 返回搜索结果
LLM: "我要改这个文件的第 285 行"
你的代码: 执行修改

这就是 tool_use 机制 —— LLM 输出一段 JSON 说"我想调什么工具、传什么参数",你的 Python 代码去执行,把结果喂回来。LLM 从来没有直接碰过文件系统。

diagram

核心代码(harness/core/llm.pycall_with_tools())只有 60 行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
for turn in range(max_turns):
    # 1. 问 LLM:"你想做什么?"
    response = await api.messages.create(messages=对话, tools=工具列表)
    
    # 2. 如果 LLM 没有要调工具 → 它说完了,退出
    if not response.tool_calls:
        return response.text
    
    # 3. LLM 要调工具 → 你的代码执行 → 把结果喂回去
    for call in response.tool_calls:
        result = await registry.execute(call.name, config, call.input)
    
    # 4. 结果追加到对话历史,继续循环
    对话.append(tool_results)

就这些。这是整个项目最核心的 60 行。

第 2 层:LLM 会改出 bug

问题:LLM 改的代码可能有语法错误,甚至会搞坏项目。

解法:每次改完代码,自动跑 python -m py_compile 检查语法。如果出错,把错误信息告诉 LLM,让它修。

1
LLM 改了代码 → py_compile 报错 → 错误信息喂回 LLM → LLM 再改 → 通过

这就是 harness/pipeline/hooks.py 里的 SyntaxCheckHook

第 3 层:不知道改得好不好

问题:LLM 改了一轮,改得好吗?怎么判断?

解法:用另一次 LLM 调用来评判。而且用两个独立的评判者(互相看不到对方的评分),从不同角度打分:

  • Basic evaluator:找最关键的缺陷(安全漏洞、逻辑错误、代码质量)
  • Diffusion evaluator:分析二阶效应(会不会破坏其他模块?会不会让上下文膨胀?)
1
2
3
4
5
LLM 改了代码
  ├── Basic evaluator:    "这个改动有安全问题" → 5 分
  └── Diffusion evaluator: "但不会影响其他模块" → 7 分
  
合计 12 分。跟其他方案比,选最高分的。
diagram

这就是 harness/evaluation/dual_evaluator.py。本质上是让 LLM 自己评自己,但通过隔离两个评估视角来减少自我吹嘘。

第 4 层:一次改一点,多轮迭代

问题:一次性让 LLM “把所有问题都修了” 效果很差。大改容易翻车。

解法:拆成多个阶段(phase),每个阶段专注一件事,一轮一轮迭代:

1
2
3
4
5
6
7
8
9
Outer Round 1:
  Phase 1: 分析代码,找问题(debate 模式,只看不改)
  Phase 2: 改进核心框架(implement 模式,用工具改文件)
  Phase 3: 安全加固 + 删死代码
  Phase 4: 整合工具 + 加测试
  Phase 5: 改善可追踪性

Outer Round 2:
  ...重复,但基于 Round 1 的改进继续

每个阶段内部还有内层轮次(inner rounds):生成多个方案,评估器选最好的,然后合成。

diagram

这就是 harness/pipeline/pipeline_loop.py(外层)和 harness/pipeline/phase_runner.py(阶段执行)。

第 5 层:改了不生效

问题:LLM 在 Round 3 改了 harness/core/llm.py,但运行中的进程还在用 Round 1 加载的旧代码。改了等于没改。

解法:你设计的重启闭环。

1
2
3
4
5
服务器跑 10 轮 → git commit + push → 打 tag → push tag
GitHub Actions 看到 tag → SSH 到服务器 → 部署新代码 → 重启进程
                                              新代码生效,再跑 10 轮...

这是你做的最核心的架构决策。 没有这个循环,自我优化就是假的——LLM 以为自己改了代码,但改动永远不会执行。

第 6 层:循环别断了

问题:各种情况会让循环意外停止。

问题解法
代码太多上下文爆了给 LLM 工具,让它按需读
LLM 改出 bug语法检查 hook 自动验证
不知道改得好不好双重评估器打分,选最高的
改了不生效push → tag → CI → 重启进程
连续几轮没进步 → 早停 → 没打 tagauto_tag_at_end 强制每次退出都打 tag
崩溃 3 次 systemd 放弃心跳 cron 每 30 分钟检查并重启
用户推 commit 导致 push 冲突git pull --rebase 自动合并
LLM 把部署脚本改坏了prompt 里的 PROTECTION 黑名单
部署了坏代码CI 烟测 + 回滚到 harness-last-good
磁盘满清理 cron 每天删旧数据

工具系统:不是核心,是优化

如果只有一个 bash 工具,LLM 会这么干:

1
2
3
4
bash("cat harness/core/llm.py")           → 读文件
bash("grep -rn '_check_path' harness/")   → 搜索
bash("sed -i 's/old/new/' file.py")       → 改文件
bash("python3 -m py_compile file.py")     → 检查语法

完全能跑。那为什么还搞 30 个专用工具?

用 bash用专用工具为什么换
cat /etc/passwdread_file 会拒绝安全:路径检查限制在 workspace 内
grep 输出 10 万行grep_search 自动截断省 token:不会撑爆上下文
sed 改错了没法回退edit_file 精确替换可控:替换失败会报错,不会静默改坏
LLM 编造参数registry 参数校验容错:未知参数直接拦截
diagram

工具是给 LLM 戴的安全手套,不是给它的超能力。

30 个工具分类

1
2
3
4
5
6
7
文件操作:    read_file, write_file, edit_file, delete_file, move_file, copy_file
目录操作:    list_directory, create_directory, tree
搜索:       glob_search, grep_search
Git:        git_status, git_diff, git_log
执行:       bash, python_eval, test_runner
分析:       code_analysis, symbol_extractor, cross_reference, ...
可选:       web_search (需要显式开启)

工具安全边界(_check_path

每个文件操作工具在执行前都要过安全检查:

1
2
3
4
resolved = os.path.realpath(path)  # 解析符号链接
# 检查 null bytes、Unicode 伪装字符、路径越界
if path 不在 allowed_paths :
    拒绝执行

LLM 说"读 /etc/passwd" → 被拦截。说"读 ../../etc/passwd" → realpath 解析后还是被拦截。


工具循环的成本模型

Anthropic API 是无状态的。每次调用都要重发完整对话历史。所以工具循环的每一轮都比上一轮贵:

1
2
3
4
5
6
Turn  1: 发送 [系统提示 + 文件上下文 + 用户指令]       ≈ 20K tokens
Turn  5: 发送 [上面 + 4 轮工具调用和结果]               ≈ 30K tokens
Turn 10: 发送 [上面 + 9 轮]                             ≈ 40K tokens
Turn 20: 发送 [上面 + 19 轮]                            ≈ 60K tokens  ← 最贵

一个 20-turn 循环总共: ≈ 0.5M input tokens
diagram

后半程每多一轮,花的钱比前半程多得多。 这就是为什么 max_tool_turns 从 30 砍到 20 能省 40% —— 砍掉的是最贵的那几轮。

缓解措施

机制文件原理
对话剪枝llm.py总字符超 150K 时,截断旧的工具结果
主动压缩llm.pyturn >= 6 后,旧工具结果替换为一行摘要
文件读取缓存llm.py同一循环内 read_file 结果缓存,写操作使缓存失效
代码注入预算phase_runner.py只注入最相关的 30K 字符源码,不是全部

DeepSeek 成本估算

1
2
3
4
5
6
7
8
Cache miss: $0.28 / 百万 input tokens
Cache hit:  $0.028 / 百万 input tokens (缓存命中率约 90%)
Output:     $0.42 / 百万 output tokens

一个 chunk (6-10 轮 × 4-5 阶段):
  Input:  ~30M tokens → ~$3
  Output: ~1M tokens  → ~$0.4
  合计:   ~$3.5 / chunk

Pipeline 架构详解

整体结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
PipelineLoop.run()

├── for outer in range(10):                     外层: 10 
   
   ├── _run_outer_round()
      
      └── for phase in [分析, 改进, 安全, 整合, 追踪]:   5 个阶段
          
          └── PhaseRunner.run_phase()
              
              ├── 注入代码上下文 (最相关的 30K 字符)
              
              ├── for inner in range(2):        2 次尝试
                 
                 ├── debate 模式: 并行,只分析不改
                 └── implement 模式: 顺序,用工具改文件
                     └── call_with_tools()     工具循环 (最多 20 )
              
              ├── DualEvaluator.evaluate()       双重评估,选最好的
              ├── Synthesis                      合成最佳方案
              └── Hooks: 语法检查 + git commit   验证 + 提交
   
   ├── auto_push (git pull --rebase + push)      每轮推到 GitHub
   ├── patience 检查 (连续 5 轮没进步  早停)
   └── 优雅关闭检查 (收到 SIGTERM  干完当前阶段退出)

├── auto_tag_at_end   tag + push tag           触发 CI 部署
└──  summary.json

每一层做什么

外层轮次 (pipeline_loop.py):

  • 编排所有阶段的执行顺序
  • 跟踪分数趋势(3 连降警告)
  • 决定是否早停(patience)
  • 决定是否推送(auto_push)
  • 决定是否打 tag(auto_tag)

阶段执行 (phase_runner.py):

  • 按关键词 + 修改时间 + 文件大小排序,注入最相关的源码
  • debate 模式:LLM 只分析不改,并行跑多个方案,快
  • implement 模式:LLM 用工具改文件,顺序跑(因为会改文件,不能并行)
  • 评估:两个独立 LLM 调用并行打分
  • 合成:从多个方案中提取最佳元素
  • 验证:语法检查 + git commit

工具循环 (llm.py):

  • LLM 说"我要调这个工具" → 你的代码执行 → 结果喂回来 → 循环
  • 每轮都重发完整对话(API 无状态)
  • 自动剪枝防止上下文爆炸
  • 最多 20 轮(成本控制)

关键数据流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
prior_best (上一轮最佳方案)
Phase 1 (分析) → synthesis → 传给 Phase 2
Phase 2 (改进) → 改文件 → commit → synthesis → 传给 Phase 3
Phase 3 (安全) → 改文件 → commit → synthesis → 传给 Phase 4
...每个 phase 的合成结论传给下一个 phase 作为 prior_best
Round 结束 → 最终 prior_best 传给下一个 Round

自改进循环(服务器部署)

架构图

diagram

为什么需要重启

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 进程启动时
import harness.core.llm  # 加载到内存,之后不再变

# Round 5: LLM 改了磁盘上的 llm.py
edit_file("harness/core/llm.py", ...)  # 磁盘变了

# Round 6: 进程还是用内存里的旧版本
# Python 不会自动重新 import

# 只有重启进程,新代码才生效

操作手册

想做什么怎么做
看实时日志ssh server "tail -f ~/harness-everything/logs/harness.log"
看 commit 进度git log --oneline -20
推一个修复(不停服务)直接 git push,harness 会 pull --rebase 自动合并
改配置config/pipeline_example_self_improve_server.json,push,下次部署自动生效
跑完这轮就停ssh server "touch ~/.config/harness/STOP_AFTER_CHUNK"
恢复循环ssh server "systemctl --user start harness.service"
立刻停ssh server "systemctl --user stop harness.service"
彻底关闭stop + disable + 清 cron

核心数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PipelineConfig                     # 顶层配置
├── harness: HarnessConfig         #   模型/API/workspace/工具
   ├── model: "deepseek-chat"
   ├── base_url: "https://api.deepseek.com/anthropic"
   ├── workspace: "/home/ubuntu/harness-everything"
   ├── allowed_paths: [workspace]
   └── max_tool_turns: 20
├── phases: [PhaseConfig]          #   阶段列表
   ├── name, mode (debate/implement)
   ├── system_prompt ( $file_context 等模板变量)
   └── glob_patterns (注入哪些文件)
├── outer_rounds: 10               #   每个 chunk 跑几轮
├── patience: 5                    #   几轮没进步就早停
├── auto_push_interval: 1          #   每轮 push
└── auto_tag_at_end: true          #   退出必打 tag

InnerResult                        # 单次尝试的结果
├── proposal: str                  #   LLM 的提案或改动
├── dual_score                     #   双重评分
   ├── basic: (score, critique)   #     缺陷评估
   └── diffusion: (score, critique)#    波及效应
└── tool_call_log: [dict]          #   工具调用记录

PhaseResult                        # 阶段结果
├── synthesis: str                 #   合成后的最终方案
├── best_score: float              #   最高分
└── inner_results: [InnerResult]   #   所有尝试

关键文件索引

文件核心职责一句话
main.py入口解析参数,启动循环
harness/core/llm.py最核心工具循环:LLM 说 → 你执行 → 反馈 → 重复
harness/core/config.py配置JSON → 配置对象,路径安全验证
harness/pipeline/pipeline_loop.py外层循环轮次编排、push、tag、早停、关闭
harness/pipeline/phase_runner.py阶段执行代码注入、内层轮次、评估、合成、hooks
harness/evaluation/dual_evaluator.py质量把关两个 LLM 并行打分,选最好的方案
harness/tools/registry.py工具分发工具注册、参数校验、异常封装
harness/tools/base.py工具安全_check_path 路径边界检查
harness/pipeline/hooks.py验证语法检查 + git commit(富信息)
deploy/harness.service部署systemd 服务定义
.github/workflows/deploy.ymlCI/CDtag 触发 → 烟测 → 部署 → 重启/回滚
deploy/heartbeat.sh保活崩溃后自动重启

完整数据流:从 JSON 配置到代码提交

diagram

一段话总结

把项目代码扔给 LLM,让它分析、提改进方案、用工具改代码。用另一个 LLM 调用评判改得好不好,选最好的方案 commit。多轮迭代,每轮都比上一轮基于更好的代码。因为 Python 模块加载后就固化在内存里,所以每 10 轮重启一次进程让改进生效。重启通过 git tag 触发 GitHub Actions 自动部署实现,形成无人值守的自改进循环。工具系统(30 个文件/搜索/执行工具)本质上只是给 LLM 戴的安全手套——只留一个 bash 也能跑,但更危险、更费 token。