# Hermes × Home Assistant Voice Integration
<p align="center">
  <a href="https://buymeacoffee.com/rusty4" target="_blank">
    <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50">
  </a>
</p>



![Hermes + Home Assistant Logo](logo.png)

Connect **Hermes Agent** to **Home Assistant** so Hermes can understand your home, call HA services, and optionally run a wake-word → STT → LLM → TTS voice loop.

This repository is a bundle of three pieces:

| Piece | Path | What it does |
|---|---|---|
| Home Assistant custom integration | `custom_components/hermes/` | Adds the `hermes` integration, HA services, status sensors, and the Lovelace action bar. |
| Hermes Home Assistant plugin | `plugins/home_assistant/` | Gives Hermes tools for entity search, state lookup, service calls, bulk control, scene/script discovery, and HA context. |
| Hermes voice-stack plugin | `plugins/voice_stack/` | Adds wake-word, speech-to-text, text-to-speech, and voice pipeline helpers. |

| Install target | Mechanism | Artifact/source | Current maturity |
|---|---|---|---|
| HA custom integration | HACS custom repository or manual copy | GitHub tag/source distribution, `custom_components/hermes/` | Supported |
| Hermes plugins | Copy into `~/.hermes/hermes-agent/plugins` or install the Python wheel | Wheel/source distribution, `plugins/*` | Supported |
| HA add-on | Home Assistant Supervisor add-on scaffold | GitHub tag/source distribution, `addon/` | Early scaffold |

The Python wheel is intentionally plugin-focused. Use the GitHub tag or source distribution for the full HACS/custom-component/add-on bundle.

> **Release:** `v0.0.7` — fixes user-plugin import loading, README install paths, and Home Assistant voice-bridge setup guidance.

---

## What you get

- Ask Hermes natural-language smart-home questions: “Is the kitchen light on?”
- Let Hermes call Home Assistant services: lights, switches, scenes, scripts, climate, media players, and more.
- Use safety controls: blocked service domains, optional allow-list, and JSON-line audit logging.
- Expose Hermes health into HA as status sensors.
- Bridge HA-originated voice lifecycle events over the documented WebSocket protocol (`docs/ws-protocol.md`).
- Add a small Lovelace action bar to dashboards.
- Build toward local voice control with configurable STT/TTS/wake-word engines.

## Important reality check

The stack can be fully local **if you choose local engines and a local model**. The defaults are developer-friendly, not always cloud-free:

- Hermes can run local models or remote providers depending on your Hermes config.
- Edge TTS is network-backed. Use Piper for offline TTS.
- Porcupine requires a Picovoice access key. OpenWakeWord is the open-source option.
- `media_player` playback needs audio that Home Assistant can access; generated local files may require an HTTP/media bridge in more complex deployments.

---

## Architecture

```text
Voice / Chat request
        │
        ▼
Hermes Agent
  ├─ plugins/home_assistant     → HA REST/WebSocket API
  └─ plugins/voice_stack        → wake word / STT / TTS / media playback
        │
        ▼
Home Assistant
  ├─ custom_components/hermes   → config flow, services, sensors
  └─ Lovelace dashboard card    → custom:hermes-action-bar
```

### Hermes tools provided by the HA plugin

| Tool | Purpose |
|---|---|
| `ha_search_entities` | Search entities by name, domain, area-like metadata, or entity ID. |
| `ha_get_state` | Fetch current state and attributes for one entity. |
| `ha_call_service` | Call a Home Assistant service with safety checks. |
| `ha_get_overview` | Build a compact overview of the home. |
| `ha_list_services` | Discover service domains and service names. |
| `control_light_and_set_scene` | Compound helper for common light + scene actions. |
| `turn_off_all_except` | Turn off a domain while preserving chosen entities. |
| `ha_bulk_control` | Run multiple service calls and summarise results. |

### Voice tools provided by the voice plugin

