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 whatsapp Cloud API
iMessage imessage BlueBubbles macOS only. Bridges through BlueBubbles server.
Mattermost mattermost Bot API
Nostr nostr NIP-01 relay
Email 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-label that is attached to all signals from that frontend
  • Capabilities determine which A2UI components can be rendered
  • The tui frontend 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.sexp at 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:

  1. Create the crate — add a new Rust crate in lib/frontends/ implementing the frontend trait (poll, send, capabilities)
  2. Declare capabilities — add the frontend entry to config/baseband.sexp with its static capability set and security label
  3. Build — compile the crate as a cdylib
  4. 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