简

以 Codex App Server 為例,理解 Agent Loop 之外的會話管理、持久化、工具執行與客户端交互,以及 Thread、Turn、Item 和雙向事件協議。

Agent Runtime:Agent Loop 之外還需要什麼?
19 分鐘
3787 字

Agent Loop説明了一次行動怎樣完成:模型提出工具調用,外部程序執行,結果回到模型。把這個循環做成 IDE 或桌面裏的開發助手,還需要回答另一組問題:會話保存在哪裏,工具進度怎樣顯示,用户審批怎樣影響執行,斷線以後怎樣恢復界面?

這些能力不屬於模型的一次推理,也不只是 UI。它們構成了 Agent 的運行時與接入協議。

OpenAI 的《Unlocking the Codex harness: how we built the App Server》介紹了 Codex 的具體實現。本文沿三條線展開:

  • 運行時分層: Codex Core 怎樣執行任務,App Server 怎樣向客户端開放能力。
  • 會話模型: Thread、Turn、Item 怎樣組織歷史與工作過程。
  • 交互協議: 請求、通知和服務器請求怎樣承載事件流、審批與多客户端接入。

一、Agent Runtime 怎樣組織執行與會話h2

以 Codex 為例,先區分執行核心和客户端邊界,再看工作過程怎樣組織成會話記錄。

1. Codex Core 與 App Server:分開執行核心與客户端接口h3

Codex Core 包含 Agent Loop,以及與執行相連的會話持久化、配置、認證、工具和擴展邏輯。它既是可複用的代碼庫,也能運行一條 Codex 會話。

App Server 則把這些能力組織成客户端可以使用的接口,同時作為長期運行的進程承載 Core 會話。

兩者不是“兩個不同的 Agent”:

層次主要職責
Client收集輸入,展示消息、工具進度與修改,取得用户決定
App Server處理協議請求,管理會話接入,將內部事件轉換為客户端通知
Codex Core推進 Agent Loop,執行工具,維護會話與運行狀態
執行環境承載實際文件、命令和外部資源

例如,用户在 IDE 中要求運行測試。IDE 不需要自行實現“測試失敗後再讓模型判斷”的循環;它提交請求,接收過程事件,展示輸出,並在需要時提供審批交互。

這裏複用的是一整套執行能力,而不只是共享一個模型名稱。相同的核心運行時可以連接不同界面,界面也不必依賴所有內部 Rust 類型。

2. Thread、Turn 與 Item:組織會話、輪次與工作記錄h3

三個概念分別組織持續會話、一輪工作與具體輸入輸出。

Thread:持續會話的容器

Thread 保存用户與 Agent 的會話歷史,可以包含多輪任務。它不是操作系統線程,也不是模型的一次推理。

例如,先要求“檢查項目測試”,再要求“修復第二個失敗”,兩次工作可以位於同一個 Thread 中,共用此前的對話背景。

Turn:用户輸入觸發的一輪工作

Turn 包含這次請求之後的 Agent 執行。一次“運行測試並總結失敗”,內部可能包含查看目錄、執行命令、分析輸出和生成答覆。

因此,一個 Turn 可以包含多次模型調用和工具執行。不能把 Turn 直接等同於一次 LLM inference。

Item:一項有類型的輸入或輸出

用户消息、Agent 消息、命令執行、文件修改或工具調用,都可以形成對應類型的 Item。不同類型攜帶不同數據:命令有輸出與退出狀態,文件修改有路徑和差異,消息有文本內容。

以一次請求為例:

Thread:项目测试排查
└── Turn:运行测试并总结失败
├── 用户消息
├── 命令执行:查看测试入口
├── 命令执行:运行测试
└── Agent 消息:说明失败与下一步建议

這裏展示的是包含關係,不是規定每輪都必須執行這些步驟。

協議還會發送計劃、Diff 更新或審批請求。Item 是工作記錄,事件是記錄變化與交互的通知方式;並非每條事件都代表新增一個 Item。

二、運行時怎樣與客户端持續交互h2

一項任務會產生多次更新,也可能需要用户決定。事件生命週期與雙向協議共同承載這些交互。

3. Item Lifecycle:流式更新與完成狀態h3

長任務不能只在結束時返回一段文字。命令可能運行很久,消息會逐步生成,文件差異也需要在過程中展示。

Item 的公共生命週期提供了幾個邊界:

  • item/started:這條記錄開始,界面可以創建對應位置。
  • 適用類型的 delta 事件:內容逐步到達,客户端更新顯示。
  • item/completed:該 Item 結束,以最終載荷確認狀態。
圖 1 · 一次 Turn 包含多個 Item;這裏只展開一條流式 Item
圖 1 · 一次 Turn 包含多個 Item;這裏只展開一條流式 Item Thread創建或恢復持久化會話 turn/start用户請求觸發本輪工作 item/started開始一條輸出或工具記錄 增量事件適用類型持續更新內容 item/completed這條 Item 形成最終狀態 turn/completed整輪結束,檢查最終 status 本輪全部工作結束

例如,測試命令開始後,界面先顯示運行狀態,再接收輸出增量,最後顯示退出碼和完整結果。接着 Agent 還可能繼續分析,所以一個命令 Item 結束,不代表整個 Turn 已經結束。

同理,收到 RPC 的成功響應,可能只説明啓動請求已經被接受。客户端還要繼續消費事件,直到這一輪到達終態。

當前官方文檔中,turn/completed還會攜帶 completed、failed 或 interrupted 等狀態。名稱中的 completed 表示生命週期結束,不能據此把失敗或中斷顯示為成功。官方文檔:Events

即使狀態是 completed,也只是説明這輪 Agent 工作正常結束;用户要求的軟件功能是否通過驗收,仍需要檢查實際產物。

4. Bidirectional JSON-RPC:請求、通知與審批h3

雙向通信有兩層含義:服務器可以持續通知客户端,也可以主動發出需要客户端回應的請求。

客户端請求與服務器響應

請求包含 method、params 和 id;響應使用相同的 id關聯結果或錯誤。例如,以下是簡化的 Thread 創建交換:

{"method":"thread/start","id":10,"params":{}}
{"id":10,"result":{"thread":{"id":"thr_example"}}}

這不是完整客户端程序,實際連接還需要先初始化。

服務器通知

通知沒有請求 id,用於傳達狀態變化,例如一輪開始、消息增量和工具進度。客户端不能只等待自己發送的請求響應,否則會錯過大部分工作過程。

服務器請求

需要審批時,服務器向客户端發出帶 id的請求,並等待決定。客户端展示待執行動作,取得用户選擇,再以相應響應繼續協議交換。

審批請求不是一句普通的聊天提示。若只把它渲染成文字,沒有發送協議響應,執行就可能繼續等待。反過來,也不能因為界面希望快速運行,就自動把所有審批視為允許。

一個簡化的連接順序是:

建立傳輸 → initialize / initialized → 創建或恢復 Thread → turn/start → 持續處理通知與服務器請求 → 檢查 Turn 終態

App Server 使用保留請求、響應和通知形狀的 JSON-RPC 變體,線上消息省略 jsonrpc版本字段。原文主要講 JSONL over stdio:一行一條 JSON。協議規定交互含義,stdio 則是傳輸方式,二者不是同一個概念。

5. Message Processing:轉換協議消息與管理 Core 會話h3

原文介紹了四個主要組件:

組件職責
stdio Reader讀取客户端消息並交給處理層
Codex Message Processor將 RPC 請求轉換為 Core 操作,將內部事件轉換為客户端協議
Thread Manager創建、查找和管理 Thread 對應的 Core 會話
Core Threads運行各自的 Agent 工作併產生更新

一條請求可以這樣理解:Reader 取得消息,Processor 識別操作,取得對應 Thread 的會話句柄,再提交給 Core。執行過程產生的更新返回處理層,被整理為客户端可以理解的通知。

這裏重要的不是組件名稱,而是內部執行結構與外部協議之間存在轉換邊界。Core 可以演進,客户端仍然圍繞 Thread、Turn、Item 等穩定概念組織界面。

多個 Thread 也需要分別關聯請求與更新。某條測試輸出屬於哪個項目會話、哪個 Turn、哪個命令,必須由標識關聯,不能只按消息到達順序塞進當前打開的聊天窗口。

配置、認證和工具策略同樣需要明確歸屬:客户端可以提供交互入口,真正的執行仍由運行時按配置處理。App Server 不等於繞過策略的一條命令通道。

