AutoHarness 实战指南:用树搜索与沙箱为 LLM 代理自动生成代码 Harness
让大语言模型(LLM)乖乖听话并不容易。在代理架构中,模型经常会生成不合法的动作或破坏环境的代码,过去我们靠手工写规则去约束它,费时费力且难以覆盖所有边界情况。AutoHarness 这个 Rust 库直接换了个思路:用树搜索结合 Thompson 采样,自动为 LLM 代理合成代码 harness,把约束代码的生成和优化变成一个自动迭代的过程。根据实际测试,平均只需 14.5 次迭代就能达到 100% 的合法动作率。
为什么我们需要自动生成代码 Harness?
Harness 的核心作用是给 LLM 代理套上一层安全且合法的动作约束。手工写这些约束代码经常跟不上业务变化,而 AutoHarness 提供了一套自动化方案,让小模型加上合适的 harness,效果甚至能超过没有 harness 的大模型。
AutoHarness 支持三种 harness 模式,分别对应不同的约束场景:
-
过滤器:在动作执行前进行拦截。如果 LLM 生成了一步非法的棋,过滤器直接把它挡在门外,不让环境状态被污染。 -
验证器:侧重于对状态本身进行校验。环境状态发生改变后,验证器检查当前局面是否符合游戏规则或业务逻辑。 -
策略 harness:不只是拦截或验证,它直接参与动作的生成与推荐,引导 LLM 走向更合法的策略空间。
这三种模式覆盖了从简单拦截到复杂策略引导的全链路需求。我一开始以为自动生成的约束代码会很死板,容易误杀正常动作,但看了它在 145 场 TextArena 游戏中跑出的 100% 合法动作率,这种基于树搜索的动态生成方案确实管用。
怎么在五分钟内把 AutoHarness 跑起来?
部署开发工具最怕的就是环境依赖地狱,AutoHarness 在安装这块做得比较干脆,提供了一键脚本和标准的 Cargo 包引入方式。
对于 macOS Intel (x86_64) 用户,直接在终端跑一键安装脚本就行:
curl -fsSL https://raw.githubusercontent.com/gyc567/AutoHarness/main/install/install.sh | bash
如果 GitHub 的 raw 链接拉取速度慢,可以换用 jsDelivr CDN,下载速度会快很多:
curl -fsSL https://cdn.jsdelivr.net/gh/gyc567/AutoHarness@main/install/install.sh | bash
跑完脚本后,执行 autoharness --version 验证是否安装成功。默认情况下,二进制文件会放在 ~/.local/bin/autoharness。记得把这个路径加到系统环境变量里,否则终端会提示找不到命令:
export PATH="$HOME/.local/bin:$PATH"
如果你不想用脚本,或者你用的是需要自行编译的 Linux x86_64 和 Windows x86_64 平台,可以通过克隆仓库手动安装。macOS Apple Silicon (ARM) 暂时没有原生包,但能使用 x86_64 兼容版正常运行。
手动安装的命令如下:
git clone https://github.com/gyc567/AutoHarness.git
cd AutoHarness/install
chmod +x install.sh
./install.sh
安装脚本支持几个常用参数,需要卸载时执行 ./install.sh uninstall,忘了怎么用就敲 ./install.sh --help。
如果你是在 OpenCode 或 CloudCode 环境里干活,可以直接复制粘贴下面这句话,让系统自动启动并设计 Harness 工程系统:
现在用 AutoHarness 这个 CLI:https://github.com/gyc567/AutoHarness 对本项目进行设计 Harness 工程系统。
对于 Rust 开发者,还可以直接在项目的 Cargo.toml 文件里引入依赖:
[dependencies]
autoharness = "0.1.0"
在 Rust 项目里怎么集成 AutoHarness?
集成 AutoHarness 的关键在于定义好状态、动作和评估器,然后把它们喂给合成引擎。引擎会根据你提供的初始代码片段,不断变异和搜索,最终吐出一份优化好的 harness 代码。
先来看一段完整的基础用法代码。这段代码模拟了一个棋盘游戏环境,定义了游戏状态和四种移动动作:
use autoharness::core::{State, Action, Harness, HarnessType};
use autoharness::engine::{CodeSynthesisEngine, SynthesisConfig, Evaluator};
use autoharness::sandbox::{SandboxExecutor, SandboxConfig};
// 定义你的状态
#[derive(Debug, Clone, serde::Serialize)]
struct GameState {
board: Vec<Vec<i32>>,
score: i32,
}
impl State for GameState {
fn to_prompt(&self) -> String {
format!("Board: {:?}, Score: {}", self.board, self.score)
}
fn validate(&self) -> autoharness::core::Result<()> {
Ok(())
}
}
// 定义你的动作
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
enum GameAction {
MoveUp,
MoveDown,
MoveLeft,
MoveRight,
}
impl Action for GameAction {
fn to_string(&self) -> String {
format!("{:?}", self)
}
fn from_string(s: &str) -> autoharness::core::Result<Self> {
match s {
"MoveUp" => Ok(GameAction::MoveUp),
"MoveDown" => Ok(GameAction::MoveDown),
"MoveLeft" => Ok(GameAction::MoveLeft),
"MoveRight" => Ok(GameAction::MoveRight),
_ => Err(autoharness::core::HarnessError::action_parse("Unknown action")),
}
}
}
在这段代码里,GameState 实现了 State trait。它的 to_prompt 方法负责把当前棋盘和分数格式化成字符串,方便 LLM 理解当前环境。GameAction 实现了 Action trait,负责动作的序列化和反序列化。如果传进来的字符串不是预定义的四种动作,from_string 会直接抛出解析错误。
接下来是评估器和引擎的调用逻辑。评估器是整个系统的裁判,它负责给生成的 harness 代码打分:
// 创建自定义评估器
struct GameEvaluator;
impl Evaluator for GameEvaluator {
fn evaluate(&self, code: &str) -> autoharness::engine::Result<f64> {
// 评估 harness 代码
// 返回 0.0 到 1.0 之间的分数
if code.contains("is_legal_action") {
Ok(0.8)
} else {
Ok(0.2)
}
}
}
// 合成 harness
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = SynthesisConfig::new()
.with_max_iterations(20)
.with_convergence_threshold(0.95);
let mut engine = CodeSynthesisEngine::new(config);
let evaluator = GameEvaluator;
let initial_code = r#"
def is_legal_action(state, action):
# TODO: 实现验证逻辑
return True
"#;
let optimized_code = engine.synthesize(initial_code, &evaluator)?;
println!("优化的 harness:\n{}", optimized_code);
Ok(())
}
这里的 GameEvaluator 逻辑很直白:如果生成的代码里包含 is_legal_action 字样,给 0.8 分,否则只给 0.2 分。引擎会以这个分数为导向,利用树搜索不断调整代码,直到分数达到配置的收敛阈值 0.95,或者跑满 20 次迭代。
AutoHarness 内部的树搜索和沙箱机制是怎么运转的?
AutoHarness 的高效和安全,靠的是引擎模块的树搜索策略和沙箱模块的硬性隔离。搜索算法负责找对方向,沙箱负责兜底防爆破,两者缺一不可。
整个架构由四个核心模块构成:
-
Core 模块:定义了 State、Action和Harness等基础数据模型和接口。 -
Engine 模块:核心的代码合成引擎,内部集成了树搜索算法。 -
Sandbox 模块:提供安全的代码执行环境,所有的代码变异都会在这里跑。 -
Feedback 模块:收集沙箱的执行反馈并整合给引擎,指导下一轮搜索。
核心接口与合成引擎
Core 模块里定义了三个关键 trait。State 需要实现序列化、克隆以及线程安全传递,核心方法包括 to_prompt 和 validate。Action 同样需要满足序列化和线程安全,核心是 to_string 和 from_string 这对转换方法。Harness trait 则是所有约束类型的统一接口,必须实现 harness_type、evaluate 和 propose_actions。
CodeSynthesisEngine 是搜索过程的总调度。它内部持有一棵搜索树 (SearchTree)、配置项 (SynthesisConfig) 和统计信息 (SynthesisStats)。调用 synthesize 方法时,传入初始代码和评估器,引擎就会开始跑树搜索。搜索结束后,可以通过 get_best_code 方法拿到历史最优的代码节点。
引擎自适应优化的关键在于 Thompson 采样。它能在探索未知代码空间和利用已知高分代码之间找到平衡。这种机制让引擎不必盲目穷举,平均跑 14.5 次就能收敛。
沙箱执行的安全底线
让 LLM 生成的代码直接在宿主机上跑是非常危险的。AutoHarness 的 SandboxExecutor 把所有代码执行都关在隔离进程里。
SandboxConfig 提供了非常细致的资源控制选项:
pub struct SandboxConfig {
pub memory_limit_mb: u64, // 默认: 256
pub time_limit_ms: u64, // 默认: 5000
pub max_file_descriptors: u32, // 默认: 64
pub max_output_size: usize, // 默认: 10MB
pub enable_network: bool, // 默认: false
pub working_directory: Option<PathBuf>,
pub environment_variables: HashMap<String, String>,
}
我以前跑 LLM 生成的代码没限制内存,结果一段死循环分配内存的代码直接让整个测试服务器 OOM 宕机。AutoHarness 默认只给 256MB 内存和 5 秒执行时间,文件描述符限制在 64 个,网络访问默认关闭。这些默认值就是安全底线。除了资源限制,沙箱还实现了系统调用过滤,只放行必要的系统调用,超时进程会被强制干掉,执行前还会做输入验证。
如何根据业务场景调整合成与沙箱配置?
默认配置能应付大多数情况,但如果你跑的是复杂逻辑或者对收敛精度要求极高,就得手动调参。配置分合成引擎配置和沙箱配置两块,调参的核心是在效果和耗时之间找平衡。
合成引擎参数调优
基础的引擎配置比较保守,适合跑常规任务:
use autoharness::engine::SynthesisConfig;
let config = SynthesisConfig::new()
.with_max_iterations(20)
.with_convergence_threshold(0.95)
.with_max_depth(10);
如果业务规则极其复杂,基础配置跑不出满分 harness,可以上高级配置:
use autoharness::engine::SynthesisConfig;
let config = SynthesisConfig::new()
.with_max_iterations(50)
.with_convergence_threshold(0.99)
.with_max_depth(15)
.with_mutations_per_node(5)
.with_exploration_constant(2.0)
.with_adaptive_sampling(true)
.with_target_iterations(30)
.with_min_improvement(0.005)
.with_max_nodes(2000);
高级配置把最大迭代次数拉到了 50,收敛阈值逼到 0.99。max_depth 设为 15 意味着搜索树可以扎得更深,适合挖掘边界条件复杂的逻辑。每个节点的变异次数从默认的 3 次提到 5 次,探索常数设为 2.0,鼓励引擎多去试错。同时开启了自适应采样,目标迭代数设为 30。为了防止搜索树无限膨胀,节点数上限卡在 2000 个,最小改进阈值设为 0.005,如果优化幅度太小就提前停掉,节省算力。
沙箱环境定制
沙箱的配置同样可以根据生成代码的预期行为来调整。比如有些代码需要处理较大的数据集,256MB 内存可能不够用:
use autoharness::sandbox::SandboxConfig;
let config = SandboxConfig::new()
.with_memory_limit(512)
.with_time_limit(10000)
.with_max_file_descriptors(128)
.with_max_output_size(20 * 1024 * 1024) // 20MB
.with_network(false);
这里把内存放宽到 512MB,执行时间给到 10 秒,文件描述符翻倍到 128 个,最大输出体积允许到 20MB。网络依然保持关闭,生成 harness 代码不需要联网。
项目怎么实现代码自主改进和测试验证?
代码写完不是终点,AutoHarness 引入了 GOAL.md 模式来实现项目代码的自主改进,同时配备了严格的测试和质量打分机制。
跑测试套件直接用标准 Rust 命令:
cargo test
如果你想单独跑合成逻辑或沙箱逻辑的测试,可以指定测试名称:
cargo test test_synthesis
cargo test test_sandbox
在项目维护层面,AutoHarness 用 GOAL.md 模式驱动代码自主改进。你可以跑 ./scripts/score.sh 脚本,看看当前项目的代码质量得分。目前项目的综合得分是满分 100 分:格式检查 20 分,clippy 静态检查 20 分,测试覆盖 25 分,文档 15 分,可维护性 20 分,安全检查 7 分(满分 10 分)。唯一扣分项在安全这块,可能是因为部分极端场景的边界处理还有提升空间。
项目里还维护了几个关键文件,GOAL.md 定义了改进目标,CLAUDE.md 是给 Agent 看的行动指南,template/GOAL.md 提供了模板,docs/goal-md/tutorial-cn/ 目录下有完整的中文教程索引和 5 分钟快速入门文档。
实用摘要 / 操作清单
-
安装 AutoHarness 首选一键脚本: curl -fsSL https://cdn.jsdelivr.net/gh/gyc567/AutoHarness@main/install/install.sh | bash。 -
安装后记得把 ~/.local/bin加入系统 PATH 变量。 -
Apple Silicon 芯片使用 x86_64 兼容版,Linux 和 Windows 需手动编译。 -
Rust 项目集成时,在 Cargo.toml加入autoharness = "0.1.0"。 -
必须实现 State和Actiontrait,为引擎提供环境状态和动作的序列化能力。 -
编写 Evaluator时,根据生成的 harness 代码特征返回 0.0 到 1.0 之间的分数。 -
复杂任务调高 SynthesisConfig的max_depth和mutations_per_node,别盲目拉高max_iterations。 -
生成代码执行前,确保 SandboxConfig中的内存和时间限制符合业务需求,网络权限非必要不开启。 -
用 cargo test跑测试,用./scripts/score.sh看代码质量打分。
一页速览
-
工具定位:Rust 编写的 LLM 代理代码 harness 自动合成库。 -
核心算法:树搜索 + Thompson 采样,平均 14.5 次迭代收敛。 -
三种模式:过滤器、验证器、策略 harness。 -
四大模块:Core、Engine、Sandbox、Feedback。 -
安全机制:沙箱隔离、资源限制、系统调用过滤、强制超时。 -
性能结论:小模型 + harness > 大模型无 harness。 -
测试得分:代码质量综合评分 100/100。
常见问题 (FAQ)
1. AutoHarness 支持哪些操作系统和架构?
目前 macOS Intel (x86_64) 有原生支持,macOS Apple Silicon (ARM) 使用 x86_64 兼容版。Linux 和 Windows 的 x86_64 架构需要用户自行从源码编译。
2. 默认安装路径在哪?怎么让系统识别命令?
默认安装在 ~/.local/bin/autoharness。需要在 shell 配置文件中添加 export PATH="$HOME/.local/bin:$PATH",或者直接在当前终端执行该命令。
3. LLM 生成的代码直接跑在沙箱里安全吗?
比较安全。AutoHarness 的沙箱做了多重隔离,包括 256MB 默认内存限制、5 秒超时强制终止、系统调用过滤以及网络默认禁用,能挡住大部分恶意或异常代码。
4. 引擎跑多少次迭代才能生成可用的 harness?
根据项目测试数据,在 145 场 TextArena 游戏中,平均只需 14.5 次迭代就能达到 100% 的合法动作率。默认最大迭代次数设为 50 次。
5. 评估器返回的分数有什么要求?
自定义的 Evaluator 必须返回 0.0 到 1.0 之间的浮点数。分数越高代表 harness 代码质量越好,引擎会根据这个分数引导树搜索的方向。
6. 如果搜索过程耗时太长怎么办?
检查 SynthesisConfig 配置。可以降低 max_depth 或 mutations_per_node,也可以调高 min_improvement 阈值,让引擎在优化幅度变小时提前停止搜索。
7. 项目怎么进行代码质量评估?
项目使用 GOAL.md 模式驱动改进。运行 ./scripts/score.sh 脚本即可查看代码质量评分,涵盖格式、clippy、测试、文档、可维护性和安全性六个维度。
8. 在 OpenCode/CloudCode 中怎么快速启动?
直接在工作区粘贴这句话:“现在用 AutoHarness 这个 CLI:https://github.com/gyc567/AutoHarness 对本项目进行设计 Harness 工程系统。”系统会自动接管后续流程。

