跳到主要内容

一个 harness 到底改了什么:请求塑形与响应回译

30 秒导读: 上一章讲了"为什么要仿真、有哪些 harness、怎么路由"。这一章钻进单个 harness 内部,回答两个具体问题:(1)build_request 到底把一次请求塑成了什么样(换了什么系统提示、工具怎么编码);(2)那些没有原生 function-calling 的 harness,怎么把模型吐出来的一段纯文本,回译成引擎能执行的工具调用

前置阅读:Harness 仿真:为什么、枚举、路由矩阵 讲清了 harness 是什么、resolve_stream_transport_route 怎么选路;本章假设你已经知道"每个开源工具对应一个 harness 枚举值"。


1. 先建立直觉:harness 是请求的"塑形层 + 回译层"

一句话:Codex 引擎只认自己的内部格式(ResponseItem 历史 + ResponseEvent 事件流);harness 就是夹在引擎和真实模型 API 之间的一对翻译器。

┌──────────────── 引擎(继承自 Codex,见第 3 章)────────────────┐
│ Prompt{ input: Vec<ResponseItem>, tools: Vec<ToolSpec>, ... } │
└───────────────────────────┬───────────────────────────────────┘

build_request │ ← 塑形:换系统提示、压平历史、编码工具

某工具原生的 HTTP 请求体

(发给真实模型)

模型返回 SSE 流 / 纯文本

postprocess / chat-wire-compat ← 回译:纯文本 → 合成工具调用


引擎认得的 ResponseEvent 事件流

两个方向各有一个承重函数:

方向干什么入口
塑形(下行)Prompt 变成某工具的原生请求体各 harness 的 build_request
回译(上行)把模型输出变回 ResponseEventpostprocess 闭包 或 chat-wire-compat

本章就沿这两个方向,由浅入深挑代表 harness 拆开看。


2. 塑形三件套:任何 build_request 都在改的三样东西

不管哪个 harness,build_request 无非动这三处。先看最简minimal harness,把三件套一次看全。

2.1 换系统提示

minimal 直接用一段硬编码常量当系统提示,而不是引擎默认的那套:

// codex-rs/core/src/harness/minimal.rs:20 MINIMAL_SYSTEM_PROMPT
const MINIMAL_SYSTEM_PROMPT: &str = "You are an expert software engineer working in the user's current directory.\n...";

build_request 把它塞成第一条 system 消息,后面才接历史(codex-rs/core/src/harness/minimal.rs:22 build_request → 27 行起的 messages)。

2.2 把 ResponseItem 历史压平成 chat messages

引擎的历史是 Vec<ResponseItem>(带 FunctionCall、Reasoning、各种 Output 的富结构)。OpenAI chat-completions 只认 role/content/tool_calls/tool_call_id 这几种扁平消息。build_messages 就是这台压平机:

  • assistant 文本 → {role:"assistant", content};
  • FunctionCall / CustomToolCall / LocalShellCall → 攒进 pending_tool_calls,遇到下一条 user/输出时 flush 成一条带 tool_calls 的 assistant 消息;
  • FunctionCallOutput{role:"tool", tool_call_id, content};
  • Reasoning → 攒进 pending_reasoning_content,附到下一条 assistant 消息的 reasoning_content 字段。

依据:codex-rs/core/src/harness/minimal.rs:62 build_messages(match 分支 70-234);攒/刷的关键在 codex-rs/core/src/harness/minimal.rs:270 flush_pending_tool_calls

为什么要"攒—刷"而不是逐条转? 因为一条 chat assistant 消息可以同时带文本 + 多个 tool_calls,而引擎历史里它们是分开的多个 ResponseItem。攒起来一次刷,才能拼回 chat 协议要求的形状。

2.3 把 Prompt.tools 转成 OpenAI function schema

引擎给的是 Vec<ToolSpec>;chat 要的是 {"type":"function","function":{name,description,parameters}} 数组。build_tools 做这一步直译:

// codex-rs/core/src/harness/minimal.rs:246 build_tools(简化引用)
converted.push(json!({
"type": "function",
"function": { "name": name, "description": description, "parameters": parameters }
}));

