Skip to content

Phase 4C:接入无执行权的声纹 Shadow Runtime - #2408

Draft
MomiJiSan wants to merge 15 commits into
Project-N-E-K-O:codex/phase4-b-throttlingfrom
MomiJiSan:codex/phase4-c-speaker-shadow
Draft

Phase 4C:接入无执行权的声纹 Shadow Runtime#2408
MomiJiSan wants to merge 15 commits into
Project-N-E-K-O:codex/phase4-b-throttlingfrom
MomiJiSan:codex/phase4-c-speaker-shadow

Conversation

@MomiJiSan

@MomiJiSan MomiJiSan commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Draft / Stacked PR:本 PR 当前严格叠加在 #2398 27d0affc 之上,head 为 33e6dfbd。它只证明“真实 CAM++ 可以安全地在 N.E.K.O 中观察 Independent ASR 候选说话人”,没有任何音频执行权。#2398 合并前始终保持 Draft,不转 Ready、不请求正式 Review。

结果

内存 Enrollment PCM → CAM++ reference embedding
Detector 候选 PCM  → CAM++ candidate embedding
                         ↓
                   cosine similarity
                         ↓
         Shadow observation / would_block / resources
  • 实际加载并离线运行 CAM++ zh-en advanced ONNX。
  • enrollment 仅生成内存 profile;不保存 PCM、embedding、路径或用户身份。
  • 候选评分输出真实 cosine similarity,并同时模拟 0.40 / 0.44 / 0.48 / 0.52 / 0.55 五档 would_block
  • would_block 只用于观察和指标,绝不修改、延迟、丢弃或重排 Provider 音频。
  • Core 现在可以通过正式的私有 factory 组合关系把 Shadow 交给 IndependentAsrRuntime,不再需要测试直接修改嵌套私有字段。

固定模型身份

  • ModelScope ID:iic/speech_campplus_sv_zh_en_16k-common_advanced
  • Revision:v1.0.0
  • License:Apache-2.0
  • ONNX export/distribution:k2-fsa/sherpa-onnx
  • 文件大小:28,281,164 bytes
  • SHA256:aa3cfc16963a10586a9393f5035d6d6b57e98d358b347f80c2a30bf4f00ceba2
  • 输入 / 输出:float32[N,T,80]float32[N,192]

权重不进入 Git。Windows/Linux 正式构建从 manifest 固定 URL 获取,先校验大小与 SHA,再打入安装产物;运行时不联网,也不接受用户缓存目录中的同名权重。

实现边界

  • CampPlusEmbeddingModel:独立单线程 ONNX CPU session;不修改共享 OnnxModelRuntime
  • 官方兼容 frontend:16 kHz mono PCM16LE、25 ms / 10 ms、80-bin Kaldi fbank、snip edges、sample normalization、global mean。
  • CampPlusSpeakerProfile:只保存 defensive-copy 的 192 维 L2 reference embedding、profile revision 和模型身份;关闭时尽力覆盖数组。
  • CampPlusSpeakerShadowBackend:只做 PCM → embedding → dot product。
  • Core 只持有私有、默认 None_speaker_shadow_factoryIndependentAsrRuntime 只接收并转交 factory;factory/模型失败均 fail-open 到无 Shadow。
  • 模型 session 在首个满足 1.5 秒的候选时才加载;约 60 秒 idle unload;一个 session、一个串行 worker。
  • 候选状态使用有界 buffer、有序 finish marker、generation/fence、退避、超时和 stale 结果丢弃。
  • 评分达到门槛后,finalized tombstone 明确保存 sufficient / insufficient / dropped 终态;成功评分或推理失败不再误计为 insufficient。

本轮工程收敛

本轮新增三个可独立回滚的提交:

  1. fix(asr): bridge speaker shadow factory through core runtime
  2. fix(voice): keep scored shadow candidates out of insufficient metrics
  3. test(asr): replay immutable frames for shadow isolation

固定输入隔离测试只预处理一次原始 48 kHz PCM,得到同一个不可变 ProcessedVoiceFrame,再按 baseline → shadow → shadow → baseline 四轮重放。四轮连续 Provider PCM、拼接 SHA256、字节顺序、SmartTurn seal、lifecycle history、Provider final、Detector 终态以及 pending queue/buffer 清理状态完全一致;两次 Shadow similarity 均为 0.75。callback chunk 数不作为隔离合同,因为相同连续 PCM 可能被异步调度切成不同 callback 分片。

回归报告 / Regression Report

  • 改动原因:补齐原 Phase 4C:接入无执行权的声纹 Shadow Runtime #2408 尚缺的正式 Core factory bridge,修复 observation telemetry 的候选终态误计数,并把此前临时实验中的 ABBA 结论固化为可重复自动化测试。
  • 前后行为:默认值仍为 None;无 profile/无 factory 时零模型工作;factory 异常不影响 Independent ASR;Shadow 高低分均不改变 Provider PCM、endpoint authority 或生命周期。
  • 本地验证:真实 CAM++ smoke 3 passed;CAM++ / Shadow / Detector / Core / VoiceTurn / build-contract 聚焦矩阵 284 passed, 2 skipped(2 项为与本变更无关的可选 VoiceTurn 真实资产);全部 ASR 回归 455 passed;Core 回归 176 passed。Ruff、py_compile、async-blocking、Windows/Linux workflow YAML 与 git diff --check 均通过。
  • 模型资源:当前 Windows 25 次 1.5 秒推理 warm mean 10.46 ms、P95 11.30 ms;ONNX session RSS 增量 50,044,928 bytes,均低于 50 ms / 60 MB 停止线。真实模型输出 192 维、finite、L2≈1;frontend parity 门槛继续通过。
  • GitNexus:相对 Phase 4B:接入 RNNoise/Silero 节流并拆分 ASR 结束权 #2398 的 compare 为 MEDIUM,21 个预期文件,影响仅落在 Independent ASR start/close/lifecycle/status 流;没有 Provider worker/registry、transport、native Omni、SmartTurn/RNNoise/Silero 算法、UI、数据库或配置迁移文件。
  • 打包边界:本机固定缓存经准备脚本和 offline 模式双重校验,size/SHA 完全一致;build-contract 已验证 Windows/Linux workflow 都准备、打包并离线复验 ONNX、manifest、notice。完整 Linux Nuitka 安装产物仍由远端 Linux CI 验收,本地 Windows 不能替代该门槛。

