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.12— ships the Hermes-sideassist_queryreceiver and bumps all install surfaces past the stalev0.0.11metadata.
- 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.
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_playerplayback needs audio that Home Assistant can access; generated local files may require an HTTP/media bridge in more complex deployments.
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
| 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. |
| 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. |
This project and Hermes' bundled Home Assistant integration can run side by side:
- the bundled integration lets Hermes talk to Home Assistant from inside Hermes chats and skills
- this project lets Home Assistant talk back to Hermes, including the Assist conversation-agent bridge, status sensors, WebSocket lifecycle, and optional voice-stack helpers
- existing skills that call the bundled
ha_get_state,ha_call_service, or other built-in HA tools should keep working because this package installs separate plugin directories and does not replace the bundled integration - if both integrations expose similarly named tools in your Hermes profile, keep using the tool names your existing skills already reference, or disable one plugin explicitly in
~/.hermes/config.yamlif you want to avoid overlap
In short: install this when you want Home Assistant to use Hermes as a voice/chat brain. Keep the bundled integration when you want Hermes to operate Home Assistant as a tool provider.
You need:
- Home Assistant with network access from the machine running Hermes.
- Hermes Agent installed and working.
- A Home Assistant Long-Lived Access Token for Hermes.
- Python 3.11+ for local development/plugin execution.
- Optional audio dependencies if you want voice input/output on the Hermes machine.
Install Hermes using the official installer:
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bashRestart your shell, then verify:
hermes --version
hermes doctorRun the setup wizard if this is your first Hermes install:
hermes setupChoose 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.
- Open Home Assistant.
- Click your user profile/avatar.
- Scroll to Security.
- Under Long-Lived Access Tokens, click Create Token.
- Name it something clear, for example
Hermes Agent. - 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.
Install or upgrade the package directly from GitHub, then run the bundled plugin installer:
python3 -m pip install --upgrade "hermes-voice-ha-integration @ git+https://github.com/rusty4444/hermes-voice-ha-integration.git@v0.0.12"
hermes-ha-install-pluginsThe installer copies the packaged home_assistant and voice_stack plugin directories into ~/.hermes/hermes-agent/plugins. On upgrade it replaces the existing plugin directories first, so files removed from newer releases do not remain behind from older manual copies.
If your Hermes Agent profile lives somewhere else, pass it explicitly:
hermes-ha-install-plugins --profile /path/to/hermes-agent-profileIf you prefer to inspect or edit the source locally, clone the repository and copy the plugins yourself:
mkdir -p ~/dev
cd ~/dev
git clone https://github.com/rusty4444/hermes-voice-ha-integration.git
cd hermes-voice-ha-integration
mkdir -p ~/.hermes/hermes-agent/plugins
rm -rf ~/.hermes/hermes-agent/plugins/home_assistant ~/.hermes/hermes-agent/plugins/voice_stack
cp -R plugins/home_assistant ~/.hermes/hermes-agent/plugins/home_assistant
cp -R plugins/voice_stack ~/.hermes/hermes-agent/plugins/voice_stackThe rm -rf lines are intentional during manual upgrades: they avoid leaving stale files behind if a release removes or renames plugin files.
Configure Home Assistant connection details for Hermes. The plugin reads standard environment variables:
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=
EOFAbout 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_KEYorHERMES_API_KEYif either is configured.
If your HA URL is different, use that instead, for example:
HASS_URL=http://192.168.1.50:8123Enable the plugins in ~/.hermes/config.yaml:
plugins:
enabled:
- home_assistant
- voice_stackIf 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:
# 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/wsThe voice_stack plugin starts a small HA-facing WebSocket receiver at:
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.
Start Hermes:
hermesAsk:
Search my Home Assistant lights.
Then try a read-only state check:
Is the living room light on?
Expected result:
- Hermes should use
ha_search_entitiesorha_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 before enabling write actions.
-
In Home Assistant, open HACS.
-
Open the three-dot menu → Custom repositories.
-
Add this repository URL:
https://github.com/rusty4444/hermes-voice-ha-integration -
Choose category Integration.
-
Install Hermes Voice Assistant.
-
Restart Home Assistant.
From this repo checkout, copy the custom component into HA's config directory:
cp -R custom_components/hermes /config/custom_components/hermesIf you are copying over SSH/Samba from another machine, the target is the Home Assistant config directory:
/config/custom_components/hermes
Restart Home Assistant after copying.
- Open Settings → Devices & services.
- Click Add integration.
- Search for Hermes Voice Assistant.
- Complete the setup form:
| 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:
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_queryandassist_responseWebSocket 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 below for the protocol contract.
- Submit.
The integration adds:
hermes.hermes_commandservice for HA-native service dispatch.hermes.voice_settingsservice for voice/dashboard helpers.- Hermes status entities:
sensor.hermes_gateway_status— Hermes gateway reachable or offlinesensor.hermes_uptime_hours— how long Hermes has been runningsensor.hermes_total_interactions— number of voice interactionssensor.hermes_total_errors— number of voice errorssensor.ha_ws_connection— HA WebSocket connection statesensor.hermes_voice_ready— voice pipeline ready flagsensor.hermes_tts_voice— configured default TTS voicesensor.hermes_stt_engine— configured STT enginesensor.hermes_wake_word— configured wake-word keyword(s)sensor.hermes_media_player— configured media player for TTS
After adding the integration, open Settings → Devices & services → Hermes Voice Assistant → Options.
The options flow has two pages.
| 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 |
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.
Add this card to a dashboard:
type: custom:hermes-action-bar
title: Hermes Voice
show_status: trueIf 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.
The plugin blocks dangerous service domains by default, including:
shell_commandcommand_linepython_scriptpyscripthassiorest_command
For extra safety, create an allow-list at:
~/.hermes/ha_allow_list.json
Example:
{
"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:
~/.hermes/ha_audit.log
Install optional voice dependencies in the Python environment that runs Hermes.
pip install edge-ttsConfigure:
cat >> ~/.hermes/.env <<'EOF'
HERMES_TTS_ENGINE=edge
HERMES_TTS_VOICE=en-US-AriaNeural
EOFpip install piper-ttsDownload a Piper voice model from:
https://huggingface.co/rhasspy/piper-voices
Configure:
cat >> ~/.hermes/.env <<'EOF'
HERMES_TTS_ENGINE=piper
HERMES_TTS_VOICE=en_US-lessac-medium
EOFpip install faster-whisper sounddevice numpyConfigure:
cat >> ~/.hermes/.env <<'EOF'
HERMES_STT_ENGINE=faster-whisper
HERMES_STT_MODEL=tiny
EOFpip install pvporcupine pyaudioCreate a Picovoice key, then add:
cat >> ~/.hermes/.env <<'EOF'
HERMES_WAKE_WORD_ENGINE=porcupine
HERMES_WAKE_WORD=computer
PORCUPINE_ACCESS_KEY=replace-with-picovoice-key
EOFpip install openwakeword pyaudio numpyConfigure:
cat >> ~/.hermes/.env <<'EOF'
HERMES_WAKE_WORD_ENGINE=openwakeword
EOFTo route spoken responses through Home Assistant:
cat >> ~/.hermes/.env <<'EOF'
HERMES_MEDIA_PLAYER=media_player.living_room
EOFNote:
media_player.play_medianeeds a URL/path Home Assistant and the target player can access. Localfile://paths from the Hermes machine are not always playable by HA media players.
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:
- Add this repository as an add-on repository in Settings → Add-ons → Add-on Store → Repositories.
- Install Hermes Voice Assistant.
- Configure model/STT/TTS/wake-word settings in the add-on options.
- Start the add-on.
- 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.
Ask Hermes:
What Home Assistant entities can you see?
Expected: a grouped summary or list of entities.
Ask:
Is the kitchen light on?
Expected: current state and attributes from HA.
Ask:
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.
In Developer Tools → Services, call:
service: hermes.hermes_command
data:
domain: light
service: turn_on
entity_id: light.kitchenExpected: HA dispatches light.turn_on.
Ask Hermes:
Show voice status.
Expected: installed/uninstalled status for configured STT, TTS, wake-word engines.
cd ~/dev/hermes-voice-ha-integration
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
python -m pytest tests/ -qRun static compilation:
python -m compileall -q custom_components plugins testsBuild package metadata:
python -m build --sdist --wheelRun release integrity checks:
python scripts/check_release_integrity.pySee docs/release.md for the full release checklist and artifact semantics.
The tests are mocked and do not require a live Home Assistant instance.
Check:
grep -E '^HASS_URL=|^HASS_TOKEN=' ~/.hermes/.env
curl -H "Authorization: Bearer $HASS_TOKEN" "$HASS_URL/api/"A healthy HA API response looks like:
{"message":"API running."}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:
HASS_URL=http://192.168.1.50:8123- Restart Home Assistant after installing the integration.
- Clear browser cache or hard refresh.
- Confirm
hacs.jsonis at the repository root if using HACS. - Confirm the resource URL is
/hermes_static/hermes_action_bar.js.
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_querymessage type. If the WebSocket reader discards unknown types, no response is ever sent. - Check that the Hermes Agent sends a valid
assist_responsewith the sameconversation_idas 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 section for the full protocol contract.
Hermes Agent implementation checklist:
- Handle incoming
{"type": "assist_query", "text": "...", "conversation_id": "...", "language": "..."}. - Process the query through the LLM pipeline.
- Send
{"type": "assist_response", "text": "...", "conversation_id": "...", "speech": {"plain": {"speech": "...", "extra_data": null}}}. - Echo the
conversation_idexactly — do not generate a new one.
- Confirm
HERMES_MEDIA_PLAYERis a realmedia_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.
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:
HOME=/data/hermes
HERMES_HOME=/data/hermes
HERMES_VOICE_CACHE=/data/hermes/voice_cacheOnly 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.
Check:
- The domain is not in the built-in blocked-domain list.
- Your allow-list includes the target entity and service.
~/.hermes/ha_audit.logfor the denial reason.
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.
The HA integration sends this when a user speaks or types to the Assist pipeline with Hermes selected as the conversation agent:
{
"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"). |
After processing the query, the Hermes Agent server must send back this response:
{
"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. |
- HA Assist pipeline receives user input (voice or text).
- HA sends
assist_queryWebSocket message to Hermes Agent. - Hermes Agent processes the query (LLM, tool calls, HA service calls).
- Hermes Agent sends
assist_responsewith the answer. - 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").
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.
- If the Hermes Agent does not recognise the
assist_querytype, 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_responseperassist_query. Sending multiple responses for the sameconversation_idwill deliver only the first one.
- 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_responseWebSocket 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 above for the full protocol contract.
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
MIT — see LICENSE.