同时 build_request 顺手建一张 ToolKinds 表(工具名 → ToolOutputKind::Function),这张表是回译时要用的(见 §6),下行时先记下来。依据:codex-rs/core/src/harness/minimal.rs:33-37

2.4 顺带:模型能力开关(ThinkingToggle)

minimal 还演示了一个"按模型能力改请求"的小细节——如果模型的 reasoning_controlThinkingToggle,就注入一个 thinking 字段:

// codex-rs/core/src/harness/minimal.rs:49 build_request 尾部
if model_info.reasoning_control == ReasoningControl::ThinkingToggle {
request["thinking"] = json!({ "type": if none_effort { "disabled" } else { "enabled" } });
}

记住 minimal 这三件套(换提示 / 压平历史 / 编码工具),下面的 harness 都是它的变体。


3. 变体一:系统提示与工具从外部文件来

minimal 的提示是硬编码短句;真实工具的提示动辄上千行、工具几十个。这类 harness 把提示和工具外置成文件,用 include_str! 编进二进制,再在 build_request 里渲染。

三个代表:

harness系统提示来源工具清单来源
kimi-clikimi_cli_prompt.md(模板,运行时填变量)build_toolsPrompt.tools
qwen-codeqwen_code_prompt.md复用 kimi_cli::build_tools
zcode硬编码 header + harness 段 + 环境拼接zcode_tools.json(整份外部 JSON)

3.1 kimi-cli:提示是模板,工具是原生 schema

// codex-rs/core/src/harness/kimi_cli.rs:30
const KIMI_CLI_SYSTEM_PROMPT_TEMPLATE: &str = include_str!("kimi_cli_prompt.md");

build_requestbuild_system_prompt 把模板渲染成真提示(填 cwd、日期等,codex-rs/core/src/harness/kimi_cli.rs:37,渲染点在 144 行),再走和 minimal 一样的"压平历史 + build_tools + ToolKinds=Function"套路。工具仍是原生 OpenAI function schema,所以不需要 postprocess(模型原生会 function-calling)。

3.2 qwen-code:连工具构造都直接复用 kimi

qwen-code 是"换个提示文件、其余照抄"的极致:

// codex-rs/core/src/harness/qwen_code.rs:12
const QWEN_CODE_SYSTEM_PROMPT: &str = include_str!("qwen_code_prompt.md");
// :33 工具:
let tools = super::kimi_cli::build_tools(&prompt.tools)?;
// :121 历史:
for mut message in super::kimi_cli::build_messages(items)? { ... }

这就是货架的复用红利:塑形逻辑抽成 build_tools/build_messages 后,一个新工具的 harness 常常只需换一份 .md

3.3 zcode:整份工具清单外置成 JSON,且走 Anthropic Messages

zcode 更进一步——它模仿的是一个 Anthropic 系工具,所以:

  • 工具清单是整份 JSON 文件,不是从 Prompt.tools 转的:
    // codex-rs/core/src/harness/zcode.rs:38
    const ZCODE_TOOLS: &str = include_str!("zcode_tools.json");
    // :1186 build_tools 就是 serde_json::from_str(ZCODE_TOOLS)
  • 系统提示是分块拼的:一个 header、一段 ZCODE_SYSTEM_HARNESS(讲"你在什么终端里")、一段运行时环境串,每块单独一个 AnthropicTextBlock(codex-rs/core/src/harness/zcode.rs:112 build_requestsystem: 数组,122-133)。
  • 产物是 AnthropicMessageRequest,不是 chat 的 Value——它走 Messages wire(见 §5 的路由)。

zcode 因为走 Anthropic 原生 tool-use,同样不需要 postprocess


4. 变体二:没有原生 function-calling —— 文本编码工具调用 + postprocess 回译

这是本章的重头戏。有些工具(SWE-bench 系的 swe-agent / mini-swe-agent、终端基准 terminus-2)训练时根本不用 function-calling,而是让模型在正文里用约定格式写命令。于是塑形层要做两件反常的事:

  1. 下行:build_request 不给 tools(ToolKinds::new() 空表);
  2. 上行:模型吐回一段纯文本,得由 postprocess 把里面的"伪工具调用"解析出来、合成真的 ResponseItem 注回事件流。

