Routing in a mesh network is hard. Nodes move. Links break. Latency changes every second. Traditional routing protocols (OSPF, BGP) assume stable topology — mesh networks don't have that.
GhostWire solves this with a 4-layer AI routing system that adapts in real-time. Each layer handles a different timescale, from microseconds to minutes.
The 4 Layers
┌─────────────────────────────────────┐
│ Layer 4: PRoPHET (epidemic) │ Minutes — delay-tolerant
├─────────────────────────────────────┤
│ Layer 3: GNN (graph neural net) │ Seconds — topology-aware
├─────────────────────────────────────┤
│ Layer 2: Gemma 4 (LLM) │ Sub-second — contextual
├─────────────────────────────────────┤
│ Layer 1: LightGBM (gradient boost) │ Microseconds — fast path
└─────────────────────────────────────┘
The system tries Layer 1 first. If it's confident, done. If not, it escalates to Layer 2, then 3, then 4. Each layer is a fallback for the one above it.
Layer 1: LightGBM (Fast Path)
When: Every packet, microseconds budget
What: Predicts the best next hop based on 12 features
struct RoutingFeatures {
hop_count: u8,
link_quality: f32, // RSSI-based
node_uptime: u32, // seconds since last seen
queue_depth: u16, // pending messages
bandwidth_estimate: f32, // bytes/sec
latency_estimate: f32, // ms
packet_loss: f32, // 0.0 - 1.0
node_battery: f32, // 0.0 - 1.0
transport_type: u8, // TCP=0, BLE=1, LoRa=2, WebRTC=3
encryption_level: u8, // none=0, classical=1, pq_hybrid=2
geodistance: f32, // km (if GPS available)
historical_success: f32, // past delivery rate
}
The LightGBM model is trained on historical routing data from the mesh. It runs in <100μs on any hardware. Decision trees are fast.
fn predict_next_hop(features: &RoutingFeatures, model: &LGBMModel) -> NodeId {
let prediction = model.predict(features.to_vector());
// Returns probability distribution over candidate next hops
prediction.argmax()
}
Fallback: If LightGBM confidence < 0.7, escalate to Layer 2.
Layer 2: Gemma 4 (Contextual)
When: Unusual routing scenarios, sub-second budget
What: Uses a small LLM to reason about context
This is where it gets interesting. When the fast path isn't confident, we ask a small language model:
Given: Node A needs to reach Node H.
Available paths: A→B→D→H (3 hops, high latency), A→C→E→H (4 hops, low latency), A→F→G→H (3 hops, unknown).
Network state: B's battery at 15%, E's queue depth 50+, F last seen 2 hours ago.
Recommendation?
Gemma 4 (4B parameters, quantized to INT4) runs on-device. It reasons about tradeoffs that a simple model can't:
- "B has low battery — it might die mid-route, causing retransmission"
- "E's queue is full — adding more messages will increase latency"
- "F hasn't been seen in 2 hours — it might be offline"
async fn gemma_route(
source: NodeId,
dest: NodeId,
candidates: Vec<Path>,
network_state: NetworkState,
) -> Path {
let prompt = format!(
"Source: {}, Dest: {}. Candidates: {:?}. State: {:?}. Best path?",
source, dest, candidates, network_state
);
let response = gemma4.complete(&prompt, MaxTokens(128)).await;
parse_path_recommendation(response)
}
Fallback: If Gemma 4 is unavailable (low memory, battery), escalate to Layer 3.
Layer 3: Graph Neural Network (Topology)
When: Topology changes, seconds budget
What: Models the entire mesh as a graph, predicts optimal paths
A GNN operates on the mesh topology graph where nodes are vertices and links are edges. It learns patterns like:
- "Nodes in this cluster tend to have high packet loss"
- "This bridge node is a single point of failure"
- "These two nodes have correlated failures (same power source)"
struct MeshGNN {
// Graph attention network
attention_heads: usize, // 8
hidden_dim: usize, // 128
layers: usize, // 3
}
impl MeshGNN {
fn predict(&self, graph: &MeshGraph, source: NodeId, dest: NodeId) -> Vec<Path> {
// Encode node features
let node_embeddings = self.encode_nodes(graph);
// Message passing (3 layers)
let refined = self.message_passing(node_embeddings, graph.adjacency());
// Predict path probabilities
self.path_predictor.forward(refined, source, dest)
}
}
The GNN is retrained periodically on the local mesh's topology data. It learns the specific characteristics of your network, not generic routing patterns.
Fallback: If the graph is too dynamic (nodes moving fast), escalate to Layer 4.
Layer 4: PRoPHET (Epidemic)
When: No route exists, delay-tolerant, minutes budget
What: Flooding-based delivery with delivery predictability
PRoPHET (Probabilistic Routing Protocol using History of Transitivity) is the nuclear option. When no path exists, messages are carried by nodes and delivered when they encounter the destination.
struct ProphetState {
delivery_predictability: HashMap<(NodeId, NodeId), f32>,
last_encounter: HashMap<NodeId, Instant>,
transitive_weight: f32, // 0.75
}
impl ProphetState {
fn update_on_encounter(&mut self, me: NodeId, them: NodeId) {
// Direct delivery probability
let p_direct = 0.75; // Initial value
let age = self.last_encounter[&them].elapsed().as_secs();
let p_updated = p_direct * (0.98_f32).powf(age as f32);
self.delivery_predictability
.insert((me, them), p_updated);
self.last_encounter.insert(them, Instant::now());
// Transitivity: if A meets B, and B has met C,
// then A's delivery probability to C increases
for (other, &their_p) in &self.delivery_predictability {
if *other != me && *other != them {
let combined = 1.0 - (1.0 - p_updated) * (1.0 - their_p);
self.delivery_predictability
.insert((me, *other), combined);
}
}
}
}
PRoPHET is slow but reliable. Messages can take hours or days to deliver — but they will deliver, as long as there's a path (even an indirect one) through the mesh.
Putting It All Together
The routing decision happens in a single function:
async fn route_packet(
packet: &Packet,
mesh: &MeshState,
lightgbm: &LGBMModel,
gemma: &Gemma4,
gnn: &MeshGNN,
prophet: &ProphetState,
) -> NextHop {
let features = extract_features(packet.dest, mesh);
// Layer 1: Fast path
let (hop, confidence) = lightgbm.predict(&features);
if confidence > 0.7 {
return NextHop::Direct(hop);
}
// Layer 2: Contextual
if let Some(path) = gemma.route(packet.dest, mesh).await {
return NextHop::Path(path);
}
// Layer 3: Topology
let paths = gnn.predict(&mesh.graph, packet.source, packet.dest);
if let Some(best) = paths.first() {
return NextHop::Path(best.clone());
}
// Layer 4: Epidemic
NextHop::Carry(prophet.next_carry_node(mesh))
}
The whole decision takes <500μs in the common case (Layer 1 wins). Edge cases might take 1-2 seconds (Layer 2). The worst case (Layer 4) stores the message and waits.
What's Next
In the final article, we'll cover coercion resistance and censorship circumvention — duress PINs, panic wipe, dead man's switches, and how GhostWire's transport layer makes blocking difficult.
GitHub: github.com/Phantomojo/GhostWire-secure-mesh-communication
Website: ghostwire.cc
Built from Nairobi, under RVC.
Top comments (0)