Architecture¶
System overview¶
graph TB
subgraph "Home Assistant"
HA[HA Core]
CONV[Conversation API]
LLM[LLM Backend]
end
subgraph "signal-ha automations (systemd)"
P[porch-lights]
G[garage-lights]
O[office-lights]
L[living-room-lights]
MORE[...]
end
subgraph "signal-ha library crates"
CORE[signal-ha<br/>HaClient + Scheduler]
LIGHT[signal-ha-lighting<br/>Actuator + Overlay]
AGENT[signal-ha-agent<br/>LLM Observer]
SHELL[signal-ha-shell<br/>Monty Runtime]
BOARD[message-board<br/>Findings API]
REC[signal-ha-recorder<br/>State Recorder]
end
R[recorder]
P & G & O & L & MORE --> CORE
P & G & L --> LIGHT
P & G & L --> AGENT
AGENT --> SHELL
AGENT --> BOARD
R --> CORE
R --> REC
REC -->|MySQL / SQLite| DB[(Database)]
CORE <-->|WebSocket| HA
AGENT -->|conversation/process| CONV
CONV --> LLM
Design principles¶
One process per automation¶
Each automation compiles to a single binary and runs as a systemd service. There is no shared runtime, no plugin loader, and no event bus between automations. If one crashes, the others are unaffected.
systemd
├── porch-lights.service (signal-ha binary)
├── garage-lights.service (signal-ha binary)
├── office-lights.service (signal-ha binary)
├── living-room-lights.service
├── kitchen.service
├── message-board.service (REST API)
└── house-agent.service (overseer)
├── recorder.service (state recorder → MySQL)
WebSocket-first¶
All communication with Home Assistant uses the WebSocket API. State subscriptions arrive as push events — no polling. Service calls and state queries go over the same connection.
Sun-aware scheduling¶
The Scheduler calculates sunrise and sunset for a given latitude/longitude
using the sunrise crate. Automations express their timing in terms of
solar events rather than fixed clock times.
Embedded LLM agents¶
Each automation can optionally embed an agent (via signal-ha-agent) that
periodically reviews the automation's behaviour. The agent:
- Receives a SIGUSR1 signal (from a systemd timer)
- Sends a prompt to the HA Conversation API (backed by any LLM)
- Parses Python code blocks from the response
- Executes them via the Monty pure-Rust Python interpreter
- Posts findings to the message-board
Agents are read-only by default — they can query state and history but cannot call services unless explicitly allowed.
The house agent¶
A special house-agent acts as an overseer. It reads the message board, triages findings from individual automation agents, and can escalate issues.
Crate dependencies¶
graph LR
CORE[signal-ha] --> LIGHTING[signal-ha-lighting]
CORE --> AGENT[signal-ha-agent]
CORE --> REC[signal-ha-recorder]
AGENT --> SHELL[signal-ha-shell]
CORE --> PY[signal-ha-py]
AGENT --> BOARD[message-board]
style CORE fill:#1a3a2a,stroke:#6b9a8a
style LIGHTING fill:#1a2a3a,stroke:#5a7a9a
style AGENT fill:#3a2a1a,stroke:#c97a5a
style SHELL fill:#2a2a2a,stroke:#808080
style PY fill:#2a2a2a,stroke:#808080
style BOARD fill:#2a2a2a,stroke:#808080
style REC fill:#2a1a3a,stroke:#9a6ab0
| Arrow | Means |
|---|---|
signal-ha → signal-ha-lighting |
Lighting crate uses core types |
signal-ha → signal-ha-agent |
Agent uses HaClient for host calls |
signal-ha-agent → signal-ha-shell |
Agent executes Python via Monty |
signal-ha → signal-ha-py |
Python bindings wrap core types |
signal-ha-agent → message-board |
Agent posts findings via REST |
recorder → signal-ha-recorder |
Recorder binary uses recorder crate |
signal-ha-recorder → Database |
Records entity states to MySQL/SQLite |