一个模型可以很聪明,但一个 Agent 要在真实环境里连续工作几十分钟、几小时,甚至跨越好几个上下文窗口,光靠模型「想得对」是不够的。它还得能运行、暂停、观察、恢复、协调、验证和记忆。这些事不归模型管,归 Harness 管。
这篇是我和 ChatGPT 在一场很长的技术讨论里一起写出来的。起点是读 Codex 开源的 Harness,后来越聊越深,变成一个更大的问题:如果把 Agent 当成能独立承担工作的数字团队,该怎么给它设计制度、知识体系和运行系统?
先说清楚证据等级。文中的内容分三种:源码可证的,是 2026 年 10 月能检索到的 Codex 开源文件和 OpenAI 官方工程文章里写明的;架构抽象,是为了讲清楚而画的逻辑图,不是源码依赖图;设计提议,是我们在讨论里推出来的方案,Codex 并没有原生实现。三种我会逐处标明。代码会更新,具体实现以当时版本的仓库为准。
如果只记一句话:LLM 提供开放性智能,Harness 提供可执行的运行结构,两者组合起来,才是真正可用的 Agent。
Harness 不是一段 Prompt
Harness 是模型和外部世界之间的执行、状态与控制系统。
假设用户说:「找出登录模块的 Bug,修好它,并保证测试通过。」LLM 能判断该看哪个文件、生成补丁、解释错误;但读文件、起进程、批权限、写日志、处理中断,都是运行时在做。
Harness 里其实有两条运行路径:
- 模型驱动:需要语义判断时,Harness 调 LLM,让它提出工具调用或给出最终答复。比如模型输出
read_file("login.ts"),Harness 验权、读取、把文本交回去。 - 程序驱动:用户点了停止、检测到超时、要限制并发、要把状态写进 SQLite,Harness 直接跑写好的程序逻辑,不需要再问 LLM。比如收到中断事件,用 CancellationToken 取消任务并清理,这一步不产生任何模型调用。
所以准确的说法是:Harness 是一个嵌入了 LLM 的软件系统,而不是一个所有代码都由 LLM 临时生成的系统。这是我们讨论里反复确认的一条。
「Agent Runtime」和「Agent Harness」有大量重叠。可以把 Runtime 看作偏执行的内核,Harness 看作包含执行、上下文、工具、安全与协作的整体;这只是方便设计的区分,业界没有统一定义。OpenAI 官方对 Harness 的描述也是围绕 Agent Loop 及相关逻辑展开的 [1]。
Codex 是怎么拆开的
Codex CLI 的 Rust workspace 有很多 crate,数量随主分支变化,不值得死记。值得记的是几条关键边界:codex-core 承载主要的 Agent 逻辑,codex-protocol 定义内部数据协议,codex-api 对接模型,codex-app-server 给客户端提供交互协议,另外还有持久化、TUI、沙箱和补丁等组件 [2]。
一个系统既有桌面端又有 CLI 时,最不该做的就是给每个界面各写一套 Agent Loop。App Server 把底层能力用双向 JSON-RPC 暴露出去:一条客户端请求可以对应很多条流式事件,服务端还能反过来向客户端请求审批。这样「界面」和「Agent 的执行生命周期」就分开了 [1]。
还有个很 Unix 的细节:codex-arg0。程序根据启动时用的可执行文件名进入不同入口,比如 apply_patch 和 Linux 沙箱 helper 会被分派到不同逻辑 [3]。它不是在「告诉 LLM 自己是谁」,而是在进程层面做多入口复用。
Task、Turn、Sampling:一条指令怎么变成几十步工作
用户只提一次需求,Codex 会持续请求模型、执行工具、观察结果,直到这一轮结束。
常见的说法是 Task Loop → Turn Loop → Sampling Loop,方便建立直觉,但源码里并不是三个同名的 while 循环整齐嵌套。更准确地说:Session 的 submission loop 负责分发操作;SessionTask 管任务的生命周期;run_turn 在一轮之内连续发起模型采样请求 [4]。
| 概念 | 回答的问题 | 主要由谁控制 |
|---|---|---|
| Session / Task | 任务何时开始、被取消、结束? | Harness 的任务机制 |
| Turn | 这一轮还要不要继续? | 模型信号 + Harness 状态 |
| Sampling | 这次模型请求产生了哪些响应项? | 模型生成,Harness 接收处理 |
| Tool Call | 执行哪个工具、什么参数? | 模型提议,Harness 执行 |
拿修登录 Bug 举例,一个 Turn 里可能发生这样五次 Sampling(演示轨迹,真实的 Codex 不会预先定好次数):
| Sampling | 发生了什么 | 模型的输出 |
|---|---|---|
| 01 分析请求 | 模型首次拿到任务和项目环境,决定先读登录模块 | read_file("src/login.ts") |
| 02 定位问题 | Harness 把文件内容交回,模型发现验证流程漏了一个异常分支 | read_file("src/auth.ts") |
| 03 生成修复 | 模型基于新信息提出补丁,Harness 检查作用范围并应用 | apply_patch({ ... }) |
| 04 跑测试 | Harness 执行测试、收集退出码和日志,模型下一次会看到真实反馈 | run("npm test -- login") |
| 05 结束 | 测试通过且没有待处理指令,模型给出最终答复,Harness 结束这个 Turn | Final Message · turn/completed |
用伪代码写出来是这样(不是 Codex 原样代码):
async fn run_turn() {
loop {
let context = build_model_input();
let result = run_sampling_request(context).await;
if result.has_tool_calls() {
execute_and_record(result.tool_calls()).await;
}
if !needs_follow_up(result, pending_input) {
break;
}
}
}
Sampling 的次数谁决定?不是主 Agent 事先分配「十次」,也不是用户指定。LLM 在一次请求里产生工具调用等响应,Harness 执行后,根据 needs_follow_up、待处理输入、Hook 和停止条件,动态决定要不要再请求一次 [4]。
还有一点:Task 结束不等于任务成功。模型说「我修好了」和测试真的通过,是两件事。后者需要 Harness 去读外部的验证证据。在高可靠系统里,测试、验收和资源预算都应该独立于模型的自我声明。
并行执行,有序收集
一次 Sampling 里,模型可以给出多个 Tool Call。能不能并发,取决于工具和执行环境,而不是「每个调用都要再 Sampling 一次」。
模型一次要读三份互不相关的文件,可以同时执行;两个调用都要改同一条数据库记录,就得小心。Codex 的工具运行时用读写锁等机制,区分可以并发的工具和需要独占执行的工具 [5]。
这里顺便把 Tool 和 Skill 分开:Tool 是已经实现好的执行能力,比如读文件、跑 Shell;Skill 更像工作手册,告诉模型按什么流程使用现有工具,有时附带脚本、模板和参考资料。
一个很有用的分界:LLM 可能输出 read_file(path="login.ts") 这样的结构化调用,根本不需要写代码;也可能生成一条 Shell 命令,交给 Harness 已有的命令执行工具。至于取消任务、写日志、处理超时,通常是 Harness 自己的 Rust 程序在做。
Op / Event:客户端怎么看见 Agent 在干什么
Codex 不会等任务全部做完,才把一个巨大的结果一次性返回。客户端给 Harness 发操作(Operation),Harness 持续给客户端发事件(Event)。
要注意有两层协议:Rust core 内部的 Op / Event 类型,和 App Server 对外的 JSON-RPC 方法、通知。两者语义相关,但不是同一套名字 [1]。
事件驱动也是 Multi-Agent 协作的基础。子任务完成、工具失败、审批请求、用户中断,这些变化先被运行时接收、记录、分发;只有需要重新判断时,才触发新的模型推理。
SubAgent:什么时候拆,什么时候等
Sampling 是同一个 Agent 内部的推理迭代;SubAgent 是另一个有独立上下文和工作流程的执行者。并行不等于一定更快。
假设一个网站要做数据库、后端、前端三块。全交给主 Agent 串行做很直观,但慢;拆给三个 SubAgent 可以并行,但接口约定和写入范围必须先说清楚。关键不是先决定「开三个 Agent」,而是先看任务的依赖 DAG 和共享资源冲突。
Codex 的 Multi-Agent 规格里有 spawn_agent、send_message、followup_task、wait_agent、interrupt_agent 等工具。值得注意的是,V2 的 wait_agent 可以等 Mailbox 更新、用户新输入或超时,并不一定要等所有子 Agent 做完 [6]。
| 情况 | 建议机制 | 为什么 |
|---|---|---|
| A、B 各自独立读日志 | 并行 | 没有写冲突,可以同时推进 |
| 后端依赖还没定的 Schema | 先定契约再并行 | 避免基于过时结构返工 |
| 三个 Agent 都要改同一个文件 | 协调写入范围 | 避免补丁互相覆盖、更新丢失 |
| 主 Agent 没有独立工作,又依赖结果 | wait_agent |
不需要靠 LLM 反复轮询「做完了吗」 |
Mailbox 是协作机制,不是共享大脑。send_message 发消息但不主动触发新 Turn;followup_task 在目标空闲时可以触发后续 Turn。这让主 Agent 可以暂停自己的 Sampling,而 SubAgent 继续干活 [6]。
子 Agent 继承多少上下文,背后的抽象是上下文隔离:完整继承便于接着做,但有成本和污染;少继承利于聚焦;从空白上下文启动最干净,但需要更完整的任务描述。具体选项和默认行为以当时版本的工具协议为准,不要把某一版的「三档继承」当成稳定的官方接口。
我们得出的项目管理原则:独立任务并行,强依赖任务串行;主 Agent 有关键路径上的活就继续做,没活可做才等待。子 Agent 发现会影响别人的异常,先报告主 Agent,由它决定是否重新分配;运行时负责传消息和执行中断。
World State:让模型知道世界变了什么
几十次 Sampling 之间,AGENTS.md、权限和环境配置都可能变。怎么把变化同步给模型,又尽量不破坏已有上下文?
Codex 的 WorldStateSection 给不同类型的模型可见状态定义了稳定 ID、Snapshot 和 render_diff。Harness 可以对照上一次快照,判断哪些内容需要更新 [7]。这里的 diff 是结构化的状态比较和差异化呈现,不等于所有变化都只给模型发几行文本。
为什么这和 Prefix KV Cache 有关
两次模型输入开头的 token 序列完全一样时,服务端有机会复用之前算过的前缀 KV Cache。保留旧消息、在尾部追加新信息,通常比反复改开头更容易命中缓存;但前缀相同只是必要条件,不保证一定命中 [8]。
所以:World State 优化状态同步,Prompt Caching 优化重复前缀的计算,Compaction 解决上下文增长。三者不是替代关系。
上下文满了之后怎么办
这是我觉得整套设计里最有意思的部分:从「压缩过去」慢慢转向「保存状态,重新组织工作集」。
第一条路:交接式 Compaction
Codex 的压缩提示词要求模型为接手任务的另一个 LLM 写一份交接摘要:保留进度、关键决策、约束、剩余步骤和重要引用 [9]。它要回答的不是「之前聊了什么」,而是「下一位执行者怎么接着干」。
区别很具体。「数据库模块基本完成」是摘要;「改完了哪些文件、哪条测试失败、失败原因是什么、哪些用户约束不能违反」才是能接着干的交接。两者都可能丢细节,但交接至少明确地为「任务恢复」这个目标做了优化。
第二条路:Token Budget,直接开新窗口
Codex 的 compact_token_budget.rs 里明确有这样一种模式:跳过模型或服务端的摘要压缩,调用 start_new_context_window 装一个新窗口。它仍然挂在 Compaction 的生命周期里,好复用 Hook 和事件机制;这不代表传统 Compaction 被取消了 [10]。
三种材料各管一件事:Thread Hint 是简短的恢复线索;Notes 是模型整理的当前任务笔记(进度、决策、待办);History 是可追溯的原始会话和工具记录。在相关扩展可用的场景里,新窗口先拿到恢复提示,再按需读细节。具体接口、启用条件和保留策略因版本和配置而异,不能假设每个 Codex 产品形态都有同样的工具。
这套方案最重要的限制也在这里:「历史还在」不等于「模型会想起来」。模型没写进 Notes、也不知道该去 History 搜什么,信息虽然还在,却恢复不了。问题从存储变成了检索决策。
上下文容量的问题可以靠外部存储缓解;但「什么时候该想起去找什么」,仍然是智能系统的核心问题。
长期记忆:不只记住对话,还要沉淀经验
Codex Memories 的 README 介绍了一个两阶段机制:符合条件的根 Session 启动时,后台异步触发记忆任务。Phase 1 读取符合条件的 Rollout,让模型提取 raw_memory 和 rollout_summary,存进状态数据库;Phase 2 在全局锁保护下挑选候选、整理文件工作区,再由专门的 Consolidation Agent 产出更高密度的长期记忆 [11]。
SQLite、Markdown 和 KG 是什么关系
要分清「记录的格式」和「查询的数据库」。Codex 的记忆不是全存成 Markdown:状态数据库承载结构化的任务记录和元数据,Rollout 历史有自己的持久化格式,MEMORY.md 这类文件放对 LLM 友好的长文知识。Markdown 是表达层,SQLite 是嵌入式关系数据库,Neo4j 是图数据库。KG 是一种知识模型,并不要求一定用 Neo4j。
| 存储 | 适合放什么 |
|---|---|
| SQLite · 本地状态 | 单用户、本地运行时的状态索引、元数据与事务;嵌在应用里,不需要独立数据库服务 |
| Markdown / 文件 · 叙述记忆 | 复杂解释、工作交接、可复用流程,人能读的 Notes 和 Skills |
| PostgreSQL · 共享服务(提议) | 服务端共享的 Agent 平台:事务、版本、集中权限、并发写入;KG 也可以用关系表表示 |
| Neo4j · 图(可选) | 多跳关系、路径查询、复杂图遍历真正成为核心需求时再评估 |
不是「一百万用户就必须上 PostgreSQL」。一百万个用户各自在本地跑独立的 Agent,每人一个 SQLite,完全合理。真正决定选型的,是状态要不要集中共享、跨机器访问、高并发更新。
长期记忆该知道什么
三类:稳定的用户和项目偏好、能复用的任务经验、已经验证过的环境事实。但每一条都要带有效范围:适用于哪个项目、哪个环境、什么时间、什么版本。一次探索里的猜测,不能自动升级成组织制度。
Codex 的巩固过程会参考 usage_count、last_usage 和生成时间等信号,也允许把稳定的重复流程进一步提炼成 Skill [11][12]。但要记住:被反复使用,不等于被证明正确。
让 KG、RAG、上下文窗口和 Agent Loop 接起来
这四个概念不是四个互相竞争的产品,而是四段各司其职的数据流。知识图谱维护实体和关系;RAG 是检索增强的过程,可以查文档、向量、结构化数据库或图;上下文窗口是当前这次模型请求能直接看到的工作集;Agent Loop 决定什么时候问模型下一步。把它们接起来的,是 Harness 里的检索策略和上下文构建器。
不是每次 Sampling 都要全量 RAG
常见误区是「每次调模型之前,先把整个知识库检索一遍」。这会带来额外的延迟、成本和上下文噪声。我们更倾向三种触发方式:
| 触发方式 | 什么时候检索 | 谁决定 |
|---|---|---|
| 强制检索 | 新任务、关键约束、跨窗口恢复 | Harness 的确定性规则 |
| 事件驱动 | Schema 变化、依赖过期、测试失败 | 状态事件 + 查询策略 |
| Agent 驱动 | 模型碰到未知问题,需要更多背景 | LLM 自己调用检索工具 |
放到一个任务的不同阶段:
| 阶段 | 检索怎么做 |
|---|---|
| 开始规划 | 强制检索:读取生效的规则、任务目标、关键实体关系和当前依赖;未知部分让 LLM 自己探索 |
| 持续执行 | 跳过或选择性检索:World State 和相关版本没变就不重复检索,只在出现新文件、Schema 变更等事件时针对性查询 |
| 测试失败 | 事件驱动 + Agent 驱动:优先查错误码、相关的历史失败和版本变化;模型认为还缺信息,再自己扩大范围 |
| 跨窗口恢复 | 强制的检查点检索:必须恢复任务目标、最新决策、关键约束、待办和必要的证据索引,再按需查 Notes / History |
KG 怎么参与调度
假设 KG 里记着:Backend API 依赖 Schema v1。Agent A 把 Schema 升到 v2,Harness 可以查依赖链,生成一个 DEPENDENCY_OUTDATED 事件交给主 Agent 处理。这时 KG 就不只是回答问题的资料库,而是依赖检查和任务协调的事实依据;前提是关系、版本和来源都经过验证。
MATCH (service)-[dep:DEPENDS_ON]->(schema)
WHERE schema.id = 'schema_v1'
AND dep.status = 'active'
RETURN service.id, dep.valid_from, dep.evidence_id;
这里有个很重要的认识:Agent 的检索应该感知状态,而不只是感知问题。模型此刻是在规划、修错、整合子任务还是恢复上下文,决定了它该召回哪一类信息。
记忆不只是保存,还要有事实、证据和版本
「Agent 说过什么」和「组织现在确认什么是真的」,绝不能混为一谈。
设想 Agent A 从 staging.yaml 读到最大连接数是 100,Agent B 在生产实例上查到是 200。两者未必冲突,可能只是环境不同。就算都来自生产环境,也要分清配置文件里的声明值和数据库正在生效的运行值。
我们建议把知识状态拆开:proposed、verified、conflicted、superseded、invalidated。还要区分 observed_at(什么时候观察到)和 valid_from(事实从什么时候生效)。一条事实被频繁引用,不会因此自动变成对的。
{
"entity": "production.db.connection_pool",
"attribute": "max_connections",
"value": 200,
"status": "verified",
"observed_at": "2026-10-27T10:02:00+08:00",
"valid_from": null,
"evidence": "sql-query/run-182",
"version": 2
}
这里涉及两种不同的「一致性」:数据库事务保证并发写入不互相覆盖;证据验证保证写进去的事实可信。前者可以用 SQLite / PostgreSQL 的事务、版本号、乐观锁解决;后者不是 ACID 能自动解决的。
所以我们选的是混合记忆,一条知识有三种视图:
| 视图 | 回答的问题 | 放什么 |
|---|---|---|
| 叙述 Notes | 当时为什么这么决定? | 复杂语义、背景与经验 |
| 结构化 KG | 现在什么成立? | 实体、关系、时间和版本 |
| 事件日志 | 究竟发生了什么? | 不能被静默覆盖的证据 |
自然语言 Notes 不需要全转成三元组;KG 也不该靠 Markdown 去模拟关系数据库。让自然语言保留复杂语义,让结构化事实支撑检索、验证和依赖查询,让原始事件负责追溯。
Harness 像制度,主 Agent 像项目经理
一个好的 Agent 系统和一个运作良好的远程团队,解决的是同一个组织问题:自治和协调怎么同时成立?
我们聊过远程办公团队的例子。如果所有人完全自由安排时间、没有任何规矩,协作会乱;如果把每个人的每一分钟都规定死,又会扼杀自主性。更好的方式是保留较大的个人自由,同时明确少量必须协同的规则,比如每天四小时的核心协作时间。
放到 Agent 世界里,可以把固定时间的协调换成事件驱动的检查点:任务开始时对齐目标;依赖变化时通知;关键操作前检查权限;跨窗口时保存进度;完成时验收结果。
强约束边界,弱约束过程,强验证结果。
外部知识不能自动获得指令权限
RAG 和外部文档里的 Prompt Injection 也是要面对的:一份恶意文档可能假装拥有更高优先级,诱导模型调用危险工具。可信的 Harness 不能只靠「请忽略恶意指令」来防御,而要把外部知识和控制策略隔离开,由真实的权限、工具授权和沙箱在执行的位置施加硬约束。
这也影响记忆:从不可信文档里提取的内容可以记成候选事实,但不能自动变成公司的安全制度,更不能修改 Harness 的执行权限。一句话:不可信的知识可以参与决策,但不能授予权限。
从 Memory 到 Skill 再到 Tool:Agent 怎么真正积累能力
一套系统的进步,不该只是「记住上次干过什么」,还应该减少下一次的重复推理。
假设 Coding Agent 每周都要检查一次数据库 Schema 兼容性。第一次,它可能临时写个 Python 比较脚本、读日志、生成报告;下一次又重复差不多的步骤。随着流程被证明可靠,可以先把它沉淀成工作步骤,再把其中确定的部分固化成可复用的代码。
Codex 的记忆巩固提示词里已经有「把有价值的、重复出现的流程提炼成 Skill」的指导 [12]。但「模型写了一个 Skill」不等于「系统安全地部署了一个新工具」:新 Tool 涉及代码测试、注册、授权、版本控制和可回滚的发布。
| 情况 | 更合适的形态 |
|---|---|
| 某个项目的偏好、重要业务约束 | Memory / KG |
| 需要 LLM 参与判断的多步流程 | Skill / SOP |
| 稳定的输入输出转换、格式校验 | 已验证的 Tool |
| 经常变化、还没有成熟流程的问题 | 继续交给前沿大模型探索 |
这正好对上我一直认的「异构智能」分工:确定的世界交给传统程序,已经定义清楚的概率判断交给专门的决策模型,开放世界的问题交给前沿大模型;Harness 管它们之间的协作和边界。
把所有组件放进一张图
这不是 Codex 官方的「终极架构」,而是我们从源码出发、结合组织管理和知识治理推出来的一张蓝图。
如果从零实现,最小可行版要什么
第一版不需要五种数据库,也不需要一上来就做复杂的 GraphRAG。先把 Agent Loop、事件日志、明确的权限、状态管理和可恢复的任务检查点做可靠;记忆用 Markdown + SQLite 就能起步。真出现共享访问和复杂事务需求,再考虑 PostgreSQL;复杂多跳图查询真成了瓶颈,再评估 Neo4j。
- 可取消的 Task 与持续的 Turn
- 工具调用与结果留痕
- 上下文增量更新与压缩恢复
- 可复用的 Notes 与历史索引
- 规则和知识之间的信任边界
- 清晰的任务验收与测试
衡量它有没有变好,不是看模型的回答是否更流畅,而是看任务最终成功率、关键约束遗漏率、记忆召回质量、重复工作比例、延迟和实际成本。这是下一阶段真正值得做的实验。
未来的 Agent 可能更像一个不断学习的组织:有自己的工作记忆、组织知识、工具平台和协作纪律;有时要自主探索,有时必须守规矩。最好的 Harness 不会替模型想所有事,而是让智能在清晰的边界里持续产生价值。
速查表
| 术语 | 一句话定义 |
|---|---|
| Harness | 承载模型、工具、状态、控制和执行逻辑的整体系统 |
| Agent Runtime | 偏执行引擎的叫法,现实中和 Harness 高度重叠 |
| Task / Session | 任务的生命周期,以及它所在的会话环境 |
| Turn | 由一次用户请求驱动的一段连续 Agent 工作 |
| Sampling | 对模型的一次生成请求,以及对响应的处理 |
| Tool Call | 模型提出的调用请求,由 Harness 的工具实现执行 |
| SubAgent | 有独立执行上下文、可以被委派任务的 Agent |
| Mailbox | Agent 之间异步通信与唤醒的机制 |
| World State | 模型需要知道的、类型化的运行环境状态及其更新 |
| KV Cache | 对相同输入前缀复用的注意力中间状态 |
| Compaction | 通过缩减或重建上下文,让任务继续跑下去 |
| Thread Hint | 跨窗口继续任务的简短恢复线索 |
| Notes / History | 可读的工作笔记 / 可追溯的执行记录 |
| KG / RAG | 结构化的知识网络 / 从各种来源检索来增强模型的过程 |
| Memory Consolidation | 把分散的会话记忆去重、更新、巩固成长期知识 |
| Skill / Tool | 复用工作方法 / 复用已实现的程序能力 |
参考资料
本文是围绕开源技术的独立解读,不代表 OpenAI 官方的产品文档。文中的图都是重新绘制的,代码只用了简化片段或伪代码。源码更新后,以对应版本的仓库为准。
- OpenAI · Unlocking the Codex harness: how we built the App Server
- Codex Rust workspace · Cargo.toml
- codex-arg0 · 多入口执行分派
- codex-core · run_turn 与 sampling 逻辑
- codex-core · 工具调用的并行执行
- Codex · Multi-Agent 工具与协作说明
- World State · Section / Snapshot / Diff
- OpenAI API · Prompt Caching
- Codex · Compaction 交接提示词
- Codex · Token Budget 新窗口实现
- Codex Memories · Phase 1 / Phase 2 说明
- Codex · Memory Consolidation 提示词
- World State · AGENTS.md 状态更新
- OpenAI · Unrolling the Codex agent loop
- OpenAI Developers · Codex as a platform