来源:互联网 更新时间:2026-08-21 07:28
大家好,我是三雒。
最近一段时间在做一个桌面的Agent项目,一直在研究 Codex 的实现,也尝试把它的本地工具能力复用到自己的项目里。刚开始看代码时,我脑子里有一个很朴素的理解:Codex 不就是一个会调用 工具的大模型吗?
但真正顺着源码往下追,很快就会发现这个理解太薄了。
模型确实负责推理,也会生成 Tool Call,但它并不天然拥有当前工作目录、长进程和文件权限这些操作系统状态。真正把工作区信息提供给模型,并保证命令在正确目录执行的,是模型外面的 Agent Harness。一次持续几分钟甚至几小时的任务,也不是靠模型单次回答完成的。
这一篇是“拆解 Codex”系列的第一篇,我们先不钻进某个 Tool 的参数,也不急着分析每个 Rust 模块,而是先回答一个最基础的问题:
本文分析基于本地 codex 仓库的 f3465047d1ee 版本。
假设我们给 Codex 一个很常见的任务:
如果只是普通聊天模型,它最多根据我们贴进去的 Bug 描述和代码给出修改建议。但 Coding Agent 的关键,是能把这句话转换成一个持续执行的工作流。
这条链路里,模型负责理解任务、分析结果和决定下一步;工具负责读取、搜索、修改和执行;Agent Loop 则把两者连接起来,让一次 Tool Result 成为下一次模型判断的输入。
在第一轮模型判断之前,对当前工作目录生效的 AGENTS.md 已经在会话或 Turn 的上下文构建阶段被发现并加入指令。只有工作目录或规则作用域发生变化时,系统才需要刷新适用的规则。
说白了,这不是一次问答,而是模型在项目规则约束下,借助 Skill 和工具不断观察、行动、再观察的闭环。
从职责上看,可以把 Codex 分成三层:

如果继续拆开中间的 Harness,最核心的是五部分:
AGENTS.md、Skills、Plugins 和 MCP 它们则分别从“工作规则”和“外部能力”两个方向扩展这套 Harness,下面逐个看。
Codex 当前的主循环入口位于:
codex-rs/core/src/session/turn.rs
在这里,run_turn 的源码注释其实已经把这套基础流程交代得非常明白了:模型的返回结果无非两种,要么是 Assistant Message,要么是 Function Call。要是拿到的是 Function Call,Codex 就会先去执行对应工具,再把执行结果放进下一轮请求里回传给模型;反过来,如果模型返回的只是最终消息,没有额外工具调用需求,那么这个 Turn 到这里也就结束了。
抽掉错误处理、事件上报和上下文压缩后,它的骨架大致可以理解成:
loop {// 1. 先把这一轮要给模型看的上下文准备好let step_context = session.capture_step_context(turn_context).await;let history = session.clone_history().await;// 2. 向模型发起请求,并持续接收流式返回let mut stream = request_model(step_context,history.for_prompt(...),).await?;let mut in_flight = FuturesOrdered::new();let mut needs_follow_up = false;while let Some(event) = stream.next().await {if let ResponseEvent::OutputItemDone(item) = event? {match ToolRouter::build_tool_call(item.clone())? {// 3. 如果模型产出了 Tool Call,就交给 Runtime 去执行Some(call) => {in_flight.push_back(Box::pin(tool_runtime.clone().handle_tool_call(call, cancellation_token.child_token())));needs_follow_up = true;}// 如果只是普通 Message 或 Reasoning,就直接记成会话 ItemNone => session.record_conversation_items(turn_context, &[item]).await,}}}// 4. 等工具执行完,再把 Tool Result 回写进 Historywhile let Some(tool_result) = in_flight.next().await {session.record_conversation_items(turn_context, &[tool_result?.into()]).await;}// 5. 只要这一轮出现了 Tool Result,就继续下一轮,让模型接着判断if !needs_follow_up {break;}}
不过,真实源码并没有把这些步骤一股脑平铺在 run_turn 这一层。更准确地说,run_sampling_request 负责构造 ToolRouter 和 ToolCallRuntime,try_run_sampling_request 负责消费模型返回的流,handle_output_item_done 负责识别并调度 Tool Call,最后再由 drain_in_flight 等待工具执行结束,并把 Tool Result 写回 Conversation History。
除此之外,真实实现还要处理用户中途追加的消息、重试、取消、Hooks、Context Window、自动 Compaction,以及可能并行返回的多个 Tool Call。
但最稳定的核心没有变:
模型判断 → 执行动作 → 观察结果 → 再次判断
所以,模型负责产生“下一步应该做什么”,Agent Loop 负责让这一步真的发生,并把结果重新送回模型。两者组合起来,才会出现我们看到的持续工作能力。
问题来了,第二次请求模型时,Codex 应该发送什么?
显然不能只发送最初那句“修复 APP-1427”。模型还需要知道:
AGENTS.md 规则;在当前实现里,这部分不是一个简单的 Vec。
运行层至少可以看到三个不同生命周期的对象:
Session:承载整个线程级的服务、历史和活动任务;TurnContext:固定一轮任务使用的模型、权限、模式和配置;StepContext:固定某一次 Sampling 看到的环境、MCP Tool 快照和 AGENTS.md 状态。这里 StepContext 很关键。一次 Turn 可能包含很多次模型请求,而工作区、可用环境、MCP 工具列表在期间可能变化。Codex 会为一次具体的 Sampling 捕获一致的请求视图,保证“展示给模型的工具”和“随后真正执行的工具”来自同一份上下文。
然后,Conversation History 会经过 for_prompt(...) 转换成模型输入。随着任务持续运行,Tool Output、Assistant Message 和用户追加信息不断进入历史。
上下文快到窗口上限时,run_turn 还会触发自动 Compaction,把冗长历史压缩成后续仍可继续工作的状态。
因此,上下文工程并不是“把整个仓库全部塞进 Prompt”。更准确地说,它是在每个决策点构造一个有边界、可复现、对当前动作足够的信息快照。
模型决定运行命令时,返回的不是一段直接交给操作系统的代码,而是结构化 Tool Call,例如:
{"name": "exec_command","arguments": {"cmd": "npm test","workdir": "/workspace/project"}}
Tool 系统需要完成两件不同的事情:
当前源码中的 ToolRouter 很直接地体现了这两个世界:
pub struct ToolRouter {registry: ToolRegistry,model_visible_specs: Vec
model_visible_specs 是模型能看见的契约,registry 则保存真正可以执行这些契约的 Runtime。
在每次 Sampling 前,built_tools(...) 会根据当前 StepContext 重新构建工具视图。这里合并的不只是 Codex 内置工具,还可能包括:
模型返回结果后,ToolRouter 把 Response Item 转成内部 ToolCall,再构造 ToolInvocation。其中不仅有参数,还带着 Session、StepContext、取消信号、Call ID 和 Diff Tracker。
这也是为什么 Tool 不能简单理解成一个函数。
模型看到的是能力契约;Router 负责识别意图;Registry 负责找到 Runtime;Handler 才负责进入具体执行流程。
到了这一层,模型的意图才真正变成系统动作。
以最常见的命令执行为例,exec_command 需要处理:
如果命令没有在当前等待窗口内结束,Codex 会返回一个可继续操作的 session_id。后续 write_stdin 并不会启动第二个 Shell,而是继续向原来的进程或 PTY 写入内容,并读取增量输出。
修改文件也是类似的。
apply_patch 不是让模型随便覆盖一个文件。它需要解析 Patch、核对上下文、验证目标路径,再把文件变化转换成可追踪的 Diff 和 Tool Result。失败时,错误会回到 Agent Loop,让模型重新读取文件或调整 Patch。
所以 Tool 和 Runtime 也不是一回事:
一个可以运行任意命令、读写本地文件的 Agent,如果只依靠 Prompt 约束,风险非常高。
Codex 把安全边界继续下沉到了执行层。当前 TurnContext 中就包含:
命令执行前,系统会结合 Tool 请求、配置和当前权限判断:
不同平台的底层实现并不相同。macOS 使用 Seatbelt;Linux 当前主要通过 Bubblewrap 构造文件系统沙箱,并配合 Seccomp 和 PR_SET_NO_NEW_PRIVS 限制进程与网络能力,Landlock 只保留为兼容旧版本的 Legacy 路径;Windows 则使用基于 Restricted Token 等机制的专用沙箱后端。但它们服务的是同一个目标:让模型无法仅凭自己的一句话越过系统授权。
安全规则不能只写在 System Prompt 里。Prompt 是给模型看的,Sandbox 才是模型绕不过去的执行边界。
把上面的部分重新串起来,“修复 APP-1427 的登录跳转问题”会经过下面这条链路:

实际过程可以简化成这样:
TurnContext,把适用的 AGENTS.md 规则和可用能力加入上下文;StepContext,向模型提供历史、环境、工具以及可用 Skill 的描述;这里没有哪个单独模块能够称为“Codex”,模型、Loop、Context、Tools、Runtime 和 Safety 缺一块,体验都会完全不同。
模型只负责根据当前输入生成下一步动作,Agent Loop 负责不断调用工具执行、观察和继续。模型本身无状态并不维护上下文,Agent 维护上下文并在每次请求时候把上下文发送给模型。
Tool Spec 是模型可见的契约,ToolRouter 和 ToolRegistry 负责路由,真正操作系统的是对应 Handler 和 Runtime。
模型输入除了对话,还包括项目规则、工作区状态、权限、工具定义、Skills、环境信息和经过压缩的历史。
这一篇只是先建立对Agent的全局认识,相当于一张地图,沿着这张地图继续往下追,还有几个更值得弄清楚的问题:
这些问题,才是理解 Codex 从“会调用工具的模型”走向完整桌面 Agent 的关键。
腾讯ima怎么把微信内容一键导入知识库?
黄金价格不断创新高!黄金稳定币XAU、PAXG市值达11亿美元
腾讯ima怎么创建共享知识库?
CC币价格预测(2026-2035):Canton币今日价格走势+长期价格预测
新浪互联网热点小时报丨2026年07月26日16时_今日实时互联网热点速递
新浪机器学习热点小时报丨2026年07月25日18时_今日实时机器学习热点速递
Celestia价格预测2026-2032:TIA币能否引领山寨币上涨行情?历史价格回顾
比特币(BTC)核心周期指标复刻历史走势 价格或跌破5.8万美元关键支撑位
新浪人工智能热点小时报丨2026年07月30日18时_今日实时人工智能热点速递
5000元起的鼠标哪个最值得入手?
蚂蚁庄园今日答案7月21日(今日已更新) 蚂蚁庄园今天正确答案是什么呢
WorkBuddy微信版怎么获得积分?
车载冰箱重置到出厂设置几步?
短剧《史上最强洪荒修为》剧情介绍
结婚家电首选:Leader懒人三筒Ultra热泵洗烘一体
腾讯ima知识库怎么分类管理?
短剧《仙人跳获透视,古玩玉器我全拿捏》剧情介绍
Windy卫星云图怎么看?云层变化识别技巧
kimi提示词专家使用方法新手指南
微博白梦妍网名大全女生(精选100个)
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc