复盘 · 对照《从 LLM 到 Coding Agent》

把《从 LLM 到 Coding Agent》的二十一个概念逐个对上 Codex 的生产实现与本专栏的篇目,列出 Codex 刻意没照教学建议做的几处,再提炼贯穿全书的几条规律:界面与内核之间隔着一层协议,历史只追加,有副作用的动作都走同一个编排器,沙箱与审批各管一件事。最后是教学实现里没有对应物的那一半。

作者 David更新于 第 55 篇(共 57 篇)

复盘 · 对照《从 LLM 到 Coding Agent》

前 54 篇把 codex-rs 从入口一路拆到了发布。收尾的第一篇回到起点:站里的《从 LLM 到 Coding Agent》用二十篇正文加一篇实战番外,把一块只会输出文本的“裸芯片”焊成了一台能自主干活的机器,每一步都配了一个用 TypeScript 写的最小实现。这一篇把两本书并排摆开:左边是教学最小实现,右边是 Codex 的生产实现,最后一列指向本专栏里讲细节的篇目。

教学循环落在 Codex 的哪一层

那本书的内核是一个 while 循环:问模型,有 tool_use 就执行,把 tool_result 喂回去,再问。在 Codex 里,这个循环几乎原样活在 run_turn 里,只是上下都多出了好几层:

图表加载中…

上面多出的一层让同一个内核服务多个前端,下面多出的一层让命令在操作系统强制的边界里执行。这两处是教学实现与生产实现距离最远的地方,本文最后一节再回来看。

逐概念对照

按那本书的阶段分组。“Codex 的生产实现”一列只写结构上的差别,细节见最后一列。

起点与“让模型能做事”

概念教学最小实现Codex 的生产实现本专栏
裸 LLM API一次 messages.create;无状态,每轮重发全部历史;一个 API key只说 Responses API;请求带 store: false,走 HTTP 时每次带完整历史,走 WebSocket 时只发增量;认证单独成一个子系统,八种凭据统一成 CodexAuth模型客户端、登录与认证
工具调用用 JSON Schema 声明工具;模型回 tool_use,宿主执行后按 id 回填 tool_result工具以 ToolSpec 发出(function、namespace、custom 等五种);ToolRouter::build_tool_call 把输出项变成 ToolCall;缺配对的调用在生成输入时补一条 aborted 输出工具系统总览、上下文与历史
Agent Loopwhile (true),没有 tool_use 就退出RegularTask 反复调用 run_turn;“还要不要再问”就是 needs_follow_up(有工具调用,或期间来了新输入);开轮准备、中途压缩与 Stop hook 都围着它一条消息的生命周期、run_turn 主循环
工具抽象一个富接口:schema、校验、权限、isConcurrencySafe 等元数据与两套渲染,默认值往安全方向兜ToolExecutor 把 spec 与处理器绑在同一个对象上,supports_parallel_tool_calls 默认 false;CoreToolRuntime 补上 hook 与遥测;工具表每次采样前按开关、模型目录与执行环境重建,暴露方式有六种工具系统总览

流式与并发

概念教学最小实现Codex 的生产实现本专栏
流式处理SSE 事件序列;文本来一片打一片,工具参数是攒到块结束才解析的部分 JSONcodex-api 把 SSE 与 WebSocket 事件统一翻译成 ResponseEvent,函数参数的增量事件直接忽略,调用以完整的输出项为准;app-server 再把消息、命令、补丁、MCP 调用统一成 item,按开始、增量、完成推给前端模型客户端、v2 协议、工程实践
边流边执行工具块一结束就派发;并发安全的一起跑,其余串行输出项一完成就 tokio::spawn,future 进 FuturesOrdered,流结束后 drain_in_flight 按调用顺序写回;并发靠一把读写锁,可并行的拿读锁、其余独占写锁,不必事先看到整批调用工具系统总览、run_turn 主循环

安全与控制