这层上行改写靠 ChatHarnessPostprocess 枚举挂载:

// codex-rs/core/src/harness/request.rs:63 ChatHarnessPostprocess
pub(crate) enum ChatHarnessPostprocess {
None,
MiniSweAgentNoToolCallFormatError,
SweAgentActionCalls { has_submit_review: bool },
Terminus2ActionCalls { request_kind: Terminus2RequestKind, pending_completion: bool },
}

build_chat_harness_request(codex-rs/core/src/harness/request.rs:75)在塑形时就决定挂哪种;apply_chat_harness_postprocess(:214)在响应流上套对应的改写器。下面逐个看。

4.1 swe-agent:解析伪 XML / 围栏命令 → 合成 shell 调用

塑形侧极简:一句硬编码 system + 无 tools。

// codex-rs/core/src/harness/swe_agent.rs:20 SWE_AGENT_SYSTEM_PROMPT
const SWE_AGENT_SYSTEM_PROMPT: &str = "You are a helpful assistant that can interact with a computer to solve tasks.";
// :29 build_request → 返回 (request, ToolKinds::new()) ← 空工具表

回译侧是核心。inject_action_calls(codex-rs/core/src/harness/swe_agent.rs:49)在流上再挂一个 tokio 任务:每看到一条 assistant Message,就把它的正文喂给 build_harness_follow_up_item,若解析出命令,就追加一条合成的工具调用事件到流里。

怎么读一次改写(从左到右,命中即停):

assistant 纯文本
│ extract_bash_action() —— 依次尝试三种编码

① <tool_invoke><name>execute_bash</name><code>…</code> (伪 XML)
② <invoke name="bash"><parameter name="command">… (另一种伪 XML)
③ ```bash … ``` (markdown 围栏)
│ 命中 → normalize_action_command()

是 str_replace_editor / submit ?
├─ 是 → CustomToolCall(swe_agent_command)
└─ 否 → LocalShellCall(bash -lc "<命令>") ← 合成的工具调用

全没命中 → 注入一条 user 纠错消息(SWE_AGENT_FORMAT_CORRECTION)

依据:extract_bash_action codex-rs/core/src/harness/swe_agent.rs:395(三级 fallback 在 399-498);合成 shell 调用 build_shell_call :272;纠错分支 build_harness_follow_up_item :237

纠错文本本身要求模型用固定格式,内容里含围栏:

// codex-rs/core/src/harness/swe_agent.rs:26 SWE_AGENT_FORMAT_CORRECTION(节选)
Your output was not formatted correctly. ...
DISCUSSION
...
```
command(s) that you're going to run
```

一个巧妙细节:submit 二次确认。 postprocess 记着 has_submit_review 标志(来自 prompt_has_submit_review,codex-rs/core/src/harness/swe_agent.rs:85——它扫历史里有没有 "Run the submit command again to confirm.")。若已在等确认、且这次命令又是 submit,build_harness_follow_up_item不合成调用(:239),避免把"确认提交"再当成一次新执行。

4.2 mini-swe-agent:反过来 —— 有 tools,但强制"每轮必须调工具"

mini-swe-agent 有意思:它 function tools(原生 schema,build_requestcodex-rs/core/src/harness/mini_swe_agent.rs:122),但它的基准要求每一轮回复都必须至少调一次工具。于是它的 postprocess 不是"解析文本成调用",而是"发现这轮没调工具就注错":

// codex-rs/core/src/harness/mini_swe_agent.rs:26 inject_no_tool_call_format_error(要点)
// 一边缓冲事件,一边记 saw_assistant_message / saw_tool_call;
// 到 Completed 时:若「有 assistant 文本但没有任何 tool call」
// → 丢掉这轮,改注入一条 user 消息 MINI_SWE_AGENT_NO_TOOL_CALL_ERROR
// → 逼模型重来

