# Genesis — Behavior and Safety Contract This document is the normative description of Genesis. Tests in `tests/` exercise what can run without a Quattro machine; the rest is documented here for manual verification. ## 1. Identity and loading - `manifest.json` declares `schemaVersion: 1`, `id: "genesis"`, and `kinds: ["service", "bar-widget"]` with `entryPoints.service = "Service.qml"` and `entryPoints.barWidget = "BarWidget.qml"`. - The shell loads `Service.qml` as a long-running service; it locates its own files via the shell-injected `manifest.__sourceDir`, falling back to the standard install path. - The bar hosts `BarWidget.qml` as a bar-widget panel: a microphone button whose popup menu expands under the icon (`KeyboardPanel`, like the audio/bluetooth/power panels). Left-click talks; right-click toggles the menu, which offers a typed command and command-and-routine management. The same menu is summonable via `omarchy-shell shell toggle genesis '{}'`, which routes to the widget's `open()`. - The bar widget holds no pipeline state and only sends IPC to the service; it must not register a second `genesis` IPC target (`manageIpc: false`). - Genesis registers an IPC target named `genesis`. External callers use `omarchy-shell -q genesis ` (the `-q` is quiet best-effort: fire the call and ignore the result). ## 2. Input triggers | Method | Signal | Contract | |--------|--------|----------| | `begin` | start recording | idempotent; caps the session at `maxListenSeconds` (10) | | `end` | stop and process | no-op unless `phase == "listening"` | | `toggle` | flip listening | `begin` if idle; otherwise stop — `end` while listening, cancel while thinking or confirming | | `text ` | typed command | runs the intent pipeline on `` directly | | `state` | query | returns the current phase | There is deliberately no `confirm` method: confirmation can only be answered from the on-screen dialog (click or a spoken "yes" during the confirm phase), so no local process — including a launched agent — can approve a destructive action out of band. Triggers map to these methods: - Bar microphone left-click → `toggle`; right-click → toggles the popup menu (a bar-widget `KeyboardPanel` under the icon, offering a typed command or the manage submenu). - A user-added keybind → `toggle` (or any other method). Genesis registers no global keybinding of its own; any keyboard trigger is a user-owned Hyprland binding, so no plugin installer is required. ## 3. Pipeline The pipeline is fixed: capture → transcribe → intent → execute. 1. `begin` starts `pw-record` (or `parecord`/`arecord`) writing a 16 kHz mono WAV to `$XDG_RUNTIME_DIR/genesis/recording.wav`. 2. `end` stops the recorder (with a short flush delay) and runs `bin/transcribe `, which delegates to `voxtype transcribe` and deletes the WAV on success. 3. `bin/intent ` normalizes and matches rules, then the `commands` registry, and emits one JSON action object. 4. For actions named in `confirmActions`, Genesis pauses at a confirmation prompt instead of executing. 5. `bin/execute ` runs the mapped Omarchy command, coding-agent launch, plugin IPC call, shell command, or status query. Each stage is a standalone script and fails closed: an unavailable backend or an unparseable result yields no action rather than a guess. The overlay shows the transcribed text while processing, so the user can confirm what was heard before the action runs. ## 4. Action schema `bin/intent` prints exactly one object on stdout: ```json {"action":"shutdown","label":"Shut down the computer","needsConfirm":true,"args":{}} ``` - `action` is one of the known names (see README tables), `run`, `ipc`, `failed`, `agent`, or `unknown`. - `needsConfirm` is derived from `confirmActions` in config; the `agent` action additionally always forces `true` (see §9). - `args` carries extracted values: Home Assistant and TV actions carry a resolved `entity` or `command`; `ipc` carries `target`, `method`, and `params`; `run` carries `command`; `speak` carries `text`; `status` carries `type`; `failed` carries `phrase` and `error`; `agent` carries `prompt`. - `unknown` never executes anything. ## 5. AI agent contract - Unmatched text becomes an `agent` action only when `agent.enabled` is `true`; otherwise it becomes `unknown`. The default is `false` (agent disabled) — both the code default and the shipped `config.example.json` leave the agent off, so enabling it is a deliberate opt-in. - `bin/execute` runs `omarchy-agent-prompt ""`, launching the user's default coding agent with approval prompts bypassed. This is the security boundary of the plugin: anything the microphone hears that does not match a rule becomes an instruction an unrestricted agent acts on. - Every `agent` action requires on-screen confirmation before launch: the dialog shows the transcribed (or typed) text and the user must approve — a misheard or ambient phrase cannot reach the agent silently. - Command- and routine-management requests ("add a command…", "update my joke command", "change the 09:00 routine…") are routed to the agent with a hint pointing at `bin/commands` and `bin/routines` respectively. The prompt lists the currently registered entries, each with its full current definition, and instructs the agent to change only the one the user names — never create or touch unrelated entries. - The popup menu's "Ask AI" (✨) — on the Commands/Routines lists and in the add forms — prefixes the typed request with `@command` or `@routine`; `bin/intent` strips the marker and instructs the agent to always register a command (or routine) with a short `--name`, never merely answer — and routes before the rules so even a phrase that looks like a built-in action becomes an entry. - The edit forms' "Ask AI to update" (✨) prefixes with `@updatecommand :: ` (or `@updateroutine :: `); `bin/intent` shows the entry's current spec and instructs the agent to apply only that change to that one entry — never create, rename, or touch another. - The fallback prompt lists the built-in actions and the registered custom commands as tools, so the agent can dynamically interpret a request and invoke the right one. Built-ins are triggered by sending a phrase back through Genesis (`omarchy-shell -q genesis text ""`), preserving confirmation; custom commands are invoked directly (`omarchy-shell ` or the script). - The agent is launched unattended but in its own terminal window; Genesis never answers on the agent's behalf and performs no privileged action itself. - If no default agent is set, `omarchy-agent-prompt` exits non-zero and Genesis notifies the user to run `omarchy default agent `. ## 6. Home Assistant contract (delegated) - Genesis does not connect to Home Assistant itself; it forwards device actions to the community `hass` plugin over IPC (`omarchy-shell hass toggleEntity | activate `). The bare call (no `-q`) is deliberate: Genesis needs a non-zero exit to detect a missing plugin and notify the user. - A spoken device name resolves through the `homeAssistant.entities` alias map: exact match, then substring, then word-subset (order-free). An unresolved name is never guessed — it falls through to `agent`. - Only toggle (`ha-toggle`) and scene activation (`ha-scene`) are forwarded; fine-grained commands (brightness, set-points) fall through to the agent. - No Home Assistant credentials live in Genesis; the token lives in the `hass` plugin. ## 7. TV contract (delegated) - TV control is delegated to the Roku Remote and Apple TV Remote plugins — directly to the device, with no Home Assistant involved. - `tv.backend` selects the target: `roku`, `apple-tv`, or `auto` (default: whichever plugin is installed, Roku first). `off` disables it. - Commands are semantic (`power-on`, `power-off`, `play-pause`, `home`, `up`, `down`, `left`, `right`, `select`, `back`) and mapped per backend: Roku keys via `omarchy-shell -q roku sendKey `, Apple TV actions via the plugin's `apple-tv-remote` CLI. - TV power phrasings are matched before the system rules, so "power off the tv" reaches the TV and not `shutdown`. - If no TV plugin is installed, Genesis notifies the user to install one. ## 8. Custom commands and routines ### Commands (`commands`) - The `commands` config maps a stable id (a slug of the `name`, or the phrase) to either an `omarchy-shell` IPC call (`target`/`method`/`params`) or a script `run` in an optional `lang`. Each entry carries its own `phrase` (the trigger speech is matched against) and an optional `name` (display label). - Matching runs after the built-in rules and before the agent fallback, by normalized word-boundary containment; the longest registered phrase wins. - `ipc` actions run `omarchy-shell `. `run` actions write the snippet to a temp file and run it with the interpreter for `lang` (default `bash`; also `python`→`python3`, `node`→`node`, `ruby`→`ruby`); unknown or uninstalled languages are recorded as failures and blocked like any other command error. Genesis matches `params` statically; dynamic value extraction from speech is the AI agent's job (the commands are surfaced as tools, §5). - Other plugins create/remove commands and routines by calling the `bin/commands` and `bin/routines` CLIs directly (they write `config.json` atomically), so registrations persist and are idempotent. - `bin/commands export` / `bin/routines export` write their section as JSON (to stdout or a file); `bin/commands import ` / `bin/routines import ` merge a JSON object back in (same-key entries are overwritten; a routine import also reinstalls the timers). `routines import --dry-run ` and `config-import --dry-run ''` preview what would be written and which timers would be installed, without changing anything. The menu's Export/Import do both sections at once, via the clipboard. ### Routines (`routines`) - The `routines` config maps a stable id to a `run` command with an optional `lang`, an optional display `name`, and its `schedule` (the trigger). `bin/routines install` compiles each into persistent systemd user timers (a `.service`, `.timer`, and a `.sh` wrapper); `systemctl --user enable --now .timer` makes them survive reboot and fire at the next scheduled time. The `.timer` suffix is mandatory — a bare name resolves to the `.service`, and `enable --now` on it would start (run) the routine on every install instead of arming the timer. There is deliberately no `Persistent=` catch-up — a routine must not fire the moment it is saved just because its schedule is earlier in the day. - A bash routine's command is embedded in the `.sh` wrapper; a non-bash routine is written to a companion file (`.`) the wrapper invokes with the matching interpreter. - `bin/routines once [--lang …] ` and `bin/routines at [--lang …]