概念教学最小实现Codex 的生产实现本专栏
权限系统工具自分类、规则、从 plan 到 bypass 的权限模式;canUseTool 返回 allow、deny 或 ask;拒绝要喂回模型拆成两个正交的量:PermissionProfile 管技术上能碰什么,由操作系统强制;AskForApproval 管越界前问不问。规则是 Starlark 写的执行策略,审批依次经过 hook、自动审查与用户,Denied 的理由作为工具结果交还权限模型、审批流程、执行策略、自动审查
中断与转向AbortSignal 贯穿到底;给悬空的工具合成“已取消”结果;转向是“打断加追加”,新话接到下一轮Op::Interrupt 触发取消令牌并以子令牌一路下传,任务有 100 毫秒自行收尾;历史里写一条 <turn_aborted> 标记,发出 TurnAborted 而不是错误事件;转向走 turn/steer,插进正在跑的这一轮任务类型与 Plan 模式、run_turn 主循环、输入框
HooksPreToolUse、PostToolUse、Stop、UserPromptSubmit;外部命令从 stdin 读事件、用 stdout 表态引擎叫 ClaudeHooksEngine,12 个事件,处理器是命令或 MCP 工具;按内容哈希记账,新增或改动的钩子要在 /hooks 里审阅信任才运行;goal 扩展相当于编译进来的 Stop 钩子Hooks、扩展 API

上下文工程

概念教学最小实现Codex 的生产实现本专栏
Token 预算字符数除以 4 粗估,以 API 回报的 input_tokens 为锚;分级水位线,给抢救操作留余量以上一次响应的 total_tokens 为准,加上之后新条目的按字节粗估;自动压缩阈值是窗口的 90%,另有 95% 的硬上限;开发中的 token_budget 开关还给模型一个 get_context_remaining 工具上下文压缩、其它内置工具
上下文注入系统提示、环境、项目记忆、历史、动态附件分层拼装;静态放高处,动态放低处系统提示只放基础指令,线程创建时定下;开发者指令、权限、协作模式、AGENTS.md、环境都是历史开头的消息,之后的变化以差异追加;每段注入都是实现 ContextualUserFragment 的具名结构体指令从哪来、上下文与历史、Skills
Prompt 缓存显式缓存断点加滚动断点;守住字节稳定,要改先拷贝请求里没有断点,只有默认取根线程 ID 的 prompt_cache_key;字节稳定靠只追加的历史、世界状态差分与确定性的合成条目 ID模型客户端、上下文与历史
自动压缩保留近期、摘要远古;proactive 为主、reactive 兜底;摘要提示词决定成败四个时机:开轮前、一轮中途、轮末、手动 /compact;OpenAI、Azure、Amazon Bedrock 走远程压缩,由服务端返回一个不透明的压缩条目,其余提供方在本地写交接摘要;新历史基本只剩最近的用户消息加上摘要上下文压缩
精细化上下文管理工具结果超限就外置存盘,按 id 定点掏空,大块内容只留句柄工具输出写进历史时按模型的截断策略截断,rollout 保留全文;命令输出先进 1 MiB 头尾缓冲,再按 token 预算保留头尾、截掉中间;压缩请求超窗时把末尾的工具输出换成占位文字上下文与历史、执行命令、上下文压缩

健壮性

概念教学最小实现Codex 的生产实现本专栏
错误恢复分清可恢复与致命;指数退避加抖动、听 Retry-After;过载换模型;清理孤儿消息;每条恢复路径都要硬闸CodexErr::retry_delay 决定重不重试;流级重试默认 5 次,从 200 毫秒倍增、带 10% 抖动,优先用服务端建议的等待;WebSocket 重试用完退到 HTTPS;只把完整的输出项写进历史,重试接着已有进度做run_turn 主循环、模型客户端
成本追踪四类 token 分价目表累加;覆盖所有出口;美元预算做硬闸客户端不计价,ModelInfo 没有价格字段;token 用量随 thread/tokenUsage/updated 通知到达,额度来自响应头里的限额快照;估算花费由服务端给出,另有 codex.turn.cost_microusd 指标模型目录与提供方、其它界面、遥测与分析
扩展思考思考块带签名,必须原样保管;签名与生成它的模型绑定请求的 include 固定为 reasoning.encrypted_content,推理以密文回到客户端、随历史再发回去;统计用量时先看服务端是否声明已计入推理;TUI 把推理摘要渲染成单独的单元模型客户端、上下文压缩、历史记录单元

扩展、规模与实战