那条错误文本明确教模型"用 bash 工具,参数 {"command": "..."},结束就 echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT"(codex-rs/core/src/harness/mini_swe_agent.rs:24)。

对比记忆: swe-agent 是"文本里挖调用";mini-swe-agent 是"没调用就打回"。都属于 postprocess,但改写方向相反。

4.3 terminus-2:请求有多种"体裁",回译是一台状态机

terminus-2 模仿一个终端基准,复杂度最高。它的一次请求不是单一形态,而是分五种 kind:

// codex-rs/core/src/harness/terminus_2.rs:103 Terminus2RequestKind
enum Terminus2RequestKind {
Action { current_workdir }, // 正常干活
Summary { original_instruction, current_screen }, // 交接前写总结
Questions, // 接手方提问
AnswerQuestions { questions }, // 原作者回答
Handoff { current_workdir }, // 交接
}

request_kind(codex-rs/core/src/harness/terminus_2.rs:316)靠扫历史里的关键句(如 "You are picking up work"、"You are about to hand off...")判定当前是哪种体裁,build_request(:42)再据此塑不同的 messages。无 tools、无 system;模型被要求吐一段 JSON(含 commands/task_complete)。

回译侧 inject_action_calls(codex-rs/core/src/harness/terminus_2.rs:58)按 kind 分流(build_harness_follow_up_items :899):

  • Action 类:parse_response 解析 JSON(:1521)→ 若有 commands,合成一条 LocalShellCall,而且命令被包进一段 tmux 驱动脚本(build_shell_call :986,tmux_execution_command :1029)去真实终端里打键;
  • Summary/Questions/AnswerQuestions:不执行命令,而是把模型这段输出接力成下一条 user 提示(问→答→交接的对话链);
  • task_complete 且尚未确认 → 注一条"确认要标记完成吗"的 user 消息(pending_completion 来自 prompt_has_completion_confirmation :120),同样是二次确认防误终止。

terminus-2 一句话:请求侧是"按对话阶段塑不同体裁",响应侧是"按体裁把 JSON 回译成命令或接力提示"。


5. 变体三:opencode 的独立标题请求

有些工具在会话第一轮会顺手向模型多发一个"给这段会话起个标题"的请求。opencode 就是,这不改主请求,而是额外塑一个请求体。

// codex-rs/core/src/harness/opencode.rs:24 build_title_request
// 单独一条 system(OPENCODE_TITLE_SYSTEM_PROMPT)+ "Generate a title..." + 用户首条消息
// codex-rs/core/src/harness/opencode.rs:99 should_generate_title
// 仅当「这一轮全是 user/developer 消息(=首轮)」且 OPENCODE_TITLE_SENT 还没置位时才发

should_generate_title 用一个进程级 AtomicBool(OPENCODE_TITLE_SENT,codex-rs/core/src/harness/opencode.rs:22)保证只发一次。塑形层在 build_chat_harness_request 里把这个可选的 title_request 一并算出(codex-rs/core/src/harness/request.rs:146-159),ChatHarnessRequest.title_request 带回给 client 先发它、再发主请求。

opencode 的主请求走常规路(build_request codex-rs/core/src/harness/opencode.rs:51):system 来自 opencode_system_prompt.md(:21 include_str)拼环境,工具是内联硬编码的十个 JSON(build_tools :631,bash/edit/glob/grep/read/skill/task/todowrite/webfetch/write),原生 function-calling,无 postprocess


6. 最重的一支:claude-code —— 一套塑形,三条 wire

claude-code 是货架里最完整的仿真:它要像真 Claude Code 那样,而 Claude Code 可以走三种传输协议。开源侧的巧思是:塑形只写一次,渲染成三种线格。

6.1 两个 profile:Full vs Bare

// codex-rs/core/src/harness/claude_code.rs:69 ClaudeCodeProfile
enum ClaudeCodeProfile { Full, Bare }

差别体现在系统提示工具集两处(build_request_for_profile codex-rs/core/src/harness/claude_code.rs:98,claude_code_system_and_tools :201):

