Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Ask DeepWiki

HiveMind Bus Client

hivemind-websocket-client (package hivemind_bus_client) is the foundation library for every HiveMind satellite. It provides an authenticated, encrypted WebSocket client that extends the standard OVOS bus client. This lets a satellite and a hivemind-core hub communicate securely, with messages routed between them.

All satellite packages build on this library: hivemind-mic-satellite, HiveMind-voice-relay, HiveMind-voice-sat, and HiveMind-cli. If you build a custom satellite or integration, start here.

Where it fits in the satellite spectrum

HiveMind satellites are differentiated by how much audio and language processing happens locally. hivemind-websocket-client sits below all of them:

Satellite Local processing Remote processing
HiveMind-cli nothing (text input) everything
hivemind-mic-satellite mic + VAD STT, TTS, intent, skills
HiveMind-voice-relay mic + VAD + wakeword STT, TTS
HiveMind-voice-sat mic + VAD + wakeword + STT + TTS skills only
This library WebSocket transport + encryption none

Every satellite in that table uses HiveMessageBusClient from this library to open the connection, complete the handshake, and exchange HiveMessage packets with the hub. The hub's hivemind-audio-binary-protocol plugin provides server-side STT and TTS. The satellite cannot choose the engine. The hub operator configures it.

See the hivemind-core protocol docs for protocol details.

Hardware and OS requirements

  • Python 3.10 or later
  • No special hardware. It runs on any machine with network access to the hub.

Install

pip install hivemind_bus_client

Async client (asyncio-native applications):

pip install "hivemind_bus_client[async]"

Verify:

hivemind-client --help

Quickstart: pair with a hub and send your first message

1. Generate an access key on the hub

On the machine running hivemind-core:

hivemind-core add-client --name "my-satellite" --access-key KEY --password PASS

add-client prints an access key and a password. Keep both. You need them on every satellite you pair.

A new client starts with an empty message-type whitelist, so the hub denies everything it sends. Grant the types the satellite needs, using the node id that add-client printed:

hivemind-core allow-msg "recognizer_loop:utterance" 1
hivemind-core allow-msg "speak" 1

Skip this and the satellite connects, but every utterance comes back as hive.policy.denied.

2. Configure the satellite

On the satellite machine:

hivemind-client set-identity \
  --key   "your-access-key" \
  --password "your-password" \
  --host  ws://192.168.1.10 \
  --port  5678 \
  --siteid living-room

Credentials are saved to ~/.config/hivemind/_identity.json and are read automatically by the client.

3. Verify connectivity

hivemind-client ping --host ws://192.168.1.10 --port 5678

4. Open a terminal session

hivemind-client terminal

Type an utterance, and the hub processes it. Spoken responses are printed to stdout.

5. Use the library

from hivemind_bus_client import HiveMessageBusClient
from hivemind_bus_client.message import HiveMessage, HiveMessageType
from ovos_bus_client.message import Message

# Credentials are loaded from the identity file set in step 2.
# Pass them explicitly if you prefer:
#   client = HiveMessageBusClient(key="...", password="...", host="ws://192.168.1.10", port=5678)
client = HiveMessageBusClient()
client.connect()

# React to spoken responses from the hub
client.on_mycroft("speak", lambda msg: print("Hub says:", msg.data["utterance"]))

# Send an utterance
client.emit(HiveMessage(
    HiveMessageType.BUS,
    Message("recognizer_loop:utterance", {"utterances": ["hello world"]}),
))

# Block until you're done
input("Press Enter to disconnect...\n")
client.close()

Library guide

Connecting

HiveMessageBusClient extends ovos_bus_client.MessageBusClient. The constructor accepts credentials either directly or through a saved NodeIdentity.

from hivemind_bus_client import HiveMessageBusClient

# All parameters optional if identity file is populated
client = HiveMessageBusClient(
    key="access-key",           # access key from hivemind-core add-client
    password="password",        # password from hivemind-core add-client
    host="ws://192.168.1.10",   # hub address, ws:// or wss://
    port=5678,                  # default 5678
    useragent="my-satellite",   # shown in hub logs
    self_signed=True,           # accept self-signed TLS certs
    share_bus=False,            # share the local OVOS bus with the hub (trusted satellites only)
    compress=True,              # zlib-compress binary frames
    binarize=True,              # use binary wire format instead of JSON
    websocket_ping_interval=25,  # keep long-lived proxy connections warm
    websocket_ping_timeout=10,   # fail fast enough for reconnect to take over
)
client.connect()                # blocks until the handshake completes

connect() runs the WebSocket in a background thread and calls wait_for_handshake(). After it returns the connection is live.