概念教学最小实现Codex 的生产实现本专栏
子 Agent一个工具,内部递归跑 runAgent,只带回结论;子 Agent 的工具集里不放 spawn_agent子代理是同一个 ThreadManager 里的另一条线程;V1 按深度限制(agents.max_depth 默认 1),V2 限驻留数,结论作为 FINAL_ANSWER 投进父线程的信箱;/review、自动审查与记忆整合也都是受限的子代理多 agent、自动审查、Codex Cloud、代码审查与 worktree
MCP 与懒加载外部工具适配成统一的 Tool;工具太多时只发简介,靠 search_tools 按需加载McpRuntime、McpConnectionSet 与每步冻结的 McpBinding 三层;工具改名进 mcp__服务器 命名空间,支持工具搜索的模型上默认延迟加载,由 tool_search(BM25,默认返回 8 个)取回;传输有 stdio 与可流式 HTTP,带 OAuth 与 elicitationMCP 工具调用、MCP 客户端、其它内置工具
会话历史append-only JSONL;抄本即真相,压缩只是运行时视图;恢复就是读回来接着循环每个线程一个只追加的 rollout JSONL,压缩结果以 compacted 条目追加;SQLite 里的线程列表与分页历史是可以重建的投影;新建、恢复、分叉都汇入 spawn_thread会话持久化、ThreadManager 与 CodexThread
仓库总结六阶段流水线:git 裁剪、确定性骨架、符号地图、重要性排序、分治归并、验证与失效没有专门的流水线:/init 把一段固定的提示词当作用户消息发出,让 agent 用平常的工具自己探索,写一份 AGENTS.md指令从哪来

最后一行值得多看一眼。/init 的实现只有三行:

            SlashCommand::Init => {
                const INIT_PROMPT: &str = include_str!("../../assets/prompt_for_init_command.md");
                self.submit_user_message(INIT_PROMPT.to_string().into());
            }

(codex-rs/tui/src/chatwidget/slash_dispatch.rs:290)

提示词 codex-rs/tui/assets/prompt_for_init_command.md 要求生成一份标题为 “Repository Guidelines” 的贡献者指南,200 到 400 词为宜,动笔前先看当前目录是否已有 AGENTS.md,有就不改。那本书用一整篇讲“注定读不完时怎么画地图”;Codex 把画地图交给模型现场探索,画好的地图再按指令从哪来的规则注入之后的每个会话,正好是那篇结尾说的分工:一份薄的全局摘要常驻上下文,具体问题靠工具现场去查。

Codex 没照教学建议做的地方

对照表里有几行,Codex 的做法与那本书的建议不同,而且是有意为之。它们都出自各篇末尾的对照小节:

那本书的建议Codex 的做法篇目
主模型过载时自动降级到备用模型过载错误不在重试之列,本轮结束并提示换一个模型,换不换交给用户模型目录与提供方
在客户端按价目表把 token 换算成钱没有价格字段,也没有计价代码,花费以服务端给的数为准模型目录与提供方、其它界面
在稳定前缀末尾打缓存断点不打断点,靠稳定的缓存键与处处维护的字节稳定模型客户端
plan 是最严的权限模式,写操作一律拒绝Plan 是协作模式,“不改文件”只写在提示词里,代码只拦下 update_plan;要硬性只读得靠沙箱与审批任务类型与 Plan 模式
Stop 钩子要配最大重试次数不设硬上限,把 stop_hook_active 传给钩子,由钩子自己刹车Hooks
超长的工具结果存盘,只给模型预览和路径不存盘,保留头尾、截掉中间执行命令
转向时结束当前回合,新消息接到下一轮插话并入正在跑的这一轮,下一次采样就交给模型;想排到下一轮按 Tabrun_turn 主循环、输入框
子 Agent 是一次同步的函数调用spawn_agent 立即返回,结论经通知或信箱回来多 agent

这些差别大致出于两种考虑。一是 Codex 只说 Responses API 这一种协议,服务端能做好的事(计价、远程压缩、提示缓存与粘性路由)就交给服务端;二是能交给模型判断的事(什么时候申请提权、用哪个技能、Plan 模式下别动手),它更愿意写进提示词与工具说明,而不是写死在循环里。

