YazSes v0.3.0 — Production Readiness Checklist¶
1. Observability¶
1.1 Daemon Logging¶
- All new state transitions (
REMOTE_SETUP,REMOTE_ACTIVE,ENROLLING) are logged atINFOlevel with ISO-8601 timestamp, current state name, and transition trigger. -
stt/streaming.pylogs each decode tick atDEBUGlevel: tick index, rolling buffer duration (ms), stable prefix length (chars), and chars emitted in this tick. -
inject/streaming.pylogs_chars_injectedcounter value atDEBUGlevel on eachinject_partial(),commit(), andcancel()call. -
commands/grammar.pylogs classifier result atDEBUGlevel: matched pattern name, intent type, action, extracted args, and elapsed time (ms). -
stt/filters/disfluency.pylogs filter result atDEBUGlevel: input length, output length, chars_removed, rules fired (bitmask of A/B/C). -
remote/forwarder.pylogs tunnel health atINFOlevel: connection established (host, port), connection lost (reason), reconnect attempt (attempt N). -
accessibility/enroll.pylogs calibration progress atINFOlevel: utterance N/20, measured noise floor, measured speech RMS. - Log level is configurable via
YAZSES_LOG_LEVELenvironment variable (default:WARNINGin production,INFOin daemon--verbosemode).
1.2 IPC Debug Events¶
- New IPC notification
debug_eventis emitted by the daemon whenconfig.general.debug_events = true. Carries: timestamp, event_type (string), payload (dict). Consumed byyazses status --watch. -
command_dispatchednotification (FR-003.5) is always emitted on the IPC socket when a non-DICTATE intent fires. Tray and external tooling can subscribe. -
remote_statusIPC method returns tunnel health as a structured dict:{"connected": bool, "host": str, "latency_ms": float | null, "bytes_forwarded": int}.
1.3 Daemon Status Reporting¶
-
yazses statusoutput is extended for v0.3.0 to include: - Streaming mode:
enabled|disabled, last TTFP (ms). - Remote session:
active|inactive, host, tunnel latency (ms). - Disfluency filter:
enabled|disabled, chars removed in last session. - Command grammar:
enabled|disabled, last intent classified. - Accessibility profile:
default|calibrated, enrolled VAD threshold. - All status fields have stable JSON output when
yazses status --jsonis used. Breaking changes to the JSON schema are versioned.
2. Reliability¶
2.1 SSH Tunnel Disconnect Handling¶
-
remote/forwarder.pydetects SSH subprocess exit (non-zero exit code orSIGCHLD) and sets daemon state toREMOTE_DISCONNECTED. - On disconnect, any in-progress recording is finalised locally (transcribed and discarded — text is not forwarded when no tunnel is active).
- The daemon emits a
remote_disconnectedIPC notification with reason string. -
yazses remote --reconnectflag enables automatic reconnect with exponential backoff (1 s, 2 s, 4 s, 8 s, cap 60 s). Maximum retry count configurable viaremote.max_reconnects(default: 10). - If SSH subprocess is killed by signal (e.g., SIGHUP on terminal close), the daemon cleans up the
RemoteForwarderand returns toIDLEstate cleanly.
2.2 Streaming Timeout¶
-
stt/streaming.pyenforces a decode timeout: if a singleWhisperModel.transcribe()call exceedsconfig.streaming.partial_interval_ms × 3ms (default 900 ms), it is abandoned and the streaming session continues with the previous stable prefix. AnINFOlog entry is emitted. - If the rolling audio buffer exceeds
config.audio.max_record_seconds(default 90 s), streaming is automatically committed as if the hotkey were released. The user is notified via asession_auto_committedIPC notification. - On
cancel(),StreamingInjectoremits backspace events synchronously in the same thread as the hotkey event handler. If the injector backend raises an exception (e.g.,xdotoolnot found), the exception is caught, logged atERROR, and the_chars_injectedcounter is reset to 0 (partial text may persist in the window, but the daemon continues functioning).
2.3 Command Misrecognition Recovery¶
-
commands/dispatch.pywraps all shell action executions (subprocess.run) in atry/except. If a shell command fails (non-zero exit code), the failure is logged atWARNINGand acommand_failedIPC notification is emitted. The daemon does not crash. - The
IntentType.DICTATEfallthrough path has zero side effects: if no pattern matches ingrammar.classify(), the original text is returned unmodified and no IPC notification is emitted. - User can disable the command grammar at runtime via
yazses config set commands.enabled falsewithout daemon restart (IPC methodstreaming_enableis extended to cover command grammar toggling).
2.4 Accessibility Device Disconnect¶
- On Linux, if the
evdev_devicepath becomes unavailable (device unplugged), theplatform/linux/hotkey.pybackend catches theFileNotFoundErrorfromevdev.InputDevice, logs atERROR, emits ahotkey_device_lostIPC notification, and falls back to keyboard hotkey detection automatically. -
yazses doctor --accessibilitychecks that the configuredevdev_devicepath exists and is readable before the daemon starts, and prints a specific error message if not. - The enrollment wizard (
yazses enroll) validates that the microphone device is accessible before starting the 20-utterance calibration sequence. If the device becomes unavailable mid-enrollment, the wizard saves partial results and prints instructions to resume.
3. Security¶
3.1 SSH Key Management¶
-
remote/forwarder.pynever generates or stores SSH keys. It delegates authentication entirely to the user's existing SSH agent (SSH_AUTH_SOCK) or key file specified viaremote.key_filein config. - The
remote.key_fileconfig value is never logged (masked to"[redacted]"in all log output). - SSH subprocess is spawned with the minimum required arguments: no
StrictHostKeyChecking=noby default (user controls their~/.ssh/known_hosts). Aremote.no_host_check = trueoption exists for CI/testing environments but emits aWARNINGlog when used. -
yazses remote install user@host(the bootstrap command to installyazses-agenton the remote) uses the standardsshclient andpip installover the tunnel. It does not handle credentials itself.
3.2 No Audio in Transit¶
- ADR-001 is enforced architecturally:
remote/forwarder.pyandremote/local_proxy.pyhave no imports ofsounddevice,numpy, oraudio.*. Code review and CI lint check enforce this. - The only data that crosses the SSH tunnel is the JSON-RPC payload: UTF-8 text strings and control signals (
inject,remote_start,remote_stop). No binary audio buffers are serialised or transmitted at any point. [EVIDENCE src-001] - The SSH tunnel itself is encrypted by OpenSSH (AES-128-CTR or better, depending on the server's cipher list). YazSes does not add a second encryption layer.
3.3 No Cloud Endpoints¶
- The daemon has no outbound HTTP/HTTPS calls in any code path. A CI lint step (
grep -r "requests\|httpx\|urllib.request" src/yazses/) verifies this. - The optional LLM disfluency enhancement (
filters.disfluency.llm_enabled = true) connects only to a user-configured local endpoint (llm_endpoint, default:http://localhost:11434). The endpoint is never a cloud URL by default. -
yazses doctorchecks for unexpected network socket activity (Linux:ss -tp | grep yazses-daemon) and warns if any non-IPC sockets are open.
3.4 Config File Permissions¶
- On Linux and macOS,
config.pychecks thatconfig.tomlis not world-readable (chmod 600) and logs aWARNINGif it is. The warning includes a remediation command. - On first run,
config.tomlis created withmode=0o600.
4. Scalability¶
4.1 Daemon Architecture (Still Single-Process)¶
- The daemon remains a single Python process with no worker processes added by v0.3.0. The streaming decode runs in a
threading.Thread(not a subprocess), sharing the existingFasterWhisperEngine._modelinstance without creating a second model. [EVIDENCE src-001] - Verified: memory footprint of the daemon with all v0.3.0 features enabled (
tiny.en, streaming, grammar, disfluency filter, remote) does not exceed baseline by more than 50 MB RSS. Measured usingtracemallocin the integration test suite.
4.2 Streaming Memory Bounds¶
-
stt/streaming.pymaintains a single rolling audio buffer capped atconfig.audio.max_record_seconds × sample_rate × 4 bytes/sample. At the default 90 s cap with 16 kHz:90 × 16000 × 4 = 5.76 MB. This is the upper bound on streaming memory growth per session. [EVIDENCE src-002] -
inject/streaming.py's_chars_injectedcounter is a plainint. It does not grow with session length; it is reset on eachcommit()orcancel(). -
commands/grammar.py's compiledre.Patternlist is instantiated once at daemon startup and held as a module-level singleton. No per-request allocation beyond the match operation.
4.3 Concurrent Remote Sessions¶
- v0.3.0 supports exactly one remote session at a time (single
RemoteForwarderinstance). Attempting to start a second remote session while one is active returns a JSON-RPC error{"code": -32003, "message": "Remote session already active"}. - The single-session constraint is documented in the CLI help text:
yazses remote --host(note: only one active remote session supported per daemon instance).
5. Operational Runbook¶
5.1 Startup / Stop / Restart¶
# Start daemon (systemd, Linux)
systemctl --user start yazses
# Start daemon (launchd, macOS)
launchctl start com.yazses.daemon
# Start daemon (direct, any platform)
uv run yazses-daemon --config ~/.config/yazses/config.toml
# Stop daemon
yazses stop
# Restart daemon (Linux)
systemctl --user restart yazses
# Check daemon status
yazses status
yazses status --json
5.2 Troubleshooting SSH Remote Sessions¶
Tunnel fails to establish: 1. Run yazses doctor — check that remote.key_file is accessible and that SSH to the target host works independently (ssh user@host echo ok). 2. Check that AllowTcpForwarding yes is set in the remote /etc/ssh/sshd_config. 3. Verify yazses-agent is installed on the remote machine: ssh user@host yazses-agent --version. 4. Check firewall: the remote agent listens on loopback (127.0.0.1:9875 by default) — no external port is required.
Text appears in wrong window on remote: - The remote agent uses the same injector probing as the local daemon. If xdotool is not installed on the remote, the agent falls back to clipboard injection (Ctrl+V). - Install xdotool on the remote machine for keyboard-native injection: apt install xdotool.
High latency on WAN: - Enable stt.model = "tiny.en" (fastest model) if using base.en. - Disable streaming in remote mode (it is already non-default for remote): yazses config set streaming.enabled false. - Check tunnel RTT: yazses remote --status --json reports latency_ms.
5.3 Resetting the Enrollment Profile¶
# Re-run enrollment (overwrites vad_threshold, min_silence_ms, pre_speech_padding_ms)
yazses enroll
# Reset to factory defaults (removes all accessibility-tuned values)
yazses config reset accessibility
# Manually edit config
yazses config edit # opens config.toml in $EDITOR
# View current accessibility config
yazses doctor --accessibility
If the enrollment wizard produces a vad_threshold that causes too many false recordings (threshold too low), increase it manually:
# ~/.config/yazses/config.toml
[audio]
vad_threshold = 0.05 # default post-enrollment is ~0.02–0.04; increase if false triggers occur
5.4 Debugging the Disfluency Filter¶
# Pipe a test transcript through the filter
yazses filter --text "um let me uh go to line go to line 42"
# Output: "let me go to line 42"
# Disable filter temporarily
yazses config set filters.disfluency.enabled false
# View filter log (requires --verbose)
yazses-daemon --verbose 2>&1 | grep disfluency
6. Release Checklist¶
6.1 Version Bump¶
-
pyproject.toml:version = "0.3.0" -
src/yazses/__init__.py:__version__ = "0.3.0" -
yazses --versionoutputsYazSes 0.3.0(verified by running the command after bump).
6.2 Changelog¶
-
CHANGELOG.mdupdated with v0.3.0 section at the top. - Changelog entries for: cap-001 (SSH remote), cap-002 (streaming), cap-003 (code commands), cap-004 (disfluency filter), cap-005 (accessibility + enrollment).
- Breaking changes section: none for v0.3.0 (all config additions have safe defaults).
- Upgrade guide section: instructions for users upgrading from v0.2.x (run
yazses enrollto opt into accessibility tuning; no other action required).
6.3 Test Suite¶
-
uv run pytest tests/ -vpasses on Linux (Python 3.11 and 3.12). -
uv run pytest tests/ -vpasses on macOS (Python 3.11 and 3.12). -
uv run pytest tests/ -vpasses on Windows (Python 3.11 and 3.12). - All pass/fail thresholds from §4 (Eval Plan) are met.
- All regression tests from R-001 through R-004 pass.
- Integration test (
pytest --integration tests/integration/test_ssh_tunnel.py) passes on Linux CI. - Test coverage ≥ 80% on all new modules (measured by
pytest --cov=src/yazses).
6.4 Snap Package¶
-
snapcraft.yamlversion updated to0.3.0. - New Python modules added to
snapcraft.yamloverride-buildsection (if snapcraft does not auto-detect them frompyproject.toml). - Snap built locally and installed:
sudo snap install yazses_0.3.0_amd64.snap --dangerous. -
yazses --versionfrom Snap installation outputsYazSes 0.3.0. - Snap published to
edgechannel first; promoted tostableafter 48-hour soak.
6.5 apt Repository¶
-
scripts/build-deb.shproducesyazses_0.3.0_amd64.deb. -
.debinstalls cleanly on Ubuntu 22.04 and 24.04:sudo dpkg -i yazses_0.3.0_amd64.deb. -
apt-repo.ymlGitHub Actions workflow triggered byv0.3.0tag publishes to the apt repository. -
apt update && apt install yazsesinstalls0.3.0on a clean Ubuntu 24.04 container.
6.6 Homebrew Tap (macOS)¶
- Homebrew formula updated in the
novafabric/taprepository withversion = "0.3.0"and updated SHA256. -
brew install novafabric/tap/yazsesinstalls0.3.0on macOS 13 (Ventura) and macOS 14 (Sonoma). -
yazses --versionfrom Homebrew installation outputsYazSes 0.3.0.
6.7 GitHub Release¶
-
v0.3.0tag pushed tomain. - GitHub Release created with title
v0.3.0and changelog body. - Release assets attached:
.deb,.snap, macOS.dmg(frombuild-macos.yml), Windows.exeinstaller (frombuild-windows.yml). - Release marked as
Latestafter Snap and apt packages are confirmed live.