<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>App-Server on Codexer</title><link>https://codexer.com/tags/app-server/</link><description>Recent content in App-Server on Codexer</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 18 Aug 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://codexer.com/tags/app-server/index.xml" rel="self" type="application/rss+xml"/><item><title>逆向 Codex 的网页搜索：原来搜索和推理早就分家了</title><link>https://codexer.com/posts/2026-08-18-codex-standalone-search-reverse-engineering/</link><pubDate>Tue, 18 Aug 2026 09:00:00 +0800</pubDate><guid>https://codexer.com/posts/2026-08-18-codex-standalone-search-reverse-engineering/</guid><description>&lt;p&gt;想象一个挺常见的场景：你在用 Gemini、Claude，或者本地跑的一个小模型写代码，想让 agent 顺手搜一下「Rust 最新版本改了什么」。结果发现，要么这模型根本没有联网搜索能力，要么搜出来的结果又慢又贵，还得搭进去一堆推理 token。这时候有人干了一件很「硬核」的事：他没有自己造一个搜索引擎，而是把 OpenAI Codex 自带的搜索接口逆向了出来，做成一个插件，让任何模型都能直接调它，而且不花一分推理 token。&lt;/p&gt;
&lt;p&gt;这个项目叫 pi-gpt-search，作者是 mateusdcc。它的目标很直接，给 Pi 这个编码 agent 用的各种模型（Gemini、Claude、本地模型）都接上实时网页搜索。但真正有意思的，不是这个插件本身，而是它逆向过程中挖出来的那个事实：Codex 的网页搜索，根本不是一个「模型调用搜索工具」那么简单的设计，后端其实藏着一个完全独立的搜索服务。&lt;/p&gt;
&lt;h2 id="意外的发现搜索是带外返回的"&gt;意外的发现：搜索是「带外」返回的&lt;/h2&gt;
&lt;p&gt;作者的第一步，是跑了一条命令 &lt;code&gt;codex app-server generate-ts&lt;/code&gt;。这条命令会把 Codex 本地的 app-server 协议，直接生成成 TypeScript 类型定义。然后他在这些类型里搜 &amp;ldquo;WebSearch&amp;rdquo;，找到了两个结构：WebSearchItem 和 WebSearchAction。&lt;/p&gt;
&lt;p&gt;其中 WebSearchItem 的文档注释写得很直白，大意是「由独立网页搜索带外返回的结构化搜索结果」。关键词是「带外」（out-of-band）：搜索结果不是从模型的推理流里长出来的，而是从旁边一条独立的通道送进来的。&lt;/p&gt;
&lt;p&gt;这句话信息量很大。它说明 Codex 的搜索是一个独立组件，有自己的协议、自己的端点、自己的返回格式，跟模型推理是两套系统。模型在推理过程中，只是「订阅」了这个独立服务的返回结果。&lt;/p&gt;
&lt;h2 id="逆向四步从类型定义到能用的接口"&gt;逆向四步：从类型定义到能用的接口&lt;/h2&gt;
&lt;p&gt;完整的手法分四步，每一步其实都很值得学。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步，生成类型。&lt;/strong&gt; &lt;code&gt;codex app-server generate-ts&lt;/code&gt; 把内部协议导成 TypeScript。这招的巧妙在于，很多编码 agent 为了方便二次开发，本身就暴露了协议 schema，等于主动递了一份地图给你。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步，挖字符串。&lt;/strong&gt; 用 &lt;code&gt;strings&lt;/code&gt; 扫一遍 Codex 的 Rust 编译产物，grep 关键词 &amp;ldquo;search&amp;rdquo;、&amp;ldquo;alpha/search&amp;rdquo;、&amp;ldquo;backend-api&amp;rdquo;。编译后的二进制不会说谎，常量字符串都嵌在里面。于是他捞到了几个关键标识：standalone_web_search、codex.web_search.results、alpha/search，还有基础域名 chatgpt.com/backend-api/。把这些拼起来，就得到了端点的候选地址：&lt;code&gt;https://chatgpt.com/backend-api/codex/alpha/search&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步，用错误信息「问路」。&lt;/strong&gt; 这是最精彩的一步。他没有瞎猜参数，而是不断发「错误」的请求，让后端自己的校验报错来告诉他正确答案。先发 &lt;code&gt;{q: &amp;quot;Rust&amp;quot;}&lt;/code&gt;，后端回 400，提示「缺参数 id」。加上 id，又回 400，提示「缺参数 model」。补上 model，这次回 200，但提示「不能给 web.run 发空的 calls」。再往下试，把 command 改成 commands，后端甚至贴心地提示「你是不是想说 commands」。最后把 search_query 从字符串改成数组，请求就通了。后端的报错，成了一份活的参数文档。&lt;/p&gt;</description></item><item><title>Codex 给对话加了「回滚」：thread/revert 能做什么、不能做什么</title><link>https://codexer.com/posts/2026-08-17-codex-thread-revert/</link><pubDate>Mon, 17 Aug 2026 09:00:00 +0800</pubDate><guid>https://codexer.com/posts/2026-08-17-codex-thread-revert/</guid><description>&lt;p&gt;想象一个很常见的场景：你让 Codex 帮你重构一个模块，它一口气干了十几轮，改动了十几个文件。聊到一半你突然意识到，第五轮的思路才是对的，后面全都跑偏了。你想回到第五轮那个节点重新出发，但又不希望它已经改过的那些文件被一并抹掉。这时候你最想要的是什么？一个「对话回滚」按钮，能把聊天历史退回到某个时间点，重新开始。&lt;/p&gt;
&lt;p&gt;这样的能力，Codex 最近真的做出来了。8 月 13 日，OpenAI 在 codex 仓库合入了一个提交（#38440），给 app-server 协议加了一个实验性的 &lt;code&gt;thread/revert&lt;/code&gt; 请求。它的作用一句话就能讲清：把一个加载中的分页线程的持久化历史，替换成某个 turn 之前的「前缀」，同时保留线程 ID 不变。&lt;/p&gt;
&lt;h2 id="先搞清楚线程和分页线程是什么"&gt;先搞清楚「线程」和「分页线程」是什么&lt;/h2&gt;
&lt;p&gt;在 Codex 里，thread（线程）可以理解成一次和 agent 的完整对话会话。你启动一个任务，它会经历很多轮 turn（轮次），每一轮里 agent 会读文件、写文件、跑命令，然后给你一个结果。所有这些轮的记录，构成了这个 thread 的历史。&lt;/p&gt;
&lt;p&gt;app-server 则是 Codex 桌面端等客户端与 Codex 后端之间的协议层，走的是 JSON-RPC。客户端通过 &lt;code&gt;thread/resume&lt;/code&gt; 恢复一个会话，通过 &lt;code&gt;turn/start&lt;/code&gt; 发起新的一轮，诸如此类。也就是说，你想回滚对话，最终都要落到这个协议层的某个请求上。&lt;/p&gt;
&lt;p&gt;这里有个背景值得一提：Codex 正在把线程从「内存态」迁到「持久化 + 分页」。老式线程会把整个历史投影到内存里，客户端一次拿到全部 turns。而较新的「分页线程」（paginated thread）把历史持久化存储，客户端可以像翻页一样按需、通过游标（cursor）向后拉取历史，而不是一次性全量加载。这样做的收益很明显，超长会话不会撑爆内存；代价是，像「回滚」这类操作变复杂了，因为历史不再是一段随手可切的数组，而是分散在持久化存储里的、带游标的分页数据。&lt;/p&gt;
&lt;h2 id="threadrevert-到底做了什么"&gt;thread/revert 到底做了什么&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;thread/revert&lt;/code&gt; 的语义可以拆成几条来看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它接收两个参数：&lt;code&gt;thread_id&lt;/code&gt; 和 &lt;code&gt;before_turn_id&lt;/code&gt;。后者是「分界点」，从这一轮开始，连同之后的所有轮次，都会被从持久化历史里砍掉，只留下它之前的「前缀」。&lt;/li&gt;
&lt;li&gt;它只改「持久化的对话历史」，不动本地文件。这是最关键、也最容易误解的一点：回滚的是「聊天的记忆」，不是「改过的代码」。&lt;/li&gt;
&lt;li&gt;如果此刻有一轮 turn 正在跑，它会先把这一轮打断。&lt;/li&gt;
&lt;li&gt;它保留线程 ID 不变，也保留线程的那些可变设置，只是把历史重载一遍。&lt;/li&gt;
&lt;li&gt;完成后，它会发一条 &lt;code&gt;thread/reverted&lt;/code&gt; 通知，并返回两个向后翻页的游标（turns 和 items 各一个），方便客户端继续按需拉取剩下的历史。&lt;/li&gt;
&lt;li&gt;旧的 rollout 文件保持不可变；revert 之后，那些指向「已被砍掉的历史」的旧路径会被判定为过期而拒绝。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="和已废弃的-threadrollback-有什么区别"&gt;和已废弃的 thread/rollback 有什么区别&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;thread/revert&lt;/code&gt; 之前，Codex 其实已经有一个 &lt;code&gt;thread/rollback&lt;/code&gt;，但它被标记为 deprecated，很快就要移除。两者的区别值得说清楚。&lt;/p&gt;</description></item><item><title>我们搞定了 Prompt Cache，流水线反而更慢了：Codex App-Server 的四个反直觉真相</title><link>https://codexer.com/posts/2026-08-06-codex-app-server-performance-trap/</link><pubDate>Thu, 06 Aug 2026 09:00:00 +0800</pubDate><guid>https://codexer.com/posts/2026-08-06-codex-app-server-performance-trap/</guid><description>&lt;p&gt;你跟同事聊 Codex 的性能优化时，有没有听过这句话？&lt;/p&gt;
&lt;p&gt;「你每次都 spawn 一个新的 &lt;code&gt;codex exec&lt;/code&gt; 干嘛？直接跑个 app-server 复用线程啊，光 prompt cache 就值回票价了。」&lt;/p&gt;
&lt;p&gt;听起来太对了，对到没人会去质疑。开一个常驻服务进程，预热几条线程，所有请求走同一个会话，cache 命中率拉满，token 消耗直接腰斩，对不对？&lt;/p&gt;
&lt;p&gt;我们团队也是这样想的。然后我们测了四次。&lt;/p&gt;
&lt;p&gt;结果是：prompt cache 最终打到了 86% 的命中率，但流水线比最朴素的基线&lt;strong&gt;更慢&lt;/strong&gt;，原始 token 消耗反而&lt;strong&gt;多了 39%&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这篇文章讲的就是为什么，以及 app-server 到底是为谁设计的。&lt;/p&gt;
&lt;h2 id="背景两种运行模式"&gt;背景：两种运行模式&lt;/h2&gt;
&lt;p&gt;Codex CLI 有两种运行模式，很多人分不清它们的适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;codex exec&lt;/code&gt;&lt;/strong&gt;：每次调用启动一个独立进程，执行完就退出。没有状态，没有记忆，每次都是「从头来过」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;codex app-server --stdio&lt;/code&gt;&lt;/strong&gt;：启动一个常驻服务进程，通过 JSON-RPC 协议维持长连接。可以创建线程、复用上下文、支持中断和恢复。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从表面上看，app-server 显然是更「高级」的选择。进程复用省启动时间，线程上下文省 token，还有一套完整的会话管理协议（&lt;code&gt;thread/list&lt;/code&gt;、&lt;code&gt;thread/resume&lt;/code&gt;、&lt;code&gt;thread/fork&lt;/code&gt;、&lt;code&gt;turn/interrupt&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;但表面正确不等于实际正确。&lt;/p&gt;
&lt;h2 id="实验设计公平对比"&gt;实验设计：公平对比&lt;/h2&gt;
&lt;p&gt;我们的实验设计刻意保持了公平性。不搞连接池，不搞常驻守护进程。app-server 为每个 formation 私有启动、跑完所有 turn 后立即销毁，生命周期跟 &lt;code&gt;codex exec&lt;/code&gt; 子进程一模一样。&lt;/p&gt;
&lt;p&gt;这样对比的只有一件事：&lt;strong&gt;复用同一个线程到底能不能省钱省时间？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在正式测量之前，我们先写了一个探针程序去直连真实的 app-server，验证了八个关键行为，顺手挖出了三个文档里没写但会坑死你的细节：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cwd&lt;/code&gt; 和 &lt;code&gt;sandboxPolicy&lt;/code&gt; 是粘性参数&lt;/strong&gt;。如果你不每个 turn 显式传一遍，turn 3 会悄悄继承 turn 2 的写权限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;token 用量上报在 &lt;code&gt;thread/tokenUsage/updated&lt;/code&gt; 事件上&lt;/strong&gt;，不在 &lt;code&gt;turn/completed&lt;/code&gt;。等 turn 结束再去读，永远晚一步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;空线程不会被物化&lt;/strong&gt;。一个从未跑过任何 turn 的线程，你无法恢复它。线程池预热是个陷阱。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然后探针跑了一个简单的两轮对话，turn 2 报告了 &lt;strong&gt;15,104 个缓存的输入 token&lt;/strong&gt;。Cache 管用！赶紧上线！&lt;/p&gt;</description></item><item><title>深入理解 Codex 嵌入架构：App Server 与 SDK 双路径解析</title><link>https://codexer.com/posts/2026-08-05-codex-embedding-architecture/</link><pubDate>Wed, 05 Aug 2026 09:00:00 +0800</pubDate><guid>https://codexer.com/posts/2026-08-05-codex-embedding-architecture/</guid><description>&lt;p&gt;你跟同事聊 Codex 的时候，有没有过这种体验？你说「我用 Codex 写了个脚本」，「我用 Codex 审查了 PR」，他说「我在 VS Code 里用 Codex 重构了模块」。听起来都是 Codex，但背后走的路径完全不同。&lt;/p&gt;
&lt;p&gt;你用的是 CLI，他是 IDE 插件，运维那边用 &lt;code&gt;codex exec&lt;/code&gt; 跑 CI，平台组在往自己的产品里嵌入 Codex SDK。七种入口，七种交互方式，但底层跑的是同一套东西：一个 Agent 循环。&lt;/p&gt;
&lt;p&gt;这篇文章把 Codex 的嵌入架构从里到外拆开：先从 Agent 循环讲起，然后看 Sandbox 和 Approval 两个关键控制点，最后深入 App Server 协议和两个 SDK 的本质差异。读完你就知道什么时候该用 SDK，什么时候必须直接上 App Server。&lt;/p&gt;
&lt;h2 id="codex-的七扇门"&gt;Codex 的七扇门&lt;/h2&gt;
&lt;p&gt;先看全局。Codex 的入口可以分成三类：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交互式入口（给人用的）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CLI&lt;/strong&gt;：在终端里直接跟 Codex 对话，检查代码、做修改、跑命令，沙箱和审批策略按会话配置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDE 扩展&lt;/strong&gt;：支持 VS Code 和 JetBrains，直接把打开的文件和选中内容送进 Prompt，就地审查修改，还能把长任务丢到云端。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;桌面应用&lt;/strong&gt;：2026 年 7 月 9 日起合并进了 ChatGPT 桌面应用，像一个指挥中心，可以同时管理多个项目和 Agent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codex Cloud&lt;/strong&gt;：完全跳过你的本地机器，在隔离的云端环境里执行任务。你可以从网页、GitHub、Linear 或 Slack 派发任务，等结果就行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;编程式入口（用来集成和构建的）&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>