维度FullBare
系统提示build_system_prompt(完整,claude_code_prompt.rs:216)build_bare_system_prompt(精简)
工具集完整工具面(过滤掉隐藏源工具)只留 Bash / Edit / Read
Bash 描述完整长描述 BASH_DESCRIPTION一句 "execute shell commands"
output effort按 effort 算固定 high

Bare 的工具裁剪就在 build_tools 一行:if profile.is_bare() && !matches!(name, "Bash"|"Edit"|"Read") { return None }(codex-rs/core/src/harness/claude_code.rs:1197)。子 agent 请求(child agent)还会再砍 AskUserQuestion / EnterPlanMode 等交互工具(:1203)。

6.2 三条 wire,一个源头

路由决定 claude-code 走哪条线(codex-rs/core/src/harness/routing.rs:56 resolve_stream_transport_route):

claude-code / claude-code-bare

┌────────────────────────────┼────────────────────────────┐
WireApi::Messages WireApi::Responses WireApi::Chat
│ │ │
MessagesHarness::ClaudeCode ClaudeCodeResponses(profile) ClaudeCodeChat(profile)
│ │ │
AnthropicMessageRequest ResponsesApiRequest ───────┐ ResponsesApiRequest
(原生 tool-use,直发) (直发 /responses) │ (喂给 chat-wire-compat)
│ │ │ │
build_request_for_profile build_claude_code_responses_shaped_request
(:98) (:253) —— 同一套 system+tools 的两个渲染出口

关键在:Responses 和 Chat 两条线共用一个 build_claude_code_responses_shaped_request(codex-rs/core/src/harness/claude_code.rs:253)。它把同一份 ClaudeCodeSystemAndTools(系统提示 + 工具选集)渲染成 Responses 风格的扁平 function 工具(claude_code_tools_as_responses_json :231),产出一个 ResponsesApiRequest:

  • Responses wire:这个请求原样发去 /responses;
  • Chat wire:同一个请求喂给 chat-wire-compat,由它转成 /chat/completions 体(见 client.rs codex-rs/core/src/client.rs:1887 的 ClaudeCodeChat 分支)。
  • Messages wire:另一条腿,build_request_for_profile 直接产 AnthropicMessageRequest(带 thinking、context-management、billing header 等 Anthropic 专属字段)。

一句话:system prompt + 工具选集是唯一真源,三条 wire 只是它的三种序列化。 这样"改一次提示,三种协议同步生效"。


7. 底层承载:chat-wire-compat 把 chat 流回译成 Codex 事件

前面凡是走 Chat wire 的(minimal / kimi-cli / opencode / claude-code-chat …),塑形产物最终都经过 ChatCompletionsCompatClient。这个 crate 是双向翻译器:下行把请求转成 chat body,上行把 chat 的 SSE 流回译成引擎的 ResponseEvent

7.1 下行:convert_request

// codex-rs/chat-wire-compat/src/client.rs:71 stream_request
let (body, tool_kinds) = convert_request(&request)?; // :76

convert_request(codex-rs/chat-wire-compat/src/request.rs:104)把 ResponsesApiRequest 压成 ChatCompletionRequest(:41):instructions→system 消息、input 逐条转 chat 消息、tools 转 ChatTool同时产出那张 ToolKinds——这是回译的钥匙。

7.2 ToolKinds:回译时区分三种工具产物

chat 协议里工具调用只有"function"一种形状,但引擎内部有三种。ToolOutputKind 记住每个工具该回译成哪种:

// codex-rs/chat-wire-compat/src/request.rs:31 ToolOutputKind
enum ToolOutputKind {
Function, // → ResponseItem::FunctionCall
NamespacedFunction { name, namespace }, // → 带 namespace 的 FunctionCall
Custom, // → ResponseItem::CustomToolCall
}
pub type ToolKinds = HashMap<String, ToolOutputKind>; // :37 工具名 → 种类

convert_tools(request.rs:500 起)在下行时按工具定义填这张表(function/custom/namespaced 分别在 528/579/620 行插入)。

7.3 上行:spawn_chat_stream 逐块重建

