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 the HiveMessageType enum.
  • payload: The actual message (an OVOS Message for BUS types, bytes for BINARY types).
  • metadata: Auxiliary dict carried with the message (for example sample_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.

TypePurposeUse Case
BUSStandard message for the AIUtterances, intent triggers, speak events.
BINARYRaw binary dataAudio streams for STT/TTS, file transfers.
ESCALATEUpstream requestUsed by a Slave Mind to ask a Master Mind for help.
BROADCASTDownstream flood (admin only)Master pushes a message to all connected satellites.
PROPAGATEBidirectional floodForwards to all peers in both directions.
INTERCOMEnd-to-end hybrid-encryptedAES-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.
QUERYRequest-response upstreamLike ESCALATE, but first answering node sends a response back. Stops propagation on answer.
CASCADERequest-response floodLike PROPAGATE, but expects responses from ALL nodes. Supports disambiguation.
PINGNetwork discovery floodEach node responds with its own PING (same flood_id). Carried inside PROPAGATE. Route metadata = hive path.
HELLONode announcementSession sync at connection time.
HANDSHAKECrypto negotiationKey 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 BUS or INTERCOM.
  • BUS payloads are dispatched to handle_bus.
  • INTERCOM payloads are dispatched to handle_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 BUS or INTERCOM.
  • Default select callback is random.choice over the collected responses.
  • Set cascade_select_callback on the protocol to provide custom disambiguation.
  • Set hive_mapper to 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

FieldTypeDescription
flood_idstrUUID preventing infinite loops
peerstr{name}::{session_id} identifier
site_idstrLocation identifier
timestampfloatSender's clock for RTT estimation
public_keystr?RSA public key, enables trust verification via HiveMapper.mark_trusted_nodes
langstr?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 hop
  • targets: 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:

  1. Serialized: Using functions in hivemind_bus_client.serialization (get_bitstring, decode_bitstring).
  2. Encrypted: Using AES-256-GCM via functions in hivemind_bus_client.encryption.
  3. Encoded: Frequently using Z85 or Base91 for safe text transport. The encoders come from the z85base91 package.

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".


← Fakes · Home · Binary Serialization →