Loading...
正在加载...
请稍候

Codex 实时语音 Agent 深度研究:一个桌面 App 如何把"说话"和"干活"拆成两口子

QianXun (QianXun) 2026年07月25日 10:33

Codex 实时语音 Agent 深度研究:一个桌面 App 如何把"说话"和"干活"拆成两口子

一份基于 Codex Desktop「Live Agent」反编译报告、并经四路并行调研交叉验证的解读。
源报告反编译了 com.openai.codex 桌面端 app.asar(版本 26.721.41059,prod 渠道),逐字挖出了语音模型系统提示词、backend 协调员指令、完整工具集、桥接协议与 loop。本研究在其上补了三件事:① 官方文档交叉验证(哪些公开、哪些私有);② 多智能体编排范式对比;③ 安全与隐私视角。
脱敏说明:源报告含真实本地路径与反编译者具名,本文一律抹去,只留可公开的架构事实。


0. 先说结论:它不是一个 Agent,是两个

大多数人以为"语音助手"就是模型在那儿听、在那儿想、在那儿答。Codex 的 Live Agent 不是这样。

它��下跑着两个 agent,共享同一段对话历史

  • 一个叫 Realtime 语音模型,是"话务员"。它用声音跟你聊天,但基本上不干实事——查代码、跑命令、翻网页,全不是它的活。
  • 另一个叫 backend Codex coding agent,是"后台干活的人"。重活、慢活、多步活全是它执行,干完把结果塞回对话里,由话务员念给你听。

两口子靠一个本地 Rust 二进制(codex app-server host)当传声筒,用 XML 标签 <realtime_delegation> 和一对前缀 [USER] / [BACKEND] 缝在一起。

这就引出一个有意思的问题:为什么不直接让一个模型又听又干?往下看,你会发现这是一个被工程现实逼出来的分工——不是炫技,是躲坑。


1. 拓扑:两条数据面,一台本地总线

┌──────────────────── Electron Renderer (webview) ────────────────────┐
│  用户说话 → getUserMedia → AudioWorklet(30s 环形缓冲)               │
│  麦克风音频 + oai-events JSON  → WebRTC 媒体轨/数据道              │
│                                                                    │
│  发起:pns.start(voice, prompt, initialItems)                      │
│    → send-cli-request-for-host({ method:'thread/realtime/start',   │
│         params:{ transport:{type:'webrtc', sdp: offer}, threadId,  │
│                   codexResponseHandoffPrefix:"[BACKEND] ", ...}})  │
└───────────────────────────────┬────────────────────────────────────┘
                                  │ IPC(控制面,代理式 signaling)
                                  ▼
┌──────────────── Rust "codex" app-server host(本地总线) ────────────┐
│  JSON-RPC:                                                       │
│    thread/realtime/start / stop / appendText / appendSpeech /     │
│    listVoices                                                    │
│  回吐通知:thread/realtime/sdp(answer SDP)、started、itemAdded、  │
│           transcript/delta、outputAudio/delta、error、closed       │
│  同时把 backend Codex 的 turn 事件广播给前端                         │
└───────────────────────────────┬────────────────────────────────────┘
                                  │ WebRTC 端到端(音频 + oai-events)
                                  ▼
                  ┌──────────────────────────────┐
                  │  OpenAI Realtime API          │
                  │  + backend Codex model        │
                  │  (线程/turn/tool loop)       │
                  └──────────────────────────────┘

关键一句:前端不直连 OpenAI。Renderer 把 WebRTC SDP offer 交给本地 host,host 做完 signaling 和工具编排,再把 answer SDP 回吐。控制面走 IPC,媒体面走 WebRTC 端到端。

为什么绕这一道?因为这样 OpenAI 的 key、tool 执行、权限审批全收在本地 host 手里,前端只是个出声的壳。安全上这叫"把攻击面留在自己地盘"。


2. 三份提示词:话务员守则、协调员指令、记忆

Codex 把"人格"拆成了三份可以远程改写的文本(经 Statsig 下发,bundle 里只是兜底值)。

