第一轮 · 第 2 站 · 所在层:App Server · 真实源码精读 · 预计 60–75 分钟

App Server 层的
turn_start_inner

你已经知道 submit_op 成功不等于任务完成。今天只补上中间这一站:App Server 收到 turn/start 后,怎样检查、整理并把工作交给 Core。

第一件事:先定位它在哪一层

答案:turn_start_inner 在 App Server 层。

TUI 应用层(上游) → App Server / turn_start_inner(今天) → Core / start_or_steer_turn(下游)

它不是 TUI 里的界面组件,也不是 Core 里的任务执行者;它是客户端和 Core 之间的“受理、检查、转换与交接”位置。

今天采用两种视角:先用四句话看懂整个函数的职责,再精读函数签名、paramsload_thread。后面的 140 多行会继续逐块展开,不用一口气死记全部 Rust 语法。

先把它放回你每天使用的动作里

ChatWidget
整理输入
submit_op
TUI 内部上交
TUI 应用层
调用 turn/start
turn_start_inner
受理与转换
start_or_steer_turn
交给 Core
通知返回
逐步更新 TUI

submit_op 先回到 TUI 应用协调层,是因为 ChatWidget 只是一个子组件;TUI 应用层接住内部命令后,才调用 App Server。到了 turn_start_inner,我们才真正站在 App Server 层。

源码依据:ChatWidget 组装并提交命令 · TUI 调用 turn/start · 组装 TurnStartParams

四个动作,先看整个函数的鸟瞰图

1

接到什么?

一张“启动 Turn 的申请单”。

主要包括:请求编号 request_id、目标 thread_id、文字/图片等 input,以及工作目录、模型、权限、审批方式等可选设置。

先把 TurnStartParams 理解成“本次任务申请表”,不要背全部字段。

2

检查什么?

确认这张申请单能安全、正确地往下交。

它查找目标 Thread,检查是否允许直接输入、文字是否过长,并整理工作目录、环境、模型、权限、审批等设置。外层 turn_start 还会先检查图片网址。

这是“受理检查”,不是模型推理。

3

交给谁?

把协议输入转换后,交给 Core。

它将 App Server 的 UserInput 映射成 Core 使用的 CoreInputItem,再调用 thread.start_or_steer_turn(...)。是否新开一轮还是追加到活动轮,后面第三轮再研究。

关键词:格式转换 + 调用 Core,不是自己执行任务。

4

立即返回什么?

返回 Turn 编号和 InProgress 状态。

返回的 Turn 此刻 items 还是空列表,状态是进行中。这相当于“已经受理,编号是 turn_xxx”,不等于任务已经成功完成。

真正结束要等后续 turn/completed 通知。

鸟瞰图:先把 140 多行压缩成 9 行

// 1. 找到要继续的 Thread
let (thread_id, thread) = self.load_thread(&params.thread_id).await?;

// 2. 检查输入,并整理本次设置
validate_input(...)?;

// 3. 把 App Server 输入转换成 Core 输入,再交给 Core
let mapped_items = params.input.map(UserInput::into_core);
let submission = thread.start_or_steer_turn(...).await?;

// 4. 返回“已受理、进行中”
Ok(TurnStartResponse { turn: Turn { status: InProgress, ... } })

这不是原代码逐字复制,只用于先建立地图。下面开始读真实代码;职责压缩图不能代替源码精读。

源码依据:turn_start_inner 完整实现 · 请求与响应类型 · 官方生命周期说明

真实代码精读 ①:先拆函数签名

