CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to computah are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
[semantic versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Syl voice dispatcher policy (#49): Syl's persistent-bridge prompt keeps quick
conversation inline and instructs Syl to send explicit handoffs, tool use, and
multi-step work to an ephemeral background subagent after any required spoken
confirmation. Syl gives a short acknowledgment so the listening loop can resume,
accepts corrections while the subagent runs, and speaks each saved result once in
bounded later voice replies without adding an unsolicited bridge reply or changing
later text-channel behavior.
- Capture-device suitability warning (#34): `capture_quality.py` judges whether a
mic can carry continuous speech, and the live loop says so at startup instead of
letting it surface as a garbled transcript. A Bluetooth hands-free (HFP) mic on
Windows downsamples to narrowband and drops frames, so faster-whisper returns
garbled text while wake detection keeps firing at 0.998 -- the loop looks
healthy right up to the transcript. Two property signals are a hands-free
marker in the device name and, on WASAPI only, a default rate below the
pipeline's 16 kHz. Other host APIs expose a preferred default rather than a
bandwidth limit. `audio.py --list` tags unsuitable inputs, and `--test-mic`
also flags the 4 kHz cliff left by an upsampled narrowband stream. It exits 3
for a flagged device and 4 when the audio is unanalyzable or the narrowband
cliff is absent, since that check cannot rule out wideband HFP recompression
and frame drops. The module is hardware-free so `test_capture_quality.py` runs
with no PortAudio.
- `prep_wake_samples.py --clean` (#8): removes leftover clips of a take the run
just re-recorded (a `<stem>_NNN.wav` clip whose stem this run wrote) from
`--output`, so re-recording with fewer utterances no longer strands orphaned
higher-numbered clips that training and eval would still read. Off by default
(deletion is opt-in). The source manifest below broadens cleanup to the
prep-owned clips of an intentionally omitted take while still sparing source
recordings and hand-curated audio. With no manifest, the narrow same-stem rule
remains as a warned legacy fallback; any present-but-unusable record fails
closed until removed. Without `--clean` the run still warns and names the
leftover count. `test_prep_wake_samples.py` covers the
re-run-with-fewer-clips case with and without `--clean`, and that source-aware
`--clean` spares files prep did not record.
- `prep_wake_samples.py` output manifest (#84): prep records each resolved source
path and the clips it owns in `.prep-manifest.json` in the output dir.
`--clean` uses that provenance to remove orphans the stem match cannot reach --
clips from an input dropped between runs, and clips under a disambiguated stem
(`take-1_000.wav`) that a later run no longer produces. Before a destructive
run, prep names every omitted source and how many of its clips `--clean` will
remove. A run must share an exact recorded source path before it may write to
that dataset, so a different dataset with the same label and generic basename
cannot overwrite or clean it.
Malformed ownership maps, including clip names that are paths instead of safe
output filenames or clips claimed by multiple sources, are refused before any
audio is decoded or written. Manifests are read and written explicitly as
UTF-8 so non-ASCII recording paths round-trip across supported platforms.
Manifested stems remain reserved for their source while new output names are
assigned, so a new same-basename recording cannot overwrite the only good
clips from a source whose rerun is silent. A source this run attempted but got
no clips from retains those prior clips. With a readable source map,
hand-curated audio stays outside it and is never deleted. A non-clean legacy
refresh that leaves ambiguous same-stem files does not write a manifest; the
next `--clean` remains on the documented filename fallback instead of turning
a guess into durable provenance. A no-output run cannot claim an unrecorded
directory, and a first-time source that writes no clips cannot gain cleanup
authority over an existing dataset. Manifest writes use atomic replacement.
Only an absent manifest degrades `--clean` to the narrow stem match with an
explicit warning; that fallback cannot distinguish a prep leftover from a
hand-added `<same-stem>_NNN.wav`. An unreadable, malformed, or non-v2 manifest
fails closed until the user removes it to bootstrap ownership explicitly.
Existing pre-manifest directories must bootstrap with the complete source set;
clips under stems omitted from a partial first run remain deliberately unowned.
- Configurable request endpointing (#15): the trailing-silence window that ends a
captured request and the max-request cap that bounds a runaway are now config keys
(`endpoint_silence_ms`, `max_request_ms`, milliseconds) instead of fixed constants,
so the live loop is tunable without a code change. `run_turn` and the `live_driver`
loop thread the config through `capture_request`; both keys default to the built-in
values (800 ms endpoint, 8000 ms cap), so the shipped config leaves capture behavior
unchanged. `test_endpoint_config.py` covers the conversion, the no-drift default
invariant, the tuned endpoint and cap, and the `run_turn` wiring, model-free.
- Mishear confidence guard: both live paths (`pipeline.run_turn` and the
`live_driver` hardware loop) now gate the transcript on faster-whisper confidence
before dispatching to the brain, through a shared `guard_transcript`, so a garbled
command cannot trigger an action on either path. `avg_logprob` is the gate;
following faster-whisper's own no-speech rule, a high `no_speech_prob` only marks a
reject as silence when the decode is also unconfident, so a clear command is never
dropped for it alone. A rejected turn speaks a short re-prompt instead of calling
the brain. New config keys `stt_confidence_guard`, `stt_min_avg_logprob`, and
`stt_max_no_speech_prob` (defaults mirror faster-whisper's own thresholds).
`transcribe_detailed` returns the new `Transcript` (text plus the two signals);
`transcribe` still returns text.
- `test_confidence_guard.py`: fast, no-model coverage of the gate decision, segment
aggregation, and the shared `guard_transcript`. `test_live_driver.py`: fast,
no-model proof the hardware path honors the guard. Two model-tier checks in
`test_stream_turn.py` prove a low-confidence transcript skips the brain and a
confident one passes through.
- Config-selectable brain backend: the `brain_backend` key chooses `cli` (the
`claude -p` fallback, default) or `bridge` (a persistent assistant session), so the
bridge is reachable from a real run, not only from tests.
- `config.local.json` overlay (gitignored) for deployment-specific bridge settings —
persona, transport, ssh host, reply path — overriding the committed `config.json`.
`config.local.example.json` is a template.
- `test_brain_dispatch.py`: proves config alone selects the backend and that
persisting the wake word does not leak local config into `config.json`.
### Changed
- `brain()` now dispatches on `brain_backend`; the `claude` CLI path moved to
`_brain_cli`. `load_config()` merges `config.local.json` over `config.json`.
### Fixed
- `set_wake_word()` wrote the merged config (defaults plus the local overlay) back to
`config.json`. It now round-trips only the committed base, so the gitignored bridge
settings can no longer leak into the tracked file.
## [0.1.0] - 2026-06-21
First working core. The full voice chain runs mic-free, driven by audio files.
### Added
- Four-stage pipeline (`pipeline.py`): openWakeWord wake detection, faster-whisper
speech-to-text (int8, no PyTorch), the brain stage, and Piper text-to-speech, plus
an end-to-end chain and a CLI.
- Changeable wake word from `config.json`, with `--list-wake-words` and
`--set-wake-word`.
- Brain bridge (`brain_bridge.py`): routes the transcript to a persistent assistant
session over a file-based message-passing contract, with injected transports
(local CLI, ssh, and a test stand-in).
- Simulated persona (`sim_persona.py`) for exercising the brain loop with no live
session and no microphone.
- Tests: bridge round-trip (`test_brain_bridge.py`), full chain with the bridge
brain (`test_pipeline_bridge.py`), and full chain with the fallback CLI brain
(`test_pipeline.py`).
- Pinned dependencies (`requirements.txt`), CPU-only, no PyTorch.
[Unreleased]: https://github.com/jamditis/computah/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/jamditis/computah/releases/tag/v0.1.0