明确不做

  • 不做生产阈值、硬拦截、fail-closed、音频暂存/重放、Provider 分支或全 ASR 节流。
  • 不接 native Omni;不修改 Provider worker/registry、transport、AsrAudioDispatcher、RNNoise、Silero、SmartTurn 算法或生命周期状态机。
  • 不提供产品设置 UI、enrollment API、持久化、数据库迁移、云同步或自动第一说话人 enrollment。
  • 不宣称可防录音回放、声音克隆、多人混说,或可可靠阻止非本人音频上传。

Ready 状态

在前置 PR #2398 真正合入最终目标分支前,本 PR始终保持 Draft。前置合并后,才把纯 #2408 提交重叠到最新 main,执行最终 Windows/Linux 发布构建、完整 CI、GitNexus 和人工 review;所有硬门槛通过后才转 Ready。

当前准确表述:N.E.K.O 已能用真实 CAM++ 在 default-off、无执行权的 Shadow 中观察 Independent ASR 候选说话人;该观察不能决定音频是否继续。

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (3)
  • steam
  • react-rewrite
  • plugin

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cfda0a02-c461-499a-af70-08930364c388

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

新增 CAM++ 说话人模型资产管理、ONNX 嵌入推理和异步 speaker shadow 运行时,并将其接入 ASR 检测、独立打包、离线验证、评估脚本及测试体系,覆盖 Linux、Unix 与 Windows 构建流程喵。

Changes

CAM++ 说话人资产与构建交付