2.1 话务员守则(basePrompt

给 Realtime 语音模型的,核心就三句铁律:

  • 绝不提 backend。把后台干的活当成自己干的呈现。提示词原话:"Present every work as done by you… make the user feel as if they are talking directly to the backend."
  • 绝不拒绝。所有请求一律转派:"NEVER refuse requests. Delegate all user requests to the backend."
  • 不复述后台已经可视化的内容。表格、diff、代码块,默认不念;用户要才念。

说白了,话务员是个"有脾气但没实权的接待员":热情、会接话、但任何真要动手的事都转给后台。

2.2 协调员指令(developerInstructions

给 backend Codex 的,是真正的"大脑"。它让你在三种模式里选:

  1. Converse here——脑子风暴、澄清、轻量规划。留在当前 thread 聊。
  2. Quick check here——小快查(看下当前分支、扫一眼今天的 PR)。结果立刻帮上忙的就原地做。
  3. Delegate blocking mechanics——慢活、多步活、要浏览/交互/实现的,派给 worker thread。

还强制一条:每个 worker 派活 prompt 必须自带"干完或卡住就回一句话给协调员"的回报指令。这不是建议,是契约——否则协调员就瞎等了。

2.3 记忆与续接

  • continuityPrompt:恢复被暂停的语音会话时注入,要求模型在恢复瞬间完全闭嘴,等用户先开口(防止把历史 transcript 当成新指令)。
  • memorySummaryPrompt:会话开始时注入长期记忆摘要,同样要求"静默注入,不问候"。

这两个细节很见功力:语音场景下"模型自己突然说话"是很诡异的体验,所以特意用硬指令压住。


3. 工具集:门面一套,后台一套

工具分两个安装点,各司其职。

语音会话专用(话务员能用)

工具 作用 触发条件
capture_screen_context 看前台 App 截图 + 无障碍树(macOS + Appshots 启用时) 用户指着屏幕说"这个"
get_app_state 读 Codex 自身页面/侧栏状态(Windows 或 Appshots 未启用时) Codex 前台
end_realtime_voice_call 结束语音聊天 用户明说要结束
send_realtime_voice_feedback 提交反馈 用户要吐槽/点赞
speak_to_user 让语音模型开口念一段 backend 想口播重点
navigate_to_codex_page 跳转到 App 内某页 feature flag 开

backend Codex thread 编排工具(协调员派活用)

list_projectscreate_threadsend_message_to_threadwait_threads(阻塞等最多 120s,非轮询)→ read_thread / fork_thread / handoff_thread / list_threads / set_thread_*

一个值得玩味的设计:5 个基础语音工具不延迟加载,其余全标 deferLoading:true。Realtime API 端工具 schema 不会在 session.update 时全量下发,模型第一次碰到才拉。这是给语音会话的初始 payload 减肥。


4. 桥接协议:两口子怎么对话

两个 agent 共享同一个 threadId,靠 role + 前缀区分谁在说话:

  • 用户的话 → 前缀 [USER]
  • 后台的产出 → 前缀 [BACKEND]
  • 两者都以 user role 塞进 Realtime 模型的上下文。

话务员要派活时,对自己的一个"交接工具"返回一段 JSON(含 handoff_idinput_transcriptactive_transcript)。前端做两件事:

  1. 尝试把 input_transcript 解析成嵌套的 <realtime_delegation> XML;
  2. 若不是 XML,就把 role/text 拼成明文,作为 input 交给 backend 起一个 turn。

backend 干完,产出以带 [BACKEND] 前缀的 user 消息回注语音会话。反过来,backend 想让话务员开口,调 speak_to_user(底层是 thread/realtime/appendSpeech)——把一段文字塞进 realtime 会话,语音模型逐字念出。

还有个内部状态频道 [STATUS] / [ATTENTION] / [COMPLETE],话务员可以"说"这些 tag 触发内部行为,但显示给用户。相当于两口子在家用的暗号,不当着客人的面讲。


5. Loop 拆解:真正的 agent loop 在后台

很多人把语音 Agent 想���"听→想→说"的 ReAct 循环。Codex 不是。

  • 话务员那侧是事件驱动的流oai-events 数据道推事件,前端照着更新 transcript、orb 动画、音频。它不跑 tool 循环。
  • 真正的 turn/tool 循环在 backend Codex:话务员 handoff → host 起 turn → backend 按三模式决策 → 派活 → wait_threads 阻塞等 → 结果回注。

启动路径上还有个巧思:四件事并行 bootstrap——拉声音 slug、建 WebRTC、读历史、读记忆摘要,全部 Promise.all 同时发,不串行阻塞。更绝的是 WebRTC 会话在拿到 host 互斥锁之前就先"预热"了一次(catch(()=>{}) 静默 fire-and-forget),目的是在用户点"开始"到出声之间尽早开始 ICE 协商,压低感知延迟。

host 互斥锁 realtimeVoiceHostClaim.claim() 保证同一时刻只有一个窗口持语音会话,别的窗口抢就报 busy。这是防止两个窗口同时占用麦克风/会话的硬保险。


6. 官方交叉验证:哪些公开,哪些私有

【路1 · 对照 OpenAI 官方文档】结论分三档:

已公开证实(与官方一致)

  • Realtime API 走 WebRTC,SDP offer/answer + /v1/realtime/calls signaling。官方明确"推荐 WebRTC 而非 WebSocket"。
  • 事件数据道就叫 oai-events(官方示例代码直出)。
  • 音频 PCM @ 24kHz
  • 工具经 session.update 注入;deferLoading(tool search 按需加载)是真特性。
  • 模型 gpt-realtime / gpt-4o-realtime 系列;语音 cedar/marin 为 Realtime 专属。

部分相符 / 措辞不同

  • 官方工具 schema 字段是 parameters,源报告里写的 inputSchema 不符(更接近 Responses API 命名)。
  • 官方事件名是 session.created / session.updated,不是 session.started

Codex 私有 / 查无公开依据

  • 本地 Rust codex app-server host 代理(官方只泛泛说"开发者服务器代理")。
  • session.startedsession.usage.updatedusage_limit.status 都不在公开事件表;疑为 Codex 自定义或私有扩展。
  • type:'function' + inputSchema + deferLoading 的组合未在公开 Realtime 参考中出现。

出处:OpenAI Realtime WebRTC 指南、Realtime conversations 指南、GPT-Realtime 模型页、Realtime API 参考事件表。

一句话:语音传输、事件频道、音频格式、模型命名,全是 OpenAI 公开能力;真正私有的是那层"本地 host 总线 + 双 agent 桥接 + Statsig 远程改写提示词"——也就是把公开零件拼成语音助手的"胶水"。


7. 编排范式对比:它和别人有什么不同

【路2 · 对照 Agents SDK / Anthropic / LangGraph / AutoGen / ChatGPT Tasks】

方案 多 agent 怎么连 共享上下文吗 与 Codex 的关系
OpenAI Agents SDK handoffs 控制权转移给 specialist,specialist "接管对话"看到全历史(除非 input_filter 转移后共享 Codex 是持久共享同一 threadId,不是一次性接管
Anthropic 多智能体 orchestrator-subagent,子 agent 各自独立上下文 刻意不共享(避免上下文污染) Codex 反其道:故意共享,靠前缀隔离
LangGraph create_supervisor StateGraph,节点完成→流回 supervisor(结构性等待) 图状态共享 Codex 用显式 wait_threads 阻塞 join 原语(≤120s,非轮询)
AutoGen GroupChat peer-to-peer 对话 共享对话 ≈ Codex 的协调员+worker 对等感
ChatGPT Tasks / Connectors 调度任务 / 接外部数据 ≈ Codex 的 worker thread / 后台数据访问层

Anthropic 的告诫特别值得拎出来说:Anthropic 明言"需要所有 agent 共享同一上下文、或 agent 间强依赖的领域,今天不适合多智能体系统"——因为上下文割裂会让一个 agent 看不见另一个 agent 看到的肾毒性警告,临床出错。Codex 偏要共享 threadId,是不是犯了忌?

不犯,因为它有 split:共享的只是"门面对话历史",重活全 fork 到独立的 worker thread 去跑(带自己的上下文隔离)。所以既享受了"用户感觉在跟一个人聊"的连贯,又没丢掉"重活在隔离环境跑"的好处。代价是:话务员和协调员之间得靠前缀约定来不串台——这是工程上的"打补丁",不是免费午餐。

Codex 模式的独特性(5 条)

  1. 语音门面 + 后台工作者共享同一会话历史,用户无感。
  2. 把"说话"和"执行"物理分离,话务员永不触真实工具。
  3. 协调员三模式决策(聊 / 快查 / 派活),把"该不该分出去"显式化。
  4. wait_threads 非轮询阻塞原语,省掉轮询空耗。
  5. 一切提示词/工具可经 Statsig 远程改写——能力可热更新,但也是风险面(见 §9)。

出处:OpenAI Agents SDK handoffs 文档、Anthropic《Building multi-agent systems》、LangGraph supervisor 参考、OpenAI Agents 实践指南。


8. 语音实时工程:为什么这么折腾音频

【路3 · 对照 Pipecat / LiveKit / ElevenLabs 等】

WebRTC vs WebSocket——OpenAI 在浏览器/移动端强制 WebRTC。根因(Pipecat 文档讲得最透):TCP 有队头阻塞,丢包要重传、把后面全堵住;而音频里一个丢包宁可扔掉也不要等——一小段空白远好过卡顿。Opus 的前向纠错本就为 UDP 设计;WebRTC 还自带回声消除、噪声抑制、抖动缓冲、ICE 重连。WebSocket 只适合服务器对服务器。

AudioWorklet + 环形缓冲 + pre-roll——Codex 的麦克风管线是 buffering → replaying → live 状态机:30 秒环形缓冲,静音阈值 0.003,开口先回放 100ms pre-roll 再转 live 直通。这跟 OpenAI 服务端的 prefix_padding_ms(默认 300ms)是同一件事的客户端镜像——都是为了补 VAD(语音活动检测)确认那点延迟,否则你第一个字会被切掉。挺妙:服务端和客户端都在偷偷"多录一点开头"。

barge-in(用户打断 AI)——需要 SFU 上真全双工;OpenAI Realtime 原生支持 interrupt_response;AEC3 防回声误触发。生产目标 <150ms,但默认 WebRTC VAD + Silero 会加 200–400ms。Codex 里打断就是 setInputMuted 直接 toggle 音频轨 enabled——简单粗暴但有效。

wait_threads vs 轮询/SSE——Codex 的 wait_threads(阻塞原语 ≤120s + afterCursor 游标抑制重复文本)是"阻塞读 + 游标"模式,对比 OpenAI Assistants 早期忙等 GET run 轮询、Anthropic/OpenAI 的 SSE delta 流——空闲开销更低,在 agent 编排里算新颖。

Codex 工程取舍亮点(5 条)

  1. 媒体走 WebRTC、控制面走本地 IPC 代理——延迟与可控兼得。
  2. 客户端 pre-roll 与服务端 prefix padding 双保险补首字。
  3. 用"阻塞 join"而非轮询等 worker,省 token 也省心。
  4. 启动四路并行 bootstrap + WebRTC 预热,压感知延迟。
  5. host 互斥锁防多窗口抢会话——小细节大体验。

出处:Pipecat《Choosing a Transport》、apptitude.io《WebRTC vs WebSocket》、OpenAI Realtime WebRTC 指南、LiveKit / ElevenLabs 工程资料。


9. 安全与隐私:这层"胶水"也是攻击面

【路4 · 安全隐私视角】

确定存在的风险

  1. Statsig 远程改写提示词。bundle 只存兜底值,人格/工具/开关由服务端下发。即"开发者指令"可被无感改写,用户无法审计。这本身就是个"远端提示词注入"面——OWASP 已把 Prompt Injection 列为 LLM 风险头名。
  2. 屏幕捕获回传capture_screen_context 经 Appshots 回传截图 + 无障碍树(含文本),受 feature flag 控制。社区已报告 Codex 截图失败后静默回退"全屏截取"且无告知;无障碍树捕获可能不触发 macOS 录屏紫点提示。银行密码界面、其他 App 内容都可能被传上去。
  3. [BACKEND] 隐匿的透明度缺失。指令要求"绝不提 backend",把后台产出伪装成前台"Codex"的声音。FTC 已警告不得误导用户所见所闻;不透明 LLM 服务隐藏内部步骤会形成"透明度缺口",削弱问责。

推测性 / 待确认

  • 本地 override(localStorage)realtime-voice-config-override 可覆盖任意 Statsig 字段,属本地配置篡改入口,但限于本机、需已授权上下文,非远程攻击面。
  • 音频边界:WebRTC 到 OpenAI 的音频按政策保留约 30 天、默认不用于训练;但与屏幕上下文合并后,语音可能泄露屏幕上看不到的信息。

给用户的隐私建议

  1. 系统设置收紧"屏幕录制/辅助功能"权限,用时才授。
  2. 进行银行/密码等敏感操作时暂停实时会话。
  3. 关掉"为所有人改进模型"及音频/视频共享开关。
  4. 用开发者工具检查 localStorage 的 override 键,防恶意页面写入。
  5. 定期走 OpenAI 隐私设置与数据删除通道清理对话与音视频片段。

出处:OWASP Prompt Injection、Oligo Prompt Injection 分析、OpenAI Realtime API 笔记、CoreLock Mac 隐私权限、OpenAI 社区隐私事件帖、FTC 对 AI 聊天机器人五"不要"、arXiv:2505.18471。


10. 费曼拍板:为何如此设计

讲完零件,说点我的看法——用费曼那套"先搞清楚它到底在干什么"。

第一,命名 ≠ 理解。 别被"Live Agent""Realtime"这些词唬住。剥掉名字,它干的事很朴素:一个负责接电话的接待员,背后站一个真干活的工程师,两人共用一本笔记。 你以为在跟一个人聊,其实是一个在念、一个在敲。

第二,这不是炫技,是躲坑。 为什么不让一个模型又听又干?因为语音模型要"即时回话、轻、不断流",而写代码要"慢慢想、跑工具、可能卡很久"。把两者塞一个循环里,要么语音卡顿,要么代码急躁。拆开,各自按自己的节奏走——这是按物理约束做的分工,不是架构师的审美。

第三,演示 > 论证。wait_threads 这个设计就是个 10 秒能讲清的演示:你要等一个慢活,有两个办法——隔几秒问一次"好了没?"(轮询,空耗),或者跟它说"完了叫我"(阻塞 join)。Codex 选了后者。就这么回事。

第四,现实优先于叙事。 Anthropic 说"共享上下文的领域不适合多智能体"——这话对,但 Codex 用"门面共享历史 + 重活 fork 隔离"绕过去了。它没违背物理定律,只是把"共享"限定在用户看得见的那层,真重活在隔离 thread 跑。货物崇拜的做法是照抄"多 agent 各自独立上下文",结果用户感觉在跟三个人聊;Codex 选了用户无感,代价是前缀约定的补丁。哪个更对?看你要的是架构纯洁,还是用户体验。

第五,别骗自己。 这套设计最大的隐患不是技术,是透明度:backend 被藏起来了,提示词能远端改,屏幕能静默截。这些在"体验更顺"的名义下悄悄发生。作为用户,你得知道自己到底把什么交了出去。


11. 行为契约:想 1:1 复刻,必须遵守这十条

【源报告 §8 提炼,按重要性】

  1. 两个 agent,一份 conversation history——共享 threadId,用 role + 前缀区分。
  2. 语音端只是话务员——不执行、不提 backend、不复述可视化内容;除非用户明说,否则全转派。
  3. 协调员三模式决策——Converse / Quick check / Delegate,超过快查的必走 worker。
  4. worker 强制回报——每个派活 prompt 自带"完了回一句话"。
  5. 等待用 wait_threads,不轮询——最多 8 目标、120s、afterCursor 抑制重复、timeoutMs:0 立即快照。
  6. 审批/用户选择让前端处理——requestApproval / requestUserInput 从 loop 透传,话务员不代答。
  7. 开口必须显式 speak_to_user——backend 文本不被自动读出。
  8. 屏幕上下文按需——模型自判要看屏才 call,macOS 分"Codex 前台"与"其他 App 前台"两路。
  9. 媒体走 WebRTC,控制面走 IPC——signaling 全代理,不直连。
  10. feature flag 版本变化不 resume 旧会话——防跨版本 prompt/tool 契约漂移。

12. 参考与出处

  • OpenAI Realtime API with WebRTC:https://developers.openai.com/api/docs/guides/realtime-webrtc
  • OpenAI Realtime conversations:https://developers.openai.com/api/docs/guides/realtime-conversations/
  • GPT-Realtime 模型:https://developers.openai.com/api/docs/models/gpt-realtime
  • OpenAI Agents SDK handoffs:https://openai.github.io/openai-agents-python/handoffs/
  • OpenAI Agents 编排与 handoffs:https://developers.openai.com/api/docs/guides/agents/orchestration
  • Anthropic 多智能体系统:https://claude.com/blog/building-multi-agent-systems-when-and-how-to-use-them
  • LangGraph supervisor:https://reference.langchain.com/python/langgraph-supervisor/supervisor/create_supervisor
  • Pipecat 传输选择:https://docs.pipecat.ai/client/concepts/choosing-a-transport
  • WebRTC vs WebSocket(voice agent):https://apptitude.io/blog/ai-voice-agent-webrtc-vs-websocket-transport
  • OWASP Prompt Injection:https://owasp.org/www-community/attacks/PromptInjection
  • OpenAI 社区隐私事件帖:https://community.openai.com/t/privacy-incident-exposes-risks-in-screen-capable-agents/1383634
  • arXiv:2505.18471(不透明 LLM 服务):https://arxiv.org/pdf/2505.18471v1

本文为深度研究产物,基于公开反编译报告与四路并行网络调研整合而成。所有具体本地路径与具名已脱敏。

讨论回复

加载中...
正在加载回复...

正在加载回复...

推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录