The client owns its own reconnect loop. If the socket closes, a single worker thread reopens it and repeats the handshake. wait_for_handshake() waits through reconnects forever by default. Pass handshake_max_retries to connect() to make a bad password fail fast instead of blocking.

Ping settings can also be supplied with environment variables. Client-specific variables (HIVEMIND_WEBSOCKET_CLIENT_PING_INTERVAL, HIVEMIND_WEBSOCKET_CLIENT_PING_TIMEOUT) win over shared hub defaults (HIVEMIND_WEBSOCKET_PING_INTERVAL, HIVEMIND_WEBSOCKET_PING_TIMEOUT). Set the interval to 0 to disable client pings.

Sending messages

You can send any OVOS Message directly. The client wraps it in a HiveMessage(BUS, ...) automatically:

from ovos_bus_client.message import Message

client.emit(Message("recognizer_loop:utterance", {"utterances": ["what time is it"]}))

For explicit HiveMessage control:

from hivemind_bus_client.message import HiveMessage, HiveMessageType

client.emit(HiveMessage(HiveMessageType.BUS,
                        Message("recognizer_loop:utterance", {"utterances": ["what time is it"]})))

Receiving messages

Register handlers for OVOS (inner) message types:

client.on_mycroft("speak", lambda msg: print(msg.data["utterance"]))
client.on_mycroft("ovos.common_play.play", handle_play)

Register handlers for HiveMind protocol messages:

from hivemind_bus_client.message import HiveMessageType

client.on(HiveMessageType.BROADCAST, lambda hm: print("Broadcast:", hm.payload))
client.on(HiveMessageType.PING, lambda hm: print("Ping from", hm.metadata))

Wait helpers

# Send and block until a reply arrives
response = client.wait_for_response(
    Message("recognizer_loop:utterance", {"utterances": ["what time is it"]}),
    reply_type="speak",
    timeout=10,
)
if response:
    print(response.payload.data["utterance"])

# Wait for the next message of a given HiveMind type
hive_msg = client.wait_for_message(HiveMessageType.BROADCAST, timeout=30)

# Wait for the next inner OVOS message of a given type wrapped in BUS
bus_msg = client.wait_for_mycroft("speak", timeout=10)

ESCALATE, QUERY, CASCADE, BROADCAST, PROPAGATE

# ESCALATE — forward up the authority chain (supervisor hubs)
client.emit(HiveMessage(HiveMessageType.ESCALATE,
                        Message("recognizer_loop:utterance", {"utterances": ["call admin"]})))

# QUERY — first answering node wins
inner = HiveMessage(HiveMessageType.BUS,
                    Message("intent.request", {"utterance": "what time is it"}))
client.emit(HiveMessage(HiveMessageType.QUERY, payload=inner))

# BROADCAST — admin pushes to all connected satellites (requires admin access key)
client.emit(HiveMessage(
    HiveMessageType.BROADCAST,
    payload=HiveMessage(HiveMessageType.BUS,
                        Message("speak", {"utterance": "System update in 5 minutes"}))
))

Peer-to-peer encrypted messages (INTERCOM)

INTERCOM uses hybrid encryption (random AES-256-GCM key per message, RSA-encrypted key exchange). It has no binary wire code, so the client always sends it as a text frame:

target_pubkey = "-----BEGIN PUBLIC KEY-----\n..."
client.emit_intercom(
    HiveMessage(HiveMessageType.BUS, Message("speak", {"utterance": "private message"})),
    pubkey=target_pubkey,
)

Incoming INTERCOM messages are only injected into the internal bus if the sender's public key is in NodeIdentity.trusted_keys.

Async client

For asyncio-native applications (FastAPI, aiohttp, async chat bots):

from hivemind_bus_client import AsyncHiveMessageBusClient

async def main():
    client = AsyncHiveMessageBusClient(key="...", password="...", host="ws://192.168.1.10")
    await client.connect()
    await client.emit(Message("recognizer_loop:utterance", {"utterances": ["hello"]}))

Requires: pip install "hivemind_bus_client[async]"

Binary payloads (TTS audio, files)

Override BinaryDataCallbacks to handle incoming binary data:

from hivemind_bus_client.client import BinaryDataCallbacks, HiveMessageBusClient

class MyCallbacks(BinaryDataCallbacks):
    def handle_receive_tts(self, bin_data: bytes, utterance: str, lang: str, file_name: str):
        with open(file_name, "wb") as f:
            f.write(bin_data)

client = HiveMessageBusClient(bin_callbacks=MyCallbacks())
client.connect()

Message types