Layer / File(s) Summary
模型资产准备与构建交付
.github/workflows/*, data/speaker_models/*, tools/voice_eval/prepare_speaker_model.py, specs/launcher.spec, pyproject.toml
新增模型清单、许可证通知、缓存/下载校验、构建打包及离线验证流程。

模型推理与运行时集成

Layer / File(s) Summary
CAM++ 资产、特征与嵌入模型
main_logic/asr_client/campplus.py
新增资产校验、特征提取、ONNXRuntime 推理、speaker profile 和评分后端。
异步 speaker shadow 后端与生命周期
main_logic/asr_client/speaker_shadow.py
新增候选队列、异步评分、回调、退避加载、卸载、重置和指标统计。
ASR 注入与候选状态管理
main_logic/asr_client/runtime.py, main_logic/asr_client/detector_runtime.py
将可选 shadow runtime 注入独立 ASR,并在 provider、SmartTurn 和分段流程中管理候选生命周期。

验证与评估

Layer / File(s) Summary
运行时行为与模型验证测试
tests/unit/test_asr_detector_runtime.py, tests/unit/test_asr_speaker_shadow.py, tests/unit/test_campplus_*.py, tests/unit/test_core_independent_asr.py, tests/unit/voice_turn/test_build_contracts.py
新增运行时、模型合同、资源生命周期、构建交付和真实模型烟雾测试。
离线相似度评估
tools/voice_eval/evaluate_campplus_shadow.py
新增 WAV 校验、profile 构建、候选评分及 JSON 结果输出脚本。

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested labels: enhancement, Needs Check!

Poem

模型缓存落进包,喵
影子候选队列跑,喵
相似度儿轻轻跳,喵
离线校验守门牢,喵
构建成功尾巴摇,喵

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 标题准确概括了将无执行权的声纹 Shadow Runtime 接入独立 ASR 的主要改动喵
Description check ✅ Passed 描述包含概述、回归与测试信息,且未拆分理由不适用,整体基本符合模板要求喵

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MomiJiSan

Copy link
Copy Markdown
Contributor Author

已按收敛方案完成本轮修复,并把 #2408 精确 rebase 到 #2398 c324e53f

本轮三个提交:

  1. 4285a24a fix(asr): bound speaker shadow candidate lifecycle
    • Shadow 使用独立 candidate identity,不再推进 ASR _candidate_generation
    • RNNoise PREWARM / Silero CONFIRMED / PAUSE finish;pause 后 2 秒静音不再被镜像。
    • SmartTurn completion fence 只结束旧 Shadow candidate,successor 使用新 generation。
    • audio/finish 同队列;评分前释放 PCM;短 candidate、queue pressure、pending buffer 与 tombstone 全部有界。
  2. e2724aab fix(asr): detach speaker shadow shutdown and retry loading
    • load failure 改为 5/10/20/40/60 秒退避并可恢复。
    • sync/async callback 100 ms 超时。
    • Shadow close 100 ms grace;卡住的 score 不再阻塞 Detector/Core,backend 在 score 结束后串行关闭,不与推理并发。
  3. 83f0e4e7 test(asr): validate observation-only speaker shadow boundaries
    • 补齐 stale/duplicate observation、2 秒 pause 静音、跨轮、10,000 candidate、queue pressure、load recovery 与 bounded close 回归。

验证结果:

  • ASR + Core independent ASR + SmartTurn + VoiceTurn:410 passed
  • Shadow + Detector 聚焦:55 passed
  • speaker_shadow.py coverage:90.38%
  • ruff check / check_async_blocking.py / git diff --check:通过
  • GitNexus compare:Low risk,0 affected processes

边界未变化:默认关闭、无生产 backend、无 enrollment、无 UI/telemetry、无硬拦截;不修改 Core 生命周期、Provider/Registry、RNNoise/Silero 参数、SmartTurn 模型或任何 fallback。PR 继续保持 Draft,等本次 force-push 后的 CI/review 对精确堆叠组合重新确认。

@MomiJiSan
MomiJiSan force-pushed the codex/phase4-b-throttling branch from abb7b18 to 4240984 Compare July 21, 2026 13:00
@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch from 83f0e4e to d381f4d Compare July 21, 2026 13:00
@MomiJiSan

Copy link
Copy Markdown
Contributor Author

本次已将 Phase 4C 重叠到收敛后的 #2398speaker_shadow.py 作为完整、内聚的 Shadow runtime 保持独立,集成只进入 asr_client/detector_runtime.py;没有进入 core,也没有恢复任何旧 ASR 私有路径。Shadow 聚焦回归 55 passed,完整堆叠回归 486 passed;Ruff、compileall、core contracts 通过,GitNexus LOW、0 affected processes。

Copy link
Copy Markdown
Contributor Author

已沿堆叠链检查 #2396 的两个 P2 修复(14fe8f5a)对 4C 的影响。

#2408 仅涉及 Speaker Shadow runtime/integration/tests,没有修改 static/app/app-audio-capture.js,也不拥有语音设置弹窗或 Pending 状态,因此无需在 4C 重复收敛这部分代码。修复应保持在 4A 单点实现。

堆叠顺序仍为 #2396#2398#2408:待 #2398 吸收新的 4A 基线后,#2408 再跟随新的 4B head 做精确 rebase 和组合回归即可;不向 Shadow runtime、Core、Provider 或 Electron 原生层扩散本次修复。

MomiJiSan commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

CAM++ real-model Shadow implementation has been pushed at 6d8a78fb6.

集中 review 请重点检查以下边界:

  1. CampPlusEmbeddingModel 不继承/修改共享 ONNX runtime,不联网,不搜索用户缓存;manifest URL、size、SHA、tensor 和 metadata 都是 fail-fast。
  2. Core 只有私有默认 None 的 factory;无 profile、关闭或 factory/model 失败都不会创建 session,也不会改变 ASR route/fallback。
  3. SpeakerShadowObservation 的五档 would_block 全是模拟结果;Provider PCM 字节、顺序、生命周期不受 similarity 影响。
  4. enrollment/profile 只在内存;runtime close 会关闭 backend 并尽力覆盖 reference 私有副本。
  5. Windows/Linux workflow 必须准备并离线复验模型,Nuitka/PyInstaller 都声明包含 data/speaker_models

验证证据:

  • 完整 ASR + VoiceTurn:415 passed;2 个 skip 是既有 VAD/SmartTurn 权重未准备,与 CAM++ 无关。
  • CAM++ / Shadow / Detector / Core / build contract 聚焦:211 passed;新增核心覆盖率 88.8%。
  • Ruff / compile / workflow YAML / async-blocking:通过。
  • frontend embedding cosine >= 0.99999。
  • 官方同人样本 0.8304/0.8194;异人样本 0.1487/0.1178/0.1157。
  • Windows 50 次 1.5 秒 warm P95 12.013 ms,session RSS 49,618,944 bytes。
  • Ubuntu WSL 真实离线加载:SHA/size 复验通过,192 维 finite/L2=1.0,推理 16 ms,session RSS 56,360,960 bytes。
  • GitNexus compare:LOW / 0 affected processes。

PR 继续保持 Draft。当前远端 CI 未运行的原因是仓库 Actions 只监听 base=main,而本 PR 是 stacked base;fork head 也不能作为 upstream workflow_dispatch ref。跨平台手动 workflow 会覆盖公开 nightly release,因此未冒险触发。

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added enhancement New feature or request Needs Check! need doctor to check labels Jul 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/build-desktop.yml (1)

640-646: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

macOS 这里别再写死 dist/Xiao8
vad_modelsspeaker_models 的离线校验都改成 $NEKO_NUITKA_RUNTIME_DIR/data/... 喵。macOS 分支里 RUNTIME_DIR 实际是 dist/Xiao8/projectneko_server.app/Contents/MacOS,继续写死 dist/Xiao8/data/... 会在 app bundle 下找错目录,离线检查会误报缺失喵。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-desktop.yml around lines 640 - 646, Update the macOS
asset paths in the voice evaluation commands invoking
prepare_voice_turn_assets.py and prepare_speaker_model.py to use
$NEKO_NUITKA_RUNTIME_DIR/data/vad_models and
$NEKO_NUITKA_RUNTIME_DIR/data/speaker_models instead of the hardcoded dist/Xiao8
prefix, while preserving the existing offline validation and missing-status
handling.
🧹 Nitpick comments (3)
main_logic/asr_client/campplus.py (2)

706-724: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

静默失败缺少日志,调试会摸黑喵。

CampPlusSpeakerShadowFactory.__call__() 捕获资产校验/模型契约异常后直接返回 None,没有任何日志记录(本文件也没有引入 logging)。作为默认关闭、失败即优雅降级的观察型功能,这个方向是对的,但生产环境如果因为路径错误/资产损坏而始终拿不到 shadow runtime,排查起来会很麻烦——除非调用方(如 detector_runtime.py)自己记录了原因喵。

建议在 except 分支加一条 debug/warning 级别日志,说明具体失败原因(不影响返回值,也不影响"零副作用"的设计)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main_logic/asr_client/campplus.py` around lines 706 - 724, Update
CampPlusSpeakerShadowFactory.__call__() to log the caught CampPlusAssetError,
RuntimeError, or ValueError with the exception details before returning None;
add or reuse the module’s logging facility without changing the existing
graceful-degradation behavior or return value.

315-376: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

每次重新 load() 都会对 28MB 模型重新算一遍完整 SHA-256 喵。

load()validate_assets() 每次都会触发 resolve_verified_campplus_asset 做一次全量 SHA-256(约 28MB)。结合 idle-unload 设计(模型闲置后会 unload(),下次需要时再 load()),只要这个 shadow 组件间歇性工作,就会反复对同一份没变过的文件做重复哈希计算——虽然频率不高,但对一个"观察即可、不该抢资源"的后台组件来说,这点 CPU/IO 抖动本可以省掉喵。

可以考虑在同一进程内缓存"已验证过且文件 mtime/size 未变"的结果,跳过后续重复的全量哈希(仍保留首次加载时的完整校验)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main_logic/asr_client/campplus.py` around lines 315 - 376, Update CampPlus
asset validation around validate_assets and load so the same process caches a
successfully verified asset keyed by its current file mtime and size, allowing
subsequent loads after unload to skip the full SHA-256 when those metadata
values are unchanged. Preserve the initial complete verification and invalidate
or revalidate the cache whenever the asset file changes.
specs/launcher.spec (1)

68-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

文件名判断硬编码,未来改名容易漏更新喵。

speaker_model_assets_present 直接硬编码 campplus-zh-en-advanced.onnx,而没有像其他资产一样只判断目录存在,也没有从 data/speaker_models/manifest.json 读取 filename 字段。目前和 CAMPPLUS_FILENAME 完全一致,暂时没问题,但如果模型换代改名,这里可能被遗忘同步喵(本猫娘只是提醒一下,不是非改不可啦)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/launcher.spec` around lines 68 - 70, 更新 specs/launcher.spec 中
speaker_model_assets_present 的判断,避免直接硬编码 campplus-zh-en-advanced.onnx;改为复用
data/speaker_models/manifest.json 的 filename 字段或现有模型文件名配置,并保持对 speaker_models
资产存在性的判断行为。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/build-desktop.yml:
- Around line 640-646: Update the macOS asset paths in the voice evaluation
commands invoking prepare_voice_turn_assets.py and prepare_speaker_model.py to
use $NEKO_NUITKA_RUNTIME_DIR/data/vad_models and
$NEKO_NUITKA_RUNTIME_DIR/data/speaker_models instead of the hardcoded dist/Xiao8
prefix, while preserving the existing offline validation and missing-status
handling.

---

Nitpick comments:
In `@main_logic/asr_client/campplus.py`:
- Around line 706-724: Update CampPlusSpeakerShadowFactory.__call__() to log the
caught CampPlusAssetError, RuntimeError, or ValueError with the exception
details before returning None; add or reuse the module’s logging facility
without changing the existing graceful-degradation behavior or return value.
- Around line 315-376: Update CampPlus asset validation around validate_assets
and load so the same process caches a successfully verified asset keyed by its
current file mtime and size, allowing subsequent loads after unload to skip the
full SHA-256 when those metadata values are unchanged. Preserve the initial
complete verification and invalidate or revalidate the cache whenever the asset
file changes.

In `@specs/launcher.spec`:
- Around line 68-70: 更新 specs/launcher.spec 中 speaker_model_assets_present
的判断,避免直接硬编码 campplus-zh-en-advanced.onnx;改为复用 data/speaker_models/manifest.json
的 filename 字段或现有模型文件名配置,并保持对 speaker_models 资产存在性的判断行为。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c90c71f-9b7e-47fa-8d88-f367c6b39e31

📥 Commits

Reviewing files that changed from the base of the PR and between 4240984 and 6d8a78f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .github/workflows/build-desktop-linux.yml
  • .github/workflows/build-desktop.yml
  • .gitignore
  • data/speaker_models/THIRD_PARTY_NOTICES.md
  • data/speaker_models/manifest.json
  • main_logic/asr_client/campplus.py
  • main_logic/asr_client/detector_runtime.py
  • main_logic/asr_client/runtime.py
  • main_logic/asr_client/speaker_shadow.py
  • pyproject.toml
  • specs/launcher.spec
  • tests/unit/test_asr_detector_runtime.py
  • tests/unit/test_asr_speaker_shadow.py
  • tests/unit/test_campplus_real_model.py
  • tests/unit/test_campplus_speaker_model.py
  • tests/unit/test_core_independent_asr.py
  • tests/unit/voice_turn/test_build_contracts.py
  • tools/voice_eval/evaluate_campplus_shadow.py
  • tools/voice_eval/prepare_speaker_model.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Project-N-E-K-O/N.E.K.O.-PC (manual)

@MomiJiSan

Copy link
Copy Markdown
Contributor Author

补充验收记录(head 6d8a78fb6):

  • 完整 ASR + VoiceTurn 组合回归:415 passed, 2 skipped。两个 skip 均为既有 VAD/SmartTurn 权重未准备,与 CAM++ 无关。
  • CAM++ / Shadow / Detector / Core / build-contract 聚焦回归:211 passed;新增核心覆盖率 88.8%
  • Windows 真实模型:50 次 1.5 秒 warm 推理 mean 11.126 ms、P95 12.013 ms、max 12.115 ms;session RSS 增量 49,618,944 bytes
  • Ubuntu WSL 真实离线加载:模型 size/SHA 复验一致,输出 (192,)、finite、L2 norm 1.0;推理 16 ms;session RSS 增量 56,360,960 bytes
  • 官方样本分离:同人 0.8304 / 0.8194,异人 0.1487 / 0.1178 / 0.1157
  • GitNexus compare:LOW / 0 affected processes;Ruff、compile、workflow YAML、async-blocking 均通过。

边界保持不变:默认关闭、无 profile 零加载、仅 Shadow observation;不接硬拦截、Provider/Omni、UI 或持久化,不改变 Provider PCM 字节、顺序或生命周期。

远端 CI 尚未运行并非测试失败:仓库 PR Actions 只监听 base=main,而 #2408 是 stacked PR;fork head 也不能作为 upstream workflow_dispatch ref。跨平台手动 workflow 会覆盖公开 nightly release,因此没有冒险触发。PR 继续保持 Draft,等待可安全运行的 Windows/Linux 构建与人工 review。

Copy link
Copy Markdown
Contributor Author

已按备忘录独立核对并处理本轮 review,修复提交:a26437768

  • ✅ macOS 资产校验路径:已改用 $NEKO_NUITKA_RUNTIME_DIR/data/{vad_models,speaker_models},可正确指向 app bundle 内的 Contents/MacOS
  • ✅ Factory 静默降级:保留返回 None 的 fail-open 行为,同时记录不含 PCM/embedding 的 warning 与异常原因。
  • ✅ PyInstaller 模型文件名:改为从 data/speaker_models/manifest.jsonfilename 读取,并拒绝目录穿越形式的文件名。
  • ❌ SHA/mtime 缓存:不采纳。CAM++ 的固定权重身份要求每次 load 都完整校验 SHA-256;仅以 size/mtime 作为缓存键会允许同大小、同时间戳的替换权重绕过校验,和本 PR 的 provenance/fail-fast 合同冲突。

验证:

  • 完整 ASR / VoiceTurn / CAM++:511 passed, 2 skipped(两个 skip 仍为既有 VAD/SmartTurn 权重未准备)。
  • CAM++ / Shadow / Detector / Core / build-contract 聚焦:181 passed
  • campplus.py coverage:88.27%
  • Ruff、compile、async-blocking、workflow YAML、git diff --check:通过。
  • GitNexus:LOW,0 affected processes。

边界未变化:默认关闭、无 profile 零加载、仅 Shadow observation;不接硬拦截、Provider/Omni、UI 或持久化。

@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch from a264377 to fec62d3 Compare July 22, 2026 03:07

MomiJiSan commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Stacked rebase / Soniox 边界验证

本 PR 已从旧 #2398 head 4f48780e3 rebase 到修复后的 #2398 b9e40984b,当前 head 为 20dafe93f;唯一冲突是相邻 import,保留 retry_policyspeaker_shadow 两组导入,未增加任何 Soniox 特判。

复核结果:

  • Speaker Shadow 自有 runtime diff 只创建 default-off 私有 factory 并注入 Detector;
  • Shadow 不读取或改写 _asr_transport_selection_asr_providerprovider_policy、endpoint、fallback、retry 或 blocked 状态;
  • _resolve_core_follow_selection、fallback selection 与跨 Provider 状态改写在继承后的生产 runtime 中均为零;
  • Shadow 分数/失败只进入 observation metrics,不改变 Detector/Provider PCM 字节、顺序或结果。

验证:完整堆叠回归 515 passed / 3 skippedruntime.py 覆盖率 80.11%;Core package contract、Ruff、stacked diff check 通过;GitNexus LOW、0 affected flows。

@MomiJiSan
MomiJiSan force-pushed the codex/phase4-b-throttling branch 2 times, most recently from 4f48780 to b9e4098 Compare July 22, 2026 03:34
@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch from fec62d3 to 20dafe9 Compare July 22, 2026 03:38

Copy link
Copy Markdown
Contributor Author

真实麦克风人工实测补充(2026-07-22)

在 Windows 上通过临时、仅内存的开发测试入口完成了一组 CAM++ 实录验证:

  • 输入设备:Samson C01U Pro Mic
  • 输入合同:16 kHz、mono、PCM16LE
  • enrollment:由 owner 录制 4 段、每段约 6 秒;分别生成 embedding 后平均并再次 L2 归一化
  • 隐私边界:录音 PCM、reference/candidate embedding 均未写入文件、Git、配置或 telemetry;profile 仅存在于测试窗口进程内存
  • 评分:真实 campplus-zh-en-advanced.onnx 推理与 cosine similarity,不使用 mock

同一份 owner profile、同一设备和同一环境下的独立候选结果:

候选 Similarity 0.40 / 0.44 / 0.48 / 0.52 / 0.55
owner(第 5 段独立录音) 0.7234 全部 pass
非 owner(独立录音) 0.2142 全部 would_block

本次同 profile 分数间隔为 0.5092。另一次已释放的 owner profile 下,非 owner 的 4 次独立候选结果为 0.0085 / 0.0297 / 0.0394 / 0.0357;这些只作为方向性补充,不与本次 profile 混合计算。

当前结论

这组结果证明:真实 CAM++ 链路能够对内存 enrollment 建立 192 维 reference profile,并在相同麦克风、相同环境、同一 profile 下对 owner 与非 owner 产生明显分离的真实 cosine similarity;五个候选阈值的 would_block 仅作为 Shadow observation,未修改、延迟或丢弃任何 Provider PCM。

这不是准确率统计,也不用于确定生产阈值。样本规模仍不足以覆盖跨设备、远场、噪声、多人混说、录音回放、声音克隆或 native Omni 路径,因此本 PR 的结论仍严格限定为:真实 CAM++ 可用于无执行权的 speaker Shadow 观察,不启用硬拦截。

@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch 2 times, most recently from 21e1212 to 01345cf Compare July 22, 2026 06:42
@MomiJiSan

Copy link
Copy Markdown
Contributor Author

本 PR 已 rebase 到包含 TTL 与 Detector binding 修复的 #2398 最新 head 4a4a4637a

Speaker Shadow 没有复制或拥有 transport TTL、binding cleanup、Provider selection、endpoint、fallback、retry 或 recovery 逻辑;这些行为全部继承 Phase 4B 基线。边界扫描确认 Shadow 实现对相关策略字段的引用为 0,相对新 #2398 base 的重复修复 diff 为 0;rebase 前后 9 个 Shadow commit 均为 patch-equivalent。

组合回归覆盖 5 秒 PREWARMING、Provider policy WARM_IDLE、100 轮 binding 容量、Speaker Shadow、CAM++、DetectorRuntime、Core 和 build contracts:470 项通过,3 项因真实模型资产未准备而条件性跳过;runtime.py 覆盖率 80%,detector_runtime.py 覆盖率 85%,合计 82.48%。Ruff、stacked diff check 通过,GitNexus compare 为 LOW / 0 affected flows。

Shadow 结果仍不能影响 transport 保温、candidate completion 释放、Soniox retry/blocked 状态或 Provider endpointing。

Copy link
Copy Markdown
Contributor Author

P0 / P1 收敛记录(01345cfdf

分支谱系

Draft 内工程验收

  • 聚焦与完整相关回归:536 passed, 2 skipped。两项 skip 是未准备的既有 VAD / SmartTurn 权重,与 CAM++ 无关。
  • Ruff、compileall、async-blocking、git diff --check 均通过。
  • 模型资产:28,281,164 bytes;SHA256 aa3cfc16963a10586a9393f5035d6d6b57e98d358b347f80c2a30bf4f00ceba2
  • Windows 真实模型:feature max error 2.8133e-4;embedding (192,)、finite、L2≈1;warm P95 11.04 ms;session RSS 增量 49,647,616 bytes
  • Linux 原生暂存运行:相同 SHA/size;feature max error 2.8133e-4;embedding (192,)、finite、L2=1;warm P95 12.62 ms;session RSS 增量 56,152,064 bytes
  • Windows 完整 PyInstaller 构建通过;产物离线包含 ONNX、manifest、notice,打包路径真实推理通过,缺失模型明确失败。
  • Linux 原生离线暂存加载与缺失模型 fail-closed 通过;完整 Linux Nuitka 安装产物仍保留为 Phase 4B:接入 RNNoise/Silero 节流并拆分 ASR 结束权 #2398 合并后最终远端 CI 的硬门槛。
  • GitNexus:LOW risk;20 个预期变更文件;0 个受影响 execution flow。

真实麦克风证据

此前评论中的同一内存 profile 实测:

  • owner:0.7234
  • non-owner:0.2142
  • gap:0.5092

这只证明当前环境下真实 CAM++ embedding / cosine 链路具有明显分离,不代表准确率、生产阈值或抗回放能力;不再要求补录真人语音。

当前门禁

#2408 现在进入等待阶段并继续保持 Draft

@MomiJiSan

Copy link
Copy Markdown
Contributor Author

本 PR 已 rebase 到包含 DRAINING successor overflow / Provider final fence 修复的 #2398 最新 head 3a1e124de,当前 #2408 head 为 758e4a983

Speaker Shadow 没有复制或拥有 discard watermark、successor discard、final acceptance、transport TTL、binding cleanup、Provider selection、endpoint 或 recovery 逻辑;相关行为全部继承 Phase 4B 基线。唯一 rebase 冲突位于 DetectorRuntime.__init__ 的相邻字段,已同时保留 #2398 的单值 watermark 与 #2408 的 shadow identity,二者没有读写关系。

相对新 #2398 base 的 diff 仍为 20 个 Speaker Shadow/CAM++ 预期文件;没有新增或覆盖 discard_provider_successor()complete_provider_candidate()_handle_audio_ingress_backpressure()_accept_final_key()。组合回归覆盖 Speaker Shadow、DetectorRuntime、ASR lifecycle/transport、CAM++、Core、VoiceTurn 和 build contracts,共 554 passed / 5 skipped;Ruff、git diff --check 通过,GitNexus compare 为 LOW / 0 affected processes

@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch from 758e4a9 to a4899c2 Compare July 23, 2026 03:48
@MomiJiSan
MomiJiSan force-pushed the codex/phase4-b-throttling branch from 3a1e124 to b35c325 Compare July 23, 2026 03:49

Copy link
Copy Markdown
Contributor Author

Restack 完成回执

本 PR 已完成 restack:

9 个 speaker-shadow 提交已按原顺序重放到新版 #2398。冲突收敛遵循当前架构:

  • CAM++ speaker shadow 保持 default-off、观察态、无执行权
  • speaker-shadow factory 接入新版 IndependentAsrRuntime,失败时 fail-open,不阻塞 ASR;
  • 没有把 MicLease、consumer routing 或插件生命周期带入 asr_client
  • 不改变 transcript 投递目标,不参与 endpoint/final 决策;
  • 打包仍使用受 manifest、SHA-256、模型来源和许可证约束的 CAM++ 资源。

验证结果:

  • ASR、voice-input、speaker-shadow 与打包回归:545 passed, 3 skipped
  • 3 项跳过仅因本地尚未运行 prepare_speaker_model.py 准备真实 CAM++ 模型;
  • ruff、Core 分层契约、uv lock --check 和打包契约检查均通过;
  • CodeRabbit check 通过。

上游 codex/phase4-b-throttling stacked-base 镜像已同步到 b35c3251f,因此本 PR 与 #2398 的实际祖先关系和 GitHub base 状态一致。

Copy link
Copy Markdown
Contributor Author

真实综合链路复核结论(CAM++ / Provider / SmartTurn)

本轮在未改变 #2408 Draft 状态、未启用任何拦截权的前提下,对真实麦克风结果和固定输入 A/B 结果进行了复核。

真实麦克风结果

  • CAM++ cosine similarity:0.7692
  • would_block:0.40 / 0.44 / 0.48 / 0.52 / 0.55 五档均为 false
  • 推理:13 ms
  • ONNX session RSS 增量:37,724,160 bytes
  • load / inference / callback / queue:无失败、无丢弃、无 stale result

这证明当前内存 owner profile 与候选音频能够完成真实 CAM++ embedding 和 cosine 评分;0.7692 是 embedding 相似度,不是“76.92% 准确率”。

Provider PCM 隔离

真实综合链路中,baseline 与 shadow 均为 255,432 bytes,拼接后的 SHA256 均为:

d1806045c5ccd13dae1bef82936873db11a355c57d9d3dd726be01ad8a9b03d4

两侧 callback chunk 数分别为 234 / 226,但这只是相同连续 PCM 字节流的异步调用分片不同。后续使用完全相同、只预处理一次的 immutable ProcessedVoiceFramebaseline -> shadow -> shadow -> baseline 严格 ABBA 重放:四轮 PCM 总字节、拼接 SHA、生命周期和 SmartTurn seal 结果全部一致;两个 shadow 重放自身的 chunk 数仍可不同。因此验收应比较连续 PCM 字节与顺序、SHA 和生命周期结果,不应要求 callback chunk 数完全相同。

SmartTurn 复核

原始单次结果中的 baseline seals=0 / shadow seals=1 在严格 ABBA 重放中未复现:四轮均 seals=1,生命周期均完整到达 deep_sleep,Detector 最终为空闲状态。当前没有证据表明 Speaker Shadow 会改变 SmartTurn endpoint 或生命周期;原差异更符合旧测试工具分别预处理两次、异步分帧边界及首个 seal 后提前停止造成的边界敏感结果。

真实录音 PCM 已按隐私边界释放,因此不能对该原始样本逐字节重放;上述结论来自固定预处理输入的可重复隔离实验。

新发现:指标计数缺陷

成功评分的候选当前可能同时被计入:

  • evaluated_candidate_count=1
  • insufficient_candidate_count=1

原因是评分后候选 buffer 已释放,后续 finish marker 再处理时被误判为“音频不足”。该问题只影响 telemetry 计数,不影响 similarity、Provider PCM、SmartTurn、ASR 或候选评分,但应在最终验收前修复,并补充“成功评分不得同时计入 insufficient”的回归测试。

此外,真实运行中的 buffered_candidate_count=1unload_count=0 是 runtime 关闭前的瞬时快照,不能单独证明泄漏或卸载失败;等待稳定结束的复测中 buffer 已归零,关闭路径仍需继续由生命周期测试覆盖。

当前边界结论

现有证据支持:真实 CAM++ 已可作为 default-off、无执行权的 Speaker Shadow 观察链路运行,并且固定输入复测未发现它改变 Provider PCM、SmartTurn seal 或生命周期。

仍不能据此宣称面向普通用户的声纹安全功能或生产硬拦截已经完成:本 PR 没有产品 enrollment 身份体系、阈值校准、误接收/误拒绝统计、回放/克隆防御、native Omni 覆盖或任何音频执行权。#2408 继续保持 Draft;下一步先修复指标计数与测试判定,再按既定门禁完成前置合并后的 Windows/Linux 发布验收。

@MomiJiSan

Copy link
Copy Markdown
Contributor Author

#2408 工程收敛回执(factory / 指标 / 固定输入隔离)

当前远端状态:

本轮严格只补齐 #2408 原有范围内的三个缺口,并保持为三个可独立回滚的小提交:

  1. 5d6106638 fix(asr): bridge speaker shadow factory through core runtime
  2. 73e7161d9 fix(voice): keep scored shadow candidates out of insufficient metrics
  3. 394894703 test(asr): replay immutable frames for shadow isolation

1. 正式 factory bridge

正式组合关系现在为:

Core private factory
→ IndependentAsrRuntime.start(... speaker_shadow_factory=...)
→ DetectorRuntime
→ SpeakerShadowRuntime

Core factory 默认值为 None;无 factory 时不创建 Shadow;factory 抛异常时 fail-open 到 None,Independent ASR 仍正常可用。Core 不导入 CAM++ backend、ONNX、embedding 维度或阈值;没有增加 Provider 条件分支。

2. insufficient 指标修复

旧状态中,候选达到评分门槛后 buffer 已移交评分线程,finish marker 随后因找不到 buffer 又把该候选记为 insufficient。10,000 candidate 压力用例会错误得到 insufficient_candidate_count=10000

现在 finalized tombstone 明确保存 sufficient / insufficient / dropped 终态:

候选结果 evaluated insufficient
成功评分 1 0
少于最短时长 0 1
推理失败 0 0
队列丢弃 0 0

finish marker 保持幂等;reset/close 不制造额外 insufficient;10,000 candidate 压力测试仍保持有界。

3. 固定输入 ABBA 隔离合同

测试只把原始 48 kHz PCM 通过 VoiceInputAudioPipeline 处理一次,生成同一个不可变 ProcessedVoiceFrame,然后按以下顺序重放:

baseline → shadow → shadow → baseline

四轮均满足:

  • 连续 Provider PCM、拼接 SHA256 与字节顺序一致;
  • SmartTurn seal、lifecycle history、Provider final 和 Detector 终态一致;
  • dispatcher queue、transcript reservation、pre-roll、Shadow candidate buffer 均归零;
  • close 后没有遗留 Detector、audio dispatcher、transcript worker 或 close task;
  • 两次 Shadow similarity 均为 0.75

callback chunk 数不再作为隔离失败条件:连续 PCM 相同并不要求异步 callback 分片数量相同。

验证结果

  • 新 factory 聚焦测试:3 passed
  • Shadow + Detector:71 passed
  • Core/Independent ASR:114 passed
  • 真实 CAM++ smoke:3 passed
  • CAM++ / Shadow / Detector / Core / VoiceTurn / build-contract:284 passed, 2 skipped
  • 全 ASR 回归:455 passed
  • Core 回归:176 passed
  • Ruff / py_compile / async-blocking / Windows+Linux workflow YAML / git diff --check:通过

真实模型资源复核:

  • size:28,281,164 bytes
  • SHA256:aa3cfc16963a10586a9393f5035d6d6b57e98d358b347f80c2a30bf4f00ceba2
  • 25 次 1.5 秒 warm mean:10.46 ms
  • warm P95:11.30 ms
  • session RSS 增量:50,044,928 bytes
  • 无 >25 ms 推理;未越过 50 ms / 60 MB 停止线

GitNexus 相对 #2398 compare 为 MEDIUM,21 个预期文件;影响仅为 Independent ASR start/close/lifecycle/status 流。最终 diff 未触及 Provider worker/registry、transport、AsrAudioDispatcher、native Omni、SmartTurn/RNNoise/Silero 算法、产品 UI、数据库或配置迁移。

尚未宣称完成的门槛

Windows 本机不能替代 Linux 完整安装产物验证。远端 Windows/Linux CI 仍需确认正式产物包含 ONNX、manifest、notice,并能断网加载;#2398 合并后还要把纯 #2408 提交重叠到最终 main 再跑最终验收。

因此当前结论仍然是:真实 CAM++ Speaker Shadow 的工程基础已收干净;它默认关闭、无 profile 零工作、只有 observation,没有生产阈值、产品 enrollment、攻击防御、Omni 覆盖或音频拦截权。

@MomiJiSan

Copy link
Copy Markdown
Contributor Author

阶段衔接说明(2026-07-23)

#2408 原定工程范围已经收敛:

  • Core → IndependentAsrRuntime → DetectorRuntime 的 default-off speaker factory 已通过正式组合关系接线。
  • 无 profile 时模型加载次数为 0;factory、模型、评分和 callback 失败均保持 fail-open,不影响 ASR。
  • 已评分候选不会再被 finish marker 误计为 insufficient;短候选、duplicate finish、reset/close 的指标合同已固定。
  • A/B 隔离改为复用同一组 immutable processed frames;比较连续 Provider PCM 总字节、拼接 SHA256、顺序、seal、lifecycle 和 final,不再把 callback chunk 数量当作字节一致性条件。
  • 真实 CAM++、前处理 parity、192 维 embedding、资源指标及 Windows/Linux build-contract 已通过聚焦验证。
  • Phase 4C:接入无执行权的声纹 Shadow Runtime #2408 继续保持 Draft;Phase 4B:接入 RNNoise/Silero 节流并拆分 ASR 结束权 #2398 合并前不转 Ready。

后续“生产基础链路”没有继续塞进 #2408,已按文件归属和审查边界拆到 stacked Draft #2450
#2450

#2450 只建立:

内存 profile → Core 正式内部入口 → Independent ASR candidate → CAM++ observation → revision/stale/close 清理。

它不反向扩大 #2408 的声明。以下内容仍明确不属于 #2408:生产阈值、声纹硬拦截、产品 enrollment UI/API、持久化、native Omni、回放/AI 克隆防御、多说话人处理,以及 Provider PCM 的修改、延迟、缓存或重放。

因此 #2408 的最终边界仍然是:证明真实 CAM++ 能在 N.E.K.O 的 Independent ASR 候选旁路中安全观察并产生 similarity,但不决定音频是否继续传递。

@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch 2 times, most recently from 33e6dfb to bd87b6e Compare July 24, 2026 01:52
@MomiJiSan
MomiJiSan force-pushed the codex/phase4-c-speaker-shadow branch from bd87b6e to 69d8d5e Compare July 24, 2026 02:24

Copy link
Copy Markdown
Contributor Author

已完成本轮 rebase 并解决冲突:

  • 已按 GitHub 当前 base codex/phase4-b-throttling@b35c325 完成 rebase。
  • Head:bd87b6e69d8d5e;GitHub 当前为 MERGEABLE / CLEAN
  • Git 自动跳过 18 个 base 已包含的等价提交,成功重放 15 个独有提交;rebase 前后最终文件树完全一致,没有人工冲突取舍或语义改动。
  • 声纹/CAM++、Detector/Core、结构与构建合同聚焦回归:452 passed;Ruff 与 git diff --check 通过。
  • GitNexus:MEDIUM,覆盖 32 个文件、738 个符号及 5 条流程;rebase 未扩大既有影响范围。

下一步继续处理 #2450

@MingTianSang

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69d8d5e41f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +290 to +293
item_id = event.get("item_id")
if isinstance(item_id, str):
item_keys.pop(item_id, None)
await _emit_error(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore failed OpenAI events for unknown items

When OpenAI sends conversation.item.input_audio_transcription.failed for an item_id that was already cleared or belongs to an older route epoch, this path still emits ASR_OPENAI_TRANSCRIPTION_FAILED using the current last_generation and tears down the worker. The completed/delta handler already ignores unknown item ids for exactly this stale-event case, so the failed handler should only fail the route when item_keys.pop(...) actually finds a live item.

Useful? React with 👍 / 👎.

Comment on lines +2191 to +2192
if SpeechActivityEvent.CANDIDATE_PAUSE in events:
self._finish_speaker_shadow_candidate(expected=candidate)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Finish provider shadow candidates on provider endpoints

Provider-endpointed routes seal a turn from the provider callback via seal_provider_candidate(), which can happen before local Silero emits CANDIDATE_PAUSE; in that case this is the only finish condition, so _speaker_shadow_state stays CONFIRMED and the next speech frames reuse the previous provider_pause key. That merges separate provider turns in the shadow scorer and corrupts the would-block observations, so the shadow candidate needs to be finished when the provider endpoint seals the turn as well.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Needs Check! need doctor to check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants