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() only binarizes types listed in BINARY_ENCODABLE_TYPES. A type with no binary wire code, such as INTERCOM, goes out as a text frame. The ValueError for an unassigned wire code is raised by serialization.get_bitstring, which emit() never calls for those types.

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. The payload size the peer accepts depends on the peer: hivemind-core decrypts the envelope with plain RSA, so the serialized message must fit one RSA block (about 214 bytes with a 2048-bit key).
  • 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

# Forget the pinned key of a master that was reinstalled
hivemind-client forget-server --host 192.168.1.10 --port 5678

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.

server Noise static key CHANGED: The master is answering with a different encryption key than the one this node pinned. If you reinstalled or restored the master, the pinned key is stale: run hivemind-client forget-server --host HOST --port PORT and connect again. If you changed nothing, another machine may be answering at that address. The node keeps retrying and reports this on every attempt.

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.1.0a1.tar.gz (499.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.1.0a1-py3-none-any.whl (98.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: hivemind_bus_client-1.1.0a1.tar.gz
  • Upload date:
  • Size: 499.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.1.0a1.tar.gz
Algorithm Hash digest
SHA256 2cd547832acfbb63cc524edd3ad9e5da80f4a334e18afa1f11d3cdde432f3476
MD5 8b01fa8f40fab128090d8f25f2d1f8ad
BLAKE2b-256 81dd67d98c3d9dc7b3497ab84b0d621e61cb6d710efd01c0fe8df61d19f858c0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for hivemind_bus_client-1.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 f9bab0fb891be87316a3b4332bd68f734f28f6b2d566e5a0ac9e9bedbac7cede
MD5 999a018562c64a153d2d654f65098659
BLAKE2b-256 e8b6bfa40ba0d7ac619df39e6016e2ba46018678ec8ce6388454ff68c38cdaa0

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.1.0a1 This release

2 files

0.4.4

2 files

0.4.3

2 files

0.2.1

2 files

0.2.0

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.1

2 files

0.1.0

2 files

0.0.4

2 files

0.0.2

1 file

0.0.1

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page