qwen-audio-agent 架构
本文档定义产品边界。违反这些不变性的变更属于架构变更,而非局部功能开发。
历史重构计划见 Realtime Voice Chatbot Runtime Roadmap。 本文描述当前已实现并受测试保护的运行时行为;概念与部署关系见架构总览。
1. 用户可见模型
用户与一个 qwen-audio 助手对话。逻辑架构由三个核心组件组成:
- 前台 Agent — 通过实时模型、提示词、上下文和工具进行自然交流,处理对话及轻量工具请求。
- 编排运行时(Orchestration Runtime) — 管理任务、权限、会话和事件,调度后台执行与结果投递,不增加一个负责推理的协调 Agent。
- 后台 Agent — 一个用户配置的办事 Agent,负责处理需要操作用户环境、持续执行或制作交付物的请求。
Gateway 是承载这些能力的服务化运行形态,负责应用装配与网络接入,不是第四个核心组件。 客户端负责 I/O 与展示,通过 Gateway Client Protocol 使用服务,不等于前台 Agent。 下文在服务部署语境中使用“Gateway 管理/投递”等表述时,业务行为由其承载的编排运行时完成。
后端可以是 OpenCode、OpenClaw、Qoder、Qwen Code、MiniMax Code、Kimi Code、Pi 等 ACP Agent, 也可以是远程 A2A Agent 或自定义 BackendPort Adapter。 它内部可以使用工具、技能、Agent 或其他 Session。这些都是后端私有实现细节, 不会创建额外的 qwen-audio-agent 层。ACP、A2A 或自定义协议细节只存在于各自 BackendPort Adapter 内;后端特定的启动和能力行为位于已注册的驱动程序中。
2. 非阻塞请求流
final ASR
│
├─ immediately answerable ───────────────► Realtime speech
│
└─ requires work
│ spawn_thinking(objective)
▼
Task accepted
│ response returns to Realtime immediately
▼
owner FIFO queue
│
▼
configured BackendPort
│ the backend decides how to work
▼
final presentation
│ waits for a safe duplex insertion window
▼
Realtime naturally speaks the resultspawn_thinking 永不等待所请求的工作完成。用户可以在多个 Task 项排队期间继续 说话。对于每个 owner,一次只有一个 Task 项被发送到配置的 BackendPort。
3. 实时边界
实时前端可组合调用当前可用的工具完成范围明确的请求;不会仅因调用次数多就转交后台。 工具按提示词职责分为两类:
| 契约 | 工具 |
|---|---|
| 核心契约 | spawn_thinking、get_agent_task_status、cancel_agent_task、respond_permission、respond_agent_input、get_current_time |
| 可选能力 | memory、web_search、fetch_url、knowledge、recall、notes、schedule_reminder、enter_sleep;动态 MCP 工具同样随配置提供 |
固定的 config/frontend-agent/PROMPT.md 只可点名核心契约工具,维护对话、工作受理与结果、 取消和确认等稳定流程。可选工具的用途与调用条件随工具提供,不能在固定 Prompt 或核心工具中反向引用;可选工具之间也不写死对方的名称。 记忆持久化规则放在 server/src/memory/PROMPT.md,仅在工具可用时注入;基础 Prompt 保留用户偏好与事实上下文的一般信任边界。 字段含义与填写规则归工具 schema,当前回执或投递阶段的指令归对应事件, 可用性、权限和执行校验由 Gateway 负责,不依赖模型遵守提示词。
核心与可选是提示词维护约定,不是运行时分类;核心工具名单仅保留在边界测试中。 注册表只保存工具定义和实际生效的可选策略,例如能力要求、结果大小限制和重复调用处理, 不要求工具声明分组或执行模式。未配置后台时不提供 spawn_thinking,待确认工具仅在 存在真实请求时提供;即使属于核心契约,未提供的工具也不能调用。测试检查固定 Prompt、 工具引用和能力开关,防止新增可选能力重新耦合进核心规则。
当 Gateway 存在待确认的后台权限时,提供 respond_permission。 模型只回答权限请求;Gateway 负责查找请求及其关联 Task,再转发决定。
memory 通过一个扁平接口维护两份普通 Markdown 文档。每次调用只执行一个 原子操作:read、append 或 replace;replace 使用唯一匹配的原文定位, 找不到或匹配多处时安全失败。同一轮可逐项调用多次,Gateway 合并后续回应。 两个文档的权限边界不同:
user是当前用户的长期个性化覆盖:称呼、关系、助手在其面前的名称、语言、表达 风格和默认做法。它作为用户授权的指令材料注入,覆盖ASSISTANT.md中的实例级 默认人设,并服从用户当前的话语。memory是只用于理解和回答的持久事实与决定,绝不作为行为指令。两者按是否具有 行为权威区分,而不是按主题机械分类。
两者都不能授权泄露内部结构、跳过权限检查,或改变任务和安全协议。随包的 PROMPT.md 是不可被个性化覆盖的核心规则;本地 ASSISTANT.md 只保存助手实例的默认 身份、人格、关系定位和表达风格。首次启动从随包模板创建,升级不覆盖,下一次语音会话 读取修改。助手不会通过记忆工具修改它。
除显式工具写入外,会话结束提取器还会整理遗漏的明确用户指令和持久事实,分别路由到 USER.md 与 MEMORY.md。它与 Realtime 工具共用同一个上下文服务,不直接写文件, 永远不能修改 ASSISTANT.md,并会拦截文档边界错误和敏感内容。补丁结果记录在本地审计 文件中;未配置文本模型 API 密钥时自动静默禁用。
notes 将用户命名的列表(购物清单、待办事项、阅读列表)作为前台自有的持久集合 进行管理:单次调用即可完成添加、展示、匹配移除、清空和删除,无需后端参与。列表是 条目数据,而非记忆;稳定事实保留在 memory 中,列表条目绝不会被写入用户偏好或 事实记忆。条目和列表解析首先匹配精确文本,然后匹配唯一的不区分大小写子串, 否则报告歧义并将候选名称返回给模型以澄清。清单保存到共享数据目录的 frontend-notes.json;写入通过跨进程文件事务,避免不同 Gateway 覆盖彼此的修改。
get_agent_task_status 是生命周期、进度和中间结果问题的唯一实时入口。 Gateway 直接读取自身持有的 Task 记录,包括 Adapter 归一化后的最新消息、活动和 产物摘要。状态查询不会创建另一个 Task,不调用协调 Agent,也不进入异步播报队列。
实时前端没有以下工具:
- 选择、创建、继续或取消后端 Session;
- 选择同步、异步、前台或后台执行;
- 选择后端执行策略;
- 选择工具、Agent 或子 Agent。
respond_permission 是实时前端不控制执行策略这一规则的唯一例外。 它只能转发由 Gateway 提供的、针对待处理的、owner 作用域权限请求的明确当前轮次 用户决策。它可以理解自然的肯定或否定措辞,如"可以"或"不允许",但不能在没有 当前轮次用户话语的情况下虚构同意、创建请求、选择工具或修改后端权限策略。 权限请求使用同一个短 permission_id 贯穿模型、卡片和 Gateway,不另设模型别名。 只有一个待确认请求时,工具只需 decision;多个请求时需明确 permission_id, 指定了无效 ID 时不会改为授权其他请求。通知保留 task_id 供理解上下文,调用时不必重复传入。 同一用户轮次的重复确认复用回执,不会扩大授权范围。ACP 原始选项 ID 由 Adapter 私下映射。 内置 ACP Adapter 使用随机短 ID,避免进程重启后从头编号而与旧上下文碰撞。 回复分为 task、always 和 reject:分别表示允许当前 Task 及其后续操作、 在当前前端会话中跨 Task 始终允许,以及拒绝当前操作。Gateway 的任务层 PermissionPolicy 由语音、客户端命令和定时工作共用,以 BackendPort 的逐次 once 决定放行后续请求,也处理同一 Task 已经排队的请求。Task 授权在完成、失败 或取消时失效,不传递给其他 Task。两种授权都不会写入后端持久规则,也不跨 Gateway 重启保存。 权限、进度和恢复上下文的协议标签由 Gateway 独占。模型生成的同名标签不构成事件, 不能启用相应工具,也不会写入持久对话。
传递给 spawn_thinking 的 objective 是对用户请求的保守解释,而非执行计划。 提交前必须结合当前对话把"继续那个页面"之类的指代解析成一条自包含指令。该指令就是 后台 Agent 唯一收到的模型可见文本;ASR 原文不再作为第二份任务描述附加。后台 Agent 不接收前台人格、长期记忆或最近聊天历史;与执行有关的事实必须先解析进指令,而不是 转发这些文档。
一次后台轮次可以返回结果,也可以提出继续工作所需的问题、选择、确认或补充信息。 前台自然转达后者;用户随后回答时,再通过 spawn_thinking 以同一工作的续办关系提交, 而不是在前台推测或模拟后续执行。这一语义不依赖具体问题内容或关键词。
当前轮附件由 Gateway 自动作为协议原生 Part 随任务传递,而不是放入模型可见的 JSON 清单。只有任务明确依赖此前轮次的图片或文件时,前台才通过可选的 input_refs 引用 Gateway 分配的会话内输入 ID。Task ID、owner、生命周期、时间戳和路由继续作为 Gateway/BackendPort 的结构化数据,不进入后台 Agent 的任务指令。工作目录和用户 时区也不重复拼入每轮文本;协议或后台自身的运行上下文负责这些信息。
4. 固定后端 Agent Session
这里的“协调 Session”属于 ACP 后台接入,不是框架的编排运行时。 编排运行时通过 BackendPort 管理工作,不要求 A2A 或自定义后台提供同样的 Session 结构。
ACP 适配器为每个 owner 和后端拥有一个持久协调器 Session 身份:
qwen-audio-agent:<owner>:backendGateway 在该稳定键之后存储原生 ACP Session ID,并在后续轮次调用 session/resume。项目委派同样在其记录的工作目录中恢复选定的原生 Session, 因此语音发起的工作保留在后端自己的 Session 历史中,而非 Gateway 副本中。
语音浏览器会话 ID 和 Task ID 不会更改该身份。因此,新的语音对话会继续使用 相同的后端 Agent 上下文。
Gateway 队列和 ACP 适配器都对写入进行串行化。这种双重保护防止并发消息在一个 后端 Session 内部发生竞争。
后端 Agent 拥有自己的执行策略。qwen-audio-agent 只提供一条自包含自然任务指令和 当前轮次的协议原生附件;它不转发前台历史或偏好,不规定状态 JSON,也不指导后端 Agent 如何使用后端特定能力。
5. Task 状态
qwen-audio-agent Task 记录是交付回执,而非后端内部任务图的镜像。
queued → running ─────────────────────────→ completed
│ └→ delegated → finalizing ────────┘
└────────────→ cancelling → cancelled
↘ failed公共字段仅限于用户请求、时间戳、最终结果/错误、通用活动、带可选安全操作详情的 有界待处理权限摘要和通知状态。不存在执行模式、交付模式、子 Agent 状态、后端 权限标识符、后端拓扑或后端取消内部信息。
UI 将 queued 和 running 呈现为相同的"处理中"状态。队列位置是内部调度细节, 不会改变用户的双工对话。
排队中和直接执行中的 Task 在 Gateway 重启后无法安全恢复,因此会变为 failed 并附带 明确的重启原因。委派 Task 只有在 Adapter 能确认持久化原生 Session 时才会重新挂接。 已完成的结果和通知交付状态会被持久化。
6. 进度动画
进度是可观测性,而非控制。ACP 适配器将标准 session/update 通知投射为通用活动:
- 工具名称、有界的用户安全详情和运行中/已完成状态;
- 计划进度;
- 不含思考内容的通用思考信号;
- 有界的 Session 标题和当前模式元数据。
UI 将此映射为稳定的任务说明或短语,如"搜索中"、"读取中"、"生成图像"或当前模式; 通用思考信号继续显示任务目标。包括 thinking 在内的所有活跃后台工作,在桌面宠物上 统一呈现为 working;processing 只保留给前台 Realtime 轮次。Session ID、 子 Agent ID、原始权限载荷和原始思考内容不显示。待处理权限可以在类密钥值被脱敏后, 显示精确且有界的标题、说明、命令或路径,以便知情同意,但不会引入单独的 Agent 动画状态。
活动绝不会产生语音状态更新,也绝不影响队列。
7. 最终结果交付
后端 Agent 通过标准 ACP 回合返回结果:正文来自 agent_message_chunk,图片、音频和 资源保留为原生 ContentBlock,回合以 session/prompt 的 PromptResponse 结束。 ACP Adapter 不再把这些内容压成一段文本,也不要求模型补写专有结果 JSON; 它将文本和非文本内容分别投射为 BackendPort 的 content 与 artifacts。只有 stopReason=end_turn 代表回合成功完成;取消、拒绝、Token 或 Agent 请求次数耗尽分别进入 Gateway 的取消或失败路径。Gateway 再根据客户端能力 决定对话展示、资源卡片和语音表达。
已完成的结果优先返回到发起对话。在全新连接时,可以恢复同一 owner 的旧对话中 未完成的结果。可续期声明防止两个实时前端呈现相同结果。结果被注入实时上下文, 音频播报由客户端 playback.started 回执确认已开始交付,而非仅凭服务端生成完成。 用户正在说话或有其他响应时等待安全窗口;已开始播放的结果不因中途打断而反复重播。 上下文注入和播放确认分别管理,重试有次数上限。
当后端 Agent 调用 session_start 或 session_send 时,委派成立的权威事实是 Session 工具已经成功创建或续接目标任务,并返回 Adapter 验证过的运行与 Session 标识,而不是模型输出的某个字段。ACP 只负责如实传递工具调用、工具结果和当前回合的 终止;Adapter 将验证后的关联发布给 TaskManager,后者据此把原始 Task 移至 delegated,并释放后端 Agent 串行化锁和 Task 调度通道。因此,其他语音请求可以在 目标 Session 运行期间使用协调器。
协调 Agent 可以在工具成功后自然结束当前 ACP 回合,但该文本不控制任务状态,也不会 被解释为完成信号。关联 ID、目标 Session 和生命周期完全保留在 Gateway 的 Task Registry 与 Adapter 运行时中,不要求模型回显。
适配器独立地保持 Task 生命周期和事件订阅存活。只有与委派 ID 关联的匹配 ACP 目标提示完成才能完成 Task。然后适配器短暂重新获取后端 Agent 锁,并将经验证的 结果及其原生 ContentBlock 发送给它整理最终答复。繁忙的目标、空结果、无关的 Session 更新或旧结果 都无法完成 Task。
ACP Agent 轮次不设人为墙钟超时。初始协调轮次、委派目标轮次和最终整理轮次, 只会在 ACP 报告完成、用户显式取消 Task,或后端进程退出/关闭时结束。连接初始化 和有界控制 RPC 仍保留超时,避免不可用的后端无限阻塞 Gateway 启动。
取消是确认式的,而非乐观式的。queued Task 在本地取消。running 或 finalizing Task 中止其活跃后端请求。对于 delegated Task,Adapter 根据已记录的关联取消目标执行, 不需要让协调模型先选择一个取消工具。Task 在取消请求处理期间保持 cancelling, 由 Gateway 根据返回结果更新终态;不能仅凭模型口头回应认定已取消。在适配器直接中止后,Gateway 会记录一个取消事实,并在下一个安全的 协调器轮次中注入一次。这样可以在不延迟取消或重复停止的情况下协调协调器的历史。
前台的受理确认来自 Gateway 已经创建的 Task,而不是协调 Agent 自报的委派状态。 协调轮次仍依 ACP 生命周期信号自然结束;Gateway 不会根据受理文本或 Session 工具调用成功来推断该轮次已完成。
8. 后端内部能力
对于接受客户端提供的 MCP 服务器的 ACP 后端(包括 OpenCode、Qoder、Qwen Code、 MiniMax Code 和 Kimi Code),Gateway 向协调器注入相同的五个工具:Session list、start、 send、status 和 cancel。OpenClaw ACP 不接受客户端提供的 MCP 服务器, 因此相同的协调契约映射到 OpenClaw 的原生 Session 工具。session_start 和 session_send 返回不透明的委派 ID。在任一成功后,后端 Agent 不得轮询、 重复工作或从自己的上下文中回答;适配器负责等待、取消、权限路由和结果关联。
协调 MCP Server 还会通过 MCP 初始化响应的 instructions 字段发布稳定协调契约。 后台 Driver 只有在确认 Agent Host 会把 MCP Server instructions 投射进模型上下文后, 才声明 coordinatorMcpInstructions;这些后台每轮只接收动态自然任务指令,避免把 相同的路由与返回规则反复追加到持久 Session 历史。目前已确认 OpenCode、Qoder、Qwen Code 和 Claude Code,并将共享内容控制在 2 KiB 的可移植预算内。尚未验证的后台,或未来 超过预算的内容,继续使用完整的逐轮 Prompt 安全回退。该标志不表示后台是否普遍支持 MCP。项目 Session 不会连接协调 MCP Server。
session_status 仅用于观察。如果查询失败,后端 Agent 必须报告失败; 不得使用原生工具检查目标目录或复制委派的工作。
前端代码不得依赖于选择了哪个内部能力。前端任务快照只能暴露有界的标题和 通用委派状态,绝不暴露委派 ID、目标 Session ID、目录或原始事件。
9. 依赖方向
逻辑调用与网络接入分别遵守以下边界:
- 前台 Agent 通过
spawn_thinking请求执行,编排运行时管理任务并调用BackendPort;Adapter 将内部任务投影为后台支持的输入,适用于 ACP、A2A 和自定义后台。 - 实时模型服务通过 Realtime Provider 接入。模型供应商的事件与协议细节不能进入通用任务逻辑。
- 客户端通过 Gateway Client Protocol 接入 Gateway。传输层把协议事件交给运行时,并把运行时结果投影为客户端事件;HTTP 宿主管理接口不等于对话工具调用。
后端特定的 API 细节仅属于 server/src/backend/adapters。前台工具不得导入后端适配器。 UI 仅消费公共 Task 与对话事件。包级别的 shared 模块是基础运行时 工具;server core 和 process 可以依赖它们,但它们不得依赖 server 层。
源码目录
编排运行时是逻辑职责的集合,不与单个目录或类一一对应。orchestration/ 负责共用任务操作与会话级投递协调,任务状态仍由 task/ 管理,模型会话与播报由 voice/ 管理;app/ 将这些模块装配成 Gateway 应用。
服务端按功能归属组织目录,避免同一个功能分散在多个技术层目录中:
memory/:记忆接口、运行时、工具、提示与上下文、自动学习,以及 Markdown/VoiceMem Provider。knowledge/:知识库接口、工具、检索运行时、入库服务与内置本地 Provider。frontend/:核心前台指令、工具装配与执行、MCP/OpenAPI 工具,以及网页检索和搜索 Provider。voice/:Realtime Provider 协议、连接、音频轮次、打断与播报投递。orchestration/:与传输无关的共用用户任务操作与会话级投递协调。backend/:协议无关的 BackendPort 与执行逻辑;backend/adapters/管理 ACP/A2A 实现及适配器选择。conversation/与session/:分别管理对话上下文与投影、持久化事件回放,不作为全部记忆能力的容器。
app/ 保持为组合根,负责跨模块接线;可选模块自行装配内部 Provider 和路由。 操作审计、引用来源规范化、无状态文本模型调用等小型服务端公共能力放在 core/, 具体 Provider 跟随所属功能模块。 依赖测试区分模块核心与具体适配器:文件聚合不代表运行时可以导入具体 Provider, 也不代表前台工具可以导入 Realtime 或后台 Adapter。内部文件移动时,公共包导出 名称保持不变。详见源码导航。
裁剪记忆或知识库时,删除模块目录,并取消 app/optional-modules.mjs 与 frontend/optional-features.mjs 中对应的 import 和数组项;运行时服务、工具、路由和 专属提示同步移除。这是两处显式装配入口,不是新的插件框架。精简发行包还应清理对应 包导出、专属测试/文档和依赖。前台运行时只发布通用会话生命周期事实,记忆模块自行 管理学习观察器,退出时先等待观察完成再关闭 Provider。测试会真实删除其中一个或 两个模块,验证网关仍能完成对话。
server/src/orchestration/ 中的 TaskOperations(task-operations.mjs)统一任务 提交、查询、取消、权限决定和补充输入。app/ 将同一个实例注入前台工具与客户端命令, 定时后台工作复用其执行和权限链路。TaskManager 仍是任务状态的唯一权威; BackendWorkRuntime 只负责把执行请求转换为 BackendPort 调用,系统作业不并入用户工作。 语音授权工具立即返回本地受理回执,客户端卡片等待同一决定的后台确认。模型工具回执 和公开协议响应仍由各自入口生成,不引入额外 LLM、传输协议或任务状态机。
每条前台连接还拥有独立的 SessionTaskCoordinator,按 owner/session 观察用户任务、 协调待确认权限与补充输入,并从 TaskManager 领取最终结果通知。它不运行模型,也不解析 协议帧,更不是 ACP 后台的协调 Session。voice/realtime-task-presentation.mjs 生成模型可见文本并接入已有播报管理器, 场景仍通过 taskAnnouncementFactory 定制播报。
权限/补充输入投递前先暴露对应工具;前台忙碌或暂时不可用不会消耗待处理请求, 请求解决后排队中的对应回复失效。断开连接清理订阅与重试、释放通知领取,但不取消后台工作。 重连可重新领取结果,不会再次执行工作;结果可用与播放确认仍是两个独立阶段。 公开任务事件继续经过传输投影器发给客户端。
transport/gateway-client-transport.mjs 只保留传输职责:鉴权、能力协商、连接归属、心跳、协议 编解码和公开事件投影。它为每条客户端连接接入独立的 voice/realtime-session-runtime.mjs 前台运行时(createRealtimeSessionRuntime)。运行时 复用已有组件,管理模型会话与上下文、工具调用、音频轮次、播放、恢复和客户端休眠状态; 接收解码后的事件与可信身份,通过回调发出内部事件,不持有 Socket、凭据或协议握手。
app/frontend-runtime.mjs 负责前台装配:共用依赖、一次性工具源初始化、每条连接的 独立会话创建,以及退出时等待生命周期观察完成。传输层使用注入的运行时,不装配工具 或解析模型 Provider;工具源服务由持有它们的应用层关闭。
静音、语音打断、休眠和前台断连不取消已受理的后台工作。关闭前台会清理定时器、未完成的 前台工具调用、订阅和投递领取;迟到的模型回调不能创建新工作。显式任务取消仍由 TaskOperations 处理。app/ 仍是组合根,不新增服务、线上协议或共享模型会话。
#477 的三个增量均直接测试生产中的 任务操作、协调器和前台运行时,只模拟模型与后台边界,不依赖网络。已有 WebSocket 和 WebRTC 集成测试则验证传输层如何接入同一套运行时。
server/src/client 管理北向 Client Event Registry、客户端命令到任务操作的转换、 ClientActionPort 与幂等 Presence 状态机。Client Action 描述一次环境操作并等待 当前 Client 回传结果,不导入 Electron 或任何 UI 实现。该层只能依赖公开 shared 协议值、与供应商无关的 delivery 值,以及协议无关的 Task 与 Orchestration 层。 server/src/delivery 只管理 AgentDelivery 值,不依赖 Client、Realtime 或 Backend 具体实现。server/src/app 组合根把这些服务注入 Realtime Transport;语音链路与 Client 代码都不能导入其具体实现。
Gateway 可以将不可变的 web/dist 产物作为部署便利来提供,但这仅是静态托管。 Gateway 源码不得导入 UI 组件、呈现文本、样式、终端行为或桌面行为。 所有三个 UI 拥有自己的渲染,并将结构化协议字段映射到各自的标签和交互模式。
后台 Task 播报只保留一个代码级装配接缝:嵌入方可以向 createGatewayApplication 传入 taskAnnouncementFactory。默认 factory 原样组合 现有的最终结果播报与低频进度播报管理器;场景方也可以整体替换两者。这是产品代码的 依赖注入点,不是用户设置、策略注册表或新的线上协议。
10. 进程所有权
当前标准部署以 Gateway 作为框架的服务宿主。逻辑组件不要求各自成为独立进程;后台生命周期由共享的 owned/external 归属模型管理:
owned:Gateway 启动后端所需的本地进程,并在退出时停止它们。后台原生进程负责 加载自己的用户配置、模型、工具和 MCP;适配器只提供协议参数和必要的公共能力。external:仅适用于声明了外部服务能力的后端。Gateway 不启动、改端口或停止该 后台,只通过后端公开的协议地址连接,把配置与状态完全留给外部服务管理。
后台服务归属与 ACP 连接方式是两个相互独立的维度。每个后台 profile 声明一个 acpConnection;连接工厂当前实现 process,即启动一个本地 ACP stdio 子进程。 未来的远程 ACP bridge 可以新增另一种连接类型,而无需修改协调、权限、Task 或 Session 生命周期代码。声明外部后台服务,并不意味着 ACP 连接也自动变成远程连接。
每个后台通过一份经过校验的 Plugin 契约注册。目录项统一拥有身份、安装、原生配置 入口、进程环境和归属元数据;Agent Driver 与 Runtime Driver 必须显式声明完整的布尔 能力,缺失或互相矛盾时在启动阶段直接拒绝。后台子进程只接收跨平台运行所需的系统 变量和当前 Plugin 声明的凭证命名空间,Gateway 身份、Realtime、Memory 以及其他 后台的密钥不会跨过该边界。通用 ACP 命令如确有需要,可通过 QWEN_AUDIO_AGENT_ACP_FORWARD_ENV 显式列出额外变量名。
HTTP/WebSocket 应用由可注入的组合根构造。导入应用工厂不会监听端口;CLI 和桌面版 使用轻量 bootstrap,而测试及未来客户端可以注入彼此隔离的 Agent、任务、会话、 配置和日志服务。
共享适配器通常拥有一个 ACP stdio 子进程,并随 Gateway 一起停止。OpenCode、Qoder、 Qwen Code、MiniMax Code 和 Kimi Code 直接作为 ACP Agent 运行;OpenCode 还可以额外启动其原生本地 Session UI 服务。当前 OPENCODE_BASE_URL 表示这个 UI 服务地址,不是远程 ACP 执行端点, 因此 OpenCode 仍属于 owned。
OpenClaw 使用一个小型 ACP bridge。未显式配置地址时,Gateway 启动一个具有隔离运行时 和 Session 状态的 OpenClaw Gateway;显式配置 OPENCLAW_BASE_URL 时,则直接连接用户 已有的 OpenClaw Gateway,并且不读取、复制或修改其认证和 Agent 状态。此时服务归属为 external,ACP 连接仍是本地 process:本地官方 bridge 通过 WebSocket/WSS 连接远程 OpenClaw Gateway。外部连接不使用面向本地启动的短时端口探测,而由 bridge 报告实际的 网络、TLS 和认证结果。本地 bridge 退出只会中断 ACP 连接,不会触碰远程 Gateway。
Codex 也遵循同一边界:qwen-audio-agent 通过 ACP stdio 启动 codex-acp,该适配器再 通过自己的本地 stdio 协议启动 Codex App Server。Codex App Server 可以提供其他传输, 但它们不是远程 ACP 端点,不应泄漏进共享 ACP 适配层。
Desktop、TUI 和 WebUI 是可替换的 Gateway 客户端。Gateway 是当前 Realtime 模型的 唯一所有者,并通过 health 发布精确模型档案与传输能力。Desktop 只能配置和重启其 本地自有 Gateway;WebUI 与 TUI 将档案视为只读。借用或远程 Gateway 的模型不一致 会被拒绝,而不会被静默覆盖。关闭 UI 不会影响排队中的工作或固定的后端 Agent Session。更改实时或后端行为的配置在下次 Gateway 启动时生效;更改 UI 的 Gateway URL 仅重新连接该 UI。
macOS 桌面渲染器打包在应用程序内部。Electron 从私有的随机回环路径提供这些 不可变资源,并仅代理 Gateway HTTP API 和 Realtime WebSocket 流量。桌面 UI 资源不得从 Gateway 加载:重新构建桌面应用程序必须足以更新其外观,而无需升级 正在运行的 Gateway 前端。
11. 审查清单
合并变更前,请验证:
- 后端工作排队或运行时,实时前端是否仍能对话?
- 工作是否经由 BackendPort 执行,并由 Adapter 保持正确的续办上下文(如 ACP 协调 Session)?
- 前台是否只依赖通用任务与授权语义,而不依赖后台私有 Session、子 Agent 或执行模式?
- 工具事件是否仅用于通用 UI 进度?
- 完成播报是否仅来自最终后端 Agent 结果?
- UI 是否通过宿主生命周期接口管理本机 Gateway,且不绕过它直接管理后台进程?
- 打断是否能在不取消已提交 Task 的情况下推迟语音?
- 测试是否覆盖 FIFO 串行化、固定 Session 复用、工具动画和交付重试?