Reliably find LAN direct paths in mDNS and default modes, regardless of which node hosts - #851
Reliably find LAN direct paths in mDNS and default modes, regardless of which node hosts#851michaelneale wants to merge 9 commits into
Conversation
… fast path With a shared token (no public listing), two LAN peers now reliably reach a real direct UDP path (~7-15ms) regardless of which node started. Previously, on multi-homed hosts (e.g. macOS with several utun/VPN interfaces) the direct path repair would connect but stay on the relay/WAN-hairpin path (~200ms), because iroh issues sendmsg with an unspecified source and the kernel picks a wrong egress interface (EHOSTUNREACH or a slow hairpin route). This auto-detects the primary LAN IPv4 (a no-send UDP connect() probe) and pins QUIC's bind address to it when --bind-ip is not given, so packets leave the correct interface. The bounded direct-path repair then settles on the clean LAN path instead of relay. Verified on a 2-node LAN (M4 + Mac mini), token-share, non-public, both directions, cold starts: - pure repair alone: 3/3 "direct" but every RTT sample relay (~207ms) - with auto-bind: 6/6 clean LAN direct (7-15ms) Also clears the default IPv6 socket when binding a specific IPv4 in relay-disabled mode, so the single LAN candidate avoids an IPv4+IPv6 MultipathNotNegotiated stall. Default (relay/public) mode is untouched: WAN/relay candidates are still advertised and only the source interface is pinned, so remote reachability is unaffected. Co-authored-by: direct-path-repair (PR #846)
In relay-less (mDNS) mode, two LAN peers now reliably establish a direct connection no matter which one created the mesh and which one joined. Before this, a join only reached a fast direct path when the multi-homed node hosted; when the single-homed node hosted, the multi-homed joiner could not complete its own outbound QUIC handshake and the connection stalled. Nodes now advertise their own reachable LAN EndpointAddr and dial peers back on the direction that works: - mDNS advertisements carry an additive `ep_addr` TXT key (the node's LAN-filtered EndpointAddr). Older nodes ignore it. - Each mDNS node browses the LAN and dials back any advertised peer it is not already connected to, and also emits a lightweight LAN beacon (multicast plus unicast to known join targets) so a peer learns a dial-back address even when the joiner cannot initiate its own direct path. - The mDNS service instance name is now per-node (was per-mesh), so multiple nodes in one mesh no longer clobber each other's advertisements. Combined with QUIC auto-bind to the LAN interface, default-iroh token joins and mDNS joins both reach a direct LAN path (~8-30 ms) in either direction. ## Protocol The `ep_addr` mDNS TXT key is additive and backward-compatible: older nodes ignore unknown TXT keys, and the LAN beacon uses a separate port from mDNS. No gossip/QUIC wire changes.
* origin/main: Add bounded direct path repair (#846) Fix skippy smoke PR gate (#850) Stabilize skippy smoke chain startup (#849) # Conflicts: # crates/mesh-llm-host-runtime/src/mesh/direct_path.rs # crates/mesh-llm-host-runtime/src/mesh/mod.rs # crates/mesh-llm-host-runtime/src/protocol/mod.rs # crates/mesh-llm-protocol/src/protocol/mod.rs
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/mesh-llm-host-runtime/src/mesh/mod.rs`:
- Around line 4751-4765: The known_peer_lan_ipv4 method at lines 4751-4765 and
the related method at lines 4901-4914 currently return all IPv4 candidates
including public STUN addresses, but they should only return addresses that are
actually on the selected LAN interface or subnet. Modify both methods to filter
the IPv4 addresses extracted from TransportAddr::Ip(SocketAddr::V4(_))
candidates to exclude public/WAN addresses, keeping only those that are
confirmed to be on the local LAN interface/subnet before adding them to the
output vector.
- Around line 447-468: The mesh/mod.rs file exceeds 1000 lines and contains a
separable LAN-bootstrap responsibility (interface selection, join-target
retention, and LAN-facing helper APIs) that should be extracted into its own
semantically named module per coding guidelines. Create a new submodule for
LAN-bootstrap functionality and move the detect_primary_lan_ipv4 function from
lines 447-468 (anchor), along with the LAN-bootstrap related code at lines
2305-2308 and lines 4721-4914 (siblings) into this new module. Then re-export
the necessary public items from mesh/mod.rs to maintain the current public API.
- Around line 447-468: The detect_primary_lan_ipv4() function uses connect() to
a public routable IP (192.88.99.1) to determine the source address, but this
returns the default route source which is unreliable on multi-homed hosts. On
full-tunnel VPN hosts it returns the VPN address instead of the LAN address, and
on isolated LANs with no default route it returns None. Replace this approach
with a method that directly examines available network interfaces to identify
the actual LAN interface (excluding VPN/utun interfaces, loopback, and
unspecified addresses) rather than inferring from the default route.
- Around line 4892-4899: The remember_join_target function currently skips
adding a new address for a peer if that peer's id already exists in the
join_targets list, which means stale addresses are never updated when the same
peer advertises a new endpoint after restarting or rebinding to a new port. Fix
this by updating the stored address when the same EndpointId is encountered with
a new endpoint address rather than ignoring it—either remove the old entry and
add the new one, or update the address in the existing entry.
In `@crates/mesh-llm-host-runtime/src/mesh/tests.rs`:
- Around line 3161-3284: Move the three test functions
`direct_path_maintenance_requires_candidate_and_grace_period`,
`direct_path_maintenance_cooldown_and_inflight_suppress_requests`, and
`direct_path_request_keeps_only_previously_advertised_direct_candidates` from
the main tests.rs file into a dedicated direct path test module (either create
`mesh/tests/direct_path.rs` or use an existing `mod direct_path` if it exists).
Ensure any necessary imports and helper functions (such as
`make_test_endpoint_id`) are either moved with the tests or properly imported in
the new module to maintain test functionality.
In `@crates/mesh-llm-host-runtime/src/runtime/mod.rs`:
- Around line 7933-7966: The spawn_mdns_reverse_dial function spawns three tokio
tasks (the conditional mDNS publisher, mdns_reverse_dial loop, and lan_beacon)
but does not return any handles for them, causing resource leaks during shutdown
cycles. Create a small guard or handle struct that collects the JoinHandles from
all three tokio::spawn calls in spawn_mdns_reverse_dial, return this guard from
the function, store it in the appropriate runtime state, and update
shutdown_run_auto_runtime() to abort these tasks during shutdown alongside the
existing discovery_publisher teardown.
- Around line 6033-6053: Move the networking helper functions
effective_quic_bind_ip() and spawn_mdns_reverse_dial() out of runtime/mod.rs
into a semantically named module under the network/ directory. Create a new
module file in crates/mesh-llm-host-runtime/src/network/ (such as
network/bootstrap.rs or network/lan.rs) and move both function implementations
there. Update the call sites in runtime/mod.rs (at lines 6033-6053 for
effective_quic_bind_ip and at lines 7922-7966 for spawn_mdns_reverse_dial) to
use the new module path instead of defining the functions locally. This
addresses the oversized-file concern by extracting networking-specific
responsibilities into a dedicated module as per the coding guideline for files
exceeding 1,000 lines.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5d5ede6e-2f28-43e9-bfd9-18ecdec8782c
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
crates/mesh-llm-host-runtime/Cargo.tomlcrates/mesh-llm-host-runtime/src/mesh/mod.rscrates/mesh-llm-host-runtime/src/mesh/tests.rscrates/mesh-llm-host-runtime/src/network/discovery.rscrates/mesh-llm-host-runtime/src/network/lan_beacon.rscrates/mesh-llm-host-runtime/src/network/mdns_reverse_dial.rscrates/mesh-llm-host-runtime/src/network/mod.rscrates/mesh-llm-host-runtime/src/runtime/mod.rs
Auto-bind previously pinned QUIC to whatever source address the default route produced. On a full-tunnel VPN host that source is the VPN/utun address, and in relay-less (mDNS / --disable-iroh-relays) mode the clear_ip_transports() + bind_addr() path would then hard-pin QUIC to that off-LAN interface with no fallback, instead of treating it as a weak hint. Hosts with no default route would also get a wrong/None result. detect_primary_lan_ipv4() now returns only a genuine RFC1918 private LAN IPv4 (or None): - It still uses the fast connect-trick to read the default-route source, but accepts that result only when it is a private LAN address. - Otherwise it scans local interfaces and picks the first private, operational, non-loopback, non-link-local, non-point-to-point IPv4. Point-to-point interfaces are skipped so VPN/tunnel interfaces are never chosen. A wrong default route can therefore never hard-pin relay-less QUIC off-LAN; we fall back to binding 0.0.0.0 instead. Public-relay (Nostr) mode is unchanged and keeps its IPv6/relay paths, so a multi-homed host can still form tight direct UDP on the LAN while reaching a remote mesh over relay/STUN without losing fidelity in either direction. Adds unit tests covering the private/public/CGNAT/link-local/loopback classification.
i386
left a comment
There was a problem hiding this comment.
I rechecked the latest head before posting. The LAN detection issue from the earlier pass looks addressed by the private-interface fallback, so I’m leaving the remaining two actionable findings only.
| // --publish, so peers can discover a dial-back address. The standard | ||
| // publish path only runs with --publish; spawn a publisher here otherwise. | ||
| if !options.publish { | ||
| tokio::spawn(Box::pin(mesh_discovery::publish_lan_loop( |
There was a problem hiding this comment.
This extra mDNS publisher is spawned without its JoinHandle, and the same happens below for the reverse-dial loop and LAN beacon. shutdown_run_auto_runtime() only aborts discovery_publisher, so in shutdown/restart paths these new loops can keep publishing, dialing, and holding the beacon UDP socket until process exit. Please return/store a small guard of these handles and abort it alongside discovery_publisher during runtime shutdown.
| /// hint to it even before a direct connection forms. | ||
| async fn remember_join_target(&self, addr: EndpointAddr) { | ||
| let mut targets = self.join_targets.lock().await; | ||
| if !targets.iter().any(|t| t.id == addr.id) { |
There was a problem hiding this comment.
This keeps the first address ever seen for a peer and ignores later invite tokens with the same endpoint id. If the peer restarts or rebinds to a new QUIC port, the LAN beacon will keep unicasting dial-back hints to the stale socket address, so this recovery path stops working after a common rebind/restart case. Please replace/update the existing entry when t.id == addr.id instead of dropping the new address.
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (2)
lab-default.sh (1)
1-52: 🏗️ Heavy liftExtensive code duplication across lab scripts creates maintainability risk.
The functions
mini(),cleanup(), andpoll_console()are duplicated identically acrosslab-default.sh,lab-default-846.sh,lab-default-rb.sh,lab-mdns-846.sh,lab-mdns-autobind.sh,lab-mdns-final.sh, and others. Changes to shared logic (e.g., timeout tuning, error handling) must be applied to all copies, increasing the risk of inconsistency.♻️ Recommended refactor: extract shared utilities
Create a shared
lab-common.shsourced by all harnesses:# lab-common.sh MINI="sshpass -p ${LAB_MINI_PASSWORD:?} ssh -o ConnectTimeout=10 ... michaelneale@192.168.86.60" mini() { for t in 1 2 3 4; do O=$($MINI "$1" </dev/null 2>&1 | grep -v Warning); echo "$O" | grep -q "Permission denied" || break; sleep 1; done; echo "$O"; } cleanup() { pkill -9 -f "target/debug/mesh-llm" 2>/dev/null; mini "pkill -9 -f mesh-llm 2>/dev/null; echo c" >/dev/null; sleep 3; } poll_console() { ... }Then source it in each harness:
#!/bin/bash source "$(dirname "$0")/lab-common.sh" ...🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lab-default.sh` around lines 1 - 52, The functions mini(), cleanup(), and poll_console() along with the MINI variable are duplicated across multiple lab scripts, creating maintenance risk. Extract these shared utilities into a new lab-common.sh file containing the MINI variable definition and all three function definitions. Then source this common file at the beginning of each lab script (lab-default.sh, lab-default-846.sh, lab-default-rb.sh, lab-mdns-846.sh, lab-mdns-autobind.sh, lab-mdns-final.sh, and any others with these duplicates) using source "$(dirname "$0")/lab-common.sh", and remove the duplicate function definitions and MINI variable assignments from each individual script. This ensures changes to shared logic only need to be applied once.lab-mdns.sh (1)
1-52: ⚖️ Poor tradeoffSignificant code duplication across lab test scripts. All five lab scripts share nearly identical structure (helper functions, polling logic, result classification), differing only in configuration variables (
MINIBIN, discovery mode flags). Changes to polling, error handling, or result reporting must be manually propagated.
lab-mdns.sh#L1-L52: extract common logic (polling, cleanup, SSH helpers) into a shared library sourced by each scriptlab-mdns-rb.sh#L1-L52: same; parameterize the differing configuration (MINIBIN path)lab-mdns-rd.sh#L1-L52: samelab-norelay.sh#L1-L48: same; parameterize discovery mode flagslab-reliability.sh#L1-L57: same; unify token polling and result classification🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lab-mdns.sh` around lines 1 - 52, Extract the common logic and helper functions from all five lab test scripts into a shared library file (e.g., lab-common.sh) containing the `mini()`, `cleanup()`, and `poll_console()` functions along with shared variables and the main test loop logic. In lab-mdns.sh (lines 1-52), replace the duplicated functions and loop structure with a source statement that includes the shared library, then define only the script-specific configuration variables (MINI, BIN, MINIBIN, M4MODEL, MINIGGUF, MD). Apply the same refactoring pattern to lab-mdns-rb.sh (lines 1-52) and lab-mdns-rd.sh (lines 1-52), parameterizing their unique MINIBIN paths. In lab-norelay.sh (lines 1-48) and lab-reliability.sh (lines 1-57), source the shared library and parameterize their discovery mode flags (the MD variable) and any other script-specific configuration. This ensures changes to polling logic, error handling, result classification, or the cleanup and token retrieval flows only need to be made in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lab-default.sh`:
- Line 9: The MINIGGUF variable assignment at lab-default.sh#L9-L9,
lab-default-846.sh#L9-L9, lab-default-rb.sh#L9-L9, lab-mdns-846.sh#L9-L9,
lab-mdns-autobind.sh#L9-L9, and lab-mdns-final.sh#L9-L9 uses tilde within double
quotes, which prevents shell expansion. Fix all six occurrences by replacing the
double-quoted tilde path with either
MINIGGUF="$HOME/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf" or
MINIGGUF=~/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf (unquoted) to ensure the
home directory expands correctly.
- Line 5: Hardcoded plaintext credentials embedded in shell scripts create a
critical security vulnerability that can expose credentials through repository
history and CI logs. In lab-default.sh (line 5-5), replace the hardcoded
password in the MINI variable definition with an environment variable reference
such as ${LAB_MINI_PASSWORD:?} in the sshpass -p argument. Apply the identical
fix to lab-default-846.sh (line 5-5), lab-default-rb.sh (line 5-5),
lab-mdns-846.sh (line 5-5), lab-mdns-autobind.sh (line 5-5), and
lab-mdns-final.sh (line 5-5) where the MINI variable contains the plaintext
password. For lab-deploy-mini.sh (line 7-7), replace the plaintext password in
the PW variable definition with the same environment variable reference pattern.
Alternatively, migrate to SSH key-based authentication to eliminate
password-based login entirely.
In `@lab-deploy-mini.sh`:
- Line 12: The LSIZE variable assignment uses macOS-specific stat syntax with
the -f %z flag, which will fail on Linux systems. Since this script already
contains macOS-specific dependencies like the codesign command, either add a
comment at the top of the script documenting that the script requires macOS
(specifically noting dependencies on codesign and stat -f), or make it portable
by detecting the operating system and using stat -c %s for Linux and stat -f %z
for macOS. The simpler approach given the existing macOS-only dependencies is to
add a clear macOS requirement comment near the top of the script.
In `@lab-mdns-rb.sh`:
- Line 9: The MINIGGUF variable assignment wraps the path in double quotes,
which prevents tilde expansion since the shell does not expand tildes inside
double quotes. To fix this, remove the double quotes from the MINIGGUF
assignment to allow proper tilde expansion, or alternatively replace the leading
tilde with the $HOME variable for explicit path expansion.
- Line 32: The variable $RUNS in the for loop at the seq command is unquoted,
which could cause word splitting issues if the variable contains spaces. Quote
the $RUNS variable expansion in the seq call to apply defensive programming
practices and prevent unintended word splitting when the for loop iterates
through the sequence.
- Line 5: The MINI variable contains a hardcoded password in plaintext which
exposes the credential to anyone with access to the repository and its history.
Remove the hardcoded password spankychat2000 from the MINI variable definition
and replace it with a reference to an environment variable (such as SSH_PASSWORD
or similar) that can be set at runtime, or preferably switch to SSH key-based
authentication by removing the password-related SSH options and using a private
key instead. Ensure the script can read the credential from the environment or a
secure configuration file that is excluded from version control.
In `@lab-mdns-rd.sh`:
- Line 32: The variable expansion $RUNS in the for loop expression is unquoted
and could cause word splitting issues if the variable contains whitespace. Quote
the variable reference by changing $RUNS to "$RUNS" in the seq command to ensure
the value is treated as a single argument and prevent unwanted word splitting.
- Line 9: The tilde character inside double quotes in the MINIGGUF variable
assignment will not expand to the home directory and will be treated as a
literal tilde, which is fragile. Replace the tilde (~) with $HOME to ensure
proper path expansion, so the variable MINIGGUF is set to
"$HOME/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf" instead. This is the idiomatic
way to handle home directory paths in shell scripts and will work reliably
across different environments.
- Line 5: The MINI variable contains a hardcoded SSH password in plaintext,
exposing credentials in version control. Replace the password-based
authentication (sshpass with -p spankychat2000) by either using SSH key-based
authentication (remove sshpass and password-related SSH options like
ConnectTimeout and PreferredAuthentications), or by reading the password from an
environment variable (e.g., using ${SSH_PASSWORD} or similar) instead of
hardcoding it. Whichever approach you choose, document how to securely set up
the authentication credentials outside of the repository.
In `@lab-mdns.sh`:
- Line 9: The tilde character inside double quotes in the MINIGGUF variable
assignment will not expand to the home directory and will be treated as a
literal character. To fix this, replace the tilde with the $HOME environment
variable in the MINIGGUF assignment, changing
"~/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf" to
"$HOME/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf" to ensure proper path
expansion in a shell-idiomatic way.
- Line 32: The variable expansion $RUNS in the seq command within the for loop
iteration is unquoted, which could cause word splitting if the variable contains
spaces. Quote the variable expansion by changing $RUNS to "$RUNS" in the seq 1
$RUNS command to prevent potential word splitting and follow defensive shell
scripting practices.
- Line 5: The MINI variable in lab-mdns.sh contains a hardcoded password
"spankychat2000" in plaintext, which exposes a credential in version control
history and to anyone with repository access. Replace the hardcoded password by
either switching to SSH key-based authentication (removing the sshpass command
and password-related options like -p spankychat2000,
PreferredAuthentications=password, PubkeyAuthentication=no, and
NumberOfPasswordPrompts=1) or by reading the password from an environment
variable at runtime (replacing the literal password value with a reference like
$SSH_PASSWORD). Store sensitive credentials outside the repository, such as in a
.env file (gitignored) or secure credential management system.
In `@lab-norelay.sh`:
- Line 9: The MINIGGUF variable assignment uses a tilde inside double quotes,
which the shell will not expand and will be treated as a literal character
instead of the home directory path. Replace the tilde with the $HOME environment
variable to ensure proper path expansion, changing the assignment to use
$HOME/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf instead of
~/.models/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf.
- Line 28: Quote the $RUNS variable expansion in the seq command within the for
loop to prevent potential word splitting if the variable contains spaces. Wrap
$RUNS with double quotes in the $(seq 1 $RUNS) command to make it $(seq 1
"$RUNS").
- Line 5: The MINI variable contains a hardcoded SSH password in plaintext,
which exposes credentials in version control history. Replace the hardcoded
password "spankychat2000" in the MINI variable definition by either switching to
SSH key-based authentication (removing the sshpass and password-related options)
or by reading the password from an environment variable such as $SSH_PASSWORD
instead of embedding it directly in the script. Ensure the credential is never
committed to the repository.
In `@lab-reliability.sh`:
- Line 10: The MINIGGUF variable assignment uses a tilde inside double quotes,
which prevents shell expansion and stores a literal tilde character instead of
expanding it to the home directory. Replace the tilde with $HOME or ${HOME} to
properly expand the home directory path in the MINIGGUF variable definition.
- Line 23: The variable `$RUNS` is used unquoted in the seq command within the
for loop. To prevent potential word splitting if the variable contains spaces,
quote the variable expansion by wrapping `$RUNS` with double quotes. This is a
defensive coding practice that ensures the variable is treated as a single
argument regardless of its content.
- Line 6: Remove the hardcoded password from the MINI variable definition and
replace the password-based SSH authentication with SSH key-based authentication
or environment variable substitution. Specifically, modify the ssh command in
the MINI variable to remove the password parameter and related options (like -p
spankychat2000, PubkeyAuthentication=no, and NumberOfPasswordPrompts=1).
Instead, either configure SSH key-based authentication using the user's SSH
keys, or read the password from an environment variable (e.g., using
${SSH_PASSWORD} or similar) that is set securely outside of version control.
---
Nitpick comments:
In `@lab-default.sh`:
- Around line 1-52: The functions mini(), cleanup(), and poll_console() along
with the MINI variable are duplicated across multiple lab scripts, creating
maintenance risk. Extract these shared utilities into a new lab-common.sh file
containing the MINI variable definition and all three function definitions. Then
source this common file at the beginning of each lab script (lab-default.sh,
lab-default-846.sh, lab-default-rb.sh, lab-mdns-846.sh, lab-mdns-autobind.sh,
lab-mdns-final.sh, and any others with these duplicates) using source "$(dirname
"$0")/lab-common.sh", and remove the duplicate function definitions and MINI
variable assignments from each individual script. This ensures changes to shared
logic only need to be applied once.
In `@lab-mdns.sh`:
- Around line 1-52: Extract the common logic and helper functions from all five
lab test scripts into a shared library file (e.g., lab-common.sh) containing the
`mini()`, `cleanup()`, and `poll_console()` functions along with shared
variables and the main test loop logic. In lab-mdns.sh (lines 1-52), replace the
duplicated functions and loop structure with a source statement that includes
the shared library, then define only the script-specific configuration variables
(MINI, BIN, MINIBIN, M4MODEL, MINIGGUF, MD). Apply the same refactoring pattern
to lab-mdns-rb.sh (lines 1-52) and lab-mdns-rd.sh (lines 1-52), parameterizing
their unique MINIBIN paths. In lab-norelay.sh (lines 1-48) and
lab-reliability.sh (lines 1-57), source the shared library and parameterize
their discovery mode flags (the MD variable) and any other script-specific
configuration. This ensures changes to polling logic, error handling, result
classification, or the cleanup and token retrieval flows only need to be made in
one place.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cfafaf7f-e3fd-45e8-baa5-45cc6ff11eeb
📒 Files selected for processing (17)
MDNS_DIRECT_FINDINGS.mdcrates/mesh-llm-host-runtime/src/mesh/mod.rscrates/mesh-llm-host-runtime/src/mesh/tests.rscrates/mesh-llm-host-runtime/src/network/lan_beacon.rscrates/mesh-llm-host-runtime/src/runtime/mod.rslab-default-846.shlab-default-rb.shlab-default.shlab-deploy-mini.shlab-mdns-846.shlab-mdns-autobind.shlab-mdns-final.shlab-mdns-rb.shlab-mdns-rd.shlab-mdns.shlab-norelay.shlab-reliability.sh
🚧 Files skipped from review as they are similar to previous changes (4)
- crates/mesh-llm-host-runtime/src/mesh/tests.rs
- crates/mesh-llm-host-runtime/src/network/lan_beacon.rs
- crates/mesh-llm-host-runtime/src/runtime/mod.rs
- crates/mesh-llm-host-runtime/src/mesh/mod.rs
…odel (#837) * MoA: hold small-tier consensus for a bounded strong-worker patience window When the mesh mixes a big-tier model (e.g. MiniMax) with small-tier workers, two fast small models agreeing could finalize a mesh turn before the strong worker produced anything — dumbing the answer down to small-model consensus. Research (Self-MoA, arXiv:2502.00674) shows MoA quality tracks proposer quality far more than diversity. This adds a tier gate to the fan-out decision loop: - Small-tier-only answer consensus, small-tier sole-survivor answers, and the answer grace timer are held while the big-tier Strong worker is still running — bounded by a new strong_patience window (20s default at the MoA gateway). - The hold is a hard bound: at expiry every decision rule reverts to pre-gate behavior, so a stuck strong worker can never hold the turn hostage (the failure mode that sank PR #820). A dedicated wake-up in the select loop re-evaluates held outputs at expiry rather than waiting for worker_timeout. - Consensus that includes the strong worker's answer ships immediately (agreement WITH the strong model, not against it). - Tool proposals are exempt: they are schema-verified by tool_guard and agent loops (goose/claw) must stay snappy. - Same-tier pools (many small models lifting each other) are detected via has_quality_gap and keep the existing latency profile untouched. - Answer grace now prefers the Strong worker's qualifying answer over marginally-higher self-reported confidence from smaller models. Timing knobs are grouped into a GatherPolicy struct. New sim tests pin the held-consensus, patience-expiry, and same-tier contracts. * MoA: prefer strong worker's answer on dissent; address review feedback When the held strong worker lands but disagrees with the small-tier consensus, ship the strong worker's answer rather than small-model consensus. Holding for the strong worker only bought it a seat; this makes its answer actually win on disagreement, which is the point of the tier gate (don't let small models outvote the big one). Review feedback addressed: - sim_strong_patience: assert the strong answer actually wins, not just that the strong worker finished (CodeRabbit major). - sim_strong_patience: tighten patience-expiry latency bound from 5s to 1.5s so several-second regressions toward worker_timeout are caught. - fanout: document that a panicked Strong worker intentionally falls back to bounded patience expiry rather than adding fragile JoinError->slot correlation. - Add arbiter unit test pinning strong-dissent-wins behavior.
bdb3d75 to
7ad9869
Compare
Follow-up to #846. With just #846, two nodes on the same LAN still fell back to the relay (≈250 ms) when the M4 hosted and the mini joined, and mDNS discovery did not find a direct path in that direction. This makes direct LAN paths land reliably no matter which node starts the mesh — with the default (token-shared, non-public) discovery and with mDNS — by pinning QUIC to the LAN interface and adding a reverse-dial path so the host can dial the joiner back.
What you get
How
68ffa64e): detect the primary LAN IPv4 and bind QUIC to it, and clear the unused IP transport so relay-less connections don't stall on multipath negotiation.b0ece8a0): the joiner advertises its endpoint address (over mDNS TXT and a small link-local LAN beacon), and the host browses and dials it back. This covers the single-homed-initiator direction that one-way dialing missed. The mDNS instance name is now node-id-based so multiple meshes on one LAN don't clobber each other's records.Process
flowchart TD start["Node starts in token or mDNS mode"] --> bind["Choose QUIC bind address"] bind --> explicit{"--bind-ip set?"} explicit -->|yes| use_explicit["Bind QUIC to explicit address"] explicit -->|no| detect["Detect primary LAN IPv4"] detect --> lan_found{"LAN IPv4 found?"} lan_found -->|yes| use_detected["Bind QUIC to LAN IPv4"] lan_found -->|no| default_bind["Use default QUIC bind"] use_explicit --> endpoint["Build LAN-filtered EndpointAddr"] use_detected --> endpoint default_bind --> endpoint endpoint --> token_path["Invite token / gossip carries reachable candidates"] endpoint --> mdns_path["mDNS advert includes ep_addr TXT"] endpoint --> beacon_path["LAN beacon emits EndpointAddr"] token_path --> joiner["Joiner attempts normal outbound dial"] mdns_path --> browse["Peers browse LAN mDNS"] beacon_path --> listen["Peers hear multicast or unicast beacon"] joiner --> direct{"Direct path established?"} browse --> reverse["Peer dials advertised EndpointAddr back"] listen --> reverse reverse --> direct direct -->|yes| ready["Mesh connection uses low-latency LAN path"] direct -->|no| retry["Retry on next mDNS/beacon/direct-path maintenance tick"] retry --> browse retry --> listenProtocol
Backward compatible. The new
ep_addrmDNS TXT key and the LAN beacon are additive — older nodes simply ignore the TXT key and don't participate in the beacon, and all existing discovery/dial paths are unchanged. No gossip or QUIC wire-format changes.Validation
cargo fmt --all -- --check— cleancargo clippy -p mesh-llm-host-runtime --all-targets -- -D warnings— cleancargo clippy -p mesh-llm --all-targets -- -D warnings— cleancargo test -p mesh-llm-host-runtime --lib— 1449 passed, 0 failedSurvived a sleep/wake cycle on both nodes.
Summary by CodeRabbit
New Features
Improvements