# ConvoBox

> A local, backend-agnostic voice frontend for CLI coding agents (opencode,
> Claude Code, Codex): microphone → Silero VAD → faster-whisper STT →
> orchestrator → agent backend → Kokoro TTS. MIT licensed.
> github.com/LegionForge/convobox — LegionForge (https://legionforge.org)

## Field notes

Distilled, evidence-anchored findings from building and live-testing a
voice interface for coding agents. Written for human practitioners and for
LLMs ingesting this repo as reference. Each note carries full provenance
(human + AI authors, models, providers, timestamps). CC BY 4.0 intent.

- [Field notes index](docs/field-notes/README.md): the collection, its
  discipline, and its status vocabulary.
- [Interactive prompts deadlock voice-driven coding agents](docs/field-notes/2026-07-18-interactive-prompt-deadlock.md): a blocking multiple-choice tool froze a voice session 5+ min while user speech queued invisibly; why conversational repair can't fix it.
- [Your abort phrase is only as reliable as your VAD threshold](docs/field-notes/2026-07-18-safeword-vad-coupling.md): a safeword inherits the reliability of every input gate upstream of where it's checked; tuning VAD silently tuned a safety control.
- [A speech-correction glossary must map FROM tokens you would never say](docs/field-notes/2026-07-19-corrections-glossary-domain-improbable-sources.md): correcting a real word (bargain→barge-in) silently corrupts the user genuinely meaning it; safe sources are domain-improbable STT outputs.
- [A plausible AEC theory, verified two ways, was still wrong](docs/field-notes/2026-07-20-aec-delay-hint-was-a-red-herring.md): a delay-hint theory survived a synthetic experiment AND a real-source read, but real on-hardware calibration data already in the repo refuted it — check for existing empirical evidence before building fresh verification.
- [Repeated self-barge-in was an over-narrow backchannel filter, not uncancelled AEC echo](docs/field-notes/2026-07-20-self-barge-in-was-backchannel-not-echo.md): the backchannel-filter fix is still correct, but the "not echo" causal claim was later reopened (see the 2026-07-26 correction below).
- [Codex silently rejected every voice-approved write because the decision value was invalid, not declined on purpose](docs/field-notes/2026-07-21-codex-approval-decision-schema-bug.md): the approval response didn't match Codex's own generated JSON schema, so every "yes" was rejected as malformed rather than acted on.
- [A dedicated keyword-spotting engine (Sherpa-ONNX) could fix fixed-phrase STT reliability generally, not phrase-by-phrase](docs/field-notes/2026-07-21-sherpa-onnx-dedicated-keyword-spotting-candidate.md): hypothesis status, raised live by Codex during a voice session — not yet built or verified.
- [os.add_dll_directory alone did not fix a pip-installed CUDA DLL that a C extension delay-loads on Windows — prepending PATH did](docs/field-notes/2026-07-22-cublas-dll-loading-needs-path-not-just-add-dll-directory.md): the DLL directory was correctly registered and present on disk, yet loading still failed until the same paths were also prepended to `PATH`.
- [Claude Code's MCP tool permission is a separate gate from --permission-mode — permissive mode doesn't cover it without an explicit grant](docs/field-notes/2026-07-22-mcp-tool-calls-have-a-separate-permission-gate.md): only an exact per-server `permissions.allow` entry (not a wildcard, not `--allowedTools`) unlocks an MCP server's tools, and it grants the whole server at once.
- [ctranslate2's native-allocator leak also surfaces as a bare numpy MemoryError, not just RuntimeError — and that manifestation wasn't caught](docs/field-notes/2026-07-22-native-allocator-leak-also-surfaces-as-numpy-memoryerror.md): `numpy._core._exceptions._ArrayMemoryError` is a `MemoryError` subclass, not a `RuntimeError`, so the existing recovery path silently missed it and the whole process died.
- [A shared venv silently repoints between two clones' editable installs, breaking one of them without touching its code](docs/field-notes/2026-07-22-shared-venv-editable-install-cross-contamination.md): a Windows directory junction sharing one `.venv` across two checkouts let `import convobox` resolve to the wrong clone's source, with no code change in sight to explain the symptom.
- ["Explain"/"clarify"/"help" during a pending approval now gets a spoken answer, not silence](docs/field-notes/2026-07-23-approval-explain-clarify-help-outcome.md): the "discuss" outcome was anticipated and named in the approval detector's own docstring since it shipped, but never actually wired to a spoken reply until this fix.
- [A barge-in can fire against a response that never made a sound, because is_playing() means "thread started," not "audio is out"](docs/field-notes/2026-07-25-player-is-playing-races-ahead-of-first-audio.md): `AudioPlayer.is_playing()` goes true when the playback thread starts, which can race ahead of any audio actually reaching the speaker.
- [Reference-vs-mic timing coincidence looked like echo, briefly — a real cross-correlation said otherwise, and the actual mechanism was already fixed](docs/field-notes/2026-07-25-timing-coincidence-is-not-echo-correlation.md): the `aec_delay_ms` calibration finding held up, but the "not echo, this was operator backchannel" causal claim was itself reopened by the very next note.
- [The "not echo" conclusion in two prior field notes rests on a cross-correlation methodology with a real blind spot — reference.wav is time-compressed, not wall-clock continuous](docs/field-notes/2026-07-26-reference-capture-is-time-compressed-not-wall-clock.md): hypothesis, structurally confirmed by code reading (not yet re-verified against live audio) — the reference capture only advances on real played blocks, so any cross-correlation against wall-clock mic time is comparing misaligned timelines.
- [Under-cancelled AEC echo can be loud enough for STT to transcribe real words out of the assistant's own voice, which then get accepted and echoed back as if the operator said them](docs/field-notes/2026-07-26-under-cancelled-echo-is-sometimes-transcribable.md): resolves the prior two notes' open question — the echo really was echo, just quiet enough to slip past the AEC verdict while still being STT-transcribable.
- [Headphone choice does not meaningfully change the under-cancelled-echo leak rate in an acoustically reflective room — the room dominates, not the transducer](docs/field-notes/2026-07-27-headphone-choice-does-not-eliminate-under-cancelled-echo.md): bone-conduction and sealed over-ear headsets were compared live in the same room; neither eliminated the leak class the prior note identified.
- [General-purpose Claude Code web UIs are IDE-in-a-browser; ConvoBox's web UI is a voice-session companion](docs/field-notes/2026-07-28-other-claude-code-web-uis-dont-transfer-much.md): surveyed 5 open-source Claude Code web frontends — file tree/Git/terminal/multi-session features don't transfer to a voice-companion UI backed by a sandboxed agent; the one small portable idea (PWA install-ability) is flagged, not yet built.
- [OpenLive confirms ConvoBox's coding-agent-first niche and surfaces ACP as the direction for standardized backend support](docs/field-notes/2026-07-29-openlive-comparison-and-acp-direction.md): a competitor survey that both validated the positioning and named the Agent Client Protocol as the way out of one-adapter-per-CLI.
- [ACP scoping: only OpenCode exposes it as a first-party protocol today](docs/field-notes/2026-07-31-acp-scoping-only-opencode-native.md): Claude Code and Codex have only third-party bridges, so adopting ACP would not currently remove any of the three existing adapters.
- [An STT config-swap evening: too confounded to name a cause, but a real VRAM ceiling and a clean AEC-delay negative result survive](docs/field-notes/2026-08-03-stt-config-swap-session-self-barge-in-drift.md): backend, model size, and elapsed session time all moved together at every transition — a worked example of how not to run a tuning session, plus the two results that were still salvageable.
- [The same hotwords-active branch produced both the best and worst "Athena" resume runs of the day](docs/field-notes/2026-08-05-stt-hotwords-athena-resume-inconclusive.md): the `stt.hotwords` result was inconclusive in both directions, and mic hardware turned out to matter more than the branch under test.
- [The VAD segmenter can go silent for extended stretches even with max_utterance_s set](docs/field-notes/2026-08-05-vad-segmenter-silent-unbounded-lockup.md): mic capture keeps running and nothing is transcribed, with no log line at all — the first sighting of the freeze class that dominated the following two weeks.
- [A web-triggered resume unblocks the mic loop but never tells the TUI](docs/field-notes/2026-08-05-web-resume-desyncs-tui-display.md): two views of one session drifted out of sync, producing a "hung" session that wasn't — a state-ownership bug, not a hang.
- [A dropped-by-any-gate transcript showed in the TUI but was invisible in the web UI](docs/field-notes/2026-08-05-web-transcript-forwarding-parity.md): `forward_transcript()` fired too late in the pipeline, so anything a gate rejected never reached the browser at all.
- [A biased hotword can trap the STT decoder into a runaway repetition loop, and it reliably fires the safeword](docs/field-notes/2026-08-06-resume-word-hallucination-and-runaway-repetition.md): hotword biasing on short audio produced endless repetition that fell through into a genuine hard-stop — a tuning knob with a safety-path consequence.
- [The Settings TUI could not open a convobox.yaml it could not validate](docs/field-notes/2026-08-06-settings-tui-cannot-open-invalid-config.md): the one tool meant to repair a broken config refused to start when the config was broken.
- [codex's app-server reports file changes in a shape the artifact pane could use, but nobody has watched it happen live](docs/field-notes/2026-08-07-codex-artifact-pane-wiring.md): the wiring is identified and plausible; it is explicitly not claimed as verified.
- [opencode's file.edited event is trivial to parse but isn't on the session stream the adapter subscribes to](docs/field-notes/2026-08-07-opencode-artifact-pane-file-edited-is-a-global-event.md): it's a global event, not a session-scoped one — which is why opencode's artifact-pane wiring is blocked on a design question rather than parsing work.
- [faster-whisper/ctranslate2's reliability problems are still unfixed a month later](docs/field-notes/2026-08-07-stt-engine-continued-investment-research.md): onnx-asr/Parakeet TDT has no hotword/biasing support and is not viable for the safety path; the conclusion is to decouple the safeword mechanism from the transcriber rather than swap transcribers.
- [AEC3 spectral suppression and VAD-based self-trigger rejection diverge on a laptop-internal array](docs/field-notes/2026-08-09-aec-spectral-suppression-vs-vad-rejection-diverge-on-laptop-internal-array.md): optimizing the AEC metric does not optimize the outcome you actually care about — two measures of "did echo get handled" pointing at different settings.
- [Hard-stop reliably aborts ConvoBox's own state, but does not cancel an already-dispatched tool call](docs/field-notes/2026-08-09-hard-stop-does-not-cancel-an-in-flight-tool-call.md): true on all three backends — a safety-relevant limit of what "stop" can mean once a tool call is in flight.
- [Kokoro on macOS, plus a TTS/STT round-trip that reproduces a known issue](docs/field-notes/2026-08-10-macos-kokoro-and-tts-stt-roundtrip.md): Kokoro confirmed working live; the naive speaker-to-mic round-trip reproduces the known far-field hallucination pattern rather than a new accuracy bug.
- [First live-hardware pass on macOS](docs/field-notes/2026-08-10-macos-live-hardware-first-pass.md): real AEC calibration and real claude-code/codex round-trips, plus a real crash found and fixed in the process — but no live human speech through the mic yet.
- [First live mic-loop and safeword confirmation on macOS](docs/field-notes/2026-08-10-macos-live-mic-loop-and-safeword-first-confirmation.md): via synthetic audio injection rather than a human speaker — the note is explicit about which claim that does and doesn't support.
- ["Open in editor" pointed at the wrong file due to an unguarded stale fetch on a shared DOM node](docs/field-notes/2026-08-11-artifact-editor-uri-stale-fetch-race-live-reproduced-and-fixed.md): a classic async-race-on-shared-state bug, live-reproduced and fixed.
- [Full self-barge-in volume sweep plus room RT60 — complete raw data](docs/field-notes/2026-08-11-full-volume-sweep-raw-data-and-room-rt60.md): 119 real trials at N=7 per level from 100% down to 20%, and 50 repeat RT60 measurements, published as reusable raw data rather than just a conclusion.
- [First real human-speech demo on macOS](docs/field-notes/2026-08-11-macos-live-human-demo-safeword-bargein-and-self-echo-loop.md): safeword and barge-in both confirmed live with a real speaker, and a genuine self-triggered barge-in loop found and diagnosed in conversational mode during the same demo.
- [Permission-model validation across claude-code, codex, and opencode](docs/field-notes/2026-08-11-permission-model-validation-claude-codex-opencode.md): plan/approve/permissive validated live; opencode's built-in auth confirmed broken in `serve` mode across three independent causes, while a manually-declared custom provider works end to end including real tool-calling.
- [Combined self-barge-in mitigation, plus hardware notes](docs/field-notes/2026-08-11-self-barge-in-combined-mitigation-and-hardware-notes.md): a 400ms delay plus a 1200ms threshold nearly eliminates the problem; the likely root cause is speaker distortion at high volume, not AEC delay alone.
- [What actually reduces self-barge-in at the live-demo volume](docs/field-notes/2026-08-11-self-barge-in-mitigation-at-demo-volume.md): an AEC-delay sweep and a `barge_in_min_speech_ms` sensitivity run on real hardware, with the N=1-per-point limitation stated rather than hidden.
- [A safeword match skips checking the same transcript for a pause phrase](docs/field-notes/2026-08-12-safeword-and-pause-phrase-are-mutually-exclusive-within-one-utterance.md): both phrases present in one utterance, only one acted on — the two safety gates were unintentionally mutually exclusive.
- [A low-confidence hallucination bypassed the language-probability gate and landed near the approval phrase](docs/field-notes/2026-08-12-stt-hallucination-bypasses-the-language-probability-gate-near-miss-on-approval-phrase.md): the error ladder gates on language probability, not decode confidence, so a hallucination got two of three words into a configured approval phrase.
- [An exhaustive 10-cycle batch, clean of the Windows Enhancements confound, still shows a real ~30% stall rate](docs/field-notes/2026-08-12-vad-freeze-exhaustive-batch-after-fixing-windows-enhancements-confound.md): removing one confound did not make the freeze go away, and pause/resume remained unreliable.
- [A repeatable harness confirms short capture stalls, then catches a 12+ minute freeze that resisted every recovery path](docs/field-notes/2026-08-12-vad-freeze-harness-catches-short-stalls-and-a-12-minute-unrecoverable-one.md): strong evidence for two distinct bugs rather than one, from the first synthetic-speech harness built for this.
- [The VAD/mic-loop freeze still reproduces after PR #269 — three clean live repros, new diagnostic silent throughout](docs/field-notes/2026-08-12-vad-freeze-live-reproduced-three-times-pr269-did-not-fix-it.md): a dedicated-executor fix that did not fix it, and a diagnostic that never fired during the very condition it was built to catch.
- [force_kill() reliability across all three backends](docs/field-notes/2026-08-14-force-kill-reliability-across-all-three-backends.md): 90/90 on the two subprocess-owning backends across three tool-call shapes; opencode has no equivalent guarantee by architecture and measured an inconsistent 23/30.
- [A structurally distinct mic-freeze isolated from the backend via typed text — then an unnoticed ~41-minute compound freeze found only in forensic review](docs/field-notes/2026-08-14-mic-pipeline-silence-freeze-isolated-from-backend-via-typed-text-then-a-41-minute-compound-freeze.md): typed text removed the backend from the equation for the first time, and the log review afterwards found a freeze nobody had noticed while it happened.
- [Two more codex readline() freezes reproduced live, this time with real stall telemetry instead of silence](docs/field-notes/2026-08-14-vad-freeze-harness-live-catches-two-more-readline-stalls-with-real-telemetry.md): 65.5s and 236.7s, caught by a rapid-fire-hotwords-while-paused stress pattern.
- [claude-code re-confirmed clean at good volume](docs/field-notes/2026-08-15-claude-code-vad-freeze-re-confirmed-clean-at-good-volume.md): completes the volume-confound re-verification across all three backends — codex clean, claude-code clean, opencode a real bug found and fixed.
- [force_kill() does NOT reach real tool-call children on macOS for codex; claude-code stays reliable](docs/field-notes/2026-08-15-force-kill-does-not-reach-real-tool-call-children-on-macos.md): codex 0/10 and claude-code 10/10 — the exact opposite of each backend's own Windows result, and the finding that opened the macOS force-kill gap.
- [Two candidate fixes for codex's macOS force_kill() gap both fail](docs/field-notes/2026-08-15-force-kill-macos-fix-attempts-killpg-and-processid-both-fail.md): `os.killpg()` fails because the real child is its own process-group leader regardless of sandboxing, and the reported `processId` targets the wrong process; a real termination RPC exists but covers a different execution path.
- [The pgrep/ps fallback works — 15/15, after fixing a quoting mismatch](docs/field-notes/2026-08-15-force-kill-macos-pgrep-fallback-implemented-and-validated-15-of-15.md): codex's reported command text keeps its shell-quoting wrapper while the live process's argv has already consumed it, so a naive substring match silently matched nothing; note carries its own correction — the first validation's survivor check couldn't see orphaned grandchildren.
- [kill_phrase live-verified during a genuine freeze — and it was needed because the resume word failed every attempt](docs/field-notes/2026-08-15-kill-phrase-live-verified-during-a-genuine-freeze-resume-word-stt-unreliable.md): the emergency path worked through the real mic pipeline, in the exact circumstance it exists for.
- [The opencode freeze reproduces with zero audio — a pure orchestrator/adapter bug](docs/field-notes/2026-08-15-opencode-freeze-deterministic-audio-free-repro-orchestrator-only.md): a ~90-line async script against a real `opencode serve` hits the identical freeze 4-for-4, eliminating audio, VAD, STT, and mic hardware as necessary conditions.
- [The retry-cancel fix works — 7/7, always resolves on the first retry](docs/field-notes/2026-08-15-opencode-freeze-fix-validated-retry-cancel-turns-indefinite-hang-into-3s-bound.md): ~3.0s every time, against 15-90s indefinite hangs that never self-resolved before the change.
- [opencode has a real, live, reproducing event-loop freeze — not the volume confound](docs/field-notes/2026-08-15-opencode-freeze-genuine-event-loop-hang-not-a-volume-confound-live-repro.md): ConvoBox's own asyncio loop genuinely stalls (main thread parked in kevent, 0% CPU) while the OS mic callback keeps firing underneath it.
- [The minimal hard-stop/resubscribe repro did not reproduce the freeze](docs/field-notes/2026-08-15-opencode-freeze-minimal-repro-attempt-inconclusive-not-a-single-hard-stop.md): a negative result that narrows the hypothesis without disproving it — one clean hard-stop-then-turn is not sufficient on its own.
- [A second cancel() call reliably unsticks the freeze — fast](docs/field-notes/2026-08-15-opencode-freeze-repeated-cancel-mitigates-mechanism-and-workaround-candidate.md): recovery in under 6s, 2-for-2, suggesting the cancellation is being lost or coalesced rather than being fundamentally impossible.
- [The opencode freeze is confirmed real and reliably reproducible — and now looks like an overlapping-hard-stop race](docs/field-notes/2026-08-15-opencode-freeze-reproduced-again-narrows-to-overlapping-hard-stops-not-a-single-one.md): it took three closely-spaced hard stops, one with its TCP connect cancelled mid-flight — consistent with overlapping stops poisoning a shared connection pool.
- [Root cause pinpointed: `_consume_events()`'s `async for` never honors cancellation](docs/field-notes/2026-08-15-opencode-freeze-root-cause-pinpointed-cancel-does-not-unstick-aiter-sse.md): the stuck task's dumped stack is byte-identical before and after `cancel()` is called on it, established by live asyncio task introspection.
- [The opencode retry-cancel fix holds under an automated hard-stop storm on Windows](docs/field-notes/2026-08-15-opencode-retry-cancel-fix-holds-under-automated-hardstop-storm-on-windows.md): zero timeouts across 143 calls, though ~22% still pay a 1.8-4.3s resolution cost the macOS numbers didn't show.
- [PR #276's fix live-verified: hard stop and pause both register from one chained utterance](docs/field-notes/2026-08-15-pause-phrase-fix-live-verified-hard-stop-and-pause-both-register.md): closes the mutually-exclusive-gates finding from 2026-08-12, confirmed twice in a row.
- [Safety-phrase reliability battery: "halt" and bare "Athena" are unreliable; "stop"/"abort" are solid](docs/field-notes/2026-08-15-safety-phrase-reliability-battery-halt-and-bare-athena-unreliable.md): "halt halt halt" failed 4/5 round-trip tests and the bare resume word failed 3/5, against a codebase claim of 5/5 that only reproduced with surrounding context; no false positives found from gibberish or foreign-language input.
- [Both original freeze-repro conditions ran clean once volume was confirmed good](docs/field-notes/2026-08-15-vad-freeze-both-repro-conditions-clean-at-confirmed-good-volume.md): 35/35 utterances processed at 65% output volume, and the one 150.6s stall fully explained by genuine idle time.
- [claude-code survives a 6-cycle VAD stress run where codex froze on cycle 5 of 5](docs/field-notes/2026-08-15-vad-freeze-claude-code-backend-more-resilient-than-codex-on-macos.md): also isolates a benign false-alarm shape in the stall diagnostic itself.
- [The audio harness is exonerated for short stalls; the severe freeze looks coupled to real audio-pipeline activity](docs/field-notes/2026-08-15-vad-freeze-harness-confound-check-pure-text-still-stalls-severe-variant-audio-coupled.md): short stalls reproduce identically via pure text with no mic, speakers, or STT — 0/10 severe in the text batch against 1/10 and 1/5 in matched audio batches.
- [The severe freeze does NOT require active stress — confirmed directly](docs/field-notes/2026-08-15-vad-freeze-idle-trigger-confirmed-no-active-stress-needed.md): one ordinary interaction followed by 100+ seconds of pure idle listening was enough to trigger it.
- [The idle-trigger freeze does not reproduce on claude-code](docs/field-notes/2026-08-15-vad-freeze-idle-trigger-does-not-reproduce-on-claude-code.md): four minutes of idle under the identical condition that reliably hits codex at ~90-100s onset.
- [opencode does not freeze under the idle-trigger condition either](docs/field-notes/2026-08-15-vad-freeze-idle-trigger-does-not-reproduce-on-opencode.md): 5+ minutes idle plus two probe utterances stayed fully responsive; includes a self-caught false alarm kept deliberately as a methodology note.
- [The definitive idle-trigger re-run: readline() stalls were harmless, a separate mic-layer freeze was the real problem](docs/field-notes/2026-08-15-vad-freeze-idle-trigger-re-run-with-busy-diagnostic-readline-was-harmless-mic-layer-freeze-real.md): a 335.6s "stall" — longer than any prior severe catch — was entirely benign, while a structurally different mic-layer freeze occurred twice in the same session.
- [The mic-layer freeze reproduced this round was a volume confound, not a bug](docs/field-notes/2026-08-15-vad-freeze-mic-layer-repro-was-a-test-harness-confound-system-output-volume-at-25pct.md): system output at 25% meant the synthesized test WAVs never crossed the VAD speech threshold; at 70% the identical files worked every time.
- [A new freeze variant: pure mic-layer silence, no backend involved, 6+ minutes, self-resolved](docs/field-notes/2026-08-15-vad-freeze-new-variant-mic-layer-only-6-minutes-self-resolved.md): no codex subprocess ever spawned, and the first freeze in the investigation to recover with no manual intervention — this is the variant that remains open.
- [A major correction: many "stalls" are ordinary idle time, not a hang](docs/field-notes/2026-08-15-vad-freeze-readline-stalls-often-just-idle-time-not-a-hang-busy-state-fix.md): the diagnostic never distinguished "waiting for a response" from "correctly idle, nothing sent" — fixed by adding busy state to the log line, which retroactively reclassified much of the investigation.
- [Re-run with the busy-aware diagnostic: zero genuine hangs in a fresh 10-cycle batch](docs/field-notes/2026-08-15-vad-freeze-re-run-with-busy-diagnostic-zero-genuine-hangs-in-fresh-10-cycle-batch.md): every stall of 5.5s or longer showed busy=False, confirming the correction generalizes rather than being a one-off.
- [Root cause captured: codex is blocked on its OWN stdin, not ConvoBox's event loop](docs/field-notes/2026-08-15-vad-freeze-root-cause-codex-blocked-on-own-stdin-not-convobox-event-loop.md): a live native stack sample directly refutes the long-standing "synchronous audio call blocks the event loop" hypothesis — ConvoBox was entirely idle at that moment.
- [A 10-cycle batch surfaces a self-resolving 66.3s stall AND a second severe freeze](docs/field-notes/2026-08-15-vad-freeze-second-severe-instance-plus-a-self-resolving-66s-stall.md): two for two batches, both near the tail after explicit stress had already stopped.
- [The VAD/mic-pipeline freeze reproduces live on macOS](docs/field-notes/2026-08-15-vad-mic-freeze-live-reproduced-on-macos.md): a 94.4s readline() stall plus 2+ minutes of total mic silence that survived a safeword, a subprocess kill, and a fresh utterance.
- [PR #302's race fix is live-reverified, closing its own unchecked test-plan box](docs/field-notes/2026-08-18-artifact-active-race-fix-live-reverified-post-restart.md): 5/5 clean across three runs against a genuinely fresh process with real concurrent HTTP requests, no TestClient shortcuts.
- [Bare "Athena" mis-transcribed by real human speech on Windows](docs/field-notes/2026-08-18-bare-athena-stt-unreliable-real-voice-windows.md): extends the earlier macOS/synthetic-speech-only finding to real voice on a second platform.
- [A ~1s ambiguous barge-in was hallucinated into 69x "brake", false-triggering the safeword hard-stop](docs/field-notes/2026-08-18-brake-safeword-stt-hallucination-false-positive-hard-stop.md): the first confirmed false positive on the safety path — a hallucination, not a mishearing of a real word.
- [The first real-voice kill_phrase test finds two real gaps, both previously invisible to scripted testing](docs/field-notes/2026-08-18-kill-phrase-live-voice-test-finds-two-real-gaps.md): the just-merged pgrep fallback had a 15-char minimum-length guard silently excluding short commands like `sleep 90`, and the "ends this session" claim didn't hold — the self-signal SIGINT never reached `asyncio.run()`'s handler.
- [The pgrep-fallback fix is reconciled, re-verified, and shipped](docs/field-notes/2026-08-18-macos-force-kill-pgrep-fallback-reconciled-and-shipped.md): clean rebase, 20/20 on real spawned processes, and the disclosure across KNOWN-ISSUES/README/STATUS/CHANGELOG corrected from "candidate fix, unconfirmed" to "fixed, closed".
- [kill_phrase ends the session but leaves an orphaned, resource-heavy child running on Windows](docs/field-notes/2026-08-19-kill-phrase-windows-orphaned-descendant-survives-force-kill.md): a deliberately detached PowerShell write loop survived 2+ minutes past full session exit, stopping only on its own timer; the macOS `ps` fallback doesn't apply on Windows, and an automated harness driving the identical scenario failed to reproduce it 8/8.
- [A real Windows amplitude sweep confirms AEC struggles at high volume there too, but Audio Enhancements' effect on it is genuinely mixed](docs/field-notes/2026-08-20-windows-amplitude-sweep-audio-enhancements-mixed-effect.md): matched N=3 sweep (36 trials) on Realtek+amplified-7.1 hardware reproduces the macOS "AEC worse than off at high volume" finding on different hardware, but Windows Audio Enhancements' effect on the pipeline is a mixed result, not simply good or bad.

## Development record (primary sources behind the notes)

- [UAT checklist](docs/UAT-checklist.md): per-subsystem live-testing matrix and raw findings ([E*]/[L*]/[U*]).
- [Known issues](docs/KNOWN-ISSUES.md): diagnosed-and-deferred problems, indexed by component/platform/severity.
- [Permission model](docs/PERMISSION-MODEL.md): canonical reference for `backend.permission_mode` across all three backends.
- [AI attribution convention](docs/AI-ATTRIBUTION.md): how every AI-assisted change in this repo is provenance-stamped.
- [Working agreements](AGENTS.md): change-scope discipline binding every agent in the repo, each rule citing its incident.
