Frontends
Harmonia supports 10 communication channels through its gateway/baseband architecture. Frontends are hot-loaded as compiled cdylibs via dlopen, route messages based on declared capabilities, and adapt output through the A2UI component system.
Supported Frontends
| Frontend | Crate | Transport | Notes |
|---|---|---|---|
| TUI | tui |
Terminal | Always on. Cannot be disabled. |
| Telegram | telegram |
Bot API | |
| Slack | slack |
Bot API | |
whatsapp |
Cloud API | ||
| iMessage | imessage |
BlueBubbles | macOS only. Bridges through BlueBubbles server. |
| Mattermost | mattermost |
Bot API | |
| Nostr | nostr |
NIP-01 relay | |
email-client |
IMAP / SMTP | Inbound via IMAP polling, outbound via SMTP. | |
| MQTT | mqtt-client |
MQTT 5.0 | Fingerprint validation on connections. |
| Tailscale | tailscale-frontend |
Mesh network | HMAC-SHA256 transport with 5-minute replay window. |
Gateway / Baseband Signal Flow
Every frontend connects through the Baseband port (ports/baseband.lisp to lib/core/gateway + frontend crates). The signal flow on each tick:
gateway-poll (tick start)
│
┌────────────┼────────────┐
▼ ▼ ▼
telegram slack email ... (all enabled frontends polled)
│ │ │
└────────────┼────────────┘
▼
harmonia-signal structs
(12 fields per signal)
│
▼
orchestrator
(process-prompt)
│
▼
gateway-flush (tick end)
│
┌────────────┼────────────┐
▼ ▼ ▼
telegram slack email ... (responses dispatched)
The harmonia-signal Struct
Every inbound message is converted to a harmonia-signal struct with 12 fields:
| Field | Purpose |
|---|---|
id |
Unique signal identifier |
frontend |
Originating frontend name |
sub-channel |
Channel/thread/conversation within the frontend |
security-label |
:owner, :authenticated, :anonymous, or :untrusted |
payload |
Message content |
capabilities |
Frontend capability set |
metadata |
Per-message dynamic metadata |
timestamp-ms |
Signal creation timestamp |
taint |
:external, :tool-output, :memory-recall, or :internal |
dissonance |
Injection pattern score (0.0 to 0.95) |
origin-fp |
Origin fingerprint for tracking |
Poll Output Format
Frontends produce a 3-field tab-separated output on poll:
sub_channel\tpayload[\tmetadata]
The metadata field is optional. The gateway converts this into the full harmonia-signal struct, adding security labels, taint tracking, and dissonance scoring.
A2UI (Agent-Adaptive UI)
Harmonia includes a 21-component A2UI (Agent-Adaptive UI) catalog defined in config/a2ui-catalog.sexp. A2UI is capability-driven: the orchestrator selects components based on what the target frontend can render, not based on the frontend name.
Components include:
- Text blocks, headings, and paragraphs
- Tables and lists
- Code blocks with syntax highlighting
- Charts and visualizations
- Forms and interactive elements
- File attachments and media
- Status indicators and progress bars
Capability vs Metadata
- Capabilities are static per-frontend declarations in
config/baseband.sexp. They define what the frontend can render (text, images, files, threads, voice, etc.). - Metadata is dynamic per-message information attached at poll time. It carries context like thread IDs, reply targets, or user display names.
The orchestrator dispatches based on capabilities, not frontend names. A Telegram user and a TUI user receive the same information, rendered appropriately for their channel. Frontends that do not support A2UI components receive a text fallback automatically.
Configuration
Frontend behavior is controlled in config/baseband.sexp:
(baseband
(frontends
(tui
(enabled t)
(capabilities (text code-blocks tables)))
(telegram
(enabled t)
(token :vault "telegram-bot-token")
(security-label :authenticated)
(capabilities (text images files code-blocks tables)))
(slack
(enabled t)
(token :vault "slack-bot-token")
(security-label :authenticated)
(capabilities (text images files threads code-blocks tables)))
(email-client
(enabled t)
(imap-host "imap.example.com")
(smtp-host "smtp.example.com")
(credentials :vault "email-credentials")
(security-label :authenticated)
(capabilities (text files)))
(mqtt-client
(enabled nil)
(broker "mqtt.example.com")
(fingerprint :vault "mqtt-fingerprint")
(security-label :anonymous)
(capabilities (text)))
(tailscale-frontend
(enabled nil)
(hmac-key :vault "tailscale-hmac")
(replay-window-seconds 300)
(security-label :owner)
(capabilities (text images files code-blocks tables charts forms)))))
Key points:
- Tokens and credentials reference vault keys, never plaintext
- Each frontend declares a
security-labelthat is attached to all signals from that frontend - Capabilities determine which A2UI components can be rendered
- The
tuifrontend is always on and cannot be disabled
Hot-Loading via dlopen
Frontend crates are compiled as cdylibs and loaded at runtime via dlopen. This enables:
- Hot-reload — enable or disable frontends without restarting the agent
- Isolation — a crashing frontend does not take down the orchestrator
- Late binding — frontends are loaded based on
config/baseband.sexpat startup and can be reconfigured at runtime
The gateway manages the lifecycle of loaded frontend libraries, including initialization, polling, flushing, and cleanup.
Adding a New Frontend
To add a new frontend:
- Create the crate — add a new Rust crate in
lib/frontends/implementing the frontend trait (poll, send, capabilities) - Declare capabilities — add the frontend entry to
config/baseband.sexpwith its static capability set and security label - Build — compile the crate as a cdylib
- Enable — set
(enabled t)in the baseband config
The gateway auto-discovers and loads the new frontend on the next tick. No changes to the orchestrator or other frontends are needed.
Next Steps
- Architecture — 9-port boundary, tick model, and full crate inventory
- Self-Improvement — how evolution interacts with frontend signals
- Harmonia Overview — design principles and quick start