async fn turn_start_inner(
    &self,
    request_id: ConnectionRequestId,
    params: TurnStartParams,
    app_server_client_name: Option<String>,
    app_server_client_version: Option<String>,
) -> Result<TurnStartResponse, JSONRPCErrorError> {
代码片段逐词含义放在本函数里意味着什么
async异步函数标记函数内部可以使用 .await 等待 Thread 查询等异步操作;调用者也要 .await 它。
fnRust 的“声明函数”关键字后面紧跟函数名、参数和返回类型。
turn_start_inner函数名;inner 不是关键字外层 turn_start 先检查图片 URL,再调用这个内部实现。
&self借用当前对象,不取得所有权当前对象是 TurnRequestProcessor;函数借助它持有的 thread_manageroutgoing 等服务完成工作。
名称: 类型Rust 参数写法例如 params: TurnStartParams 表示变量名叫 params,编译期类型是 TurnStartParams
Option<String>可能有字符串,也可能没有客户端名称和版本不是每次请求都必须提供;有值是 Some(...),无值是 None
-> Result<A, B>返回“成功 A 或失败 B”成功返回 TurnStartResponse;失败返回可发回客户端的 JSONRPCErrorError

params 到底是什么?

params 是 parameters(参数)的缩写,只是程序员取的变量名,不是 Rust 关键字。

客户端发来的 JSON-RPC params 对象,会被 App Server 反序列化成一个 Rust 结构体 TurnStartParams。因此,params.thread_id 就是在读取这张“启动 Turn 申请单”的 thread_id 字段。

字段类别TurnStartParams 中的字段回答的问题
必填身份thread_id: String这次输入属于哪个 Thread?
必填输入input: Vec<UserInput>用户提交了哪些文字、图片、音频、Skill 或 Mention?Vec 表示列表,因此一条提交里可以同时有文字和图片。
消息与触发来源client_user_message_idturn_trigger客户端如何标识这条消息?是谁或什么触发了 Turn?
运行环境cwdruntime_workspace_rootsenvironments在哪个目录、哪些工作区和环境中工作?
安全控制approval_policyapprovals_reviewersandbox_policypermissions什么操作需要审批?允许访问什么?
模型行为modelservice_tierservice_tier_for_turneffortsummarypersonalitycollaboration_mode用哪个模型、速度档位、推理强度和协作方式?
上下文与输出additional_contextresponsesapi_client_metadataoutput_schemacyber_access_program额外带什么上下文或元数据?最终输出是否必须满足 JSON Schema?
已弃用multi_agent_mode为了协议兼容仍保留,但当前实现说明它已被忽略。

只有 thread_idinput 是非 Option 字段。源码用 snake_casethread_id,JSON 因 serde(rename_all = "camelCase") 使用 threadId

展开:一条同时含文字和本地图片的 params 大致长什么样
{
  "threadId": "019...",
  "input": [
    { "type": "text", "text": "解释这张图", "textElements": [] },
    { "type": "localImage", "path": "C:\\Temp\\diagram.png" }
  ],
  "cwd": "C:\\Users\\admin\\project",
  "model": "gpt-5.6-sol"
}

未出现的可选字段反序列化后通常是 None。这也解释了你之前说的“列表里既有图片也有文字”:它们就是 input: Vec<UserInput> 中的两个元素。

源码依据:TurnStartParams · UserInput 类型

真实代码精读 ②:逐词拆 load_thread 这一句

let (thread_id, thread) =
    self.load_thread(&params.thread_id)
        .await
        .inspect_err(|error| {
            self.track_error_response(&request_id, error, None);
        })?;
代码片段含义
let创建局部变量绑定。
(thread_id, thread)元组解构:右边成功返回两个值,分别放进“强类型 ID”和“Thread 对象句柄”。
self.load_thread(...)调用当前 TurnRequestProcessor 上的辅助方法。
&params.thread_id从申请单取出 thread_id,再借用它。& 避免移动或克隆字符串;Rust 会把 &String 自动借用转换为方法需要的 &str
.await异步等待查询结果;等待期间运行时可以调度其他任务,并不等于把整个程序线程卡死。
.inspect_err(...)如果结果是错误,顺便记录埋点;它观察错误但不改变错误类型。
|error| { ... }闭包,即匿名函数;error 是传给闭包的错误引用。
?错误传播:若为 Err,当前 turn_start_inner 立即返回该错误;若为 Ok,取出里面的二元组继续执行。
;结束这一条语句。

load_thread 内部实际做了什么?

async fn load_thread(
    &self,
    thread_id: &str,
) -> Result<(ThreadId, Arc<CodexThread>), JSONRPCErrorError> {
    let thread_id = ThreadId::from_string(thread_id)
        .map_err(|err| invalid_request(format!("invalid thread id: {err}")))?;

    let thread = self.thread_manager
        .get_thread(thread_id)
        .await
        .map_err(|_| invalid_request(format!("thread not found: {thread_id}")))?;

    Ok((thread_id, thread))
}
  1. ThreadId::from_string:把普通字符串校验并转换成专门的 ThreadId 类型;格式不合法就返回 invalid thread id
  2. thread_manager.get_thread:拿强类型 ID 去当前内存中的 Thread 表查找;找不到就返回 thread not found
  3. Arc<CodexThread>:返回可在异步代码间安全共享所有权的 Thread 句柄。Arc 是原子引用计数智能指针。
  4. Ok((thread_id, thread)):成功时同时返回强类型 ID 和 Thread 句柄,供后续检查、配置和提交 Turn 使用。

名字可能误导你的地方

这里的 load_thread 并不是“读取昨天的 JSONL 历史并恢复 Thread”。它最终调用的是 ThreadManager::get_thread,从当前已经加载的、非内部 Thread 集合中取句柄。真正把冷 Thread 从持久化记录恢复到内存,属于 thread/resume 等更早的生命周期步骤。

源码依据:load_thread · ThreadManager::get_thread

最容易混淆的一句话

turn/start 返回成功 ≠ Turn 成功完成。

前者表示 App Server 已经把申请交给 Core,并拿到了 Turn 编号;后者必须等执行过程结束,再看 turn/completed 中究竟是 completedfailed 还是 interrupted

面向最终目标:GEODYNA 对应哪一层?

今天只做架构映射,不修改 GEODYNA:未来 GEODYNA 智能体也需要一个“Agent 受理与交接层”,位置在自然语言界面与正式 Command API 之间。
Codex 的 turn_start_inner未来 GEODYNA 智能体
接收 Thread、输入和运行设置接收用户工程目标、当前项目、选择对象和执行约束
确认 Thread、输入长度、权限与环境确认项目状态、对象是否存在、命令是否允许、是否需要用户确认
把协议输入转换成 CoreInputItem把自然语言计划转换成正式 ProjectCommand 或原子命令批次
调用 start_or_steer_turn通过既有 Smart Command / Script API 提交预览或原子事务
先返回 InProgress先返回“任务已受理/待确认/执行中”,后续持续报告进度与结果

只读源码已经显示,GEODYNA 现有 useSmartCommandApplication.ts 会准备上下文、生成预览、校验计划、编译命令并提供执行/撤销入口;domain/project/scriptApi.ts 会通过 runWorkflow 执行正式的原子命令事务。这些是未来 Harness 可以复用的基础,但它们还不等于完整智能体。

GEODYNA 只读依据:D:\GEODAMNEW\features\smartCommand\useSmartCommandApplication.ts:277 · D:\GEODAMNEW\domain\project\scriptApi.ts:221。本课没有修改 GEODYNA。

产品经理应该从这一站带走什么?

产品问题这一站给出的答案
用户点发送后,界面能否直接说“已完成”?不能。只能先显示“已受理/进行中”,最终状态由后续通知确认。
为什么不让模型直接调用底层代码?中间层要校验身份、项目状态、权限、参数和安全策略,并转换成稳定的正式命令。
接口成功率应该统计什么?至少区分“请求受理成功”和“业务任务最终完成”,否则指标会虚高。
GEODYNA 智能体的价值证据是什么?不仅能理解意图,还能安全交接、展示状态、处理失败并给出可审计结果。

今天明确不学

不展开环境覆盖、Memory 启动、Realtime transcript、Responses API metadata、Start/Steer 的具体判断,也不学习 Rust 的 awaitmap_err 和所有权。它们分别属于后续数据轮、状态轮和运行机制轮。

即时小测

1. turn_start_inner 返回 InProgress,是否说明模型已经完成任务?

2. 下面哪一句最接近它的职责?

3. GEODYNA 智能体能否绕过 Command API,直接改项目内部数据?

最后用一句话交作业

请不要照抄,按你的理解补完整:

turn_start_inner 位于 ______ 层。它收到 ______,先 ______,再把 ______ 交给 ______,立即返回 ______;这不表示 ______。

你已经通过本课,可以进入第一轮第 3 站:start_or_steer_turn 源码精读