diff --git a/openspec/changes/add-full-duplex-agent-voice-assistant/.openspec.yaml b/openspec/changes/add-full-duplex-agent-voice-assistant/.openspec.yaml new file mode 100644 index 0000000..95ae5a2 --- /dev/null +++ b/openspec/changes/add-full-duplex-agent-voice-assistant/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-18 diff --git a/openspec/changes/add-full-duplex-agent-voice-assistant/design.md b/openspec/changes/add-full-duplex-agent-voice-assistant/design.md new file mode 100644 index 0000000..af04bf4 --- /dev/null +++ b/openspec/changes/add-full-duplex-agent-voice-assistant/design.md @@ -0,0 +1,312 @@ +# Design: 完整全双工 Agent 语音助手架构 + +## Context + +当前 Owner 项目已经具备真实语音桌宠的基础能力:本地唤醒词、麦克风采集、VAD/端点、实时字幕、final STT、云端 LLM、本地 TTS、播报、连续追问和初步打断规划。现有实现仍以 turn-based 轮次为中心:用户唤醒后系统录一段、识别、生成、播放,再回到待机或短暂追问窗口。 + +完整 Agent 语音助手需要更强的架构边界: + +1. 音频输入和输出必须同时运行,而不是录完再播。 +2. 播放音频必须回注给 AEC reference,避免助手自己的声音污染麦克风输入。 +3. VAD、STT、LLM、TTS、工具执行和播放都必须能被统一取消。 +4. 长期记忆和工具执行必须纳入安全策略,而不是让 LLM 随意执行文本命令。 +5. 终端、未来 GUI 桌宠和自动测试必须消费同一事件流。 + +本设计仅用于 OpenSpec 规划。本阶段不修改 `src/`、不安装依赖、不下载模型、不运行外部工具、不接入 Open Interpreter,也不提交本地未跟踪的 `openinterpreter/`。 + +## Goals / Non-Goals + +**Goals:** + +1. 定义全双工音频底座:麦克风 capture、扬声器 render reference、WebRTC APM AEC/NS/AGC、环形缓冲和时钟对齐。 +2. 定义状态机:`idle/listening/thinking/speaking/interrupted/tool_running/recovering`。 +3. 定义并发任务模型:音频输入、STT、LLM、TTS、播放、工具执行相互独立但可被统一取消。 +4. 定义低延迟打断:用户在 `speaking` 中说话时,VAD 快速触发 interruption,目标小于 200 ms。 +5. 定义 Streaming STT/TTS:partial/final transcript、LLM token stream、句子切分、TTS chunk 播放。 +6. 定义长期记忆:SQLite 保存文本和元数据,FAISS 保存向量索引,敏感内容默认不自动保存。 +7. 定义 Tool Router:结构化工具调用协议、安全分类、确认策略、执行预算、防循环、结果脱敏回注。 +8. 定义 Open Interpreter 外部 adapter:只作为 CLI/子进程后端候选,不复制外部仓库。 +9. 定义 Playwright browser adapter 和未来 `ComputerControlProvider` 预留边界。 +10. 定义测试策略:fake APM/VAD/STT/TTS/memory/tool、端到端模拟、性能指标和安全回归。 + +**Non-Goals:** + +1. 本阶段不实现运行代码。 +2. 本阶段不安装 WebRTC APM、Silero、Faster Whisper、SenseVoice、CosyVoice、FAISS、Playwright 或 Open Interpreter。 +3. 本阶段不下载模型。 +4. 本阶段不改变 `.env`、`pyproject.toml`、`src/`、`tests/`、`scripts/`。 +5. 第一版规划不做 GUI 点击、键盘、屏幕控制,只预留公共 provider。 +6. 第一版不做长期主人声纹注册和多人身份鉴权。 +7. 第一版不自动执行高风险电脑控制、上传、交易、删除、权限修改或账号操作。 + +## Decisions + +### Decision 1: WebRTC APM 作为默认音频底座 + +采用 `WebRtcAudioProcessingStage` 作为全双工默认音频预处理层,启用 AEC、NS、AGC。 + +**理由:** + +1. AEC 需要播放 reference 才能可靠消除助手自己的声音,音色门控只能做后验抑制。 +2. NS 能降低风扇、环境声和麦克风底噪对持续 VAD/STT 的影响。 +3. AGC 能减少用户远近变化导致的阈值不稳定。 + +**替代方案:** + +1. 继续使用 GTCRN 降噪:能改善噪音,但不能处理扬声器回声。 +2. 继续使用音色门控:实现成本低,但对不同扬声器、房间回声和 TTS 音色变化不稳定。 +3. 只靠 VAD/STT 置信度:误触发风险高,不适合作为全双工底座。 + +### Decision 2: capture/render 双环形缓冲 + +设计 `CaptureRingBuffer` 和 `RenderReferenceRingBuffer`。麦克风帧和播放帧都按 monotonic timestamp 写入,APM 处理 capture frame 时读取相邻 render reference。 + +**理由:** + +1. 全双工需要持续输入和持续输出,不能用同步读写阻塞。 +2. AEC 对 reference 时序敏感,必须保留时间戳和 drift 监控。 +3. Ring buffer 可为 STT、VAD、打断检测和测试提供一致帧来源。 + +**替代方案:** + +1. 直接从 sounddevice callback 推到各 stage:耦合高,难测试,易阻塞。 +2. 文件式临时 wav:延迟高,不适合全双工。 + +### Decision 3: 统一状态机和事件总线 + +新增 `FullDuplexAgentStateMachine` 和 `PipelineEventBus`。状态机只负责合法转移,事件总线承载终端、GUI 和测试可观察行为。 + +**理由:** + +1. 现有 turn-based 状态无法表达 `tool_running` 和 `interrupted`。 +2. 终端和未来 GUI 必须共享事件,不应各自读取内部字段。 +3. 测试可以断言事件顺序、耗时和错误恢复。 + +### Decision 4: Cancellation Graph 管理所有可中断任务 + +每个用户输入 turn 创建 root cancellation token,LLM、TTS、playback、tool 子任务挂在 root 下。用户打断时 root token 广播取消。 + +**理由:** + +1. 播放停止但 LLM 继续生成会浪费资源并污染上下文。 +2. TTS 合成继续运行会造成卡顿和旧回复残留。 +3. 工具执行必须区分可取消和不可安全取消。 + +### Decision 5: Streaming STT 分层输出 + +`StreamingSttProvider` 输出 `partial`、`stable_partial` 和 `final`。终端可展示 stable partial;LLM 默认只接收 final。 + +**理由:** + +1. 用户需要实时看到系统听到了什么。 +2. partial 抖动不能直接写入上下文。 +3. final 是唯一可靠的 LLM 用户输入。 + +**开发候选:** + +1. `faster-whisper`:开发阶段优先,生态成熟。 +2. `SenseVoice`:产品候选,中文和情绪能力更强,需要评估部署成本。 +3. `sherpa-onnx`:保留当前兼容 adapter,降低迁移风险。 + +### Decision 6: LLM token stream 按句进入 Streaming TTS + +LLM delta 进入 `SentenceSegmenter`。达到完整句子、语义停顿或最大等待阈值后,将净化后的文本交给 TTS。 + +**理由:** + +1. 不等待完整回复能显著缩短首音延迟。 +2. 句子级 TTS 比 token 级 TTS 更自然。 +3. 现有 TTS sanitizer 可复用,避免表情包、emoji 被读出。 + +### Decision 7: 长期记忆使用 FAISS + SQLite + +SQLite 保存记忆文本、类型、敏感度、来源、时间和 checksum;FAISS 保存 embedding 向量。 + +**理由:** + +1. SQLite 适合可审计元数据和删除。 +2. FAISS 适合向量相似检索。 +3. 两者分离便于一致性检查和索引重建。 + +**替代方案:** + +1. 只用 SQLite FTS:部署简单,但语义召回弱。 +2. 直接把历史对话全文塞进 prompt:隐私和成本都不可控。 +3. 使用外部向量数据库:当前单机桌宠不需要额外服务复杂度。 + +### Decision 8: Tool Router 先安全工具后电脑控制 + +第一版只规划低风险工具:`memory.search`、`memory.save`、`shell.readonly`、`openinterpreter.run`、`browser.playwright`。GUI 电脑控制只预留。 + +**理由:** + +1. 语音助手执行工具的风险高于文本聊天,必须先定义确认策略。 +2. Open Interpreter 能节省电脑任务开发时间,但必须被当成外部受限后端。 +3. GUI 点击/键盘/屏幕控制需要 Accessibility 权限和更复杂安全策略,不适合第一阶段自动执行。 + +### Decision 9: Open Interpreter 作为外部 CLI adapter + +本地 `openinterpreter/` 是未跟踪新 Rust 版 Open Interpreter。Owner 不复制、不 vendoring、不提交它,只在后续实现中通过命令路径调用外部 CLI。 + +**理由:** + +1. 避免把外部大型仓库混入 Owner。 +2. 保持 Owner 的工具边界清晰。 +3. 可以对 CLI 调用做超时、目录限制、输出截断和确认策略。 + +### Decision 10: Codex Computer Use 只参考安全策略 + +Codex App 的 Computer Use 能力可作为“需要确认、限制高风险操作、避免私自执行 GUI 动作”的安全策略参考,但不复制私有实现。 + +**理由:** + +1. 用户希望从 Codex 控制电脑技能中复刻思路,而不是复制私有实现。 +2. 公共落地路线应基于 macOS Accessibility、Playwright、trycua 等能力。 +3. 第一版先不做 GUI 控制可降低安全和实现风险。 + +## Concurrent Task Model + +### Runtime task groups + +1. `audio_capture_task`:从麦克风读取帧,写入 capture ring buffer。 +2. `audio_render_task`:从 playback queue 取 PCM,送扬声器并写入 render reference buffer。 +3. `apm_task`:处理 capture frames,输出 cleaned frames。 +4. `vad_interrupt_task`:对 cleaned frames 做 VAD 和打断检测。 +5. `stt_task`:消费 cleaned frames,输出 partial/stable/final transcript。 +6. `llm_task`:消费 final transcript、记忆和工具结果,输出 token stream 或 tool call。 +7. `tts_task`:消费句子片段,输出 PCM chunks。 +8. `tool_task`:执行已批准工具,输出脱敏结果。 +9. `event_task`:聚合事件、指标和 reporter 输出。 + +### Backpressure + +1. Ring buffer 有固定容量,超出容量丢弃最旧非关键帧并发出 `audio_buffer_overrun`。 +2. TTS queue 超限时暂停 LLM sentence enqueue 或请求 LLM stream 暂停/取消。 +3. Tool output 超限时截断并返回 `truncated=true`。 + +### Cancellation + +1. `interrupt_detected` 触发 root cancellation。 +2. LLM stream 立即关闭连接或停止读取。 +3. TTS session 停止合成并释放资源。 +4. Playback queue 清空未播放 chunks,保留已播放文本边界。 +5. 可取消工具收到 token 后停止;不可取消工具标记为 pending cleanup。 + +## Audio Ring Buffer Design + +### Frame format + +1. 默认 16 kHz 或 48 kHz 内部采样率需在实现前确认。 +2. 默认 mono capture;render reference 可 mono 或 stereo downmix。 +3. 默认 frame size 为 20 ms。 +4. 每帧包含 `frame_id`、`timestamp_monotonic_ms`、`sample_rate`、`channels`、`samples`。 + +### Reference alignment + +1. render frame 写入 reference buffer 时记录实际播放排队时间和预计播放时间。 +2. capture frame 进入 APM 时按 timestamp 查找 reference window。 +3. drift 超过阈值时发 `audio_reference_drift`,并降低打断置信度。 + +### Fallback + +1. APM 不可用时,若 `OWNER_AUDIO_APM_REQUIRED=1`,全双工模式启动失败。 +2. 若允许 fallback,则使用现有 GTCRN denoiser、assistant playback gate 和 conservative VAD,但必须标记 `apm_fallback=true`。 + +## Tool Router Protocol + +### Tool call request + +```text +ToolCallRequest: + id: str + name: str + arguments: dict + requested_by_turn_id: str + natural_language_intent: str + timeout_ms: int +``` + +### Tool decision + +```text +ToolDecision: + action: execute | reject | require_confirmation + risk_level: low | medium | high | forbidden + reason: str + sanitized_arguments: dict + confirmation_prompt: str | null +``` + +### Tool result + +```text +ToolResult: + id: str + status: success | failed | cancelled | rejected | confirmation_required + output_text: str + output_truncated: bool + error_code: str | null + duration_ms: int + audit_summary: str +``` + +### First-version tools + +1. `memory.search`:只读,低风险。 +2. `memory.save`:低到中风险,敏感内容需要确认。 +3. `shell.readonly`:低到中风险,只允许 allowlist 只读命令。 +4. `openinterpreter.run`:默认中风险,第一版默认关闭;执行前必须经过目录、意图和风险校验。 +5. `browser.playwright`:默认中风险,第一版默认关闭;账号、支付、提交、购买流程必须确认或拒绝。 + +## Memory Retrieval Chain + +1. 用户 final transcript 进入 `MemoryQueryBuilder`。 +2. Query builder 结合当前任务、会话摘要和用户文本生成检索 query。 +3. Embedding provider 生成 query vector。 +4. FAISS 返回候选 ids。 +5. SQLite 读取 metadata,过滤 disabled、sensitive、expired、low-confidence records。 +6. Re-ranker 按相似度、类型、最近使用、用户显式偏好排序。 +7. Top-K 以独立 `memory_context` 注入 LLM。 +8. LLM 回复完成后,`MemoryWriteCandidateExtractor` 生成候选记忆。 +9. Safety classifier 决定保存、丢弃或请求确认。 + +## Migration Plan + +1. 保留现有 turn-based `run-live` 作为稳定路径。 +2. 后续实现新增 feature flag 或新入口启用全双工 Agent。 +3. 先落地 fake provider 和模拟端到端,再接真实 WebRTC APM。 +4. STT/TTS provider 先用兼容 adapter 接现有能力,再替换为 streaming provider。 +5. 长期记忆默认关闭或空库启动,完成删除/禁用/隐私文档后再默认开启。 +6. Tool Router 默认只启用 `memory.search`;其他工具按风险逐步开放。 +7. Open Interpreter 和 Playwright adapter 默认关闭,用户显式配置后才可用。 +8. GUI 电脑控制另开 OpenSpec 变更,不混入第一版全双工音频和安全工具验收。 + +## Rollback Strategy + +1. 若全双工音频不稳定,可通过配置回退 turn-based `run-live`。 +2. 若 APM provider 失败,可禁用 full-duplex mode 或启用 fallback 标记的旧链路。 +3. 若长期记忆异常,可设置 `OWNER_MEMORY_ENABLED=0`,继续短期对话。 +4. 若 Tool Router 风险过高,可设置 `OWNER_TOOL_ROUTER_ENABLED=0`,保留纯聊天。 +5. 若 Open Interpreter 或 Playwright adapter 出错,只禁用对应工具,不影响语音主循环。 + +## Risks / Trade-offs + +1. WebRTC APM 依赖复杂 -> 通过 provider 抽象、fake APM 测试和 fallback 降低风险。 +2. 全双工并发复杂 -> 通过 bounded queue、cancellation graph、虚拟时钟测试和事件指标控制。 +3. 流式 STT/TTS 模型较重 -> 分阶段实现,先 provider interface 和 fake tests,再接真实模型。 +4. 长期记忆有隐私风险 -> 默认敏感不保存、可禁用、可删除、可审计。 +5. 工具执行有安全风险 -> 安全工具优先,高风险确认或拒绝,输出脱敏和审计日志。 +6. Open Interpreter 能力强但风险大 -> 默认关闭、低风险受限执行、外部 CLI adapter、禁止复制仓库。 +7. Playwright 登录态风险 -> 第一版不默认使用用户登录态,敏感流程确认或拒绝。 +8. 第一阶段 scope 大 -> 按音频、状态机、STT/TTS、记忆、工具拆分里程碑,每个里程碑独立提交。 + +## Open Questions + +1. 全双工入口是否命名为 `run-agent-live`,还是通过 `run-live --mode full-duplex` 启用。 +2. WebRTC APM 的具体 Python/macOS 绑定选择需要验证。 +3. 内部采样率统一用 16 kHz 还是 48 kHz,需要结合 APM、STT、TTS provider 决定。 +4. SenseVoice 和 CosyVoice 是否作为产品强依赖,还是只保留候选。 +5. 长期记忆是否默认开启,需要用户确认隐私预期。 +6. Open Interpreter 是否允许写操作;若允许,确认流程和 sandbox 需要单独设计。 +7. Playwright 是否允许使用现有 Chrome 登录态。 +8. 后续 ComputerControlProvider 是否采用 macOS Accessibility、trycua 或其他公共方案。 diff --git a/openspec/changes/add-full-duplex-agent-voice-assistant/proposal.md b/openspec/changes/add-full-duplex-agent-voice-assistant/proposal.md new file mode 100644 index 0000000..3cf7710 --- /dev/null +++ b/openspec/changes/add-full-duplex-agent-voice-assistant/proposal.md @@ -0,0 +1,516 @@ +# OpenSpec:完整全双工 Agent 语音助手架构 + +## 功能目标 + +### 完整业务价值 + +当前 Owner 语音桌宠已经从最初的“唤醒词 -> VAD -> STT -> LLM -> TTS -> 播放”演进到可真实运行的 `run-live`,并具备本地唤醒、实时字幕、噪音过滤、连续追问、结束提示音和初步播报打断规划。但现有核心仍然是 turn-based 语音桌宠:用户先唤醒,系统听完一段话,识别完再思考,回复播放完再决定是否继续。这个模型能完成普通问答,但距离“小爱同学式持续对话”和完整 Agent 仍有明显差距。 + +本变更的业务目标是把现有语音桌宠规划升级为完整全双工 Agent 语音助手:麦克风持续输入、播放端持续输出、WebRTC APM 提供 AEC/NS/AGC 音频底座、Streaming STT 实时生成用户话语、LLM 流式回复按句进入 Streaming TTS、用户在 AI 讲话时可以自然打断,长期记忆和 Tool Router 让助手不只聊天,还能在安全边界内检索记忆、执行只读系统任务、调用 Open Interpreter 外部后端和 Playwright 浏览器自动化。 + +本阶段只产出 OpenSpec 审核材料,不改运行代码、不安装依赖、不下载模型、不接入工具执行、不把本地未跟踪的 `openinterpreter/` 复制进 Owner 代码,也不提交该目录。`openinterpreter/` 在本变更中只被定义为外部 CLI/子进程工具后端候选。 + +### 目标用户场景 + +1. 用户在 macOS 上启动语音助手后,不需要按住说话;麦克风常驻监听,系统通过本地唤醒或持续 VAD/STT 管理输入。 +2. 用户听 AI 回复时可以直接插话;系统在 200 ms 目标延迟内停止 TTS 播放、取消当前 LLM/TTS 任务,并把用户新话语作为下一轮输入。 +3. AI 自己的扬声器声音不会被误当成用户输入;WebRTC APM 的 AEC 使用播放 reference 音频消除回声,NS 降低环境噪声,AGC 统一麦克风音量。 +4. 用户可以连续追问,不必每轮都说“小杰小杰”;Conversation Manager 根据状态机和上下文判断当前是继续听、执行工具、播报结果还是恢复待机。 +5. 助手可长期记住非敏感偏好、事实、项目摘要和任务摘要;重启后仍可通过 FAISS+SQLite 召回相关记忆,但敏感信息默认不自动保存。 +6. 用户要求“整理下载目录”“查一下项目文件”“打开网页抓取内容”时,Tool Router 先做安全分类,低风险只读工具可执行,高风险写操作、删除、上传、交易、权限变更必须要求确认,第一版不自动做 GUI 点击/键盘控制。 +7. 后续桌宠 GUI 可订阅同一事件流展示听、想、说、工具执行、被打断、等待确认等状态,而不是重新实现 pipeline 逻辑。 + +### 量化成功指标 KPI + +1. 打断延迟:在可控音频测试环境中,`speaking` 状态下检测到有效用户语音后,进入 `interrupted` 并停止播放的 P95 延迟目标小于 200 ms。 +2. 回声抑制:播放 reference 音频注入 AEC 后,纯助手回放不触发用户 VAD/STT,不产生有效 `barge_in_detected`。 +3. 实时识别:用户开始说话后,Streaming STT 首个稳定 partial transcript 的 P95 目标小于 800 ms。 +4. 端到端首音:LLM 首个可播报句子产生后,Streaming TTS 首个可播放 PCM chunk 的 P95 目标小于 1000 ms。 +5. 流式播报:LLM 不等待完整回复;中文回复按句切分进入 TTS,第一句可播放后立即播放。 +6. 取消可靠性:用户打断时,当前 LLM stream、TTS synthesis、playback、工具执行候选任务均收到 cancellation token;被取消内容不得继续写入上下文。 +7. 记忆召回准确性:保存的偏好/事实/项目摘要在相关查询中可被 Top-K 检索召回;关闭记忆时不得读写 SQLite/FAISS。 +8. 工具安全:未授权工具、越权路径、写操作、删除操作、账号/上传/交易类任务必须被拒绝或进入确认流程;自动化测试覆盖非法工具拒绝、防循环、超时、输出截断。 +9. 兼容性:现有 turn-based `run-live` 语义在迁移期不得被破坏;新全双工入口可分阶段实现,允许先以 feature flag 启用。 +10. OpenSpec 完整性:`openspec validate add-full-duplex-agent-voice-assistant --strict` 与 `openspec validate --all --strict` 必须通过。 + +### 预期影响 + +1. 主规范 `voice-pet-pipeline` 将从“Python 桌宠语音 pipeline”升级为“全双工 Agent 语音助手 pipeline”,覆盖音频底座、并发模型、记忆、工具和安全策略。 +2. 后续实现将新增 WebRTC APM provider、全双工音频环形缓冲、Streaming STT/TTS provider、Conversation Manager、MemoryManager、ToolRouter、Open Interpreter adapter、Playwright browser adapter。 +3. 现有 GTCRN 降噪、主说话人音色门控和本地 KWS 保留为 fallback 或局部能力,不再作为完整全双工回声消除的主方案。 +4. 当前进程内临时上下文保留,但长期记忆成为独立层;临时会话历史和长期记忆必须有清楚边界。 +5. 工具执行引入新的安全面,必须规划确认策略、权限边界、路径限制、超时、输出截断、审计日志和敏感信息脱敏。 +6. 第一版电脑控制不做 GUI 点击/键盘/屏幕控制;只预留 `ComputerControlProvider`。Codex Computer Use 只参考安全确认策略,不复制私有或捆绑实现。 + +### 对现有问题的系统性总结 + +1. 性能问题:现有 turn-based pipeline 在录音结束后才进入 final STT 和 LLM,用户感知延迟集中爆发;全双工架构要求持续识别和流式回复。 +2. 打断问题:当前打断规划仍围绕播放 chunk 检查或后台监听补丁,缺少全局 cancellation graph;用户插话无法统一取消 LLM、TTS、播放和工具任务。 +3. 回声问题:现有音色门控只能降低误触发,无法从音频底座消除扬声器回放;没有 AEC reference,后续 VAD/STT 容易被 AI 自己声音污染。 +4. 噪音问题:GTCRN 只覆盖正式问题采集阶段;全双工持续监听需要系统级 NS/AGC,避免背景噪声导致持续 STT 和打断误判。 +5. 架构问题:现有 pipeline 仍以“轮次”为主,状态机缺少 `tool_running`、`interrupted`、`recovering` 等 Agent 必需状态。 +6. 记忆问题:当前上下文只存在于本次进程内;无法记住长期偏好、项目背景和任务摘要,也没有隐私分类和删除策略。 +7. 工具问题:当前 LLM 只能生成自然语言;没有结构化工具协议、工具路由、安全策略、执行预算、防循环机制和工具结果回注。 +8. Open Interpreter 边界问题:本地 `openinterpreter/` 是未跟踪外部仓库,不能复制进 Owner;需要把它规划为可选外部 CLI 后端并限制风险。 +9. UI/UX 问题:后续桌宠如果直接绑定 runtime 方法,会继续重复逻辑;需要统一事件总线承载状态、字幕、音频、打断、工具和确认请求。 +10. 安全问题:长期记忆和工具执行都会扩大数据面与操作面;必须默认最小权限、敏感内容不自动保存、高风险操作确认、日志脱敏。 + +## 详细需求 + +### 功能需求 + +1. 系统 SHALL 新增完整全双工 Agent 语音助手架构规划,保留现有 turn-based 能力作为迁移期兼容路径。 +2. 系统 SHALL 规划 `ContinuousAudioRuntime`,负责麦克风持续输入、扬声器播放 reference、音频环形缓冲、状态机事件和任务取消。 +3. 系统 SHALL 规划 `WebRtcAudioProcessingStage`,默认 `OWNER_AUDIO_APM_PROVIDER=webrtc`,开启 AEC、NS、AGC。 +4. 播放链路 SHALL 把 TTS PCM/render audio 提供给 AEC reference,麦克风 capture 音频经过 APM 后再进入 VAD、STT 和打断检测。 +5. 系统 SHALL 规划全双工状态机:`idle`、`listening`、`thinking`、`speaking`、`interrupted`、`tool_running`、`recovering`。 +6. 任意状态下检测到有效用户说话 SHALL 能进入 `interrupted`,但 `tool_running` 的中断语义必须区分可取消工具和不可安全取消工具。 +7. 系统 SHALL 规划 Silero VAD 或等价本地 VAD 作为打断触发的主要人声检测层;打断检测先判断“有人开始说话”,不依赖已识别出完整文本。 +8. 打断目标 SHALL 是在 `OWNER_BARGE_IN_TARGET_LATENCY_MS=200` 内停止播放和取消当前回复。 +9. 系统 SHALL 规划 Streaming STT provider;开发默认候选为 `faster-whisper`,产品候选为 `SenseVoice`,保留现有 `sherpa-onnx` 兼容 adapter。 +10. Streaming STT SHALL 输出 partial transcript、stable partial transcript 和 final transcript;只有 final transcript 或明确提交的 stable transcript 可进入 LLM。 +11. 系统 SHALL 规划 Streaming TTS provider;目标候选为 `CosyVoice`,支持句子级和流式 PCM 输出。 +12. LLM SHALL 流式输出 token;Sentence Segmenter SHALL 在检测到完整中文/英文句子或安全停顿时,把文本片段送入 TTS,不等待整段回复完成。 +13. TTS 播放 SHALL 支持中途停止;停止后未完整播出的 assistant 文本不得写入短期上下文或长期记忆。 +14. 系统 SHALL 规划 `ConversationManager`,负责短期会话历史、长期记忆召回、工具调用闭环和状态推进。 +15. 系统 SHALL 规划 `MemoryManager`,默认 `OWNER_MEMORY_PROVIDER=faiss_sqlite`,SQLite 存文本和元数据,FAISS 存向量索引。 +16. 记忆类型 SHALL 至少包含 `preference`、`fact`、`project`、`task_summary`。 +17. 每轮用户输入进入 LLM 前 SHALL 根据当前用户文本、会话摘要和任务上下文检索 Top-K 长期记忆,并以明确的 memory context 注入 LLM。 +18. 敏感内容 SHALL 默认不自动保存;记忆写入必须经过分类器、安全策略或用户明确指令。 +19. 系统 SHALL 规划 `ToolRouter` 和结构化工具调用协议,工具请求包含 name、arguments、risk_level、requires_confirmation、timeout_ms、budget、cancellation_policy。 +20. 第一版工具 SHALL 以安全工具为主:`memory.search`、`memory.save`、`shell.readonly`、`openinterpreter.run`、`browser.playwright`。 +21. `shell.readonly` SHALL 限制为只读命令和允许目录,禁止删除、写文件、修改权限、网络上传、安装依赖等高风险行为。 +22. `openinterpreter.run` SHALL 作为外部 CLI/子进程 adapter,默认只允许低风险、受限目录、超时和输出截断任务。 +23. `browser.playwright` SHALL 作为浏览器自动化 adapter,默认只允许可审计、非支付、非账号敏感的浏览和提取流程。 +24. 第一版 SHALL NOT 实现 GUI 点击/键盘/屏幕控制;只在规范中预留 `ComputerControlProvider`,后续可基于 macOS Accessibility、Playwright、trycua 等公共能力实现。 +25. Codex Computer Use 能力 SHALL 仅作为安全确认策略参考,不复制私有实现、捆绑脚本或内部协议。 +26. 系统 SHALL 规划工具结果回注:工具输出进入 Tool Result Message,经过脱敏和长度限制后返回 LLM;工具失败进入可恢复错误路径。 +27. 系统 SHALL 规划防循环策略:单轮最大工具调用次数、最大总耗时、最大输出字节、重复工具调用检测。 +28. 系统 SHALL 规划安全确认策略:写文件、删除、上传、交易、账号、权限、联网提交、安装依赖、执行任意代码等高风险操作必须确认,第一版默认拒绝自动执行。 +29. 系统 SHALL 规划桌宠/终端共用事件总线,事件覆盖 audio、vad、stt、llm、tts、playback、memory、tool、confirmation、interruption、recovery。 +30. 本阶段 SHALL 只创建 OpenSpec 文档;不得创建或修改 `src/`、`tests/`、`scripts/`、`pyproject.toml`、`.env`、模型文件、音频资产或运行时代码。 + +### 非功能需求 + +1. 性能优化:全双工音频处理必须以固定帧长和环形缓冲为基础,避免 Python 线程阻塞导致播放卡顿或输入积压。 +2. 延迟目标:VAD frame interval 默认不超过 20 ms;播放停止 chunk 默认不超过 30 ms;整体打断目标小于 200 ms。 +3. 稳定性:所有 provider 必须支持超时、取消、关闭和资源释放;异常必须进入 `recovering`,不得让后台线程泄漏。 +4. 可扩展性:APM、VAD、STT、TTS、LLM、Memory、Tool adapter 都必须是可替换 provider,不能把具体模型硬编码进状态机。 +5. UI/UX:终端和未来 GUI 不直接调用 provider,只消费事件;显示文案、字幕、工具确认和桌宠动画均由事件驱动。 +6. 安全:API key、Authorization header、原始音频、声纹特征、工具敏感输出不得写入日志或长期记忆。 +7. 隐私:默认不保存原始麦克风音频;长期记忆只保存文本摘要和必要元数据;用户必须能关闭记忆。 +8. 兼容性:现有 `.env` 中 LLM 配置继续可用;新增配置必须有默认值和迁移说明。 +9. 可测试性:每个并发 stage 必须可用 fake provider 和虚拟时钟测试;端到端模拟不依赖真实麦克风、扬声器或外部工具。 +10. 可观测性:事件必须携带 turn/session id、stage、时间戳、latency、error code 和脱敏 payload,便于定位卡顿、误触发和工具风险。 + +### 边缘案例 + +1. APM 初始化失败:系统必须报告 `AUDIO_APM_UNAVAILABLE`,可按配置降级到现有 GTCRN/音色门控 fallback 或拒绝进入全双工模式。 +2. AEC reference 丢失:播放中没有 reference 音频时,系统必须降低打断置信度或临时禁用高风险 barge-in,避免 AI 自己声音触发。 +3. 麦克风权限缺失:启动失败并提示设备检查,不进入假监听状态。 +4. 用户在 AI 说第一个字前打断:取消 LLM/TTS stream,未播报文本不写入上下文。 +5. 用户在工具执行中打断:可取消工具立即取消;不可安全取消工具进入“正在收尾/等待结果”状态,并向用户播报或显示限制。 +6. LLM 已发起工具调用但用户打断:未执行工具调用应取消;已执行且低风险的只读工具结果可丢弃或标记为 stale。 +7. 记忆库损坏:FAISS 或 SQLite 不可用时,系统可禁用长期记忆并继续基本语音对话,但必须报告结构化错误。 +8. 记忆召回命中敏感内容:默认不注入 LLM,除非用户明确要求并通过安全策略。 +9. Open Interpreter 路径缺失:`openinterpreter.run` adapter 标记不可用,不影响其他工具。 +10. 工具输出过长:按配置截断并附 `truncated=true` 元数据,不把完整大输出塞进 LLM。 +11. Playwright 未安装或浏览器不可用:工具返回可恢复错误,不影响语音主循环。 +12. TTS 输出卡顿:播放队列应能反压 TTS 合成;卡顿事件必须可观测,不能阻塞麦克风监听线程。 +13. Streaming STT partial 抖动:只显示稳定 partial;final transcript 才进入对话。 +14. 网络 LLM 慢或断线:取消和超时必须生效;恢复后回到 listening 或 idle。 +15. 多人同时说话:第一版不承诺身份鉴权,只要求 AEC 后的人声触发和用户体验合理;多人区分列为需人工澄清。 + +### 输入输出规格 + +新增或规划配置: + +1. `OWNER_ASSISTANT_MODE=full_duplex_agent` +2. `OWNER_AUDIO_APM_PROVIDER=webrtc` +3. `OWNER_AUDIO_AEC_ENABLED=1` +4. `OWNER_AUDIO_NS_ENABLED=1` +5. `OWNER_AUDIO_AGC_ENABLED=1` +6. `OWNER_AUDIO_FRAME_MS=20` +7. `OWNER_AUDIO_RING_BUFFER_MS=3000` +8. `OWNER_VAD_PROVIDER=silero` +9. `OWNER_INTERRUPT_ENABLED=1` +10. `OWNER_INTERRUPT_TARGET_LATENCY_MS=200` +11. `OWNER_STREAMING_STT_PROVIDER=faster_whisper` +12. `OWNER_STREAMING_STT_PRODUCT_CANDIDATE=sensevoice` +13. `OWNER_STREAMING_TTS_PROVIDER=cosyvoice` +14. `OWNER_LLM_STREAMING_ENABLED=1` +15. `OWNER_MEMORY_ENABLED=1` +16. `OWNER_MEMORY_PROVIDER=faiss_sqlite` +17. `OWNER_MEMORY_TOP_K=5` +18. `OWNER_MEMORY_AUTO_SAVE_SENSITIVE=0` +19. `OWNER_TOOL_ROUTER_ENABLED=1` +20. `OWNER_TOOL_MAX_CALLS_PER_TURN=5` +21. `OWNER_TOOL_TIMEOUT_MS=30000` +22. `OWNER_OPENINTERPRETER_ENABLED=0` +23. `OWNER_OPENINTERPRETER_COMMAND=openinterpreter` +24. `OWNER_BROWSER_PLAYWRIGHT_ENABLED=0` +25. `OWNER_COMPUTER_CONTROL_ENABLED=0` + +核心事件输出: + +1. `audio_capture_started` +2. `audio_apm_started` +3. `listening_started` +4. `speech_started` +5. `stt_partial` +6. `stt_final` +7. `llm_stream_started` +8. `llm_sentence_ready` +9. `tts_chunk_ready` +10. `playback_started` +11. `interrupt_detected` +12. `playback_cancelled` +13. `llm_cancelled` +14. `memory_retrieved` +15. `tool_call_requested` +16. `tool_confirmation_required` +17. `tool_call_started` +18. `tool_call_finished` +19. `tool_call_rejected` +20. `session_recovered` + +### 数据验证规则 + +1. Provider 配置必须在启动前校验,不允许未知 provider 静默回退。 +2. APM 输出帧采样率、声道数、帧长必须与 VAD/STT 输入一致;不一致必须显式 resample 或报错。 +3. Streaming STT partial 不得进入长期记忆;final transcript 必须经过空文本、重复文本、敏感内容和最小置信度检查。 +4. TTS 播报文本必须经过现有 TTS sanitizer;emoji、表情包和 Markdown 图片不得进入语音。 +5. 长期记忆写入必须包含 type、text、source_turn_id、created_at、sensitivity、embedding_model、checksum。 +6. FAISS index 和 SQLite metadata 必须可一致性检查;缺失或 checksum 不匹配时不得返回伪造记忆。 +7. Tool Router arguments 必须按工具 schema 校验;未知字段、路径越界、命令注入风险必须拒绝。 +8. 工具结果进入 LLM 前必须截断、脱敏,并标注工具名、耗时、退出码和是否截断。 +9. 所有 cancellation token 必须可幂等触发,多次取消不得抛出未处理异常。 + +### 需人工澄清 + +1. 全双工入口是替换 `run-live`,还是新增 `run-agent-live` 并保留 `run-live` 为稳定 turn-based 入口。 +2. WebRTC APM Python 绑定优先选择哪个包或本地封装,是否允许引入需要系统编译的依赖。 +3. 产品阶段是否确定采用 SenseVoice 和 CosyVoice,还是只在规范中保留候选。 +4. 长期记忆是否需要用户可视化管理、删除、导出和禁用命令。 +5. Open Interpreter CLI 的实际本机命令、工作目录、沙箱策略和是否允许写操作需要人工确认。 +6. Playwright 浏览器工具是否允许使用用户当前 Chrome 登录态,还是只允许独立 browser context。 +7. 多人说话场景是否需要主人声纹注册;本变更默认不做身份鉴权。 +8. 桌宠 GUI 和电脑控制是否必须同期开工;本变更建议第一版先做音频全双工、记忆和安全工具。 + +## 设计方案 + +### 文字版全新架构图 + +```text +Microphone + -> Capture Ring Buffer + -> WebRtcAudioProcessingStage(AEC + NS + AGC, render reference from Speaker) + -> SileroVadStage + -> InterruptDetector + -> StreamingSttStage(faster-whisper dev / SenseVoice candidate / sherpa fallback) + -> ConversationManager + -> ShortTermSessionContext + -> MemoryManager(SQLite metadata + FAISS vectors) + -> LlmStage(streaming OpenAI-compatible provider) + -> ToolRouter(memory.search/save, shell.readonly, openinterpreter.run, browser.playwright) + -> ResponseStream + -> SentenceSegmenter + -> StreamingTtsStage(CosyVoice candidate / local fallback) + -> Playback Ring Buffer + -> Speaker + -> Render Reference back to WebRtcAudioProcessingStage +``` + +### 数据流 + +1. `ContinuousAudioRuntime` 启动后初始化 capture device、playback device、APM、VAD、STT、TTS、LLM、Memory、ToolRouter。 +2. 麦克风音频以固定 20 ms 帧写入 capture ring buffer;播放 PCM 以相同时间轴写入 render reference buffer。 +3. `WebRtcAudioProcessingStage` 使用 render reference 对 capture frame 执行 AEC,再执行 NS/AGC。 +4. APM 后音频同时进入 VAD、Streaming STT 和 Interrupt Detector。 +5. `listening` 状态下,VAD/STT 产生用户输入;final transcript 进入 Conversation Manager。 +6. Conversation Manager 召回短期上下文和长期记忆,生成 LLM streaming request。 +7. LLM delta 进入 Sentence Segmenter;完整句子进入 Streaming TTS;TTS chunk 立即入 playback queue。 +8. 播放开始后,播放 PCM 仍持续进入 render reference,使 AEC 能抑制助手回声。 +9. 如果用户讲话,Interrupt Detector 发出 `interrupt_detected`,Cancellation Graph 同时取消 LLM stream、未完成 TTS、播放队列和可取消工具。 +10. Tool Router 在 LLM 请求工具时执行 schema 校验、安全分类、确认策略、执行、结果脱敏和回注。 +11. 任何 stage 失败进入 `recovering`,释放后台任务和音频资源后回到 `listening` 或 `idle`。 + +### 接口定义 + +```text +AudioFrame: + samples: float32 PCM + sample_rate: int + channels: int + timestamp_monotonic_ms: int + frame_id: str +``` + +```text +WebRtcAudioProcessingStage.process_capture(frame: AudioFrame) -> AudioFrame +WebRtcAudioProcessingStage.process_render(frame: AudioFrame) -> None +WebRtcAudioProcessingStage.reset_stream() -> None +Errors: + AUDIO_APM_UNAVAILABLE + AUDIO_APM_FORMAT_MISMATCH + AUDIO_APM_PROCESS_FAILED +``` + +```text +StreamingSttProvider.start_session(session_id: str) -> StreamingSttSession +StreamingSttSession.accept_audio(frame: AudioFrame) -> list[TranscriptEvent] +StreamingSttSession.finish() -> TranscriptFinal +StreamingSttSession.cancel(reason: str) -> None +``` + +```text +StreamingTtsProvider.start_stream(voice: str, sample_rate: int) -> StreamingTtsSession +StreamingTtsSession.accept_text(text: str) -> list[AudioFrame] +StreamingTtsSession.flush() -> list[AudioFrame] +StreamingTtsSession.cancel(reason: str) -> None +``` + +```text +MemoryManager.search(query: str, *, top_k: int, filters: dict) -> list[MemoryRecord] +MemoryManager.save(record: MemoryRecordInput) -> MemoryRecord +MemoryManager.delete(memory_id: str) -> None +MemoryManager.health_check() -> MemoryHealth +``` + +```text +ToolRouter.route(call: ToolCallRequest, context: ToolContext) -> ToolDecision +ToolRouter.execute(decision: ToolDecision, cancellation: CancellationToken) -> ToolResult +ToolDecision: + action: execute | reject | require_confirmation + risk_level: low | medium | high | forbidden + reason: str +``` + +### 状态机 + +```text +idle + -> listening +listening + -> thinking on final user transcript + -> interrupted on explicit cancel or new speech over assistant residue + -> recovering on audio/STT failure +thinking + -> speaking on first playable TTS chunk + -> tool_running on approved tool call + -> interrupted on user speech + -> recovering on LLM failure +speaking + -> listening on reply finished and no follow-up/tool pending + -> interrupted on valid user speech + -> recovering on TTS/playback failure +tool_running + -> thinking on tool result returned to LLM + -> interrupted on cancellable tool interrupted + -> recovering on tool failure +interrupted + -> listening after current tasks cancelled and buffered user audio retained +recovering + -> listening after cleanup when runtime can continue + -> idle when required provider unavailable +``` + +### 关键算法 + +1. AEC reference 对齐:播放 PCM 写入 render buffer 时记录 monotonic timestamp;capture frame 处理前取最近 reference window,丢帧或漂移超过阈值时发 `audio_reference_drift`。 +2. 打断检测:APM 后音频先经 VAD 判断人声起点;连续人声超过最小阈值后结合 STT stable partial 或能量/频谱置信度触发 interrupt;纯 render echo 在 AEC 后应低于阈值。 +3. 句子切分:LLM delta 累积到中文句号、问号、感叹号、英文终止标点或最大等待阈值时切句;代码块、URL、数字小数点不得误切。 +4. 取消传播:每轮创建 root cancellation token;LLM、TTS、playback、tool 子任务注册 child token;用户打断时 root token 广播,所有 stage 幂等收尾。 +5. 长期记忆召回:用户 final transcript 生成 embedding,FAISS 取 Top-N,SQLite 取 metadata,按类型、敏感度、最近使用、相似度重排后注入 LLM。 +6. 记忆写入:Conversation Manager 在回合结束后提取候选记忆,按敏感分类和用户意图决定是否保存;敏感或不确定默认不保存。 +7. 工具路由:LLM tool call 先过 schema,再做风险分类和权限判断;低风险可执行,高风险进入确认,禁止类直接拒绝。 + +### 数据库/状态管理变更 + +1. SQLite 表 `memories`:`id`、`type`、`text`、`summary`、`metadata_json`、`sensitivity`、`source_turn_id`、`created_at`、`updated_at`、`last_used_at`、`embedding_id`、`checksum`。 +2. SQLite 表 `tool_audit_logs`:记录工具名、风险、确认状态、耗时、退出码、截断标记和脱敏摘要,不保存密钥。 +3. FAISS index 文件保存 embedding vectors;SQLite 保存 index 版本和 embedding model,启动时做一致性检查。 +4. 短期会话上下文仍在内存中,进程退出丢弃;长期记忆独立存储,可按配置禁用。 + +### UI 组件重构方案 + +1. 终端 reporter 只订阅事件,不直接读取 pipeline 内部状态。 +2. 未来桌宠 GUI 使用同一事件流展示 `listening`、`thinking`、`speaking`、`interrupted`、`tool_running`、`recovering`。 +3. 工具确认必须作为事件暴露,终端可先实现文本确认,GUI 后续实现按钮确认。 +4. 实时字幕分为 partial、stable partial、final 三种显示层级,避免把抖动 partial 当作最终用户输入。 + +### 依赖影响分析 + +1. WebRTC APM:新增依赖风险最高,需确认 Python/macOS 可用绑定或自建 native wrapper。 +2. Silero VAD:新增本地模型依赖,需评估 ONNX Runtime 或 torch 路线。 +3. Faster Whisper:开发体验好,但模型体积和 Metal/CPU 性能需评估。 +4. SenseVoice:中文效果强,产品候选;需确认 license、模型大小、macOS 部署成本。 +5. CosyVoice:TTS 效果强,依赖较重;第一阶段可先保留现有本地 TTS fallback。 +6. FAISS:macOS 安装和 wheel 兼容性需评估;必要时提供 sqlite-only 或 numpy fallback。 +7. Playwright:浏览器自动化依赖和浏览器安装体积需评估;第一版默认关闭。 +8. Open Interpreter:作为外部 CLI 后端,不作为 Owner 包内依赖;路径缺失时 adapter 不可用。 + +## 风险与权衡 + +| 风险 | 概率 | 影响 | 缓解措施 | +| --- | --- | --- | --- | +| WebRTC APM Python/macOS 绑定不可用或编译复杂 | 高 | 高 | OpenSpec 中把 provider 抽象出来;先验证 fake APM 和最小 native 方案;保留 GTCRN/音色门控 fallback。 | +| AEC reference 与 capture 时钟不同步 | 中 | 高 | 使用 monotonic timestamp、ring buffer drift 监控、reference gap 事件和回声测试 fixture。 | +| 全双工并发导致线程泄漏或播放卡顿 | 中 | 高 | 所有 stage 必须支持 cancellation token、bounded queue、backpressure 和统一 shutdown。 | +| Streaming TTS 依赖过重导致落地慢 | 中 | 中 | 第一阶段先按句分段合成,CosyVoice 作为目标 provider,保留现有 Mac TTS fallback。 | +| Faster Whisper/SenseVoice 模型性能不足 | 中 | 中 | 规范要求 provider 可替换,测试记录 partial/final latency,产品候选不在第一阶段强绑定。 | +| 长期记忆保存敏感信息 | 中 | 高 | 默认敏感不自动保存;记忆写入前分类;用户可关闭;日志和记忆脱敏。 | +| Tool Router 执行危险操作 | 中 | 高 | 默认安全工具优先,高风险确认,禁止类拒绝,目录限制,超时,输出截断,审计日志。 | +| Open Interpreter 外部后端越权 | 中 | 高 | 默认关闭;只允许受限目录、低风险任务;写操作必须确认或拒绝;不复制外部仓库进 Owner。 | +| Playwright 使用登录态带来账号风险 | 中 | 高 | 第一版默认独立 context;涉及账号、支付、购买、提交必须确认或拒绝。 | +| 用户期望立即实现完整 GUI 控制 | 中 | 中 | 本变更明确第一版不做 GUI 点击/键盘/屏幕控制,只预留公共 provider。 | +| FAISS 与 SQLite 一致性损坏 | 低 | 中 | 启动 health check、checksum、index rebuild 任务和 sqlite-only 降级。 | +| LLM 工具循环 | 中 | 中 | 单轮最大工具次数、重复调用检测、总耗时预算和可恢复拒绝。 | +| 不确定是否继续对话造成体验不稳 | 中 | 中 | Conversation Manager 规则优先,LLM 分类兜底,不确定默认 listening/idle 策略需人工确认。 | +| OpenSpec scope 过大导致实现周期过长 | 高 | 中 | 实施计划分阶段:先音频全双工,再流式 STT/TTS,再记忆和工具,再电脑控制。 | + +## 任务分解 + +> 本节定义 proposal 里的主要功能组或里程碑阶段。后续 `tasks.md` 必须把每组拆成不超过 1 小时的原子任务,并在完成每个大模块后按 Git 提交规范立即提交。 + +### 1. OpenSpec 与边界冻结 + +目标:只产出 `add-full-duplex-agent-voice-assistant` 文档,不改代码、不装依赖、不下载模型、不提交 `openinterpreter/`。 + +验收:OpenSpec 变更目录包含 proposal、design、tasks、spec delta;严格校验通过。 + +### 2. WebRTC APM 与全双工音频底座规划 + +目标:定义 capture/render ring buffer、AEC reference、NS、AGC、音频格式、时钟对齐和 fallback 策略。 + +验收:spec 包含 APM SHALL 要求、fake reference 测试场景和回声不触发 VAD/STT 场景。 + +### 3. 全双工状态机、事件总线与取消机制规划 + +目标:定义 `idle/listening/thinking/speaking/interrupted/tool_running/recovering` 状态机、事件模型、cancellation graph 和 recovery。 + +验收:design 包含状态转移表和取消传播;tasks 包含状态机、事件顺序和错误恢复测试。 + +### 4. Streaming STT 与低延迟打断规划 + +目标:定义 Streaming STT provider、Silero VAD、partial/final transcript、打断检测和 200 ms 目标。 + +验收:spec 包含 partial/final 行为、speaking 中用户说话进入 interrupted、纯回声不打断。 + +### 5. Streaming TTS 与响应流规划 + +目标:定义 LLM token stream、句子切分、Streaming TTS、播放队列、可中断播放和上下文写入边界。 + +验收:spec 包含不等待完整回复、未播完文本不写上下文、TTS 卡顿可观测。 + +### 6. 长期记忆规划 + +目标:定义 MemoryManager、FAISS+SQLite、记忆类型、召回、写入安全、禁用和删除策略。 + +验收:spec 包含保存、检索、重启召回、关闭记忆不读写、敏感内容不自动保存。 + +### 7. Tool Router 与安全工具规划 + +目标:定义结构化工具协议、安全路由、工具预算、防循环、工具结果回注,以及 `memory.search/save`、`shell.readonly`、`openinterpreter.run`、`browser.playwright`。 + +验收:spec 包含合法工具执行、非法工具拒绝、确认策略、Open Interpreter 缺失处理和 Playwright 安全边界。 + +### 8. Open Interpreter 与电脑控制边界规划 + +目标:明确 `openinterpreter/` 是外部未跟踪后端候选,不复制进 Owner;第一版不做 GUI 控制,只预留 `ComputerControlProvider`。 + +验收:proposal/design/tasks 均写明边界;git 提交不包含 `openinterpreter/`。 + +### 9. 测试、性能、安全和验收规划 + +目标:定义 fake APM、VAD、STT、TTS、memory、tool、Open Interpreter、Playwright、端到端模拟、性能指标和安全验证。 + +验收:tasks 中每项有前置条件、优先级、验收标准和测试要点;validation 命令明确。 + +## Spec Deltas + +### Capabilities + +#### New Capabilities + +本变更不创建独立新 capability 文件。原因:用户明确要求 delta 文件路径为 `specs/voice-pet-pipeline/spec.md`,且全双工音频、长期记忆和 Tool Router 都作为语音助手 pipeline 的能力升级纳入同一现有 capability。 + +#### Modified Capabilities + +- `voice-pet-pipeline`:从 turn-based Python 桌宠语音 pipeline 扩展为完整全双工 Agent 语音助手 pipeline,新增 WebRTC APM、持续监听、Streaming STT/TTS、低延迟打断、长期记忆、Tool Router、Open Interpreter 外部后端适配和安全工具执行要求。 + +### 与现有 `openspec/specs/voice-pet-pipeline/spec.md` 的精确差异 + +1. 修改 `Local microphone and speaker transport`:从本机麦克风/扬声器 transport 扩展为 capture/render reference 双向音频流,播放音频必须提供给 AEC。 +2. 修改 `VAD speech endpoint detection`:从 turn-based 端点检测扩展为持续 VAD、打断检测和全双工 listening。 +3. 修改 `Local STT transcription`:从 captured segment final STT 扩展为 Streaming STT partial/stable/final。 +4. 修改 `Cloud LLM streaming reply`:从流式 LLM 输出扩展为 token-to-sentence-to-TTS response stream,并要求可取消。 +5. 修改 `Local TTS synthesis and playback`:从整段或分句播放扩展为 Streaming TTS、PCM chunk 播放和中途停止。 +6. 修改 `Pipeline state machine`:新增 `listening`、`tool_running`、`recovering` 等全双工 Agent 状态,明确 `speaking -> interrupted`。 +7. 修改 `Audio feedback suppression`:从播放期间抑制输入扩展为 AEC + VAD + interrupt,允许用户有效打断。 +8. 修改 `Conversation context management`:保留进程内上下文,同时新增长期记忆召回的边界。 +9. 修改 `Security and privacy`:新增长期记忆、工具执行、Open Interpreter 和浏览器自动化安全要求。 +10. 修改 `Performance targets`:新增打断延迟、Streaming STT 首字、TTS 首 chunk、APM 帧处理等指标。 +11. 新增 requirement:`WebRTC audio processing foundation`。 +12. 新增 requirement:`Full-duplex agent state machine`。 +13. 新增 requirement:`Streaming STT and realtime transcript`。 +14. 新增 requirement:`Low-latency interruption and cancellation`。 +15. 新增 requirement:`Streaming response and TTS playback`。 +16. 新增 requirement:`Long-term memory with FAISS and SQLite`。 +17. 新增 requirement:`Tool Router and structured tool execution`。 +18. 新增 requirement:`Open Interpreter external adapter`。 +19. 新增 requirement:`Browser automation tool boundary`。 +20. 新增 requirement:`Computer control reservation`。 + +### 推翻重做的理由 + +1. turn-based VAD/STT/TTS 无法自然支持“AI 讲话时用户插话”,只能不断添加补丁。 +2. 音色门控不能替代 AEC;没有 render reference 的系统无法稳定区分助手回放和真实用户。 +3. 只靠 final STT 会让用户等待过久;完整助手需要持续识别和实时字幕。 +4. 没有 Tool Router 和 MemoryManager 的语音助手只能聊天,不能完成 Agent 任务。 +5. 工具和记忆如果后补,会难以补齐安全边界;必须在 OpenSpec 阶段先定义。 + +## 实施计划 + +### 分阶段优先级顺序 + +1. 阶段 A:OpenSpec 文档和边界冻结。 +2. 阶段 B:全双工音频底座和 fake APM 测试。 +3. 阶段 C:状态机、事件总线、取消机制和模拟端到端。 +4. 阶段 D:Streaming STT、VAD 打断和回声抑制测试。 +5. 阶段 E:Streaming LLM/TTS、句子切分和可中断播放。 +6. 阶段 F:长期记忆 FAISS+SQLite、记忆召回和隐私策略。 +7. 阶段 G:Tool Router、安全工具、Open Interpreter adapter 和 Playwright adapter。 +8. 阶段 H:文档、性能验收、安全审计和迁移收尾。 + +### 里程碑估时 + +| 里程碑 | 乐观 | 最可能 | 悲观 | +| --- | ---: | ---: | ---: | +| A OpenSpec 文档 | 0.5 天 | 1 天 | 1.5 天 | +| B 音频底座 | 2 天 | 4 天 | 8 天 | +| C 状态机与取消 | 2 天 | 3 天 | 6 天 | +| D Streaming STT 与打断 | 3 天 | 5 天 | 10 天 | +| E Streaming TTS | 3 天 | 5 天 | 10 天 | +| F 长期记忆 | 2 天 | 4 天 | 8 天 | +| G Tool Router | 3 天 | 6 天 | 12 天 | +| H 验收与收尾 | 2 天 | 4 天 | 8 天 | + +总耗时预估:乐观 17.5 天,最可能 32 天,悲观 63.5 天。 + +### 数据/状态迁移策略 + +1. 现有进程内上下文不迁移为长期记忆,避免未经确认的历史被自动保存。 +2. 新长期记忆库首次启动为空;用户明确要求保存或分类器确认低敏偏好后才写入。 +3. 现有 `.env` 继续可用,新增变量采用默认值;全双工模式可通过 feature flag 启用。 +4. 现有 turn-based `run-live` 在迁移期保留,直到全双工验收稳定后再决定是否替换默认入口。 +5. `openinterpreter/` 外部仓库不纳入 Owner 迁移;只记录 adapter 配置和安全策略。 + +## Git 提交规范 + +1. 每完成一个大模块,必须立即执行构建或相应验证,然后执行 git commit。 +2. 大模块定义为 proposal “任务分解”中的主要功能组或实施计划中的里程碑阶段。 +3. 提交信息必须使用中文,格式为:`[模块名]:完成[具体功能描述],包含[关键变更]`。 +4. 提交前必须保证本模块验证通过,避免任何未提交的中间状态。 +5. 本 OpenSpec-only 阶段完成后提交信息固定为:`[全双工Agent架构]:完成完整语音助手OpenSpec计划,包含WebRTC音频底座、长期记忆和Tool Router设计`。 +6. 本次提交只允许包含 `openspec/changes/add-full-duplex-agent-voice-assistant/` 下的规划文档,不得提交 `openinterpreter/`、模型文件、依赖锁文件、`.env` 或运行代码。 diff --git a/openspec/changes/add-full-duplex-agent-voice-assistant/specs/voice-pet-pipeline/spec.md b/openspec/changes/add-full-duplex-agent-voice-assistant/specs/voice-pet-pipeline/spec.md new file mode 100644 index 0000000..d384717 --- /dev/null +++ b/openspec/changes/add-full-duplex-agent-voice-assistant/specs/voice-pet-pipeline/spec.md @@ -0,0 +1,379 @@ +## ADDED Requirements + +### Requirement: Full-duplex agent mode +The system SHALL support a planned full-duplex agent voice assistant mode that runs continuous microphone listening, streaming recognition, streaming response generation, streaming speech playback, interruption handling, memory retrieval, and safe tool routing as one coordinated pipeline. + +#### Scenario: Full-duplex mode starts +- **WHEN** the future implementation starts with `OWNER_ASSISTANT_MODE=full_duplex_agent` +- **THEN** the system SHALL initialize continuous audio capture, audio playback, WebRTC audio processing, VAD, streaming STT, streaming TTS, conversation management, memory, and tool routing before accepting user interaction + +#### Scenario: Turn-based mode remains available during migration +- **WHEN** the future implementation starts with the existing turn-based mode +- **THEN** the system SHALL preserve the existing wake-to-capture-to-STT-to-LLM-to-TTS behavior unless full-duplex mode is explicitly enabled + +#### Scenario: Full-duplex provider is unavailable +- **WHEN** a required full-duplex provider cannot be initialized and no configured fallback is allowed +- **THEN** the system SHALL fail startup with a structured provider error rather than entering a fake full-duplex loop + +### Requirement: WebRTC audio processing foundation +The system SHALL define a WebRTC audio processing stage for full-duplex mode that applies acoustic echo cancellation, noise suppression, and automatic gain control to microphone capture audio using speaker playback audio as render reference. + +#### Scenario: AEC uses playback reference +- **WHEN** TTS playback writes PCM frames to the speaker output path +- **THEN** the same render audio SHALL be provided to the WebRTC audio processing stage as echo cancellation reference + +#### Scenario: Capture audio is processed +- **WHEN** microphone frames are captured during full-duplex mode +- **THEN** the frames SHALL pass through AEC, noise suppression, and automatic gain control before VAD, interruption detection, and streaming STT consume them + +#### Scenario: Pure assistant echo is present +- **WHEN** the microphone receives only the assistant's own playback echo and no user speech +- **THEN** the processed audio SHALL NOT produce a valid user interruption or valid user transcript + +#### Scenario: APM format mismatch occurs +- **WHEN** capture audio and render reference audio have incompatible sample rate, channel count, or frame size +- **THEN** the system SHALL either explicitly resample to the configured internal format or fail with `AUDIO_APM_FORMAT_MISMATCH` + +#### Scenario: WebRTC APM fails at runtime +- **WHEN** the WebRTC audio processing stage raises a processing error +- **THEN** the pipeline SHALL emit a structured audio processing error and transition through recovery without corrupting conversation history + +### Requirement: Full-duplex audio ring buffers +The system SHALL use bounded capture and render reference ring buffers with timestamps so full-duplex capture, playback, AEC, VAD, STT, and interruption detection can run concurrently without unbounded memory growth. + +#### Scenario: Capture frame is received +- **WHEN** the microphone callback receives a PCM frame +- **THEN** the frame SHALL be written to the capture ring buffer with a monotonic timestamp, frame id, sample rate, channel count, and duration + +#### Scenario: Render frame is queued +- **WHEN** TTS or playback queues a PCM frame for speaker output +- **THEN** the frame SHALL be written to the render reference ring buffer with timing metadata usable by AEC + +#### Scenario: Ring buffer overflows +- **WHEN** an audio ring buffer exceeds its configured capacity +- **THEN** the system SHALL drop the oldest safe frames, emit an `audio_buffer_overrun` event, and continue without blocking the audio callback indefinitely + +#### Scenario: Reference drift is detected +- **WHEN** capture and render timestamps drift beyond the configured tolerance +- **THEN** the system SHALL emit an `audio_reference_drift` event and reduce confidence in interruption decisions until alignment recovers + +### Requirement: Full-duplex agent state machine +The system SHALL define a deterministic full-duplex state machine with `idle`, `listening`, `thinking`, `speaking`, `interrupted`, `tool_running`, and `recovering` states. + +#### Scenario: User input reaches the LLM +- **WHEN** full-duplex listening produces a valid final user transcript +- **THEN** the state machine SHALL transition from `listening` to `thinking` + +#### Scenario: First playable response is ready +- **WHEN** the LLM response stream and TTS pipeline produce the first playable audio chunk +- **THEN** the state machine SHALL transition from `thinking` to `speaking` + +#### Scenario: User interrupts playback +- **WHEN** valid user speech is detected while the assistant is speaking +- **THEN** the state machine SHALL transition from `speaking` to `interrupted` + +#### Scenario: Tool call starts +- **WHEN** the LLM requests an approved tool call +- **THEN** the state machine SHALL transition to `tool_running` until the tool result is returned, rejected, cancelled, or fails + +#### Scenario: Recoverable error occurs +- **WHEN** a recoverable provider, memory, tool, STT, TTS, LLM, or playback error occurs +- **THEN** the state machine SHALL transition to `recovering` and then to a safe `listening` or `idle` state after cleanup + +### Requirement: Pipeline event bus for agent stages +The system SHALL expose a stable pipeline event bus for full-duplex audio, recognition, generation, playback, interruption, memory, tools, confirmation, and recovery events. + +#### Scenario: Terminal reporter displays state +- **WHEN** the terminal displays full-duplex runtime status +- **THEN** it SHALL derive its output from pipeline events rather than directly reading provider internals + +#### Scenario: GUI subscribes to the same events +- **WHEN** a future desktop pet GUI is implemented +- **THEN** it SHALL be able to subscribe to the same event bus used by the terminal reporter and tests + +#### Scenario: Event contains diagnostics +- **WHEN** a stage emits an event +- **THEN** the event SHALL include the session id or turn id when available, stage name, event type, timestamp, and sanitized payload + +#### Scenario: Sensitive payload is present +- **WHEN** an event involves credentials, raw audio, memory content, or tool output +- **THEN** the event payload SHALL omit secrets and SHALL truncate or summarize sensitive content according to security policy + +### Requirement: Low-latency interruption and cancellation +The full-duplex mode SHALL detect valid user speech during assistant playback and cancel the active response pipeline with a target interruption latency under 200 ms. + +#### Scenario: User speaks while assistant is speaking +- **WHEN** the assistant is playing TTS audio and APM-processed microphone audio contains valid user speech +- **THEN** the interruption detector SHALL emit `interrupt_detected`, stop remaining playback, and cancel active LLM and TTS work + +#### Scenario: Interruption latency is measured +- **WHEN** a test fixture injects user speech during `speaking` +- **THEN** the system SHALL record the latency from VAD speech start to playback stop and SHALL target P95 under 200 ms + +#### Scenario: Echo does not interrupt +- **WHEN** microphone input during playback is explained by assistant render reference echo after AEC +- **THEN** the system SHALL NOT emit `interrupt_detected` + +#### Scenario: Cancellation is idempotent +- **WHEN** interruption or recovery triggers cancellation more than once +- **THEN** LLM, TTS, playback, STT, and cancellable tool tasks SHALL handle repeated cancellation without unhandled exceptions + +#### Scenario: Interrupted assistant text is stored +- **WHEN** assistant playback is interrupted before the full response is spoken +- **THEN** only assistant text that was fully spoken before interruption SHALL be eligible for short-term context or long-term memory + +### Requirement: Streaming STT and realtime transcript +The full-duplex mode SHALL use a streaming speech-to-text provider that emits partial, stable partial, and final transcripts while the user is speaking. + +#### Scenario: Partial transcript is produced +- **WHEN** the user is speaking and streaming STT emits a changed partial transcript +- **THEN** the event bus SHALL emit a realtime transcript event for user-visible feedback + +#### Scenario: Stable partial is produced +- **WHEN** a partial transcript remains stable according to the configured stability filter +- **THEN** the system SHALL mark it as stable partial while still withholding it from the LLM unless finalization rules allow submission + +#### Scenario: Final transcript is produced +- **WHEN** the current user utterance ends +- **THEN** streaming STT SHALL emit a final transcript that becomes the user message candidate for conversation processing + +#### Scenario: Empty transcript is produced +- **WHEN** final STT returns empty text, punctuation-only text, or text below confidence thresholds +- **THEN** the system SHALL discard the utterance, avoid invoking the LLM, and recover to listening + +#### Scenario: Streaming STT provider changes +- **WHEN** the configured provider changes from `faster_whisper` to `sensevoice` or `sherpa_onnx` +- **THEN** the state machine and Conversation Manager SHALL keep the same transcript event contract + +### Requirement: Streaming response and TTS playback +The full-duplex mode SHALL stream LLM output into sentence segmentation, TTS synthesis, and playback without waiting for the full assistant reply to complete. + +#### Scenario: First sentence is ready +- **WHEN** the LLM stream produces a complete sentence or configured safe speech segment +- **THEN** the sentence segmenter SHALL send sanitized text to the streaming TTS provider immediately + +#### Scenario: TTS chunk is ready +- **WHEN** the TTS provider produces the first playable PCM chunk +- **THEN** playback SHALL begin without waiting for all remaining assistant text + +#### Scenario: TTS text contains emoji +- **WHEN** LLM output contains emoji, sticker markers, Markdown image syntax, or other non-speech content +- **THEN** the text sent to TTS SHALL be sanitized before synthesis + +#### Scenario: Playback is cancelled +- **WHEN** cancellation is requested during response playback +- **THEN** playback SHALL stop at the next configured audio chunk boundary and SHALL clear unplayed audio chunks + +#### Scenario: TTS provider fails +- **WHEN** streaming TTS fails before or during playback +- **THEN** the system SHALL emit a structured TTS error and enter recovery without appending unspoken text to context + +### Requirement: Conversation manager for agent turns +The system SHALL define a Conversation Manager that coordinates short-term context, long-term memory retrieval, LLM streaming, tool calls, response playback, interruption, and recovery. + +#### Scenario: User final transcript is accepted +- **WHEN** a valid final transcript is produced +- **THEN** the Conversation Manager SHALL create a user message candidate and gather short-term context and relevant long-term memory before invoking the LLM + +#### Scenario: Assistant response completes +- **WHEN** the assistant response is fully spoken and no tool call or interruption is pending +- **THEN** the Conversation Manager SHALL commit the spoken assistant text to short-term context + +#### Scenario: Response is interrupted +- **WHEN** user speech interrupts the assistant response +- **THEN** the Conversation Manager SHALL cancel the current response, preserve only committed spoken text, and process buffered user speech as the next input + +#### Scenario: Conversation manager recovers +- **WHEN** a provider error occurs +- **THEN** the Conversation Manager SHALL emit a recoverable error event and return the agent to a safe listening or idle state when possible + +### Requirement: Long-term memory with FAISS and SQLite +The system SHALL define a long-term memory layer where SQLite stores text records and metadata while FAISS stores vector indexes for semantic retrieval. + +#### Scenario: Memory is saved +- **WHEN** the user explicitly asks the assistant to remember a non-sensitive preference, fact, project detail, or task summary +- **THEN** the Memory Manager SHALL save a typed memory record with metadata in SQLite and an embedding vector in FAISS + +#### Scenario: Memory is retrieved +- **WHEN** a later user input is semantically related to stored memories +- **THEN** the Memory Manager SHALL retrieve Top-K relevant memories and provide them to the Conversation Manager as separate memory context + +#### Scenario: Runtime restarts +- **WHEN** the assistant process restarts and memory is enabled +- **THEN** saved non-sensitive memories SHALL remain available through SQLite and FAISS + +#### Scenario: Memory is disabled +- **WHEN** `OWNER_MEMORY_ENABLED=0` +- **THEN** the system SHALL NOT read from or write to SQLite or FAISS memory storage + +#### Scenario: Sensitive content is detected +- **WHEN** a memory candidate contains credentials, payment data, private identifiers, account secrets, or other sensitive content +- **THEN** the system SHALL NOT automatically save it and SHALL require explicit user confirmation or reject the save + +#### Scenario: Memory index is inconsistent +- **WHEN** SQLite metadata and FAISS index records are missing, mismatched, or fail checksum validation +- **THEN** the Memory Manager SHALL report a health error and SHALL NOT return unverified memory records + +### Requirement: Tool Router and structured tool execution +The system SHALL define a Tool Router that validates structured tool calls, applies security policy, routes approved tools, enforces execution limits, and returns sanitized tool results to the LLM. + +#### Scenario: Allowed memory search runs +- **WHEN** the LLM requests `memory.search` with valid arguments +- **THEN** the Tool Router SHALL execute the memory search within configured limits and return sanitized results + +#### Scenario: Tool schema is invalid +- **WHEN** a tool call has an unknown name, missing required argument, invalid type, or unknown field +- **THEN** the Tool Router SHALL reject it with a structured validation error + +#### Scenario: Tool exceeds call budget +- **WHEN** a turn exceeds the configured maximum number of tool calls or total tool runtime +- **THEN** the Tool Router SHALL reject further tool calls for that turn and return a budget error + +#### Scenario: Tool output is too long +- **WHEN** a tool returns output larger than the configured limit +- **THEN** the Tool Router SHALL truncate the output, mark it as truncated, and avoid sending full oversized content to the LLM + +#### Scenario: Tool execution fails +- **WHEN** an approved tool raises an error or times out +- **THEN** the Tool Router SHALL return a structured tool error and the Conversation Manager SHALL recover or ask the user for next steps + +### Requirement: Safe first-version tools +The first full-duplex Agent tool set SHALL prioritize low-risk tools and SHALL include only `memory.search`, `memory.save`, `shell.readonly`, `openinterpreter.run`, and `browser.playwright` as planned first-version adapters. + +#### Scenario: Read-only shell command is allowed +- **WHEN** `shell.readonly` receives an allowlisted read-only command within allowed directories +- **THEN** the tool SHALL execute with timeout and output truncation + +#### Scenario: Write shell command is rejected +- **WHEN** `shell.readonly` receives a command that writes, deletes, installs dependencies, changes permissions, uploads data, or mutates external state +- **THEN** the Tool Router SHALL reject the command + +#### Scenario: Memory save is requested +- **WHEN** `memory.save` is requested for a non-sensitive user-approved memory +- **THEN** the tool SHALL save through the Memory Manager and return the new memory id + +#### Scenario: High-risk action is requested +- **WHEN** any planned first-version tool request involves deletion, upload, payment, trading, account changes, permission changes, secret access, or arbitrary code execution +- **THEN** the Tool Router SHALL reject it or require explicit confirmation according to policy + +### Requirement: Open Interpreter external adapter +The system SHALL treat Open Interpreter as an optional external CLI or subprocess backend and SHALL NOT copy the local `openinterpreter/` clone into the Owner source tree. + +#### Scenario: Open Interpreter is disabled +- **WHEN** `OWNER_OPENINTERPRETER_ENABLED=0` +- **THEN** the Tool Router SHALL mark `openinterpreter.run` unavailable and SHALL NOT invoke any Open Interpreter command + +#### Scenario: Open Interpreter path is missing +- **WHEN** `OWNER_OPENINTERPRETER_ENABLED=1` but the configured command cannot be found +- **THEN** the adapter SHALL return `OPENINTERPRETER_UNAVAILABLE` without affecting other tools + +#### Scenario: Low-risk Open Interpreter task is approved +- **WHEN** a low-risk Open Interpreter task passes directory, timeout, and argument validation +- **THEN** the adapter SHALL run it as an external process with bounded runtime and sanitized output + +#### Scenario: High-risk Open Interpreter task is requested +- **WHEN** an Open Interpreter task would write files, delete data, change accounts, install dependencies, upload data, or execute broad arbitrary code +- **THEN** the Tool Router SHALL require explicit confirmation or reject the task in first-version policy + +### Requirement: Browser automation boundary +The system SHALL define `browser.playwright` as a planned browser automation adapter with explicit safety boundaries. + +#### Scenario: Safe browser read task is requested +- **WHEN** the user asks for a low-risk browser read or extraction task and Playwright is enabled +- **THEN** the adapter SHALL run in an approved browser context with timeout, navigation limits, and sanitized output + +#### Scenario: Sensitive browser action is requested +- **WHEN** a browser task involves login, payment, purchase, form submission, account changes, private messages, uploads, or irreversible actions +- **THEN** the adapter SHALL require explicit confirmation or reject the task according to policy + +#### Scenario: Playwright is unavailable +- **WHEN** Playwright or its browser runtime is not installed +- **THEN** `browser.playwright` SHALL report a structured unavailable error and SHALL NOT affect voice conversation + +### Requirement: Computer control reservation +The system SHALL reserve a future `ComputerControlProvider` boundary for GUI control while excluding GUI click, keyboard, screen, and Accessibility automation from the first full-duplex Agent implementation. + +#### Scenario: GUI control is requested in first version +- **WHEN** a tool call requests direct GUI click, keyboard input, screen control, or Accessibility automation +- **THEN** the first-version Tool Router SHALL reject it as unsupported + +#### Scenario: Future provider is designed +- **WHEN** a future change implements computer control +- **THEN** it SHALL use public capabilities such as macOS Accessibility, Playwright, or trycua-style providers and SHALL define separate confirmation and audit requirements + +#### Scenario: Codex Computer Use is referenced +- **WHEN** design work references Codex Computer Use behavior +- **THEN** the system SHALL use it only as a safety policy reference and SHALL NOT copy private or bundled implementation details + +### Requirement: Agent security and privacy +The full-duplex Agent architecture SHALL protect credentials, raw audio, memory data, and tool execution boundaries by default. + +#### Scenario: Credentials are configured +- **WHEN** LLM, embedding, tool, or external adapter credentials are needed +- **THEN** they SHALL be read from local uncommitted configuration and SHALL NOT be hard-coded or logged + +#### Scenario: Raw audio is processed +- **WHEN** microphone or render audio is processed for APM, VAD, STT, or interruption +- **THEN** raw audio and derived voice features SHALL remain process-local unless the user explicitly enables a documented recording feature + +#### Scenario: Long-term memory stores text +- **WHEN** memory is saved +- **THEN** the memory record SHALL include sensitivity metadata and SHALL NOT include raw audio or credentials + +#### Scenario: Tool audit is recorded +- **WHEN** a tool executes +- **THEN** the audit record SHALL include tool name, risk level, confirmation status, duration, status, and sanitized summary without secrets + +#### Scenario: User disables tools +- **WHEN** `OWNER_TOOL_ROUTER_ENABLED=0` +- **THEN** no tool calls SHALL execute and LLM tool requests SHALL be rejected or ignored with a structured message + +### Requirement: Full-duplex performance targets +The full-duplex Agent architecture SHALL define measurable latency and reliability targets for audio processing, interruption, streaming STT, LLM-to-TTS, and tool routing. + +#### Scenario: Audio frame is processed +- **WHEN** WebRTC APM receives a normal 20 ms frame +- **THEN** processing SHALL target completion before the next frame interval in normal local runtime conditions + +#### Scenario: User begins speaking during playback +- **WHEN** valid user speech begins while assistant speech is playing +- **THEN** playback stop SHALL target P95 latency under 200 ms from speech start + +#### Scenario: User starts speaking in listening mode +- **WHEN** the user begins speaking while the system is listening +- **THEN** streaming STT SHALL target first stable user-visible transcript within 800 ms P95 + +#### Scenario: LLM produces first sentence +- **WHEN** the LLM stream yields a complete sentence +- **THEN** TTS SHALL target first playable audio chunk within 1000 ms P95 + +#### Scenario: Tool call runs +- **WHEN** an approved low-risk tool runs +- **THEN** it SHALL obey configured timeout, output limit, and maximum calls per turn + +### Requirement: Full-duplex testability +The full-duplex Agent architecture SHALL be testable with fake audio devices, fake WebRTC APM reference audio, fake VAD, fake streaming STT/TTS, fake memory stores, fake tools, and deterministic virtual time. + +#### Scenario: Fake APM suppresses echo +- **WHEN** a test injects playback reference audio and matching microphone echo +- **THEN** the fake or real APM test SHALL verify that echo does not trigger user transcript or interruption + +#### Scenario: Speaking interruption is tested +- **WHEN** a test injects user speech during assistant playback +- **THEN** the state sequence SHALL include `speaking -> interrupted -> listening` and active LLM/TTS/playback tasks SHALL be cancelled + +#### Scenario: Streaming STT and TTS are tested +- **WHEN** fake providers emit partial transcripts, final transcript, LLM deltas, sentence segments, and TTS chunks +- **THEN** tests SHALL verify event order, context writes, and playback order + +#### Scenario: Memory persists across restart +- **WHEN** a memory is saved and a new runtime instance starts with memory enabled +- **THEN** the memory SHALL be retrievable from SQLite and FAISS + +#### Scenario: Tools are tested +- **WHEN** fake tool calls cover allowed, rejected, confirmation-required, timeout, and oversized output paths +- **THEN** Tool Router tests SHALL verify decisions, audit events, and sanitized tool results + +#### Scenario: OpenSpec validation runs +- **WHEN** this planning change is complete +- **THEN** `openspec validate add-full-duplex-agent-voice-assistant --strict` and `openspec validate --all --strict` SHALL pass diff --git a/openspec/changes/add-full-duplex-agent-voice-assistant/tasks.md b/openspec/changes/add-full-duplex-agent-voice-assistant/tasks.md new file mode 100644 index 0000000..f9ac69e --- /dev/null +++ b/openspec/changes/add-full-duplex-agent-voice-assistant/tasks.md @@ -0,0 +1,102 @@ +## 1. OpenSpec 与项目边界 + +- [ ] 1.1 冻结 full-duplex Agent 实施入口命名;前置条件:人工确认 `run-agent-live` 或 `run-live --mode full-duplex`;优先级:P0;验收标准:README 和配置说明使用同一入口名;测试要点:CLI help 中能看到一致入口。 +- [ ] 1.2 增加全双工模式配置读取规划;前置条件:入口命名已确认;优先级:P0;验收标准:配置包含 `OWNER_ASSISTANT_MODE`、APM、VAD、STT、TTS、memory、tool router 开关;测试要点:无效 provider 触发配置错误。 +- [ ] 1.3 规划依赖分组;前置条件:provider 候选已确认;优先级:P0;验收标准:依赖按 audio、stt、tts、memory、tools 分组且默认不强制安装重依赖;测试要点:最小安装仍可跑现有 turn-based 测试。 +- [ ] 1.4 定义迁移兼容策略;前置条件:现有 `run-live` 行为梳理完成;优先级:P0;验收标准:turn-based 路径在全双工开发期保持可用;测试要点:旧 simulate-live 和 run-live 回归不破坏。 +- [ ] 1.5 建立模块提交门禁;前置条件:本变更 OpenSpec 已合并;优先级:P0;验收标准:每个主要任务组完成后验证并中文 commit;测试要点:`git status --short` 不包含未提交中间状态。 + +## 2. WebRTC APM 与音频环形缓冲 + +- [ ] 2.1 设计 `AudioFrame` 数据结构;前置条件:内部采样率和帧长方案已确认;优先级:P0;验收标准:包含 samples、sample_rate、channels、timestamp、frame_id;测试要点:frame serialization fake fixture 可稳定回放。 +- [ ] 2.2 实现 capture ring buffer;前置条件:`AudioFrame` 已定义;优先级:P0;验收标准:固定容量、线程安全、溢出事件可观测;测试要点:超过容量时丢弃旧帧并发 `audio_buffer_overrun`。 +- [ ] 2.3 实现 render reference ring buffer;前置条件:playback frame 格式已定义;优先级:P0;验收标准:播放 PCM 写入 reference,保留时间戳;测试要点:可按 capture timestamp 取 reference window。 +- [ ] 2.4 定义 `AudioProcessingProvider` 接口;前置条件:ring buffer 已完成;优先级:P0;验收标准:支持 process_capture、process_render、reset、health_check;测试要点:fake APM 可替换真实 provider。 +- [ ] 2.5 实现 fake WebRTC APM provider;前置条件:接口已定义;优先级:P0;验收标准:测试中可模拟 echo suppression、format mismatch、processing failure;测试要点:纯回声不触发 VAD/STT。 +- [ ] 2.6 接入真实 WebRTC APM 探针;前置条件:依赖选择已人工确认;优先级:P1;验收标准:macOS 本地能初始化 AEC/NS/AGC 或返回明确不可用;测试要点:`model-check` 或 `audio-check` 报告 provider 状态。 +- [ ] 2.7 增加 APM fallback 策略;前置条件:fake/真实 provider 接口完成;优先级:P1;验收标准:`OWNER_AUDIO_APM_REQUIRED` 控制失败即退或带标记降级;测试要点:APM 不可用时不进入假全双工。 + +## 3. 全双工状态机、事件总线与取消机制 + +- [ ] 3.1 定义 full-duplex 状态枚举;前置条件:spec 状态机确认;优先级:P0;验收标准:包含 idle、listening、thinking、speaking、interrupted、tool_running、recovering;测试要点:非法状态转移被拒绝。 +- [ ] 3.2 实现状态机转移表;前置条件:状态枚举完成;优先级:P0;验收标准:每个事件对应合法 next state;测试要点:覆盖正常、打断、工具、错误恢复路径。 +- [ ] 3.3 扩展 pipeline event bus;前置条件:现有 event bus 梳理完成;优先级:P0;验收标准:事件包含 session/turn id、stage、timestamp、sanitized payload;测试要点:终端 reporter 只消费事件。 +- [ ] 3.4 实现 cancellation token;前置条件:状态机完成;优先级:P0;验收标准:root token 和 child token 支持幂等 cancel;测试要点:多次 cancel 不抛异常。 +- [ ] 3.5 建立 cancellation graph;前置条件:LLM/TTS/playback/tool task 边界已定义;优先级:P0;验收标准:打断时同时取消 LLM、TTS、playback 和可取消工具;测试要点:取消后无后台线程泄漏。 +- [ ] 3.6 实现 recovery coordinator;前置条件:错误码清单已定义;优先级:P1;验收标准:可恢复错误进入 recovering 后回 listening 或 idle;测试要点:STT/TTS/LLM/tool 错误均能恢复。 + +## 4. VAD、Streaming STT 与低延迟打断 + +- [ ] 4.1 定义 `VadProvider` 接口;前置条件:APM 输出 frame 格式完成;优先级:P0;验收标准:支持 speech_start、speech_end、confidence;测试要点:fake VAD 可注入人声起止。 +- [ ] 4.2 规划 Silero VAD adapter;前置条件:依赖策略确认;优先级:P1;验收标准:本地模型路径、采样率和阈值可配置;测试要点:模型缺失返回结构化错误。 +- [ ] 4.3 定义 `StreamingSttProvider` 接口;前置条件:transcript event contract 确认;优先级:P0;验收标准:支持 start_session、accept_audio、finish、cancel;测试要点:partial/stable/final 事件顺序稳定。 +- [ ] 4.4 实现 fake Streaming STT;前置条件:接口完成;优先级:P0;验收标准:可模拟 partial 抖动、final 空文本、provider 失败;测试要点:partial 不进入 LLM。 +- [ ] 4.5 规划 faster-whisper adapter;前置条件:模型和依赖策略确认;优先级:P1;验收标准:开发 provider 可配置模型、设备、语言;测试要点:fixture 音频产生 final transcript。 +- [ ] 4.6 规划 SenseVoice adapter;前置条件:产品候选确认;优先级:P2;验收标准:接口兼容 Streaming STT contract;测试要点:中文 fixture 输出与 faster-whisper contract 一致。 +- [ ] 4.7 实现 interruption detector;前置条件:APM、VAD、event bus 完成;优先级:P0;验收标准:speaking 中有效用户声触发 `interrupt_detected`;测试要点:纯助手 echo 不触发。 +- [ ] 4.8 增加 200 ms 打断延迟指标;前置条件:interruption detector 完成;优先级:P0;验收标准:事件记录 speech_start 到 playback_stop latency;测试要点:虚拟时钟 fixture 断言 P95 目标。 + +## 5. LLM 流、句子切分、Streaming TTS 与播放 + +- [ ] 5.1 定义 LLM streaming adapter contract;前置条件:现有 LLM provider 梳理完成;优先级:P0;验收标准:支持 delta、tool_call、finish、cancel、error;测试要点:取消时连接关闭或停止消费。 +- [ ] 5.2 实现 sentence segmenter;前置条件:LLM delta contract 完成;优先级:P0;验收标准:中文标点、英文标点、最大等待阈值可切句;测试要点:URL、小数、代码块不误切。 +- [ ] 5.3 复用 TTS 文本净化;前置条件:现有 sanitizer 可调用;优先级:P0;验收标准:emoji、表情包、Markdown 图片不送 TTS;测试要点:纯表情回复不触发语音。 +- [ ] 5.4 定义 `StreamingTtsProvider` 接口;前置条件:播放 PCM 格式确认;优先级:P0;验收标准:支持 accept_text、flush、cancel、chunk events;测试要点:fake TTS 逐 chunk 输出。 +- [ ] 5.5 规划 CosyVoice adapter;前置条件:产品 TTS 方案确认;优先级:P1;验收标准:可配置 voice、sample_rate、chunk size;测试要点:provider 缺失结构化失败。 +- [ ] 5.6 实现可中断 playback queue;前置条件:render ring buffer 完成;优先级:P0;验收标准:chunk 播放同时写 reference,取消后清空未播 chunk;测试要点:播放停止边界小于配置 chunk。 +- [ ] 5.7 记录已播文本边界;前置条件:sentence/TTS/playback 完成;优先级:P0;验收标准:只提交完整播出的 assistant 文本;测试要点:中途打断未播文本不进上下文。 + +## 6. Conversation Manager 与长期记忆 + +- [ ] 6.1 定义 Conversation Manager 接口;前置条件:状态机和 LLM contract 完成;优先级:P0;验收标准:协调 transcript、context、memory、LLM、tool、TTS;测试要点:正常问答流事件顺序稳定。 +- [ ] 6.2 保留短期会话上下文;前置条件:现有 ConversationContext 梳理完成;优先级:P0;验收标准:进程内历史继续支持截断;测试要点:新 runtime 不读取旧短期历史。 +- [ ] 6.3 设计 SQLite memory schema;前置条件:记忆类型确认;优先级:P0;验收标准:包含 type、text、metadata、sensitivity、source_turn_id、checksum;测试要点:迁移创建表成功。 +- [ ] 6.4 设计 FAISS index 管理;前置条件:embedding provider 策略确认;优先级:P1;验收标准:index 版本、embedding model 和 record id 可一致性检查;测试要点:SQLite/FAISS 不一致时报 health error。 +- [ ] 6.5 实现 fake MemoryManager;前置条件:接口完成;优先级:P0;验收标准:支持 search/save/delete/health_check;测试要点:关闭 memory 时不读写。 +- [ ] 6.6 实现记忆召回链路;前置条件:fake MemoryManager 完成;优先级:P0;验收标准:final transcript 前检索 Top-K 并注入独立 memory context;测试要点:相关偏好可召回。 +- [ ] 6.7 实现记忆写入安全策略;前置条件:sensitivity 分类规则确认;优先级:P0;验收标准:敏感内容默认不自动保存;测试要点:API key、支付信息、账号信息被拒绝或确认。 +- [ ] 6.8 增加记忆管理命令规划;前置条件:schema 和安全策略完成;优先级:P2;验收标准:列出、删除、禁用、导出策略明确;测试要点:删除后检索不到。 + +## 7. Tool Router 与安全工具 + +- [ ] 7.1 定义 ToolCallRequest schema;前置条件:LLM tool call contract 完成;优先级:P0;验收标准:包含 name、arguments、timeout、turn id、intent;测试要点:未知字段拒绝。 +- [ ] 7.2 定义 ToolDecision 和 ToolResult;前置条件:ToolCallRequest 完成;优先级:P0;验收标准:支持 execute、reject、require_confirmation、cancelled;测试要点:结果脱敏和截断。 +- [ ] 7.3 实现 Tool Router fake core;前置条件:schema 完成;优先级:P0;验收标准:按工具名路由 fake adapters;测试要点:合法、非法、超时、超预算均覆盖。 +- [ ] 7.4 实现工具风险分类规则;前置条件:安全策略确认;优先级:P0;验收标准:低/中/高/禁止风险可解释;测试要点:删除、上传、账号、交易、安装依赖触发高风险或禁止。 +- [ ] 7.5 规划 `memory.search` 和 `memory.save`;前置条件:MemoryManager fake 完成;优先级:P0;验收标准:search 只读,save 走敏感策略;测试要点:敏感 save 需要确认或拒绝。 +- [ ] 7.6 规划 `shell.readonly` adapter;前置条件:allowlist 策略确认;优先级:P0;验收标准:只允许只读命令和受限目录;测试要点:写文件、删除、chmod、pip install 被拒绝。 +- [ ] 7.7 增加工具预算和防循环;前置条件:Tool Router core 完成;优先级:P0;验收标准:单轮最大调用数、总耗时、重复调用检测生效;测试要点:循环工具调用被截断。 +- [ ] 7.8 增加工具审计日志规划;前置条件:安全字段确认;优先级:P1;验收标准:记录工具名、风险、确认、耗时、状态、脱敏摘要;测试要点:日志不包含密钥和完整敏感输出。 + +## 8. Open Interpreter、Playwright 与电脑控制边界 + +- [ ] 8.1 规划 Open Interpreter adapter 配置;前置条件:本地 CLI 路径策略确认;优先级:P1;验收标准:`OWNER_OPENINTERPRETER_ENABLED` 默认关闭,命令路径可配置;测试要点:路径缺失返回 unavailable。 +- [ ] 8.2 实现 Open Interpreter 受限执行设计;前置条件:Tool Router 风险策略完成;优先级:P1;验收标准:受限目录、超时、输出截断、确认策略明确;测试要点:高风险任务拒绝或确认。 +- [ ] 8.3 明确 `openinterpreter/` 仓库边界;前置条件:git status 检查完成;优先级:P0;验收标准:Owner 不复制、不 vendoring、不提交该目录;测试要点:提交 diff 不包含 `openinterpreter/`。 +- [ ] 8.4 规划 Playwright adapter;前置条件:浏览器安全策略确认;优先级:P1;验收标准:默认关闭,低风险读取可用,敏感流程确认或拒绝;测试要点:登录、支付、提交表单触发确认策略。 +- [ ] 8.5 规划 browser context 隔离;前置条件:用户是否允许登录态待澄清;优先级:P2;验收标准:默认独立 context,不默认复用用户 Chrome 登录态;测试要点:配置关闭时工具不可用。 +- [ ] 8.6 预留 ComputerControlProvider;前置条件:GUI 控制方案未进入第一版;优先级:P2;验收标准:直接 GUI 点击、键盘、屏幕控制在第一版拒绝;测试要点:相关 tool call 返回 unsupported。 +- [ ] 8.7 编写 Codex Computer Use 安全参考说明;前置条件:公共实现边界确认;优先级:P2;验收标准:只参考确认策略,不复制私有实现;测试要点:文档无私有 API 或实现细节。 + +## 9. 端到端模拟、性能与安全验证 + +- [ ] 9.1 构建 fake full-duplex audio fixture;前置条件:AudioFrame 和 ring buffer 完成;优先级:P0;验收标准:可注入用户语音、助手回声、噪声、打断;测试要点:fixture 可重复回放。 +- [ ] 9.2 增加 fake APM echo 测试;前置条件:fake APM 和 fixture 完成;优先级:P0;验收标准:助手回声不触发 VAD/STT/interrupt;测试要点:无用户语音时无 transcript。 +- [ ] 9.3 增加 speaking interruption 测试;前置条件:状态机、playback、interruption 完成;优先级:P0;验收标准:`speaking -> interrupted -> listening`;测试要点:LLM/TTS/playback 均取消。 +- [ ] 9.4 增加 Streaming STT/TTS 顺序测试;前置条件:fake providers 完成;优先级:P0;验收标准:partial、final、LLM delta、sentence、TTS chunk、playback 顺序稳定;测试要点:partial 不写上下文。 +- [ ] 9.5 增加 memory restart 测试;前置条件:SQLite/FAISS fake 或真实实现完成;优先级:P1;验收标准:保存后新 runtime 可召回;测试要点:禁用 memory 时不读写。 +- [ ] 9.6 增加 Tool Router 安全测试;前置条件:Tool Router core 完成;优先级:P0;验收标准:允许、拒绝、确认、超时、截断、防循环均覆盖;测试要点:高风险操作不自动执行。 +- [ ] 9.7 增加性能指标采集;前置条件:event bus latency 字段完成;优先级:P1;验收标准:记录 APM frame、STT first partial、interrupt latency、TTS first chunk、tool runtime;测试要点:指标不含敏感数据。 +- [ ] 9.8 增加安全检查扩展;前置条件:memory/tool/audit 字段完成;优先级:P1;验收标准:检查 `.env`、key、raw audio、memory secrets、tool logs;测试要点:伪 secret 不出现在日志和事件。 + +## 10. 文档、验收与模块提交 + +- [ ] 10.1 更新 README 全双工架构说明;前置条件:入口和配置确认;优先级:P1;验收标准:说明 APM、Streaming STT/TTS、记忆、工具、限制;测试要点:命令示例和默认关闭项准确。 +- [ ] 10.2 更新 `.env.example` 规划变量;前置条件:配置清单冻结;优先级:P1;验收标准:新增全双工、APM、memory、tool、adapter 配置;测试要点:默认值不启用高风险工具。 +- [ ] 10.3 编写模型和依赖准备说明;前置条件:provider 候选确认;优先级:P1;验收标准:区分开发默认和产品候选;测试要点:不承诺未验证模型已可用。 +- [ ] 10.4 编写安全确认策略文档;前置条件:Tool Router 风险规则完成;优先级:P0;验收标准:列明自动允许、需要确认、默认拒绝类别;测试要点:与工具测试用例一致。 +- [ ] 10.5 执行 Python 编译验证;前置条件:实现代码变更完成;优先级:P0;验收标准:`.venv/bin/python -m compileall src tests scripts` 通过;测试要点:无语法错误。 +- [ ] 10.6 执行单元测试验证;前置条件:测试实现完成;优先级:P0;验收标准:`.venv/bin/python -m unittest discover -s tests` 通过;测试要点:覆盖 full-duplex、memory、tool、security。 +- [ ] 10.7 执行安全和模型检查;前置条件:相关命令实现完成;优先级:P0;验收标准:security-check、model-check、device-check 按阶段通过或给出明确跳过理由;测试要点:不泄露密钥。 +- [ ] 10.8 执行 OpenSpec 校验;前置条件:docs 和 spec 更新完成;优先级:P0;验收标准:`openspec validate add-full-duplex-agent-voice-assistant --strict` 与 `openspec validate --all --strict` 通过;测试要点:无 spec 格式错误。 +- [ ] 10.9 按模块提交;前置条件:对应模块验证通过;优先级:P0;验收标准:提交信息符合 `[模块名]:完成[具体功能描述],包含[关键变更]`;测试要点:`git status --short` 不含本模块未提交改动。