Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mesh-llm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ plugins/
- Routing and demand tracking are mesh-wide. Nodes can serve different models at the same time.
- Discovery is optional and Nostr-backed. Private meshes work with explicit join tokens only.

The current control plane prefers protocol `mesh-llm/1` with protobuf framing, while keeping backward-compatible support for older `mesh-llm/0` peers in `src/protocol/`.
The control plane uses protocol `mesh-llm/1` with protobuf framing for mesh traffic.

## API surface

Expand Down
5 changes: 2 additions & 3 deletions mesh-llm/docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ enum NodeRole {
}
```

Roles are exchanged via gossip. Preferred peers use `meshllm.node.v1` protobuf on QUIC ALPN `mesh-llm/1`; legacy peers may still negotiate `mesh-llm/0` and use the older JSON gossip payloads. A node transitions Worker → Host when elected.
Roles are exchanged via gossip over `meshllm.node.v1` protobuf on QUIC ALPN `mesh-llm/1`. A node transitions Worker → Host when elected.

A newly connected peer is quarantined until it sends a valid `GossipFrame` with `gen = 1` (quarantine-until-gossip admission model). Only streams 0x01 (GOSSIP) and 0x05 (ROUTE_REQUEST) are accepted before admission. All other streams are rejected until the peer is admitted.

## Control-Plane Protocol

The control plane prefers QUIC ALPN `mesh-llm/1` using the `meshllm.node.v1` protobuf schema. Scoped control-plane streams on `/1` use 4-byte LE framing followed by protobuf bytes. For backward compatibility, peers may also negotiate `mesh-llm/0`, which preserves the legacy JSON/raw payloads on those same streams.
The control plane uses QUIC ALPN `mesh-llm/1` with the `meshllm.node.v1` protobuf schema. Scoped control-plane streams use 4-byte LE framing followed by protobuf bytes.

Mixed meshes containing both `/0` and `/1` nodes are supported. `/0` links are compatibility mode only, so they do not carry protobuf-only fields.

See [message_protocol.md](../../message_protocol.md) for the full wire format specification.

Expand Down
5 changes: 1 addition & 4 deletions mesh-llm/docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ mesh-llm --model Qwen3-Coder-Next-Q4_K_M --auto --no-self-update --split --join

## Control-Plane Protocol (Protobuf v1)

The control plane prefers QUIC ALPN `mesh-llm/1` using the `meshllm.node.v1` protobuf schema. On `/1`, all five scoped control-plane streams use 4-byte LE framing followed by protobuf bytes. For backward compatibility, nodes may also negotiate `mesh-llm/0`, which keeps the legacy JSON/raw payloads on those same streams.
The control plane uses QUIC ALPN `mesh-llm/1` with the `meshllm.node.v1` protobuf schema. All five scoped control-plane streams use 4-byte LE framing followed by protobuf bytes.

| Stream | Type | Format |
|--------|------|--------|
Expand All @@ -491,9 +491,6 @@ The control plane prefers QUIC ALPN `mesh-llm/1` using the `meshllm.node.v1` pro

Raw TCP relay streams (0x02 RPC, 0x04 HTTP) are unchanged.

### Testing legacy fallback (`mesh-llm/0`)

To verify backward compatibility, start a node built from the pre-cutover branch (or any build with `ALPN = b"mesh-llm/0"`) and attempt to join a `mesh-llm/1` mesh. The connection should negotiate `/0`, complete gossip, and exchange the legacy JSON/raw control-plane payloads without breaking peer discovery or route-table fetches.

### Verifying protobuf gossip in logs

Expand Down
12 changes: 5 additions & 7 deletions mesh-llm/docs/message_protocol.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# mesh-llm Message Protocol

This document describes the wire protocol for control-plane communication between mesh-llm nodes. Control-plane traffic prefers the `meshllm.node.v1` protobuf schema on QUIC ALPN `mesh-llm/1`, with backward-compatible support for the legacy `mesh-llm/0` JSON/raw payloads.
This document describes the wire protocol for control-plane communication between mesh-llm nodes over the `meshllm.node.v1` protobuf schema on QUIC ALPN `mesh-llm/1`.

## ALPN

Control-plane connections prefer ALPN `mesh-llm/1`.

Peers may also negotiate the legacy ALPN `mesh-llm/0` for backward compatibility. Mixed meshes containing both `/0` and `/1` nodes are supported, but `/0` links only exchange the legacy field set.

## Stream Types

Expand Down Expand Up @@ -57,7 +56,7 @@ A newly connected peer is quarantined until it sends a valid `GossipFrame` with
- All other streams (0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0a) are rejected and the stream is closed.
- The QUIC connection itself stays open so gossip can complete.

A peer is admitted when its negotiated gossip payload decodes successfully and passes validation checks. On `/1` this is a protobuf `GossipFrame`; on `/0` this is the legacy JSON gossip payload.
A peer is admitted when its `GossipFrame` decodes successfully and passes validation checks.

## Stream 0x01 — Gossip (`GossipFrame`)

Expand Down Expand Up @@ -247,8 +246,7 @@ The following are explicitly NOT protobuf and are not described here:

## Compatibility

`mesh-llm/1` remains the preferred protocol, but negotiation keeps older nodes working:
`mesh-llm/1` is the only supported control-plane protocol.

- Nodes advertise both `mesh-llm/1` and `mesh-llm/0` on accept.
- Connectors prefer `/1` and offer `/0` as a fallback when needed.
- All five scoped control-plane streams (0x01, 0x03, 0x05, 0x06, 0x07) use protobuf framing on `/1` and the legacy JSON/raw formats on `/0`.
- Nodes advertise `mesh-llm/1` on accept.
- All five scoped control-plane streams (0x01, 0x03, 0x05, 0x06, 0x07) use protobuf framing.
68 changes: 0 additions & 68 deletions mesh-llm/src/mesh/gossip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,74 +38,6 @@ pub(super) fn peer_meaningfully_changed(old: &PeerInfo, new: &PeerInfo) -> bool
|| old.gpu_reserved_bytes != new.gpu_reserved_bytes
}

impl PeerAnnouncementV0 {
pub(crate) fn into_internal(self) -> PeerAnnouncement {
let serving_models = if !self.serving_models.is_empty() {
self.serving_models.clone()
} else {
self.serving.clone().into_iter().collect()
};
PeerAnnouncement {
addr: self.addr,
role: self.role,
models: self.models,
vram_bytes: self.vram_bytes,
model_source: self.model_source,
serving_models,
hosted_models: None,
available_models: self.available_models,
requested_models: self.requested_models,
version: self.version,
model_demand: self.model_demand,
mesh_id: self.mesh_id,
gpu_name: self.gpu_name,
hostname: self.hostname,
is_soc: self.is_soc,
gpu_vram: self.gpu_vram,
gpu_reserved_bytes: self.gpu_reserved_bytes,
gpu_mem_bandwidth_gbps: self.gpu_mem_bandwidth_gbps,
gpu_compute_tflops_fp32: self.gpu_compute_tflops_fp32,
gpu_compute_tflops_fp16: self.gpu_compute_tflops_fp16,
available_model_metadata: vec![],
experts_summary: None,
available_model_sizes: self.available_model_sizes,
served_model_descriptors: self.served_model_descriptors,
served_model_runtime: self.served_model_runtime,
owner_attestation: None,
}
}
}

impl From<&PeerAnnouncement> for PeerAnnouncementV0 {
fn from(ann: &PeerAnnouncement) -> Self {
Self {
addr: ann.addr.clone(),
role: ann.role.clone(),
models: ann.models.clone(),
vram_bytes: ann.vram_bytes,
model_source: ann.model_source.clone(),
serving: ann.serving_models.first().cloned(),
serving_models: ann.serving_models.clone(),
available_models: ann.available_models.clone(),
requested_models: ann.requested_models.clone(),
version: ann.version.clone(),
model_demand: ann.model_demand.clone(),
mesh_id: ann.mesh_id.clone(),
gpu_name: ann.gpu_name.clone(),
hostname: ann.hostname.clone(),
is_soc: ann.is_soc,
gpu_vram: ann.gpu_vram.clone(),
gpu_reserved_bytes: ann.gpu_reserved_bytes.clone(),
gpu_mem_bandwidth_gbps: ann.gpu_mem_bandwidth_gbps.clone(),
gpu_compute_tflops_fp32: ann.gpu_compute_tflops_fp32.clone(),
gpu_compute_tflops_fp16: ann.gpu_compute_tflops_fp16.clone(),
available_model_sizes: ann.available_model_sizes.clone(),
served_model_descriptors: ann.served_model_descriptors.clone(),
served_model_runtime: ann.served_model_runtime.clone(),
}
}
}

pub(super) fn apply_transitive_ann(
existing: &mut PeerInfo,
addr: &EndpointAddr,
Expand Down
36 changes: 12 additions & 24 deletions mesh-llm/src/mesh/heartbeat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,18 +669,12 @@ impl Node {
let res = async {
let (mut send, _recv) = conn.open_bi().await?;
send.write_all(&[STREAM_PEER_DOWN]).await?;
match protocol {
ControlProtocol::ProtoV1 => {
let proto_msg = crate::proto::node::PeerDown {
peer_id: bytes,
gen: NODE_PROTOCOL_GENERATION,
};
write_len_prefixed(&mut send, &proto_msg.encode_to_vec()).await?;
}
ControlProtocol::JsonV0 => {
send.write_all(&bytes).await?;
}
}
let _ = protocol;
let proto_msg = crate::proto::node::PeerDown {
peer_id: bytes,
gen: NODE_PROTOCOL_GENERATION,
};
write_len_prefixed(&mut send, &proto_msg.encode_to_vec()).await?;
send.finish()?;
Ok::<_, anyhow::Error>(())
}
Expand Down Expand Up @@ -713,18 +707,12 @@ impl Node {
let res = async {
let (mut send, _recv) = conn.open_bi().await?;
send.write_all(&[STREAM_PEER_LEAVING]).await?;
match protocol {
ControlProtocol::ProtoV1 => {
let proto_msg = crate::proto::node::PeerLeaving {
peer_id: bytes,
gen: NODE_PROTOCOL_GENERATION,
};
write_len_prefixed(&mut send, &proto_msg.encode_to_vec()).await?;
}
ControlProtocol::JsonV0 => {
send.write_all(&bytes).await?;
}
}
let _ = protocol;
let proto_msg = crate::proto::node::PeerLeaving {
peer_id: bytes,
gen: NODE_PROTOCOL_GENERATION,
};
write_len_prefixed(&mut send, &proto_msg.encode_to_vec()).await?;
send.finish()?;
Ok::<_, anyhow::Error>(())
}
Expand Down
Loading
Loading