跳到主要内容

Harness 仿真:为什么、Harness 枚举、路由矩阵

30 秒导读: Open Interpreter 是 Codex 的 fork,想让一堆便宜的开源模型(DeepSeek、Kimi、Qwen…)也能好用。问题是这些模型不是按 Codex 的原生格式训练的,直接套上去表现很差。解法:给每个模型戴上它熟悉的那顶"帽子"——伪装成它见惯的那个 CLI(harness)。这一章讲清 harness 是什么、为什么要伪装、以及一次请求怎么被路由到对应的伪装方案。真正每种伪装改了哪些字节,留给 02-harness-shaping


1. 先搞懂:什么是 harness,为什么要"仿真"

一句话定义

harness(座架)= 一个 AI 编码工具对外呈现给模型的那套"外壳":系统提示 + 工具定义格式 + 走哪个 API 协议。 同一个模型,套上 claude-code 的外壳和套上 kimi-cli 的外壳,收到的 prompt 和工具 schema 是两份不同的东西

为什么要伪装成别的 harness

关键事实:开源/低价模型是在各自厂商 CLI 的提示与工具格式上做过对齐调优的。

  • Kimi 系模型见惯的是 kimi-cli 的工具名(ReadFile/Glob/Grep/StrReplaceFile…)和提示口吻;
  • DeepSeek 在 Anthropic 风格(claude-code)的塑形下表现最好;
  • Qwen 熟悉 qwen-code 的那套。

如果把这些模型直接塞进 Codex 的原生格式(Responses API + Codex 的工具定义),它们会"水土不服"——工具调用格式错、指令跟不住、效果掉一截。

所以 Open Interpreter 的策略很直接:

按模型,把请求塑形成它最熟悉的那个 harness 的样子。 模型以为自己还在 kimi-cli / claude-code 里跑,于是发挥出训练时的水平。

这一章讲的就是这套"伪装"的调度中枢:枚举有哪些 harness、一次请求怎么选中某个 harness、选中后走哪条传输路线。

这章在全景里的位置

模型 + provider 配置


①选 harness ──►②路由(WireApi × Harness)──►③按路线塑形请求 & 回译响应
(本章 §3) (本章 §4,核心) (本章 §5 只讲分发器)
具体每个 harness 干了啥 → 02 章

本章覆盖 ① ② 和 ③ 的入口分发器;不展开单个 harness 的请求体细节。


2. Harness 枚举:一共有哪些"外壳"

所有可选的外壳,集中定义在一个 Rust 枚举里(tools/src/harness.rs:1-20,Harness)——15 个具名变体 + 一个兜底的 Other(String):

变体配置名(config name)面向的模型/工具生态
Native"" / 缺省Codex 原生,不伪装
ClaudeCodeclaude-codeAnthropic Claude Code(全套)
ClaudeCodeBareclaude-code-bareClaude Code 精简版(DeepSeek 默认)
DeepSeekTuideepseek-tuiDeepSeek 官方 TUI
KimiCodekimi-codeKimi 的 code 变体
KimiClikimi-cliKimi CLI
ZCodezcode智谱 GLM 的 Z.ai code
LittleCoderlittle-coderlittle-coder
MiniSweAgentmini-swe-agentmini-swe-agent
OpenCodeopencodeOpenCode
PipiPi
QwenCodeqwen-code通义千问 qwen-code
SweAgentswe-agentSWE-agent
Terminus2terminus-2Terminus 2
Minimalminimal最小外壳(裸提示)
Other(String)任意其它字符串未识别名,原样保留

字符串 → 枚举from_config_name(tools/src/harness.rs:22-42)完成:精确匹配已知名字,None/空串落到 Native,不认识的字符串装进 Other(name)(不会报错,只是后面路由时按"通用"处理)。

配套还有一组 is_* 判定辅助(tools/src/harness.rs:44-98),比如 is_claude_code(把 ClaudeCodeClaudeCodeBare 都算 true)、is_kimi_cliis_zcode……供其它模块快速问"当前是不是某个 harness"。


3. 一个 harness 怎么被选中

选中顺序是"用户显式配置优先,否则按模型名自动猜"。

3.1 三个入口

