Skip to content

Latest commit

 

History

History
240 lines (174 loc) · 10.2 KB

File metadata and controls

240 lines (174 loc) · 10.2 KB

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.

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

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 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 →