spawn_chat_stream(codex-rs/chat-wire-compat/src/stream.rs:126)消费 chat 的 SSE:文本 delta → OutputTextDelta、reasoning delta → ReasoningContentDelta、tool_call delta 攒进 slot;到 finish_reason 时收尾。收尾时查 ToolKinds 决定合成哪种 ResponseItem:

// codex-rs/chat-wire-compat/src/stream.rs:496 finalize_tool_calls_until(要点)
let item = match tool_kinds.get(&name) {
Some(ToolOutputKind::Function) => ResponseItem::FunctionCall { namespace: None, .. },
Some(ToolOutputKind::NamespacedFunction { name, namespace }) => ResponseItem::FunctionCall { namespace: Some(..), .. },
Some(ToolOutputKind::Custom) => ResponseItem::CustomToolCall { input, .. }, // 从 arguments 里抽 "input"
None => ResponseItem::FunctionCall { .. }, // 兜底当 function
};

这就是"回译"的最底层:一段 chat 流,凭 ToolKinds 变回引擎认得的 FunctionCall / CustomToolCall。§2.3 里 build_request 顺手建表,到这里才被兑现——下行记账、上行还账。

7.4 两种"回译"别混淆

到这里要分清两层不同的回译,它们叠在一起但职责不同:

谁做解决什么依据
协议回译chat-wire-compatchat SSE 流 → ResponseEvent(所有 Chat-wire harness 都过)stream.rs:126 spawn_chat_stream
语义回译各 harness 的 postprocess模型纯文本里的伪调用 → 合成 ResponseItemrequest.rs:214 apply_chat_harness_postprocess

顺序上:先协议回译(chat 流变成事件),再语义回译(postprocess 在事件流上拦截 assistant Message 挖命令)。swe-agent / terminus-2 两层都过;minimal / kimi-cli 只过第一层。


8. 请求塑形拆解表(总览)

一张表收全本章拆过的 harness——塑形三处怎么填、要不要 postprocess。

harness系统提示来源工具编码方式需 postprocess?wire
minimal硬编码 MINIMAL_SYSTEM_PROMPTbuild_tools 转原生 function schemaChat
kimi-cli模板 kimi_cli_prompt.md 渲染build_tools(原生)Chat
qwen-codeqwen_code_prompt.md复用 kimi_cli::build_toolsChat
opencodeopencode_system_prompt.md + 环境内联硬编码 10 个 tool JSON否(但有独立 title 请求)Chat
zcode硬编码 header+harness+环境(分块)整份 zcode_tools.json否(Anthropic 原生)Messages
swe-agent硬编码一句无 tools;模型吐伪 XML/围栏:SweAgentActionCallsChat
mini-swe-agent硬编码一句build_tools(原生):…NoToolCallFormatErrorChat
terminus-2无 system,提示塞进 user无 tools;模型吐 JSON commands:Terminus2ActionCallsChat
claude-code (Full)build_system_prompt(完整)完整工具面,按 wire 渲染否(原生 tool-use)Messages/Responses/Chat
claude-code (Bare)build_bare_system_prompt(精简)仅 Bash/Edit/ReadMessages/Responses/Chat

读法:"无 tools" 这一列几乎等价于 "需 postprocess" 这一列——因为没给工具的 harness,只能靠回译从文本里挖调用。唯一例外是 mini-swe-agent:它给了工具,但用 postprocess 强制每轮必调。


9. 巧妙之处(可带走的技术)

  • 下行记账、上行还账。 build_request 顺手建 ToolKinds(minimal.rs:33),流收尾时凭它把 chat 的扁平 tool_call 变回三种内部形状(stream.rs:496)。一张小 HashMap 就把"协议丢失的类型信息"补了回来。依据:codex-rs/chat-wire-compat/src/request.rs:31
  • 一套 system+tools,三种 wire。 claude-code 用 build_claude_code_responses_shaped_request(claude_code.rs:253)当 Responses/Chat 的共同渲染出口,Messages 另走一腿,做到"改一处、三协议同步"。
  • postprocess 是流上的可插拔层。 ChatHarnessPostprocess 枚举 + apply_chat_harness_postprocess(request.rs:214)让"文本→合成调用"成为响应流的一个装饰器,加新 harness 只加一个枚举臂,不碰 client。
  • 二次确认防误操作。 swe-agent 的 submit、terminus-2 的 task_complete 都在 postprocess 里查"是否已在等确认",避免把确认动作又当成一次新执行(swe_agent.rs:239 / terminus_2.rs:957)。
  • 攒—刷成组。 文本 + reasoning + 多个 tool_calls 要拼进同一条 chat assistant 消息,靠 pending 缓冲 + flush(minimal.rs:270)才能还原 chat 协议要求的形状。