入口位置说明
配置文件字段config/src/config_toml.rs:164 harness~/.codex/config.toml 里写 harness = "kimi-cli"
guidance 开关config/src/config_toml.rs:167 harness_guidance是否额外注入 Open Interpreter 的补充指令(见 §6)
TUI 交互tui/src/slash_command.rs:16,143 /harness运行时"choose the tool harness for the current model"

配置里的字符串最终经 Harness::from_config_name 落成枚举,喂进会话(core/src/session/session.rs:1136)。

3.2 没配就按模型名自动挑

如果用户没写 harness,default_harness_for_provider_model(model-provider-info/src/lib.rs:162)会看 provider id / provider 名 / base_url / 模型名 里的关键词,自动选一个默认外壳:

命中关键词(任一)自动选中
claude / anthropic / api.anthropic.com / wire=messagesclaude-code
kimi / moonshotkimi-cli
qwen / qwq / dashscopeqwen-code
deepseek / api.deepseek.comclaude-code-bare
都不命中None → 回落 Native

装配逻辑在 core/src/config/mod.rs:3589:cfg.harness 为空时才 .or_else(...) 用自动结果。注意一个"产品决策":DeepSeek 默认给 claude-code-bare(Anthropic 风格出效果最好),但 deepseek-tui 仍可通过 /harness 手动选。智谱 zcode 不在自动名单里,需手动指定。


4. 核心:WireApi × Harness → Route 路由矩阵

这是整章的重点。选中 harness 只是第一维;第二维是 provider 声明的"线协议"(WireApi)。两维叉乘,才决定这次请求真正走哪条传输+塑形路线。

4.1 第二维:WireApi 是什么

WireApi(model-provider-info/src/lib.rs:65)是 provider 对外说的 HTTP 协议,只有三种:

WireApi端点归属
Responses/v1/responsesOpenAI Responses API(Codex 原生)
Chat/v1/chat/completionsOpenAI 兼容 Chat Completions
Messages/v1/messagesAnthropic Messages

4.2 产物:StreamTransportRoute

路由函数 resolve_stream_transport_route(core/src/harness/routing.rs:56)吃 (WireApi, &Harness),吐一个 StreamTransportRoute(routing.rs:37-48)。这个枚举就是"这次请求怎么发"的最终决议:

Route 变体含义
ResponsesApi原生 Responses,不塑形
ChatCompletionsCompat通用 Chat 兼容转换,不做 harness 伪装
ChatHarness(ChatHarnessRoute)走某个 chat 系 harness 的专属塑形
MessagesHarness(MessagesHarnessRoute)走 Messages 系 harness 塑形
ClaudeCodeResponses(ClaudeCodeProfileRoute)claude-code 塑形驮在 Responses 线上
ClaudeCodeChat(ClaudeCodeProfileRoute)claude-code 塑形驮在 Chat 线上

三个"子路线"枚举做更细的分岔:

  • ChatHarnessRoute(routing.rs:22-35):DeepSeekTui / KimiCode / KimiCli / LittleCoder / MiniSweAgent / Minimal / OpenCode / Pi / QwenCode / SweAgent / Terminus2 —— 每个 chat 系 harness 一个分支。
  • MessagesHarnessRoute(routing.rs:5-9):只有 ClaudeCodeZCode 两个。
  • ClaudeCodeProfileRoute(routing.rs:16-20):Full vs Bare——claude-code 和 claude-code-bare 共用同一套塑形原语,只在系统提示和工具集上有别,由 claude_code_profile_route(routing.rs:156)把 ClaudeCodeBare 映射成 Bare、其余为 Full

4.3 完整矩阵(核心表)

怎么读: 行是 harness,列是 provider 的 WireApi;格子里是最终 Route。 = 直接报错(CodexErr::InvalidRequest)。

Harness \ WireApiResponsesChatMessages
ClaudeCodeClaudeCodeResponses(Full)ClaudeCodeChat(Full)MessagesHarness(ClaudeCode)
ClaudeCodeBareClaudeCodeResponses(Bare)ClaudeCodeChat(Bare)MessagesHarness(ClaudeCode)
ZCodeResponsesApiChatCompletionsCompatMessagesHarness(ZCode)
KimiCliResponsesApiChatHarness(KimiCli)
KimiCodeResponsesApiChatHarness(KimiCode)
DeepSeekTuiResponsesApiChatHarness(DeepSeekTui)
LittleCoderResponsesApiChatHarness(LittleCoder)
MiniSweAgentResponsesApiChatHarness(MiniSweAgent)
OpenCodeResponsesApiChatHarness(OpenCode)
PiResponsesApiChatHarness(Pi)
QwenCodeResponsesApiChatHarness(QwenCode)
SweAgentResponsesApiChatHarness(SweAgent)
Terminus2ResponsesApiChatHarness(Terminus2)
MinimalResponsesApiChatHarness(Minimal)
NativeResponsesApiChatCompletionsCompat
Other(name)ResponsesApiChatCompletionsCompat

4.4 从矩阵读出的三条规律

规律一:Responses 列几乎"透传"。 除了 claude-code 会驮塑形(ClaudeCodeResponses),其余所有 harness 在 Responses 线上都退化成原生 ResponsesApi——因为 Responses 本就是 Codex 母体协议,大多数伪装用不上。

规律二:Chat 列是伪装主战场。 十一个 chat 系 harness 各有专属 ChatHarness(...) 分支;claude-code 走 ClaudeCodeChat;而 Native/ZCode/Other 这些"没有 chat 专属塑形"的,统一落到兜底的 ChatCompletionsCompat(routing.rs:99(WireApi::Chat, _) 通配臂)。

规律三:Messages 线是"禁区",只放行两家。 Messages wire 只有 claude-code(含 bare)和 zcode 支持,其余一律报错(routing.rs:108-150)。错误信息还分两类:

  • 具名 harness(如 kimi-cli):wire_api = "messages" is not supported by harness = "kimi-cli";
  • Native 特别提示要去配 claude-code:wire_api = "messages" requires a harness-native transport; configure harness = "claude-code" or "claude-code-bare" ...

道理是:Messages 是 Anthropic 独有的线协议,只有为 Anthropic 风格准备了塑形原语的 harness 才接得住。

4.5 这张矩阵在哪被消费

ModelClient 在两处调 resolve_stream_transport_route(core/src/client.rs:978:1037),把决议缓存成 stream_transport_route。真正发请求时,stream()(client.rs:2751-2847)对着 StreamTransportRoutematch,六个变体分别落到六个不同的 stream_* 方法:

StreamTransportRoute::ResponsesApi → stream_responses_api / websocket
::ChatCompletionsCompat → stream_chat_completions_compat
::ChatHarness(route) → stream_chat_harness_api(route, …)
::MessagesHarness(route)→ stream_messages_harness_api(route, …)
::ClaudeCodeResponses(p)→ stream_claude_code_responses_api(p, …)
::ClaudeCodeChat(p) → stream_claude_code_chat_api(p, …)

其中 supports_responses_websocket(routing.rs:50-54)只对 ResponsesApi 为真——只有原生 Responses 才可能升级到 WebSocket,伪装路线一律走 HTTP。


5. Chat harness 的统一入口(分发器)

ChatHarness(route) 这一大类,不在 client.rs 里逐个手写,而是收敛到一个分发模块 core/src/harness/request.rs。它是"塑形"的唯一集散地——加一个 chat harness = 在这里加一条 route 臂,不用改 client。

5.1 三个数据结构

类型位置角色
ChatHarnessTurn<'a>request.rs:43输入:prompt、harness、guidance 开关、model_info、effort、thread_id…
ChatHarnessRequestrequest.rs:54输出:塑好的 request_bodytool_kinds、可选 title_requestpostprocess
ChatHarnessPostprocessrequest.rs:63回译动作:None / MiniSweAgent / SweAgent / Terminus2

5.2 一次分发怎么走

build_chat_harness_request(request.rs:75)是入口:

  1. 注入 guidance:先过 prompt_with_harness_guidance(§6);
  2. 探测 yolo:看 base 指令里有没有 "Approval policy is currently never.",得出 yolo_mode;
  3. 按 route 分发:一个大 match route,每个 ChatHarnessRoute 分支调对应 harness 模块的 build_*_request(如 build_kimi_cli_requestbuild_qwen_code_request),拿回 (request_body, tool_kinds),并挑一个 postprocess;
  4. 返回 ChatHarnessRequest

client 在 client.rs:1742 调它,随后对响应流套 apply_chat_harness_postprocess(request.rs:214client.rs:1802)——把 mini-swe-agent / swe-agent / terminus-2 这些"模型不是用 tool_call 而是用文本动作块"的输出回译成标准工具调用事件。

本章到此为止: build_*_request 每个具体 harness 到底改了系统提示的哪句、工具 schema 的哪个字段、响应怎么回译——全部留给 02-harness-shaping。这里只需记住:request.rs 是分发器,不是塑形逻辑本身。


6. guidance 注入:给某些 harness 额外加一段"军规"

有些模型光靠伪装外壳还不够稳,Open Interpreter 会再叠一段补充指令,前置拼到系统提示最前面。

  • 总开关:harness_guidance(config/src/config_toml.rs:167),字段透传进 ChatHarnessTurn.harness_guidance
  • 注入函数:prompt_with_harness_guidance(request.rs:233)。开关关 → 原样返回;开关开且该 harness 有 guidance → clone prompt 并 format!("{guidance}\n\n{原文}") 前置。
  • 选哪段:guidance_for_harness(core/src/harness/guidance.rs:3)。目前只有 KimiCliKimiCode 返回非空,内容是常量 KIMI_CLI_GUIDANCE(guidance.rs:23);其余 harness 全返回 None

KIMI_CLI_GUIDANCE 是一段 <extra_instruction>,内容是编码可靠性军规,例如:多步任务早用 SetTodoList、文件操作用专用工具(ReadFile/Glob/Grep/WriteFile/StrReplaceFile)而非 shell、把数据集当只读、失败时读真实报错再改、两次修复失败就停止乱猜等。本质是给 Kimi 补一层"怎么用好这些工具"的操作纪律。


7. 边界与坑

  • Messages 线极窄。 只有 claude-code / claude-code-bare / zcode 三者能用;给别的 harness 配 wire_api = "messages" 会直接 InvalidRequest(§4.4 规律三)。
  • Other(name) 不是伪装,是兜底。 未识别的 harness 名不会伪装成任何东西:Responses→原生、Chat→通用 compat、Messages→报错。
  • guidance 目前只服务 Kimi。 guidance_for_harness 里其余 harness 都是 None——别指望配了 harness_guidance=true 就给所有模型加料。
  • claude-code 的 Full/Bare 只差提示与工具集。 传输路线相同,分岔只在 ClaudeCodeProfileRoute;bare 是 DeepSeek 的自动默认。
  • profile 映射是"非 bare 即 full"。 claude_code_profile_route(routing.rs:156)只对 ClaudeCodeBare 返回 Bare,理论上只该被 claude-code 两个变体调用。

8. 代码地图(导航索引)

主题文件路径符号名
Harness 枚举tools/src/harness.rs:1Harness
字符串→枚举tools/src/harness.rs:22from_config_name
harness 判定辅助tools/src/harness.rs:44is_claude_code / is_kimi_cli / is_zcode
配置字段config/src/config_toml.rs:164ConfigToml.harness / harness_guidance
按模型自动选默认model-provider-info/src/lib.rs:162default_harness_for_provider_model
默认值装配core/src/config/mod.rs:3589(harness .or_else 回落)
线协议枚举model-provider-info/src/lib.rs:65WireApi(Responses/Chat/Messages)
路由函数(核心)core/src/harness/routing.rs:56resolve_stream_transport_route
最终传输决议core/src/harness/routing.rs:37StreamTransportRoute
chat 子路线core/src/harness/routing.rs:22ChatHarnessRoute
messages 子路线core/src/harness/routing.rs:5MessagesHarnessRoute
claude-code Full/Barecore/src/harness/routing.rs:16,156ClaudeCodeProfileRoute / claude_code_profile_route
路由消费/分发core/src/client.rs:978,2751resolve_stream_transport_route / stream match
chat harness 分发器core/src/harness/request.rs:75build_chat_harness_request
turn/request/postprocesscore/src/harness/request.rs:43,54,63ChatHarnessTurn / ChatHarnessRequest / ChatHarnessPostprocess
响应回译入口core/src/harness/request.rs:214apply_chat_harness_postprocess
guidance 注入core/src/harness/request.rs:233prompt_with_harness_guidance
guidance 选择core/src/harness/guidance.rs:3,23guidance_for_harness / KIMI_CLI_GUIDANCE
TUI 切换tui/src/slash_command.rs:16SlashCommand::Harness(/harness)

下一章: 02-harness-shaping —— 一个 harness 到底改了什么(请求塑形与响应回译);底座怎么跑见 03-turn-loop-and-client