Files
neuron-tai/.scratch/distributed-inference-network/issues/29-relay-outbound-client.md
Dobromir Popov 2b439e8a5f docs: add US-020–029 issue files, ADR 0011–0014, update prd.json to 29/29
Issue files (.scratch/issues/20-29): retrospective specs for all work
done in the current sprint — hardening, route-timeout, start-layer
protocol, heartbeat stats, availability map, rolling RPM, smart
assignment, throughput routing, routing tests, relay outbound client.

ADRs (docs/adr/0011-0014):
  0011 — Auto-shard from memory budget and tracker network assignment
  0012 — X-Meshnet-Start-Layer overlapping shard execution protocol
  0013 — Rolling RPM statistics, smart assignment scoring, throughput routing
  0014 — Relay outbound client for NAT/internet pipeline hops

prd.json: US-020 through US-029 added, all marked done. ralph_progress.py
now shows 29/29 complete (100%).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:15:41 +03:00

2.2 KiB

US-029 — Outbound relay client: NAT/internet pipeline hops

Status: done Priority: Critical Stage: Implemented

Context

Nodes behind NAT (WSL2 with 172.x.x.x addresses, 5G mobile, home routers) register with the tracker and include a relay_addr (wss://relay/rpc/{peer_id}). When the head node needs to forward activations to such a peer, it currently fails because the direct HTTP endpoint is unreachable.

The relay server (US-017) is already running and the node already opens a persistent outbound WebSocket (RelayHttpBridge). What is missing is the outbound caller side: given a relay_addr, open a per-hop WebSocket to the relay's /rpc/{peer_id} endpoint and send the activation through it.

Protocol

Node A  →  WS connect wss://relay/rpc/{peer_id_B}
       →  send JSON: {request_id, method, path, headers, body_base64}
Relay   →  forward as relay-http-request envelope to Node B's persistent WS
Node B  →  process /forward locally
       →  send relay-http-response envelope back
Relay   →  resolve future, send response JSON to Node A
Node A  ←  {request_id, status, headers, body_base64}

Binary activations (bfloat16) are base64-encoded. No precision loss.

Acceptance criteria

  • _relay_hop(relay_addr, path, body, headers, timeout) in torch_server.py — opens WS, sends, receives, returns (status, headers_lower, body_bytes)
  • _get_remaining_route returns list[dict] with relay_addr field (was list[tuple])
  • _run_downstream_pipeline dispatches via _relay_hop when hop has relay_addr; falls back to direct HTTP if relay connection fails
  • Tracker _handle_proxy_chat includes relay_addr in downstream hop dicts when node has one
  • relay_bridge._handle_request decodes body_base64; response uses body_base64 for octet-stream content
  • All 157 tests pass (python -m pytest)
  • QUICKSTART.md updated with relay NAT/internet architecture and test scenario

WSL2 test scenario

Start two nodes in WSL2 pointing at the public tracker. Both get 172.x.x.x endpoints (unreachable from outside). Both connect to relay automatically. Send an inference request through the tracker — activations flow via relay. No --advertise-host needed.