10. 边界与局限

  • 伪调用解析是"够用即止"的字符串匹配,不是真解析器。 swe-agent 的 extract_bash_actionfind/split 逐级试(swe_agent.rs:405-498),模型若用它没覆盖的编码变体,会落到纠错分支重来,而非报错。
  • terminus-2 的 kind 判定靠英文关键句前缀(terminus_2.rs:316,如 "You are picking up work")。这些前缀一旦上游改词,判定就会失配——属于"锚在文本内容上"的脆弱点。
  • opencode 的 title 只发一次靠进程级 AtomicBool(opencode.rs:22),多会话进程内共享同一标志位,这是刻意的"每进程一次"语义,不是每会话一次。
  • 本章只讲塑形与回译;工具真正怎么执行、沙箱怎么拦手脚与护栏:工具、执行、沙箱;一次回合怎么驱动这两步在 底座引擎:一次回合怎么跑

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

主题文件路径符号名
塑形三件套(最简样板)codex-rs/core/src/harness/minimal.rsbuild_request / build_messages / build_tools / flush_pending_tool_calls
ThinkingToggle 注入codex-rs/core/src/harness/minimal.rsMINIMAL_SYSTEM_PROMPT(:49 起 thinking 分支)
提示模板外置codex-rs/core/src/harness/kimi_cli.rsKIMI_CLI_SYSTEM_PROMPT_TEMPLATE / build_request
塑形复用codex-rs/core/src/harness/qwen_code.rsbuild_request(复用 kimi_cli::build_tools/build_messages)
工具清单外置为 JSONcodex-rs/core/src/harness/zcode.rsZCODE_TOOLS / build_tools / build_request
独立标题请求codex-rs/core/src/harness/opencode.rsbuild_title_request / should_generate_title / OPENCODE_TITLE_SENT
文本→shell(伪 XML/围栏)codex-rs/core/src/harness/swe_agent.rsinject_action_calls / extract_bash_action / build_harness_follow_up_item / prompt_has_submit_review
强制每轮调工具codex-rs/core/src/harness/mini_swe_agent.rsinject_no_tool_call_format_error / MINI_SWE_AGENT_NO_TOOL_CALL_ERROR
多体裁请求 + JSON 回译codex-rs/core/src/harness/terminus_2.rsTerminus2RequestKind / request_kind / inject_action_calls / build_harness_follow_up_items / parse_response
postprocess 挂载点codex-rs/core/src/harness/request.rsChatHarnessPostprocess / build_chat_harness_request / apply_chat_harness_postprocess
claude-code 塑形/profilecodex-rs/core/src/harness/claude_code.rsClaudeCodeProfile / build_request_for_profile / claude_code_system_and_tools / build_claude_code_responses_shaped_request / build_tools
claude-code 提示生成codex-rs/core/src/harness/claude_code_prompt.rsbuild_system_prompt / build_bare_system_prompt / ClaudeCodeShellToolName
三 wire 路由codex-rs/core/src/harness/routing.rsresolve_stream_transport_route / StreamTransportRoute / ClaudeCodeProfileRoute
chat 请求转换 + ToolKindscodex-rs/chat-wire-compat/src/request.rsconvert_request / ToolOutputKind / ToolKinds / convert_tools
chat 客户端入口codex-rs/chat-wire-compat/src/client.rsChatCompletionsCompatClient / stream_request
chat 流回译codex-rs/chat-wire-compat/src/stream.rsspawn_chat_stream / finalize_tool_calls_until