{
  "version": 1,
  "id": "Jarvis-pr8-voice-perf",
  "repo": "/tmp/jarvis-pr8-perf-audit",
  "repoName": "Jarvis",
  "scope": "pr8-voice-perf",
  "title": "PR #8 voice performance audit",
  "sinceCommit": "4232fa2537a522ae106fd99f1ea653d69566ff5d",
  "lastReviewCommit": "97ba0eb7d37f9e39f34689f9182db83fdc05dafa",
  "reviewRound": 1,
  "loc": {
    "added": 2682,
    "removed": 982
  },
  "updatedAt": "2026-09-06T10:35:01.600304+00:00",
  "htmlPath": "/home/abstergo/Documents/reviews/Jarvis-pr8-voice-perf-abbyview.html",
  "jsonPath": "/home/abstergo/Documents/reviews/Jarvis-pr8-voice-perf-abbyview.json",
  "confidence": {
    "score": 1,
    "max": 5,
    "recommendation": "Request changes before merge",
    "rationale": "Four major findings and one minor telemetry finding in the voice migration; the model choice itself remains promising."
  },
  "actionableOpen": 5,
  "verdict": "CHANGES_REQUESTED",
  "baseline": {
    "frozenAt": "2026-09-06T10:35:01.600304+00:00",
    "title": "PR #8 voice performance audit",
    "lede": "Pocket is a better candidate; this integration is not ready to call low latency.",
    "walkthrough": {
      "prose": "PR #8 replaces Kokoro with a native Pocket subprocess, staged models, onset filtering and build/packaging changes. This audit concentrates on the STT/TTS runtime and its Jarvis callers.",
      "bullets": [
        "Native model generation is fast enough to stream useful early PCM.",
        "The new adapter buffers complete synthesis; existing remote transport buffers complete WAVs.",
        "Model swaps and STT thread-pool configuration dominate other measured costs."
      ]
    },
    "filesChanged": [
      {
        "path": ".github/workflows/jarvis-desktop-linux.yml",
        "delta": "+19 \u221210"
      },
      {
        "path": ".github/workflows/jarvis-desktop-mac.yml",
        "delta": "+19 \u22128"
      },
      {
        "path": ".github/workflows/jarvis-setup-windows.yml",
        "delta": "+11 \u22129"
      },
      {
        "path": "apps/desktop/pipecat/src/jarvis_voice_runtime/__main__.py",
        "delta": "+10 \u221210"
      },
      {
        "path": "apps/desktop/pipecat/src/jarvis_voice_runtime/kokoro.py",
        "delta": "+0 \u2212290"
      },
      {
        "path": "apps/desktop/pipecat/src/jarvis_voice_runtime/onset.py",
        "delta": "+91 \u22120"
      },
      {
        "path": "apps/desktop/pipecat/src/jarvis_voice_runtime/output.py",
        "delta": "+1 \u22121"
      },
      {
        "path": "apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py",
        "delta": "+528 \u22120"
      },
      {
        "path": "apps/desktop/pipecat/src/jarvis_voice_runtime/runtime.py",
        "delta": "+34 \u221227"
      },
      {
        "path": "apps/desktop/pipecat/tests/test_native_runtime.py",
        "delta": "+1 \u22121"
      },
      {
        "path": "apps/desktop/pipecat/tests/test_onset.py",
        "delta": "+98 \u22120"
      },
      {
        "path": "apps/desktop/pipecat/tests/{test_tts_runtime.py => test_pocket_runtime.py}",
        "delta": "+293 \u2212445"
      },
      {
        "path": "apps/desktop/src/voice/DesktopJarvisVoice.ts",
        "delta": "+2 \u22122"
      },
      {
        "path": "apps/desktop/src/voice/DesktopVoiceWorkerProtocol.ts",
        "delta": "+1 \u22121"
      },
      {
        "path": "apps/desktop/src/voice/desktopVoiceWorker.ts",
        "delta": "+5 \u22125"
      },
      {
        "path": "apps/server/src/environment/ServerEnvironment.test.ts",
        "delta": "+1 \u22120"
      },
      {
        "path": "apps/web/src/components/jarvis/JarvisControlCenter.tsx",
        "delta": "+6 \u22123"
      },
      {
        "path": "apps/web/src/components/jarvis/JarvisOnboarding.logic.test.ts",
        "delta": "+1 \u22121"
      },
      {
        "path": "apps/web/src/components/jarvis/JarvisOnboarding.logic.ts",
        "delta": "+3 \u22123"
      },
      {
        "path": "apps/web/src/components/jarvis/JarvisOnboarding.tsx",
        "delta": "+3 \u22122"
      },
      {
        "path": "docs/internals/glossary.md",
        "delta": "+1 \u22121"
      },
      {
        "path": "docs/internals/jarvis-controller.md",
        "delta": "+1 \u22121"
      },
      {
        "path": "docs/internals/jarvis-mobile-voice.md",
        "delta": "+3 \u22123"
      },
      {
        "path": "docs/internals/jarvis-t3-boundary.md",
        "delta": "+3 \u22123"
      },
      {
        "path": "docs/internals/jarvis-task-desk.md",
        "delta": "+1 \u22121"
      },
      {
        "path": "docs/internals/linux-full-native-voice.md",
        "delta": "+24 \u221225"
      },
      {
        "path": "docs/internals/pocket-tts.md",
        "delta": "+93 \u22120"
      },
      {
        "path": "docs/operations/jarvis-acceptance-checklist.md",
        "delta": "+3 \u22123"
      },
      {
        "path": "docs/operations/release.md",
        "delta": "+1 \u22121"
      },
      {
        "path": "docs/user/install.md",
        "delta": "+1 \u22121"
      },
      {
        "path": "docs/user/jarvis.md",
        "delta": "+5 \u22125"
      },
      {
        "path": "packages/contracts/src/environment.ts",
        "delta": "+14 \u22121"
      },
      {
        "path": "packages/jarvis-native-voice/.gitignore",
        "delta": "+2 \u22121"
      },
      {
        "path": "packages/jarvis-native-voice/native/pocket/CMakeLists.txt",
        "delta": "+89 \u22120"
      },
      {
        "path": "packages/jarvis-native-voice/native/pocket/PINNED_REVISIONS.json",
        "delta": "+58 \u22120"
      },
      {
        "path": "packages/jarvis-native-voice/native/pocket/daemon_main.cpp",
        "delta": "+492 \u22120"
      },
      {
        "path": "packages/jarvis-native-voice/package.json",
        "delta": "+3 \u22123"
      },
      {
        "path": "packages/jarvis-native-voice/resources/THIRD_PARTY_NOTICES.md",
        "delta": "+14 \u22123"
      },
      {
        "path": "packages/jarvis-native-voice/scripts/benchmark-model-swap.mjs",
        "delta": "+8 \u22128"
      },
      {
        "path": "packages/jarvis-native-voice/scripts/{benchmark-kokoro.mjs => benchmark-pocket.mjs}",
        "delta": "+1 \u22121"
      },
      {
        "path": "packages/jarvis-native-voice/scripts/build-pocket-runtime.mjs",
        "delta": "+376 \u22120"
      },
      {
        "path": "packages/jarvis-native-voice/scripts/ensure-kokoro-resources.mjs",
        "delta": "+0 \u221279"
      },
      {
        "path": "packages/jarvis-native-voice/scripts/ensure-pocket-resources.mjs",
        "delta": "+322 \u22120"
      },
      {
        "path": "packages/jarvis-native-voice/scripts/smoke-speech-runtime.mjs",
        "delta": "+1 \u22121"
      },
      {
        "path": "packages/jarvis-native-voice/src/desktop-native-voice.ts",
        "delta": "+1 \u22121"
      },
      {
        "path": "packages/jarvis-voice-runtime/src/pipecat-protocol.test.ts",
        "delta": "+1 \u22121"
      },
      {
        "path": "packages/jarvis-voice-runtime/src/pipecat-protocol.ts",
        "delta": "+2 \u22122"
      },
      {
        "path": "packages/jarvis-voice-runtime/src/pipecat-sidecar.test.ts",
        "delta": "+2 \u22122"
      },
      {
        "path": "packages/jarvis-voice-runtime/src/pipecat-sidecar.ts",
        "delta": "+3 \u22123"
      },
      {
        "path": "scripts/build-desktop-artifact.test.ts",
        "delta": "+10 \u22127"
      },
      {
        "path": "scripts/jarvis-release-workflow.test.ts",
        "delta": "+2 \u22122"
      },
      {
        "path": "scripts/jarvis-voice-packaging.ts",
        "delta": "+14 \u22125"
      },
      {
        "path": "scripts/smoke-headless-node-runtime.test.ts",
        "delta": "+2 \u22120"
      },
      {
        "path": "scripts/smoke-headless-node-runtime.ts",
        "delta": "+1 \u22120"
      },
      {
        "path": "scripts/windows-setup.ts",
        "delta": "+1 \u22120"
      }
    ],
    "flowDiagram": "Capture \u2192 Parakeet \u2192 typed Jarvis routing \u2192 response text \u2192 Pocket daemon \u2192 onset \u2192 Pipecat \u2192 sink",
    "sinceCommit": "4232fa2537a522ae106fd99f1ea653d69566ff5d",
    "loc": {
      "added": 2682,
      "removed": 982
    }
  },
  "roundDeltas": [],
  "findings": [
    {
      "id": "f1",
      "status": "open",
      "fixedAt": null,
      "reviewRound": 1,
      "title": "Deliver PCM as the daemon produces it",
      "file": "apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py",
      "lines": "425",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "category": "performance",
      "section": "actionable",
      "bodyParagraphs": [
        "PocketDaemon.synthesize collects every raw chunk and returns only after the terminal event. run_tts then sorts and reads the files. The queue is bounded only after generation has already completed, so the adapter removes the native runtime's streaming advantage.",
        "In five alternating runs of the same 25-word report, original first service PCM had a median of 1,176 ms. An experimental incremental iterator, retaining the same daemon, model, Pipecat pipeline and sink, reduced that median to 79 ms. Median native synthesis completion stayed about 1.16 seconds in both modes.",
        "The existing tests pass because their fake daemon returns a completed list. A new regression withholding the terminal event while a playable chunk is available fails against the PR. This directly affects local speech latency; remote buffering is a separate issue."
      ],
      "whyItMatters": "The existing tests pass because their fake daemon returns a completed list. A new regression withholding the terminal event while a playable chunk is available fails against the PR. This directly affects local speech latency; remote buffering is a separate issue.",
      "suggestedDiff": null,
      "fixInstruction": "change PocketDaemon and JarvisPocketTTSService.run_tts to consume ordered chunks incrementally through the onset filter and bounded queue before synthesis finishes; propagate terminal failures, cancel the active request independently of blocked reads, and retain temporary files only until their PCM has been consumed.",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn `apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py` around line 425, change PocketDaemon and JarvisPocketTTSService.run_tts to consume ordered chunks incrementally through the onset filter and bounded queue before synthesis finishes; propagate terminal failures, cancel the active request independently of blocked reads, and retain temporary files only until their PCM has been consumed. Start with a failing regression at the affected output or lifecycle boundary. Preserve cancellation, request identity, PCM ordering, and model ownership. Apply the correction to every local and remote caller that shares this path, and verify the complete behavior with focused tests and real-model measurements. Do not modify unrelated T3 internals, the live install, or merge the PR. Report measured latency and resource use separately from untested device behavior.",
      "followUp": false
    },
    {
      "id": "f2",
      "status": "open",
      "fixedAt": null,
      "reviewRound": 1,
      "title": "Use a Windows-compatible readiness reader",
      "file": "apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py",
      "lines": "157",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "category": "correctness",
      "section": "actionable",
      "bodyParagraphs": [
        "PocketDaemon.start calls select.select on a subprocess stdout pipe. Python on Windows supports select only for sockets. The OSError is swallowed and the loop retries until the 30-second deadline.",
        "A daemon can already have emitted ready and still be killed as a startup timeout. The portable regression supplies a ready line and Windows's not-a-socket error; startup fails. This is a source and simulated-platform finding, not a claim that a Windows device was tested."
      ],
      "whyItMatters": "A daemon can already have emitted ready and still be killed as a startup timeout. The portable regression supplies a ready line and Windows's not-a-socket error; startup fails. This is a source and simulated-platform finding, not a claim that a Windows device was tested.",
      "suggestedDiff": null,
      "fixInstruction": "replace select-based pipe readiness with a portable reader and bounded startup wait, surface EOF and startup-failed messages, and always reap failed startup processes. Validate real Windows startup separately.",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn `apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py` around line 157, replace select-based pipe readiness with a portable reader and bounded startup wait, surface EOF and startup-failed messages, and always reap failed startup processes. Validate real Windows startup separately. Start with a failing regression at the affected output or lifecycle boundary. Preserve cancellation, request identity, PCM ordering, and model ownership. Apply the correction to every local and remote caller that shares this path, and verify the complete behavior with focused tests and real-model measurements. Do not modify unrelated T3 internals, the live install, or merge the PR. Report measured latency and resource use separately from untested device behavior.",
      "followUp": false
    },
    {
      "id": "f3",
      "status": "open",
      "fixedAt": null,
      "reviewRound": 1,
      "title": "Close a newly created daemon when preparation is abandoned",
      "file": "apps/desktop/pipecat/src/jarvis_voice_runtime/runtime.py",
      "lines": "295",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "category": "stability",
      "section": "actionable",
      "bodyParagraphs": [
        "When shutdown or capture wins while Pocket is being constructed, _prepare_speech deletes native_tts and returns. PocketNativeHandle owns a subprocess and has no destructor that closes it. The new explicit close in _dispose_tts only covers handles already attached to a service.",
        "A regression changes the desired lifecycle state during the factory call and verifies that the newly created handle is closed. It fails. The orphan daemon can keep its model resident after the runtime has moved back to listening, undermining the single-model memory policy."
      ],
      "whyItMatters": "A regression changes the desired lifecycle state during the factory call and verifies that the newly created handle is closed. It fails. The orphan daemon can keep its model resident after the runtime has moved back to listening, undermining the single-model memory policy.",
      "suggestedDiff": null,
      "fixInstruction": "explicitly close any created or retained Pocket handle on every abandoned preparation and pipeline-start failure path. Ownership must transfer to the service only after successful startup; cleanup must not depend on garbage collection.",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn `apps/desktop/pipecat/src/jarvis_voice_runtime/runtime.py` around line 295, explicitly close any created or retained Pocket handle on every abandoned preparation and pipeline-start failure path. Ownership must transfer to the service only after successful startup; cleanup must not depend on garbage collection. Start with a failing regression at the affected output or lifecycle boundary. Preserve cancellation, request identity, PCM ordering, and model ownership. Apply the correction to every local and remote caller that shares this path, and verify the complete behavior with focused tests and real-model measurements. Do not modify unrelated T3 internals, the live install, or merge the PR. Report measured latency and resource use separately from untested device behavior.",
      "followUp": false
    },
    {
      "id": "f4",
      "status": "open",
      "fixedAt": null,
      "reviewRound": 1,
      "title": "Enforce the model's text bound without losing words",
      "file": "apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py",
      "lines": "425",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "category": "correctness",
      "section": "actionable",
      "bodyParagraphs": [
        "The PR documents a 50-token operating bound and acknowledges missing clauses in longer input, but forwards the entire finalized text to the daemon. TextAggregationMode.SENTENCE does not split these TTSSpeakFrame submissions; the measured 25-word, four-sentence report is one native request. The public host contract permits up to 32,000 characters.",
        "In three generated 46-word long-sentence samples, Parakeet produced two imperfect transcripts, including omission of \u201cunchanged\u201d. This is an ASR screening result, not a listening verdict, but it agrees with the PR's acknowledged limitation. Short and typical samples in this nine-sample screen transcribed without errors.",
        "An aggregate WER can hide a dropped negation or important clause. Production text handling should preserve the full meaning for all accepted inputs rather than rely on most reports being short."
      ],
      "whyItMatters": "An aggregate WER can hide a dropped negation or important clause. Production text handling should preserve the full meaning for all accepted inputs rather than rely on most reports being short.",
      "suggestedDiff": null,
      "fixInstruction": "add tokenizer-aware bounded text segmentation before native generation, preserving every word and punctuation needed for meaning. Stream successive segments, support interruption across boundaries, and validate long clauses, names, numbers, negation, and one-word responses with transcripts and listening checks.",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn `apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py` around line 425, add tokenizer-aware bounded text segmentation before native generation, preserving every word and punctuation needed for meaning. Stream successive segments, support interruption across boundaries, and validate long clauses, names, numbers, negation, and one-word responses with transcripts and listening checks. Start with a failing regression at the affected output or lifecycle boundary. Preserve cancellation, request identity, PCM ordering, and model ownership. Apply the correction to every local and remote caller that shares this path, and verify the complete behavior with focused tests and real-model measurements. Do not modify unrelated T3 internals, the live install, or merge the PR. Report measured latency and resource use separately from untested device behavior.",
      "followUp": false
    },
    {
      "id": "f5",
      "status": "open",
      "fixedAt": null,
      "reviewRound": 1,
      "title": "Include daemon CPU and memory in performance telemetry",
      "file": "apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py",
      "lines": "453",
      "severity": "minor",
      "pBadge": "P2",
      "type": "logic",
      "category": "performance",
      "section": "actionable",
      "bodyParagraphs": [
        "GenerationMetrics measures Python process CPU, and Runtime reports the Python process RSS. Native synthesis now happens in a separate daemon. Its terminal event already supplies native CPU and peak RSS, but the adapter discards those fields.",
        "One measured report used 3,560 ms of native CPU while the published timing reported only 159 ms. Native peak RSS was about 375 MiB while the reported host peak was about 168 MiB. The model-swap benchmark reads this incomplete telemetry, so its memory gate cannot establish the whole voice footprint."
      ],
      "whyItMatters": "One measured report used 3,560 ms of native CPU while the published timing reported only 159 ms. Native peak RSS was about 375 MiB while the reported host peak was about 168 MiB. The model-swap benchmark reads this incomplete telemetry, so its memory gate cannot establish the whole voice footprint.",
      "suggestedDiff": null,
      "fixInstruction": "carry daemon timings into the typed metrics and report host and native CPU separately plus their sum. Track process-specific current RSS and a simultaneous aggregate peak rather than silently equating host RSS or a sum of independent historical peaks with total voice memory.",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn `apps/desktop/pipecat/src/jarvis_voice_runtime/pocket.py` around line 453, carry daemon timings into the typed metrics and report host and native CPU separately plus their sum. Track process-specific current RSS and a simultaneous aggregate peak rather than silently equating host RSS or a sum of independent historical peaks with total voice memory. Start with a failing regression at the affected output or lifecycle boundary. Preserve cancellation, request identity, PCM ordering, and model ownership. Apply the correction to every local and remote caller that shares this path, and verify the complete behavior with focused tests and real-model measurements. Do not modify unrelated T3 internals, the live install, or merge the PR. Report measured latency and resource use separately from untested device behavior.",
      "followUp": false
    }
  ],
  "history": [
    {
      "round": 1,
      "at": "2026-09-06T10:35:01.600304+00:00",
      "confidence": 1,
      "actionableOpen": 5,
      "note": "Performance audit; no GitHub comments posted and no product code changed."
    }
  ]
}