| Tool | Purpose |
|---|---|
| `voice_status` | Show engine availability and pipeline state. |
| `voice_enable` | Enable continuous wake-word listening. |
| `voice_disable` | Disable continuous voice mode. |
| `voice_speak` | Speak text through the configured TTS engine. |
| `voice_listen` | One-shot record + transcription. |
| `voice_prompt` | Build the voice-optimised prompt with HA context. |

---

## Prerequisites

You need:

1. **Home Assistant** with network access from the machine running Hermes.
2. **Hermes Agent** installed and working.
3. A **Home Assistant Long-Lived Access Token** for Hermes.
4. Python 3.11+ for local development/plugin execution.
5. Optional audio dependencies if you want voice input/output on the Hermes machine.

---

## Step 1 — Install Hermes Agent

Install Hermes using the official installer:

```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```

Restart your shell, then verify:

```bash
hermes --version
hermes doctor
```

Run the setup wizard if this is your first Hermes install:

```bash
hermes setup
```

Choose a model/provider. For a local-first HA assistant, configure Hermes to use your local model endpoint (for example Ollama, vLLM, or llama.cpp). Remote providers also work.

---

## Step 2 — Create a Home Assistant token

1. Open Home Assistant.
2. Click your user profile/avatar.
3. Scroll to **Security**.
4. Under **Long-Lived Access Tokens**, click **Create Token**.
5. Name it something clear, for example `Hermes Agent`.
6. Copy the token now. Home Assistant only shows it once.

Keep this token private. It can control your Home Assistant instance with your account permissions.

---

## Step 3 — Install the Hermes plugins

Clone this repository:

```bash
mkdir -p ~/dev
cd ~/dev
git clone https://github.com/rusty4444/hermes-voice-ha-integration.git
cd hermes-voice-ha-integration
```

Copy the plugins into your Hermes plugin directory:

```bash
mkdir -p ~/.hermes/hermes-agent/plugins
cp -R plugins/home_assistant ~/.hermes/hermes-agent/plugins/home_assistant
cp -R plugins/voice_stack ~/.hermes/hermes-agent/plugins/voice_stack
```

Configure Home Assistant connection details for Hermes. The plugin reads standard environment variables:

```bash
cat >> ~/.hermes/.env <<'EOF'
# URL to your Home Assistant instance (not the Hermes URL)
HASS_URL=http://homeassistant.local:8123
# Long-lived access token from HA user profile → Security → Long-Lived Access Tokens
HASS_TOKEN=replace-with-your-long-lived-access-token
# Optional: bearer token for the HA-to-Hermes WebSocket connection. Leave empty if no auth is needed.
HERMES_HA_WS_TOKEN=
EOF
```

**About the `HASS_URL` format:** The URL must include a scheme (`http://` or `https://`) and a hostname or IP address. For example `http://192.168.1.50:8123`. If you see `"No host part in the URL"`, the URL is missing the `http://` prefix or the hostname.

**About `HERMES_HA_WS_TOKEN`:** This is optional and used to secure the connection **from Home Assistant to Hermes**. Most users do not need it:

- **Leave empty** (or omit) for unauthenticated WebSocket connections.
- **Set to any string** (e.g. `my-hermes-token`) if you want to require a matching token on the HA side. If you set this, enter the same value in the HA custom integration setup form field "Hermes API / WebSocket token".
- If unset, the system falls back to `API_SERVER_KEY` or `HERMES_API_KEY` if either is configured.

If your HA URL is different, use that instead, for example:

```bash
HASS_URL=http://192.168.1.50:8123
```

Enable the plugins in `~/.hermes/config.yaml`:

```yaml
plugins:
  enabled:
    - home_assistant
    - voice_stack
```

If your config already has a `plugins.enabled` list, add the two entries instead of replacing the whole section.

Restart Hermes after changing plugins or `.env`.

**How the WebSocket receiver starts:** The WebSocket receiver does not require a standalone `server.py` file. It starts automatically when the `voice_stack` plugin loads. After restarting Hermes, check that the WebSocket is active:

```bash
# Confirm the plugin is loaded
hermes plugins | grep voice_stack

# Test the WebSocket endpoint (replace with your Hermes host/port)
curl -N --no-buffer -H "Accept: text/event-stream" http://localhost:7860/api/hermes/ws
```

The `voice_stack` plugin starts a small HA-facing WebSocket receiver at:

```text
ws://<hermes-host>:7860/api/hermes/ws
```

Home Assistant connects to this endpoint through the **Hermes URL** you enter below. If `HERMES_HA_WS_TOKEN`, `API_SERVER_KEY`, or `HERMES_API_KEY` is set, the HA custom integration token must match it.

When running the Home Assistant add-on, expose/map TCP port `7860` so Home Assistant can reach the HA-facing receiver. The bundled add-on config maps `7860/tcp` by default.

The add-on also sets `HOME`, `HERMES_HOME`, and `HERMES_VOICE_CACHE` to `/data/hermes` so voice engines that call `Path.home()` write their cache under `/data/hermes/voice_cache` instead of `/root/.hermes/voice_cache`.

---

## Step 4 — Test Hermes ↔ Home Assistant before installing anything in HA

Start Hermes:

```bash
hermes
```

Ask:

```text
Search my Home Assistant lights.
```

Then try a read-only state check:

```text
Is the living room light on?
```

Expected result:

- Hermes should use `ha_search_entities` or `ha_get_state`.
- The response should include the current state from Home Assistant.
- If Home Assistant is unreachable, Hermes should say that it cannot reach HA rather than crashing.

