先贴一个 tool call。模型发给我的:
{"name": "EditFile",
"arguments": {"path": "src/sds.c", "old_string": "len++", "new_string": "len += n"}}
我声明的参数叫 old_content。它填的是 old_string。
schema 写得明明白白,required 里我还写了三遍。模型没看。不是没看见——它压根不按 schema 调工具,它按训练轨迹里的形状调。它见过太多长这样的调用,照着敲了一遍。你那份 JSON Schema,对它是装饰。
所以 harness 干什么?它不给模型加智力。它在模型的先验和你机器上的现实之间垫一层,把模型敲出来的东西翻译成你真正要执行的东西。关于"harness 税"的所有争论,吵的都是这一层垫多厚。
垫得最厚的那位被数出来是 80KB。有人贴过,说 Claude Code 给模型的初始指令大约 80KB,里面跟安全相关的没几行。这个数我没自己数过,先当数量级的说法。方向是对的:那 80KB 装的是工具描述、输出格式要求、什么时候用哪个工具、什么时候别用。不是安全。
有人拿垃圾处理税打比方,说你拆了安全护栏还把它算进成本,等于把不安全当负外部性让别人扛。比方打得挺聪明。对象算错了。你付的不是安全的税,是工具表面积的税。这两件事本来能拆开——有人指出 Claude 的护栏不在系统提示里,在评估模型那边,独立一次调用,有时拒掉某个 tool call,这个机制可以很便宜。Claude Code 官方还允许 --system-prompt-file 把系统提示整个换掉。要是安全真装在提示词里,这个开关就是个自毁按钮。它不是,就说明装的根本不是安全。
那 80KB 是工具描述。二十几个工具,每个工具三四种说法,每种一段"什么时候用、什么时候别用"。模型每读一段,注意力摊薄一分。这笔税的真身在这儿。
现在说为什么我认为"harness 税"整个框架不成立。
有人做过完整基准。同一个模型、同一个 harness,在 deepinfra 上两边差异很小,在 together.ai 上差异很大——大到 Kilo Code 那套在那边几乎没法编辑文件,模型反复调工具,调不动就改走脚本执行。同一个模型,同一套 harness,换了个 provider,行为变了。
harness 不是独立变量。底下那层推理栈把 tool call 的格式洗了一遍,洗出来的东西你的 harness 不一定接得住。接不住就重试,重试就烧 token,烧完还失败,模型自己找条歪路——起个 shell,sed -i 上去。评论里"有时改走脚本执行",说的就是这个。
更妙的是,帖子里有人说,年初 Kilo Code 的报错信息里,系统直接讲模型吃力、建议换更聪明的模型。自己的工具格式没对齐,锅甩给模型。这条我看了两遍,想笑。
那位做对比的作者在自己的 harness 上跑通了。他没吹自己更聪明,他说的是:把工具刻意设计成开放权重模型容易正确调用的形状。翻译一下——他的工具格式和模型的先验对上了。就这么简单,也就这么难。
"差异被夸大了"这个结论,我同意一半。harness 之间确实没那么大差距,因为决定成败的是格式对不对得上,不是你的 harness 有几个功能。也有人说 Pi 开箱比 opencode 更容易犯蠢,多给点上下文就稳一点。这个观察我不反对,我反对从它推出"那就把上下文堆厚"。难的是加到正好,不是加到最多。
我那个兼容层,删过两次又加回来。现在长这样:
const char *old = json_get(args, "old");
if (!old) old = json_get(args, "old_string");
if (!old) old = json_get(args, "old_text");
if (!old) return tool_error("missing old content");
四条 if。每一条都是替某个版本的模型擦屁股。第二条给 Claude 系的轨迹,第三条给某个换过参数名的旧版。这才是税——不是 token 那笔,是维护那笔。每换一个模型版本,就得回来看看先验挪了没有。
所以我的判断:harness 里没有秘密酱汁,只有这个对齐问题。要么跟一个流行 harness 的格式一模一样——不是相似,是一样,参数名、大小写、必填项全一样。要么完全不一样,怪到模型认不出来,只能老老实实读你的 schema。最贵的是中间态:看起来像 Claude Code,于是模型按 Claude Code 的叫法来调,字段差一个下划线,报参数错误,然后它就放弃你的工具了。
"要么一样要么完全不同",这是我从帖子里抄的,抄完发现早该写下来。
再说小模型。80KB 系统提示对顶配模型是小菜,对 9B 就是把这辈子的上下文吃掉一半。工具描述一多,它连任务本身都记不住。有人说模型越小 harness 越重要——对,但重要的方式是给 harness 减东西,不是加。上下文、记忆,卸载给 harness;工具表,砍到四个。有人测出来四个工具够用,我信。我那张表也就这个数上下。
struct tool {
const char *name;
const char *schema;
int (*fn)(struct ctx *, const char *args);
};
这张表每加一行,就多一份要维护的 schema、多一段要喂给模型的描述、多一个模型可能用错的参数名。加第四个工具之前,先问它替我省过几次 tool call 失败。答不上来的,别加。
说句题外话,"harness"这个词现在被拿去指整个 agent,我看着难受。词被撑大了,讨论就没法收敛——你骂的是壳,对方答的是芯。拉回来。
最后一行规则。模型调不对你的工具,先别改提示词,先去看它训练轨迹里那个工具长什么样。对得上就照抄,抄不动就换得面目全非,别卡在中间。中间那一段的维护成本,比两头加起来都贵。