三、會話怎樣恢復,運行時怎樣被複用h2

持久化保存交互背景,客户端接入則決定這套執行能力怎樣進入不同產品。

6. Thread Persistence:創建、恢復、分叉與歸檔h3

Thread 可以創建、恢復、分叉和歸檔。這些操作管理的是會話及其歷史,不自動完成代碼倉庫的對應操作。

  • 恢復會話讓系統重新取得已存儲的交互背景。
  • 分叉會話建立新的歷史分支,不天然等於創建 Git 分支或複製工作目錄。
  • 歸檔會話改變其存放與展示狀態,不等於刪除項目文件。

這也區分了它與第十三篇的項目交接:

狀態層次保存什麼不能單獨保證什麼
會話持久化輸入輸出、執行歷史與交互生命週期當前代碼與歷史描述一致
項目交接需求狀態、進度説明、代碼版本和驗證依據客户端完整恢復運行時事件

歷史裏曾經顯示“測試通過”,當前文件可能已經改變。因此,恢復 Thread 可以減少重新解釋背景的成本,但繼續開發前仍應核對真實工作區。

持久化也不等於恢復一個凍結的操作系統進程。正在運行的命令、外部連接和服務資源,具有自己的生命週期,不能僅從聊天記錄推斷它們仍然存在。

7. Client Integration:連接 IDE、桌面、Web 與 CLIh3

本地 IDE 與桌面

本地客户端可以啓動 App Server 子進程,通過雙向通道交互。界面負責編輯器集成、進度和審批展示,運行時負責執行與會話。

Web

原文的 Web 路徑多了一層後端 Worker:它準備包含工作區的容器,在其中啓動 App Server,再把事件經後端流向瀏覽器。瀏覽器並不是直接讀取遠程進程的 stdin / stdout。

由此,頁面關閉與執行進程結束可以分開管理。但能否持續工作,仍取決於服務器與任務生命週期,不是所有客户端斷開後都天然永久運行。

CLI

CLI 與 IDE 可以複用同一個 Core,但不代表每個版本都使用完全相同的接入路徑。原文發佈時,TUI 仍有直接連接 Core 的歷史實現,接入 App Server 被列為後續改造方向;不能把這個計劃當作當時已經完成的架構。

對自己的集成,先看需要哪一種控制範圍:

方式適合的需求
App Server豐富會話界面、持續事件、審批和運行時能力
Codex Exec非交互腳本與一次性自動化任務
Codex SDK使用庫接口編程控制,減少自己處理協議的工作
Codex MCP Server在已有 MCP 工作流中把 Codex 作為可調用能力

這不是功能優劣排名。需要豐富交互,就必須承擔狀態與事件處理;只需要一次任務結果,則沒有必要先搭完整客户端。

我對 App Server 的理解是:它把 Agent 從“能夠執行的循環”變成“能夠被產品連接、觀察和控制的運行時”。會話模型、生命週期與雙向協議,正是這一層增加的工程內容。

幾個關鍵問題,串起運行時、會話與客户端協議h2

App Server 與 Codex Core 有什麼區別?

Core 實現並運行 Agent 邏輯;App Server 承載會話並提供客户端協議,將請求與內部執行、事件更新連接起來。

Turn 等於一次模型調用嗎?

不等於。它是用户請求觸發的一輪工作,內部可以包含多次推理、工具執行與多個 Item。

收到 item/completed,可以結束加載狀態嗎?

可以結束該 Item 的狀態,但不能據此結束整輪。還需要等待 Turn 的終態,並檢查成功、失敗或中斷。

為什麼只實現流式文本還不夠?

Agent 還會運行工具、修改文件、請求審批並報告錯誤。客户端需要區分這些結構化交互,而不只是拼接回答文字。

為什麼服務器也要發送請求?

因為運行過程可能需要用户決定或補充輸入。服務器請求建立可回應的協議關係,運行時可以等待決定再繼續。

恢復或分叉 Thread,是否會恢復或分叉代碼?

不自動成立。會話歷史與文件、Git 狀態是不同層次,應分別管理和核對。

不同界面複用 Harness,是否必須顯示相同 UI?

不必。它們複用執行語義和協議,仍可採用適合終端、編輯器或瀏覽器的交互表達。

參考資料h2