ADR-005: InputBackend Protocol¶
Status: Accepted
Date: 2026-05-18
Deciders: Mohsen Seyedkazemi Ardebili
Context¶
YazSes v0.4 has two input modalities — a HotkeyBackend (evdev hold detector) and an EMGBackend (USB-serial YESP protocol) — but the interface is loosely coupled to the daemon internals. The product roadmap commits to absorbing future silent-speech, sEMG wristband, and BCI input modalities as single-file additions rather than core refactors. Trajectories in the field include sEMG wristbands (notably Meta Reality Labs), throat-EMG subvocal research, and clinical BCI devices. The SDK availability and timing for these modalities are outside YazSes' control; the architectural seam must be in place before third-party SDKs open, or integration cost balloons.
Decision¶
The v0.4 HotkeyBackend is formalised into an InputBackend trait in yazses-inputs/src/backend.rs with a uniform event vocabulary and a calibration ceremony API:
pub enum InputEvent {
HoldStart { ts: f64 },
PartialText { ts: f64, text: String },
Gesture { ts: f64, kind: String, params: serde_json::Value },
HoldEnd { ts: f64 },
CalibrationReady { artifact: CalibrationArtifact },
}
pub trait InputBackend: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> &[&str];
async fn listen(&mut self) -> Pin<Box<dyn Stream<Item = InputEvent>>>;
fn calibrate(&mut self, corpus: Vec<CalibrationSample>) -> Option<CalibrationArtifact>;
}
v1.0 ships two implementations: KeyboardHoldBackend (refactored from the v0.4 evdev hold detector, with macOS and Windows variants) and EmgYespBackend (refactored from the v0.4 platform/emg/backend.py). The capabilities string set ("hold", "gesture", "phoneme_stream", "calibration") lets future backends declare supported event types without expanding the enum.
Consequences¶
Positive: - Adding a new input modality in the future requires one file in inputs/ — no changes to the core daemon. - Calibration is a per-backend concern with a uniform API; accessibility/enroll.py becomes a per-backend driver. - A MockInputBackend can be supplied in unit tests, making daemon state-machine tests hardware-independent. - The event vocabulary covers the full range of projected silent-input modalities without requiring enum expansion.
Negative / trade-offs: - The Protocol is intentionally slightly broader than v1.0 strictly needs; some variants and capability strings are unused at ship. This adds minor documentation complexity. - Future BCI vendor SDKs may have shapes not anticipated by the current protocol; the open params: serde_json::Value field on Gesture provides an extension point, but new event variants may still be needed.
Implementation¶
The InputBackend trait and InputEvent enum are defined in yazses-inputs/src/backend.rs. KeyboardHoldBackend is in yazses-inputs/src/keyboard/ (platform-specific sub-modules). EmgYespBackend is in yazses-inputs/src/emg.rs. The daemon selects and wires backends in yazses-core/src/daemon.rs based on configuration.