message_types.md
August 13, 2026 · View on GitHub
HiveMind communication is based on the HiveMessage class (defined in hivemind_bus_client.message.HiveMessage), which wraps standard AI bus messages with HiveMind-specific metadata and routing instructions.
HiveMessage Fields
msg_type: A value from theHiveMessageTypeenum.payload: The actual message (an OVOSMessagefor BUS types,bytesfor BINARY types).metadata: Auxiliary dict carried with the message (for examplesample_rate,lang).route: Ordered hop history. See Route Metadata below.target_peers,target_site_id,target_public_key: Routing hints.
HiveMessageType (The Routing Modes)
The msg_type (defined in hivemind_bus_client.message.HiveMessageType) dictates how the Mind should handle the message.
| Type | Purpose | Use Case |
|---|---|---|
BUS | Standard message for the AI | Utterances, intent triggers, speak events. |
BINARY | Raw binary data | Audio streams for STT/TTS, file transfers. |
ESCALATE | Upstream request | Used by a Slave Mind to ask a Master Mind for help. |
BROADCAST | Downstream flood (admin only) | Master pushes a message to all connected satellites. |
PROPAGATE | Bidirectional flood | Forwards to all peers in both directions. |
INTERCOM | End-to-end hybrid-encrypted | AES-GCM payload + RSA-encrypted ephemeral key, signed with the sender's private key. Injected only if the signature verifies against a key in trusted_keys (the connected master's key is an implicit anchor, so a default deployment with an empty trusted_keys can still receive mail from its own master) — being addressed to us is not by itself evidence of origin. On delivery the verified signer's key is written into the inner bus message's context["hivemind_verified_source_peer"]. This field means exactly "this node itself verified this delivery": handle_bus, the common sink for every BUS-injection path (plain master BUS, PROPAGATE, INTERCOM), unconditionally strips any inbound value of this key before possibly re-setting it, so a sender cannot pre-set it and have it survive — the key is present if and only if this node verified it, never because the sender claimed it. The frame's own source_peer is sender-supplied and unverified, so a trusted peer can label a frame it signed itself with another trusted peer's key — read the context key, not source_peer, for the authenticated origin. This only distinguishes trusted peers from each other, not a trusted peer from an outsider: an untrusted signature is dropped before delivery. Has no binary wire code, so it always travels as a text frame. |
QUERY | Request-response upstream | Like ESCALATE, but first answering node sends a response back. Stops propagation on answer. |
CASCADE | Request-response flood | Like PROPAGATE, but expects responses from ALL nodes. Supports disambiguation. |
PING | Network discovery flood | Each node responds with its own PING (same flood_id). Carried inside PROPAGATE. Route metadata = hive path. |
HELLO | Node announcement | Session sync at connection time. |
HANDSHAKE | Crypto negotiation | Key exchange at connection time. |
QUERY: First-Match Request-Response
QUERY propagates upstream like ESCALATE, but stops as soon as one node can respond.
Satellite behavior (HiveMindSlaveProtocol.handle_query, protocol.py):
- Inner payload must be
BUSorINTERCOM. BUSpayloads are dispatched tohandle_bus.INTERCOMpayloads are dispatched tohandle_intercom.
Sending a QUERY (from satellite)
from hivemind_bus_client.message import HiveMessage, HiveMessageType
from ovos_bus_client.message import Message
inner = HiveMessage(HiveMessageType.BUS,
Message("intent.request", {"utterance": "what time is it"}))
query = HiveMessage(HiveMessageType.QUERY, payload=inner)
client.emit(query)
Listening for QUERY responses (decorator)
from hivemind_bus_client.decorators import on_query
@on_query("speak", bus)
def on_speak(msg):
print(msg.data["utterance"])
CASCADE: Collect-All Request-Response
CASCADE propagates like PROPAGATE (bidirectional flood) but expects responses from all reachable nodes. Responses are optional. Nodes that cannot answer simply stay silent.
Satellite behavior (HiveMindSlaveProtocol.handle_cascade, protocol.py):
Responses are buffered in a CascadeAggregator (protocol.py). After cascade_timeout seconds (default 5.0) or when the number of responses reaches the known node count from hive_mapper, the cascade_select_callback picks the best response and emits it on the internal bus.
- Inner payload must be
BUSorINTERCOM. - Default select callback is
random.choiceover the collected responses. - Set
cascade_select_callbackon the protocol to provide custom disambiguation. - Set
hive_mapperto enable early resolution when all nodes have responded.
Sending a CASCADE (from satellite)
inner = HiveMessage(HiveMessageType.BUS,
Message("skill.list.request", {}))
cascade = HiveMessage(HiveMessageType.CASCADE, payload=inner)
client.emit(cascade)
Custom disambiguation
from hivemind_bus_client.protocol import HiveMindSlaveProtocol
from hivemind_bus_client.hive_map import HiveMapper
def pick_best(responses):
# custom logic, for example highest confidence or a specific node
return responses[0]
proto.cascade_select_callback = pick_best
proto.hive_mapper = mapper # enables early resolution
Listening for CASCADE responses (decorator)
from hivemind_bus_client.decorators import on_cascade
@on_cascade("skill.list.response", bus)
def on_skills(msg):
print(msg.data["skills"])
PING Flood: Network Discovery
PING messages are always the inner payload of a PROPAGATE message. They are never sent bare. Each node that receives a PING responds with its own PING carrying the same flood_id. The flood_id prevents infinite loops (tracked via HiveMapper.check_flood_id). PONG is no longer used.
PING Payload Fields
| Field | Type | Description |
|---|---|---|
flood_id | str | UUID preventing infinite loops |
peer | str | {name}::{session_id} identifier |
site_id | str | Location identifier |
timestamp | float | Sender's clock for RTT estimation |
public_key | str? | RSA public key, enables trust verification via HiveMapper.mark_trusted_nodes |
lang | str? | Node's locale (e.g. "en-us"), enables localized INTERCOM communication |
Sending a PING
import time, uuid
from hivemind_bus_client.message import HiveMessage, HiveMessageType
flood_id = str(uuid.uuid4())
ping_inner = HiveMessage(
HiveMessageType.PING,
payload={
"flood_id": flood_id,
"timestamp": time.time(),
"peer": f"{identity.name}::{client.session_id}",
"site_id": client.site_id,
"public_key": identity.public_key,
"lang": "en-us",
}
)
ping_outer = HiveMessage(HiveMessageType.PROPAGATE, payload=ping_inner)
client.emit(ping_outer)
Receiving responsive PINGs
def on_ping(message: HiveMessage) -> None:
payload = message.payload # inner PING dict
route = message.route # List[{source, targets}], the hive path
print(f"PING from {payload['peer']} via {len(route)} hops")
client.on(HiveMessageType.PING, on_ping)
For automated topology collection use HiveMapper from hivemind_bus_client.hive_map.
Route Metadata
Every HiveMessage has a route field: List[Dict[str, Any]], an ordered list of hops tracking the network path.
Hop Structure
Each hop is a dict: {"source": "peer_id", "targets": ["peer_id1", "peer_id2"]}.
source: the peer that forwarded the message at this hoptargets: the peers the message was sent to from this hop
A hop entry is only trusted when it is a dict carrying a source key — the
same shape check route and update_hop_data() both apply. An inbound
frame's route is attacker-controlled and reaches update_hop_data() before
any authentication; a malformed last entry ({}, a bare string, or similar)
is treated as absent and a fresh, well-formed hop is appended, rather than
being indexed into. A malformed entry earlier in the list is left in place
untouched — only the last entry is ever read or written.
Multi-Hop Example
After S0 → R1 → M0 traversal:
message.route == [
{"source": "S0_peer_id", "targets": ["R1_peer_id"]},
{"source": "R1_peer_id", "targets": ["M0_peer_id"]},
]
Route API
# Record a hop (called automatically by protocol layer)
message.update_hop_data()
# Replace route (used when transferring between wrapper/inner messages)
message.replace_route(other_message.route)
# Read route (filters incomplete hops)
for hop in message.route:
print(f"{hop['source']} → {hop['targets']}")
Serialization
Route survives as_dict() → deserialize() roundtrips. The route field is included in JSON serialization and restored on deserialization (message.py).
Serialization and Encryption
Before being sent over the network, HiveMessage objects are:
- Serialized: Using functions in
hivemind_bus_client.serialization(get_bitstring,decode_bitstring). - Encrypted: Using AES-256-GCM via functions in
hivemind_bus_client.encryption. - Encoded: Frequently using Z85 or Base91 for safe text transport. The encoders come from the
z85base91package.
Examples
Sending a Standard Bus Message
from hivemind_bus_client.message import HiveMessage, HiveMessageType
from ovos_bus_client.message import Message
hive_msg = HiveMessage(HiveMessageType.BUS,
Message("mycroft.stop"))
Sending Binary Audio Data
from hivemind_bus_client.message import HiveMindBinaryPayloadType
audio_bytes = b"..." # Raw PCM audio
hive_msg = HiveMessage(HiveMessageType.BINARY, audio_bytes,
bin_type=HiveMindBinaryPayloadType.RAW_AUDIO)
The enum member is BINARY. HiveMessageType.BIN does not exist. The string on the wire
is "bin".