Type Direction Description
BUS satellite ↔ hub Standard OVOS bus message forwarded to the hub's skill engine
ESCALATE upstream Forward up the authority chain (supervisor hubs)
QUERY upstream + response First answering node wins
BROADCAST downstream Admin pushes to all connected satellites
PROPAGATE flood Forward to all peers in all directions
CASCADE flood + responses Collect answers from all nodes
INTERCOM any → any End-to-end hybrid-encrypted (AES-GCM + RSA)
PING inside PROPAGATE Flood-based network topology discovery
BINARY hub → satellite Raw binary payload (TTS audio, file transfer)

emit() raises ValueError for a message type that has no binary wire code. It never relabels the type to get the frame out.

Security

  • Per-link encryption: AES-GCM or ChaCha20-Poly1305, negotiated at handshake via poorman_handshake
  • Hybrid INTERCOM encryption: Random AES-256 key per message, RSA-encrypted key exchange, no payload size limit
  • Self-signed TLS: self_signed=True (default) accepts self-signed certificates. Set it to False in production.
  • Trusted peers: Only peers with a public key in NodeIdentity.trusted_keys can inject BUS messages via PROPAGATE and INTERCOM. The client silently drops untrusted messages.

Identity and credentials

from hivemind_bus_client.identity import NodeIdentity

identity = NodeIdentity()            # loads from ~/.config/hivemind/_identity.json
print(identity.access_key)
print(identity.default_master)

identity.add_trusted_key("home-hub", "-----BEGIN PUBLIC KEY-----\n...")
identity.save()

See Identity & Credentials for the full field reference.

CLI

# Persist credentials
hivemind-client set-identity --key KEY --password PASS --host ws://hub.local --port 5678 --siteid home

# Interactive terminal (type utterances, see spoken responses)
hivemind-client terminal

# Ping the hub and print round-trip info
hivemind-client ping --host ws://hub.local --port 5678

# Send a single OVOS message
hivemind-client send-mycroft \
  --msg "recognizer_loop:utterance" \
  --payload '{"utterances": ["hello world"]}'

# Send as ESCALATE or PROPAGATE
hivemind-client escalate --msg "recognizer_loop:utterance" --payload '{"utterances": ["hello"]}'
hivemind-client propagate --msg "recognizer_loop:utterance" --payload '{"utterances": ["hello"]}'

# Check that the saved identity can reach the hub
hivemind-client test-identity

# Generate a new RSA key pair for peer-to-peer messages
hivemind-client reset-pgp

Troubleshooting

RuntimeError: NodeIdentity not set: Run hivemind-client set-identity or pass key, password, and host to the constructor.

RuntimeError: timed out waiting for handshake: The hub is unreachable or the port is wrong. Verify the hub is running (hivemind-core listen) and the firewall allows port 5678. Try hivemind-client ping first.

got encrypted message, but could not decrypt!: The access key or password does not match what was registered on the hub. Re-run hivemind-core add-client and update the satellite identity.

Connection drops immediately: The hub rejected the access key, or the protocol version the client offered is below the hub's min_protocol_version. Check hub logs: journalctl -u hivemind-core -f.

Every message comes back as hive.policy.denied: The message type is not in this client's whitelist on the hub. Run hivemind-core allow-msg <msg_type> <node_id>. An empty whitelist also denies binary payloads.

Documentation

Full reference in /docs:

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hivemind_bus_client-1.0.0a1.tar.gz (451.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hivemind_bus_client-1.0.0a1-py3-none-any.whl (78.8 kB view details)

Uploaded Python 3

File details

Details for the file hivemind_bus_client-1.0.0a1.tar.gz.

File metadata

  • Download URL: hivemind_bus_client-1.0.0a1.tar.gz
  • Upload date:
  • Size: 451.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for hivemind_bus_client-1.0.0a1.tar.gz
Algorithm Hash digest
SHA256 58a2795bc334dd6b9f310be0081a934e4b85c463d5399021e500d37ca736292a
MD5 9d3c091ae121a1c2f16ec0dba8067f48
BLAKE2b-256 05a361051b4d3a63863e8b2b360b57fb8eb4b4d40dcc224bd8abe091caddb8c8

See more details on using hashes here.

File details

Details for the file hivemind_bus_client-1.0.0a1-py3-none-any.whl.

File metadata

File hashes

Hashes for hivemind_bus_client-1.0.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 dae2a8b95d308396f68c5241d5792ec3131072976bef0d0494f839cebc809f30
MD5 ead94ce9ecc7855ca73765baa658270e
BLAKE2b-256 311672f7b291a16a381b79fcee13bad6a07a2696da77dec26ccc207714a46b17

See more details on using hashes here.

Release history Release notifications | RSS feed

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page