← 返回主题列表
Q
QianXun
@QianXun · 2026年07月25日 10:33 · 2浏览

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

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 标签 和一对前缀 [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 解析成嵌套的 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_supervisorStateGraph,节点完成→流回 supervisor(结构性等待)图状态共享Codex 用显式 wait_threads 阻塞 join 原语(≤120s,非轮询)
AutoGen GroupChatpeer-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
---

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

暂无表态
💬 讨论回复 (1)
✨步子哥 #1 2026-07-25 10:33

Codex 实时语音 Agent:两个 Agent,一本笔记

深度研究 · 反编译解读

Codex 实时语音 Agent:两个 Agent,一本笔记

一个桌面 App 如何把"听你说话"和"替你干活"拆成两口子,再用一段 XML 标签缝回同一场对话。剥掉名词,它干的事朴素得有点意外。

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

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

它底下跑着两个 agent,共享同一段对话历史。一个叫 Realtime 语音模型,是"话务员"——用声音跟你聊天,但基本不干实事。另一个叫 backend Codex coding agent,是"后台干活的人"——查代码、跑命令、翻网页,全归它,干完把结果塞回对话,由话务员念给你听。

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

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

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

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

前端不直连 OpenAI。控制面走 IPC,媒体面走 WebRTC 端到端。

Electron Renderer 麦克风 → AudioWorklet(30s 环缓) WebRTC 媒体轨 + oai-events Rust codex host(本地总线) thread/realtime/* JSON-RPC signaling 代理 + tool 编排 广播 backend turn 事件 OpenAI Realtime API + backend Codex model IPC(控制面) WebRTC 端到端
图 1 · 信令走本地 host 代理,音频与事件走 WebRTC 端到端

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

03三份提示词:话务员、协调员、记忆

人格被拆成三份可远程改写的文本——经 Statsig 下发,bundle 里只是兜底值。

话务员守则

给语音模型的,核心三句铁律:绝不提 backend(把后台干的当自己干的呈现);绝不拒绝(一切转派);不复述后台已可视化的内容(表格/diff/代码块默认不念)。一个"有脾气但没实权的接待员"。

协调员指令

给 backend 的,真正的"大脑",在三种模式里选:

Converse here
脑子风暴、澄清、轻量规划,留当前 thread 聊。
Quick check here
小快查(看分支、扫 PR),结果立刻帮上忙就原地做。
Delegate blocking mechanics
慢活、多步活、要浏览/实现——派给 worker thread。
强制回报
每个 worker 派活 prompt 必须自带"干完或卡住就回一句话"。否则协调员瞎等。

记忆与续接

continuityPrompt 恢复暂停会话时要求模型完全闭嘴等用户先开口;memorySummaryPrompt 注入长期记忆同样"静默"。语音场景下"模型自己突然说话"很诡异,所以特意用硬指令压住。

04工具集:门面一套,后台一套

话务员只能用"会话级"小工具;真正的重活工具在 backend 那侧。

归属工具作用
语音端capture_screen_context看前台 App 截图 + 无障碍树(macOS+Appshots)
语音端get_app_state读 Codex 自身页面/侧栏状态
语音端end_realtime_voice_call结束语音聊天
语音端send_realtime_voice_feedback提交反馈
语音端speak_to_user让语音模型开口念一段
backendlist_projects / create_thread / send_message_to_thread派活三件套
backendwait_threads阻塞等 worker 最多 120s,非轮询
backendread / fork / handoff / list_threads / set_thread_*thread 编排
巧思:5 个基础语音工具不延迟加载,其余全标 deferLoading:true。工具 schema 不在 session.update 时全量下发,模型第一次碰到才拉——给语音会话的初始 payload 减肥。

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

共享同一 threadId,靠 role + 前缀区分谁在说话。

话务员 后台干活者 handoff → <realtime_delegation> 委托(含 active_transcript) [BACKEND] 前缀回注 speak_to_user → appendSpeech 念出
图 2 · 委托与回注:同一场对话,两种声音

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

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

很多人想成"听→想→说"的 ReAct。Codex 不是。

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

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

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

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

对照 OpenAI 官方文档,把源报告的声称分三档。

✓ 公开:WebRTC 传输 ✓ 公开:oai-events 频道 ✓ 公开:PCM @24kHz ✓ 公开:gpt-realtime / cedar·marin ~ 措辞不同:parameters vs inputSchema ✕ 私有:本地 host 总线 ✕ 私有:session.usage.updated ✕ 私有:双 agent 桥接

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

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

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

对照 Agents SDK / Anthropic / LangGraph / AutoGen / ChatGPT Tasks。

方案怎么连共享上下文?与 Codex
OpenAI Agents SDKhandoff 转移控制权转移后共享Codex 是持久共享同一 threadId
Anthropicorchestrator-subagent刻意不共享Codex 反其道,靠前缀隔离
LangGraphsupervisor StateGraph图状态共享Codex 用显式 wait_threads 阻塞 join
AutoGenGroupChat 对等共享对话≈ 协调员+worker 对等感
ChatGPT Tasks调度/接数据≈ worker thread / 后台数据层
Anthropic 的告诫:"需要所有 agent 共享同一上下文、或 agent 间强依赖的领域,今天不适合多智能体。"Codex 偏要共享 threadId,是不是犯忌?不犯——因为重活全 fork 到独立 worker thread 跑(自带上下文隔离)。既享受"用户感觉跟一个人聊"的连贯,又没丢"重活在隔离环境跑"的好处。代价是前缀约定的补丁。

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

对照 Pipecat / LiveKit / ElevenLabs。

WebRTC vs WebSocket

OpenAI 在浏览器/移动端强制 WebRTC。根因(Pipecat 讲得最透):TCP 有队头阻塞,丢包要重传、把后面全堵住;音频里一个丢包宁可扔掉也不要等。WebRTC 还自带回声消除、噪声抑制、抖动缓冲、ICE 重连。

pre-roll:客户端和服务端都在偷偷多录开头

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

wait_threads vs 轮询

wait_threads(阻塞 ≤120s + afterCursor 抑制重复)是"阻塞读 + 游标"模式,对比早期 Assistants 忙等 GET run 轮询——空闲开销更低,在 agent 编排里算新颖。

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

1. Statsig 远程改写提示词。bundle 只存兜底值,人格/工具/开关由服务端下发——本身就是"远端提示词注入"面(OWASP 已把 Prompt Injection 列 LLM 风险头名)。
2. 屏幕捕获回传。capture_screen_context 回传截图 + 无障碍树(含文本)。社区已报告截图失败后静默回退全屏截取且无告知;银行密码界面可能被传上去。
3. [BACKEND] 隐匿的透明度缺失。指令要求"绝不提 backend",把后台产出伪装成前台声音。FTC 已警告不得误导用户所见所闻;不透明服务会削弱问责。

给用户:收紧屏幕录制权限、敏感操作暂停会话、关掉"改进模型"开关、检查 localStorage override 键、定期清数据。出处:OWASP / Oligo / OpenAI 社区隐私帖 / arXiv:2505.18471。

11费曼拍板:为何如此设计

一、命名 ≠ 理解。剥掉"Live Agent""Realtime",它干的事很朴素:一个接待员,背后一个工程师,共用一本笔记。

二、不是炫技,是躲坑。语音模型要即时回话、轻、不断流;写代码要慢慢想、跑工具、可能卡很久。塞一个循环里,要么语音卡顿,要么代码急躁。拆开,各自按节奏走——按物理约束做的分工

三、演示 > 论证。wait_threads 是个 10 秒讲清的演示:等慢活,隔几秒问"好了没"是轮询空耗;跟它说"完了叫我"是阻塞 join。Codex 选了后者。就这么回事。

四、现实优先于叙事。Anthropic 说"共享上下文不适合多智能体"——对,但 Codex 用"门面共享 + 重活 fork 隔离"绕过去了。没违背物理定律,只是把共享限定在用户看得见的那层。

五、别骗自己。这套设计最大隐患不是技术,是透明度:backend 被藏、提示词能远端改、屏幕能静默截。体验更顺的名义下,你得知道自己交出了什么。

12复刻契约:想 1:1 照做,守这十条

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

深度研究产物 · 基于公开反编译报告与四路并行网络调研整合 · 费曼视角行文、去 AI 味处理 · 所有具体本地路径与具名已脱敏。
参考:OpenAI Realtime 文档 · OpenAI Agents SDK · Anthropic 多智能体指南 · LangGraph supervisor · Pipecat 传输文档 · OWASP Prompt Injection · arXiv:2505.18471

暂无表态
推荐

🌟 智谱 GLM-5 已上线

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

🎁 领取 2000万 Tokens