贯穿全书的几条规律

  • 界面与内核之间隔着一层协议。 TUI 的依赖里没有 codex-core,所有前端都经 app-server 的 v2 JSON-RPC 访问同一个服务端:turn/start 一启动就返回,进度全靠通知,审批是服务端反过来发给客户端的请求。连进程内嵌的那一份,也刻意保留请求、通知、事件这套模型(workspace 全景、app-server)。
  • 内核是一对队列。 一个线程在内核里就是一个 CodexThread:一端是提交 Op 的队列,一端是吐出 EventMsg 的队列,常驻的 submission_loop 逐个处理提交;一个会话同一时刻只跑一个任务(ThreadManager 与 CodexThread、任务类型与 Plan 模式)。
  • 历史只追加,改写只有两个出口。 根目录 AGENTS.md 把“不改写历史”写成了规矩;会变的上下文建模成世界状态的分区,第一轮全量、之后只追加差异;改写只剩压缩与回滚两处。落盘也一样:rollout 只追加,SQLite 只是它的投影(上下文与历史、会话持久化)。
  • 每次请求冻结一份快照。 设置在开轮时提交成线程默认值,再冻结成只读的 TurnContext;每次采样前还要捕获一个 StepContext,发给模型的工具清单与随后执行调用的注册表是同一份(Session 与 TurnContext、工具系统总览)。
  • 有副作用的动作都走同一个编排器。 命令与补丁都经 ToolOrchestrator:审批、选沙箱、执行、被沙箱拒绝时视策略请求提权重试;审批请求先交给 hook,开了自动审查就交给审查模型,最后才轮到你(审批流程)。
  • 沙箱与审批各管一件事,三个平台各自落地。 一个决定命令技术上能碰什么,一个决定越界前问不问,两者自由组合;macOS 现拼 Seatbelt 策略,Linux 用 bubblewrap 加 seccomp,Windows 用写受限令牌、elevated 模式另建专门的沙箱用户,联网再交给本地代理按名单放行(权限模型、网络代理)。
  • 可覆盖的配置与不可越过的约束分成两摞。 config 层逐键合并出生效值,requirements 层合成管理员的约束,再把取值关进 Constrained 校验器,会话中途切换也越不过去(配置系统)。
  • 拿不准就拒绝。 跨进程的审批答复解析失败或客户端报错,一律按拒绝处理;Linux 沙箱遇到 glob 展开过多、残留特权、代理端点解析不出这类情况直接报错,不降级成无沙箱;执行策略多条命中时取最严格的那条(app-server、Linux 沙箱、执行策略)。

Codex 独有的那一半

对照表只覆盖两本书都讲到的交集。Codex 作为一个多端产品,还有一大块在教学实现里找不到对应物:

方面做什么篇目
服务化app-server 守护进程被多个客户端共享,可经 stdio、Unix socket、WebSocket 或远程控制接入;exec-server 把起进程、读写文件、发 HTTP 请求搬到本机或另一台机器守护进程与传输、exec-server
操作系统级安全三套沙箱、联网代理、Starlark 执行策略,以及让另一个模型替你审批的自动审查macOS 沙箱、Linux 沙箱、Windows 沙箱、自动审查
组织与分发托管的 requirements、插件与 marketplace、从 Claude Code 与 Cursor 导入配置配置系统、插件、从别的 agent 迁移
跨会话的积累后台把旧会话提炼成记忆;goal 扩展在线程空闲时自动开下一轮,直到目标完成或预算耗尽记忆、扩展 API
编辑与执行的格式自有的 apply_patch 补丁格式、可跨轮续写的命令会话、让模型写 JavaScript 编排工具的 Code modeapply_patch、执行命令、Code mode
产品表面占全仓约四分之一非测试代码的终端界面,以及登录认证、实时语音、遥测、Codex Cloud 与代码审查TUI 架构、登录与认证、实时语音、遥测与分析

教学实现回答“为什么要这样做、最少要做什么”;Codex 回答的是“同一个内核要服务多个前端、三个操作系统和一个组织时,还得长出哪些层”。想深挖哪个概念,查上面的表,先读本专栏对应的篇目,再点回那本书看最小版本。

下一篇换个角度,把 Codex 和站里另外两本读得最细的开源实现 Grok Build、OpenCode 放在一起比较。


上一篇:工程实践 · 测试、构建与发布 · 下一篇:横向对比 · Codex 与 Grok Build、OpenCode

本页目录