If this fails, skip ahead to [Troubleshooting](#troubleshooting) before enabling write actions.

---

## Step 5 — Install the Home Assistant custom integration

### Option A — HACS custom repository

1. In Home Assistant, open **HACS**.
2. Open the three-dot menu → **Custom repositories**.
3. Add this repository URL:

   ```text
   https://github.com/rusty4444/hermes-voice-ha-integration
   ```

4. Choose category **Integration**.
5. Install **Hermes Voice Assistant**.
6. Restart Home Assistant.

### Option B — Manual install

From this repo checkout, copy the custom component into HA's config directory:

```bash
cp -R custom_components/hermes /config/custom_components/hermes
```

If you are copying over SSH/Samba from another machine, the target is the Home Assistant config directory:

```text
/config/custom_components/hermes
```

Restart Home Assistant after copying.

---

## Step 6 — Add the integration in Home Assistant

1. Open **Settings → Devices & services**.
2. Click **Add integration**.
3. Search for **Hermes Voice Assistant**.
4. Complete the setup form:

![Hermes setup UI field guide](docs/setup-ui-guide.png)

| Setup field | What it means | Example |
|---|---|---|
| **Hermes Agent API URL** | The base URL of the machine or container running Hermes Agent's HA-facing API/WebSocket receiver. This must be reachable from Home Assistant. It is **not** your Home Assistant URL. | `http://192.168.1.20:7860` or `http://hermes.local:7860` |
| **Hermes API / WebSocket token** | The bearer token expected by Hermes for the HA WebSocket/API receiver. Use `HERMES_HA_WS_TOKEN` if you set it; otherwise use the fallback token configured as `API_SERVER_KEY` or `HERMES_API_KEY`. This is **not** a Home Assistant long-lived access token. | the same shared Hermes token from `~/.hermes/.env` |

Visual check:

```text
Home Assistant  →  http://<hermes-host>:7860  →  /api/hermes/ws
```

Do **not** enter `http://homeassistant.local:8123` in the Hermes URL field. That URL is only used by Hermes itself when Hermes talks back to Home Assistant via `HASS_URL`.

This integration registers a Home Assistant Assist conversation agent (`HermesConversationAgent`) on the `Platform.CONVERSATION` platform and handles both incoming `assist_query` and outgoing `assist_response` WebSocket messages. After setup, Hermes will appear in the **Preferred conversation agent** selector under **Settings → Voice assistants**.

> **Important:** The Hermes Agent server must handle the `assist_query` and `assist_response` WebSocket message types for the conversation pipeline to work end-to-end. The HA integration forwards queries and awaits responses, but if the Hermes Agent does not recognise these message types, conversation queries time out after 30 seconds. See [Hermes Agent WebSocket message types](#hermes-agent-websocket-message-types) below for the protocol contract.

5. Submit.

The integration adds:

- `hermes.hermes_command` service for HA-native service dispatch.
- `hermes.voice_settings` service for voice/dashboard helpers.
- Hermes status entities:
  - `sensor.hermes_gateway_status` — Hermes gateway reachable or offline
  - `sensor.hermes_uptime_hours` — how long Hermes has been running
  - `sensor.hermes_total_interactions` — number of voice interactions
  - `sensor.hermes_total_errors` — number of voice errors
  - `sensor.ha_ws_connection` — HA WebSocket connection state
  - `sensor.hermes_voice_ready` — voice pipeline ready flag
  - `sensor.hermes_tts_voice` — configured default TTS voice
  - `sensor.hermes_stt_engine` — configured STT engine
  - `sensor.hermes_wake_word` — configured wake-word keyword(s)
  - `sensor.hermes_media_player` — configured media player for TTS

---

## Step 7 — Configure the voice pipeline via the HA options UI

After adding the integration, open **Settings → Devices &amp; services → Hermes Voice Assistant → Options**.

The options flow has two pages.

### Page 1 — Allow-listed entities &amp; SSL

| Field | What to enter |
|---|---|
| **Entity IDs to monitor** | One entity per line or comma-separated (empty = all entities) |
| **Verify SSL certificates** | Toggle off if your Hermes endpoint uses a self-signed cert |

### Page 2 — Voice pipeline

![Hermes HA config/options UI — voice pipeline](docs/ha-config-flow-screenshot.png)

*Mockup of the **Step 2 — Voice pipeline** options page. The **Step 1 — Allow-listed entities & SSL** page appears first, with an entity-ID editor and an SSL toggle.*

| Field | What to enter |
|---|---|
| **TTS engine** | `edge` (default, network), `piper` (local), `elevenlabs`, or `openai` |
| **Default TTS voice / voice-ID** | Voice name or ID for the chosen TTS engine (e.g. `en-US-AriaNeural` for Edge TTS) |
| **STT engine** | `faster-whisper` (default, local) or `whisper-cpp` |
| **STT model size** | Model size for the chosen STT engine: `tiny`, `base`, `small`, `medium`, `large` |
| **Wake-word engine** | `porcupine` (default), `openwakeword`, or `command` |
| **Wake-word keyword(s)** | One keyword per line or comma-separated (e.g. `hey jarvis`, `computer`) |
| **Media player entity ID** | HA `media_player.*` entity used for TTS playback (e.g. `media_player.living_room_speaker`) |

Values are persisted in the config entry options. After saving, Hermes reads them from `entry.options` on every restart. Screenshots of the live UI are welcome via PR.

---

## Step 8 — Add the Lovelace action bar

Add this card to a dashboard:

```yaml
type: custom:hermes-action-bar
title: Hermes Voice
show_status: true
```

If the browser shows “Custom element doesn’t exist”, hard refresh the dashboard and confirm `/hermes_static/hermes_action_bar.js` is registered by the integration/HACS install.

---

## Step 9 — Configure safety controls

The plugin blocks dangerous service domains by default, including:

- `shell_command`
- `command_line`
- `python_script`
- `pyscript`
- `hassio`
- `rest_command`

For extra safety, create an allow-list at:

```text
~/.hermes/ha_allow_list.json
```

Example:

```json
{
  "enabled": true,
  "rules": [
    {"entity_id": "light.*", "services": ["turn_on", "turn_off", "toggle"]},
    {"entity_id": "scene.*", "services": ["turn_on"]},
    {"entity_id": "media_player.living_room", "services": ["play_media", "volume_set"]}
  ]
}
```

When enabled, service calls not matching the allow-list are denied.

Audit logs are written as JSON lines to:

```text
~/.hermes/ha_audit.log
```

---

## Step 10 — Optional: fine-tune voice engines outside HA

Install optional voice dependencies in the Python environment that runs Hermes.

### TTS options

#### Edge TTS (easy, network-backed)

```bash
pip install edge-tts
```

Configure:

```bash
cat >> ~/.hermes/.env <<'EOF'
HERMES_TTS_ENGINE=edge
HERMES_TTS_VOICE=en-US-AriaNeural
EOF
```

#### Piper TTS (offline)

```bash
pip install piper-tts
```

Download a Piper voice model from:

```text
https://huggingface.co/rhasspy/piper-voices
```

Configure:

```bash
cat >> ~/.hermes/.env <<'EOF'
HERMES_TTS_ENGINE=piper
HERMES_TTS_VOICE=en_US-lessac-medium
EOF
```

### STT options

#### faster-whisper

```bash
pip install faster-whisper sounddevice numpy
```

Configure:

```bash
cat >> ~/.hermes/.env <<'EOF'
HERMES_STT_ENGINE=faster-whisper
HERMES_STT_MODEL=tiny
EOF
```

### Wake-word options

#### Porcupine

```bash
pip install pvporcupine pyaudio
```

Create a Picovoice key, then add:

```bash
cat >> ~/.hermes/.env <<'EOF'
HERMES_WAKE_WORD_ENGINE=porcupine
HERMES_WAKE_WORD=computer
PORCUPINE_ACCESS_KEY=replace-with-picovoice-key
EOF
```

#### OpenWakeWord

```bash
pip install openwakeword pyaudio numpy
```

Configure:

```bash
cat >> ~/.hermes/.env <<'EOF'
HERMES_WAKE_WORD_ENGINE=openwakeword
EOF
```

### Media player output

To route spoken responses through Home Assistant:

```bash
cat >> ~/.hermes/.env <<'EOF'
HERMES_MEDIA_PLAYER=media_player.living_room
EOF
```

> Note: `media_player.play_media` needs a URL/path Home Assistant and the target player can access. Local `file://` paths from the Hermes machine are not always playable by HA media players.

---

## Step 11 — Optional Home Assistant add-on

This repository includes an early HA add-on scaffold in `addon/`. Use it if you want Hermes voice services to run under Home Assistant Supervisor instead of a separate machine.

High-level flow:

1. Add this repository as an add-on repository in **Settings → Add-ons → Add-on Store → Repositories**.
2. Install **Hermes Voice Assistant**.
3. Configure model/STT/TTS/wake-word settings in the add-on options.
4. Start the add-on.
5. Open the add-on logs and confirm Hermes starts cleanly.

The add-on is intentionally marked `boot: manual` in `v0.0.7`. Start it manually first, verify logs, then decide whether to change boot behaviour later.

---

## Verification checklist

### Read-only test

Ask Hermes:

```text
What Home Assistant entities can you see?
```

Expected: a grouped summary or list of entities.

### State test

Ask:

```text
Is the kitchen light on?
```

Expected: current state and attributes from HA.

### Safe service-call test

Ask:

```text
Turn on the kitchen light.
```

Expected:

- Hermes calls `ha_call_service`.
- The HA light changes state.
- An audit-log entry is written if auditing is enabled.

### Home Assistant service test

In **Developer Tools → Services**, call:

```yaml
service: hermes.hermes_command
data:
  domain: light
  service: turn_on
  entity_id: light.kitchen
```

Expected: HA dispatches `light.turn_on`.

### Voice status test

Ask Hermes:

```text
Show voice status.
```

Expected: installed/uninstalled status for configured STT, TTS, wake-word engines.

---

## Development setup

```bash
cd ~/dev/hermes-voice-ha-integration
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
python -m pytest tests/ -q
```

Run static compilation:

```bash
python -m compileall -q custom_components plugins tests
```

Build package metadata:

```bash
python -m build --sdist --wheel
```

Run release integrity checks:

```bash
python scripts/check_release_integrity.py
```

See [`docs/release.md`](docs/release.md) for the full release checklist and artifact semantics.

The tests are mocked and do not require a live Home Assistant instance.

---

## Troubleshooting

### Hermes cannot see Home Assistant

Check:

```bash
grep -E '^HASS_URL=|^HASS_TOKEN=' ~/.hermes/.env
curl -H "Authorization: Bearer $HASS_TOKEN" "$HASS_URL/api/"
```

A healthy HA API response looks like:

```json
{"message":"API running."}
```

### Hermes says Home Assistant is unavailable

Common causes:

- Wrong `HASS_URL`.
- Token copied incorrectly.
- Home Assistant is using HTTPS with a certificate your machine does not trust.
- Docker/add-on networking cannot resolve `homeassistant.local`.

Try an IP address first:

```bash
HASS_URL=http://192.168.1.50:8123
```

### The Lovelace card does not load

- Restart Home Assistant after installing the integration.
- Clear browser cache or hard refresh.
- Confirm `hacs.json` is at the repository root if using HACS.
- Confirm the resource URL is `/hermes_static/hermes_action_bar.js`.

### Assist pipeline times out with "Sorry, I couldn't reach Hermes"

This means the HA integration sent an `assist_query` WebSocket message but never received an `assist_response` within 30 seconds.

**Check:**
- Confirm the Hermes Agent server recognises the `assist_query` message type. If the WebSocket reader discards unknown types, no response is ever sent.
- Check that the Hermes Agent sends a valid `assist_response` with the same `conversation_id` as the request.
- The conversation agent expects exactly one response per query — multiple responses for the same ID are silently dropped.
- See the [Hermes Agent WebSocket message types](#hermes-agent-websocket-message-types) section for the full protocol contract.

**Hermes Agent implementation checklist:**
1. Handle incoming `{"type": "assist_query", "text": "...", "conversation_id": "...", "language": "..."}`.
2. Process the query through the LLM pipeline.
3. Send `{"type": "assist_response", "text": "...", "conversation_id": "...", "speech": {"plain": {"speech": "...", "extra_data": null}}}`.
4. Echo the `conversation_id` exactly — do not generate a new one.

### Voice commands transcribe but do not play audio

- Confirm `HERMES_MEDIA_PLAYER` is a real `media_player.*` entity.
- Confirm the player supports `play_media`.
- If running Hermes outside HA, ensure HA can access generated audio. This may need an HTTP-accessible media bridge.

### Permission denied for `/root/.hermes/voice_cache` in the add-on

Update to an add-on build that sets `HOME=/data/hermes`. The add-on should log the Hermes home as `/data/hermes`, and the cache directory should be `/data/hermes/voice_cache`.

If you are running a custom container, set these environment variables explicitly:

```bash
HOME=/data/hermes
HERMES_HOME=/data/hermes
HERMES_VOICE_CACHE=/data/hermes/voice_cache
```

### Port `7860` is already in use

Only one process in the container or host can bind `0.0.0.0:7860`. If the HA WebSocket receiver logs `address already in use`, check whether another Hermes process or an old container is already listening on that port, then stop the duplicate process or move one receiver to a different `HERMES_HA_WS_PORT` and update the Hermes URL in the HA integration accordingly.

### Service call denied

Check:

- The domain is not in the built-in blocked-domain list.
- Your allow-list includes the target entity and service.
- `~/.hermes/ha_audit.log` for the denial reason.

---

## Hermes Agent WebSocket message types

The Home Assistant integration communicates with the Hermes Agent server over WebSocket. For the Assist pipeline conversation agent to function, the Hermes Agent server **must** handle these message types. Without them, the pipeline handshake succeeds but all conversation queries time out after 30 seconds.

### `assist_query` — incoming (Hermes Agent receives)

The HA integration sends this when a user speaks or types to the Assist pipeline with Hermes selected as the conversation agent:

```json
{
  "type": "assist_query",
  "text": "What's the temperature in the living room?",
  "conversation_id": "01JQABCDEFGH0000000000000000",
  "language": "en"
}
```

| Field | Type | Description |
|---|---|---|
| `type` | `string` | Always `"assist_query"`. |
| `text` | `string` | The user's input text from the Assist pipeline. |
| `conversation_id` | `string` | HA-generated ID scoped to a conversation turn. Echo it back unchanged on the response. |
| `language` | `string` | Language code from the Assist pipeline (e.g. `"en"`). |

### `assist_response` — outgoing (Hermes Agent sends)

After processing the query, the Hermes Agent server must send back this response:

```json
{
  "type": "assist_response",
  "text": "The living room is currently 22°C.",
  "conversation_id": "01JQABCDEFGH0000000000000000",
  "speech": {
    "plain": {
      "speech": "The living room is currently 22°C.",
      "extra_data": null
    }
  }
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | `string` | Yes | Always `"assist_response"`. |
| `text` | `string` | Yes | The response text shown in the Assist UI. |
| `conversation_id` | `string` | Yes | MUST match the `conversation_id` from the request. |
| `speech` | `object` | Yes | HA `AssistResponseType` dict. At minimum, provide `speech.plain.speech` with the spoken text. Set `extra_data` to `null` unless you have structured response data. |
| `response_type` | `string` | No | Explicit response type. Defaults to `"action_done"`. Use `"query_answer"` for informational responses. |

### Conversation state lifecycle

1. HA Assist pipeline receives user input (voice or text).
2. HA sends `assist_query` WebSocket message to Hermes Agent.
3. Hermes Agent processes the query (LLM, tool calls, HA service calls).
4. Hermes Agent sends `assist_response` with the answer.
5. HA Assist pipeline renders the response in its UI and/or speaks it via TTS.

If no response is received within 30 seconds, the pipeline falls back to an error speech message ("Sorry, I couldn't reach Hermes").

### Implementation reference

The reader task in `custom_components/hermes/__init__.py` routes incoming messages by `type`. When `type == "assist_response"`, the message is resolved to the pending `asyncio.Future` created by `HermesConversationAgent.async_process()`. The conversation agent uses `_send_and_wait()` to send the query and await the response future.

### Known limitations on the Hermes Agent side

- If the Hermes Agent does not recognise the `assist_query` type, the WebSocket reader discards it as unknown and no response is ever sent → 30-second timeout.
- If the Hermes Agent sends a response with a mismatched `conversation_id`, the future lookup in the HA integration fails and the response is dropped.
- The conversation agent expects exactly one `assist_response` per `assist_query`. Sending multiple responses for the same `conversation_id` will deliver only the first one.

---

## Known limitations in `v0.0.8`

- The voice stack is usable as engine wrappers and Hermes tools, but room-grade voice satellite UX still needs more work.
- TTS audio delivery to HA media players may need an HTTP/media bridge depending on deployment topology.
- The add-on scaffold may need environment-specific build adjustments before it is suitable as the primary install path for every HA setup.
- The Lovelace action bar is intentionally minimal.
- The HA custom integration and Hermes plugins are released together in one repo for now; future releases may split packaging by install target.
- The `assist_query` / `assist_response` WebSocket message types must be implemented on the Hermes Agent server side for the Assist pipeline conversation agent to work end-to-end. See [Hermes Agent WebSocket message types](#hermes-agent-websocket-message-types) above for the full protocol contract.


---

## Repository layout

```text
custom_components/hermes/      Home Assistant custom integration
plugins/home_assistant/        Hermes plugin for HA tools and context
plugins/voice_stack/           Hermes plugin for voice pipeline tools
skills/homescript/             Homescript skill for smart-home commands
addon/                         Home Assistant add-on scaffold
tests/                         Mocked unit tests
```

---

## Licence

MIT — see [`LICENSE`](LICENSE).
