Skip to content

fix(desktop): supervise and re-arm relay-mesh runtime#2823

Open
micspiral wants to merge 9 commits into
mainfrom
micn/mesh-runtime-robustness
Open

fix(desktop): supervise and re-arm relay-mesh runtime#2823
micspiral wants to merge 9 commits into
mainfrom
micn/mesh-runtime-robustness

Conversation

@micspiral

Copy link
Copy Markdown
Collaborator

Refs #2062

This carries forward the relay-mesh recovery work from #2304 by @Bartok9 (cherry-picked with original authorship/sign-offs) and adds the startup/readiness supervision and packaging fixes found while validating it against a real two-machine Buzz setup.

From #2304

  • Watch the local OpenAI ingress (:9337) after launch and re-arm a stale relay-mesh runtime.
  • Require consecutive failed probes before eviction so a transient inference stall does not cause a cold restart.
  • Bound stale-runtime shutdown, preserve runtime identity across the asynchronous probe, and never evict a concurrent replacement.
  • Re-arm only for agents that are actually running; deliberately stopped agents stay stopped.
  • Persist an actionable sentinel error under the managed-agent store lock and clear only that error after recovery.
  • Treat serve-to-client fallback as an intentional fail-safe; configured serve restoration remains on its existing path.

Added here

  • Use the inference ingress (:9337), rather than management port :3131, as Buzz's client-readiness boundary. A usable client no longer fails or holds agent-save open merely because management startup is still pending.
  • Supervise the embedded SDK startup asynchronously, publish a pending status while management is unavailable, and keep its mesh identity alive.
  • Avoid racing a replacement while SDK startup still owns the embedded runtime. If that pending startup later loses ingress while a running agent still needs it, request a controlled Buzz restart to reclaim the otherwise-unreachable SDK thread.
  • Defer roster-driven replacement while client management startup is pending.
  • Keep post-launch recovery in a dedicated module so the mesh entry point remains within the desktop file-size gate.
  • Explicitly mark generated Unix sidecars executable. On macOS, copying over an existing non-executable destination preserved its old mode, causing packaged buzz-acp, buzz-agent, and tool sidecars to be reported as missing.

Validation

Automated:

  • just ci — passed, including formatting, Clippy with warnings denied, desktop/web/mobile checks and tests, and builds.
  • Full Tauri mesh-feature suite — 1,702 passed, 0 failed, 15 ignored.
  • Mesh-feature Clippy with -D warnings — passed.
  • Release macOS app bundle with mesh-llm — built successfully; every bundled sidecar passed executable-mode and deep code-signature verification.

Live two-machine E2E:

  • M5: released Buzz serving unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M.
  • Mac mini: this branch's packaged Buzz running a saved buzz-agent/relay-mesh agent.
  • Confirmed :9337 accepted inference and the saved ACP harness started with no error while :3131 was still unavailable.
  • Exact inference succeeded before restart (CORRECTED-MINI-E2E-OK).
  • Bespoke Buzz shut down cleanly in 2.3s, then restored ingress and the saved harness in 18.8s while :3131 was still unavailable.
  • Exact inference succeeded after restart (AFTER-RESTART-E2E-OK).
  • A real Buzz @C55 message traversed desktop → buzz-acpbuzz-agent → mini :9337 → M5 compute and published the requested reply successfully.

Bartok9 and others added 9 commits July 25, 2026 15:20
Relay-mesh agents went silent with no error if the embedded mesh runtime
exited or wedged after launch: `mesh_llm_runtime` stayed `Some` while the
local OpenAI ingress on :9337 was dead, so `ensure_relay_mesh_for_record`
took the "already running" fast path and `wait_for_mesh_inference` just timed
out against a dead endpoint (#2062).

Add a fast liveness probe (`mesh_ingress_is_live`: single 3s `GET /v1/models`,
the same call the issue used to confirm the ingress was down). When a runtime
handle is present but the ingress is unreachable, drop the stale runtime
(best-effort stop; never block re-arm on a wedged runtime — the zombie-guard
motivation) and fall through to re-arm it via the normal bootstrap path.

Resolves the reported symptom (option 1 + zombie guard from the issue):
long-lived sessions recover shared-compute agents on the next dispatch instead
of staying silent until a manual restart. Additive; the healthy fast path
(live ingress) is unchanged.

Refs #2062

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
Brad #2304: mesh_ingress_is_live inside ensure_relay_mesh_for_record only ran on
start/restore — not after launch. Local agents hit :9337 themselves, so there is
no desktop turn-dispatch hook; add a bounded coordinator watchdog (15s base,
double on failure to 120s) that probes ingress, drops a zombie runtime handle,
and re-arms via ensure_relay_mesh_for_record for relay-mesh agents.

Failed re-arm writes an actionable \"Buzz shared compute offline\" last_error.
Share drop_stale_mesh_runtime_if_ingress_dead with ensure path. Unit tests cover
dead-port probe, noop drop without handle, and failure copy.

Refs #2062

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
…opped agents

Addresses Brad's #2304 review of head 6d20d20:

1. Bounded stale stop() — a wedged runtime could hang stop().await and
   block the watchdog forever, defeating the never-block-re-arm intent.
   Wrap in a 3s tokio::time::timeout (matches probe budget); log and drop
   the handle on timeout/error so the watchdog keeps making progress.

2. Probe/evict race — capture the runtime identity (new monotonic
   DesktopMeshRuntime::id) before the ingress probe .await and only evict
   if the same handle is still installed on lock reacquire, so a
   concurrent stop/start replacement is never evicted.

3. Stopped/manual records — re-arm now filters to local relay-mesh agents
   whose own process is actually running (runtime_pid + process_is_running),
   so deliberately stopped agents are not resurrected.

Adds unit tests for the running/stopped/non-mesh re-arm target filter.

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
Address remaining review gaps on the watchdog re-arm path:

1. Keep bounded stop timeout (already present) and document the
   never-block-re-arm invariant.
2. Extract identity-compare helper + injectable ingress probe so
   probe/evict never drops a concurrent replacement runtime.
3. Intersect relay-mesh records with live managed_agent_processes
   pubkeys (not every configured mesh record / pid-only heuristic).
4. persist/clear mesh last_error under managed_agents_store_lock,
   bump updated_at, preserve unrelated errors, surface save failures.

Tests: process-map filter, identity skip, stop budget, error
classifier; hardware-gated kill-:9337 recovery documented as

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
#[ignore] for manual mesh machines.
…micspiral #2304)

Address the mesh-applicability review on the re-arm watchdog:

1. Consecutive-failure debounce before eviction (merge-blocker). A single
   dead /v1/models probe past the 3s budget (transient stall: model
   load/reload, VRAM alloc, GC/mmap pause, inference saturation) no longer
   cold-restarts a healthy runtime. Require DEAD_PROBE_EVICT_THRESHOLD (2)
   consecutive dead probes on the watchdog cadence; a live probe or an
   identity-mismatch replacement resets the streak. Counter lives on
   AppState (AtomicU32).
2. clear_mesh_last_error_if_set now matches a sentinel prefix
   (MESH_REARM_ERROR_SENTINEL) instead of the loose "shared compute"
   substring, so recovery only clears errors this watchdog set.
3. Documented the serve->client re-arm mode transition as
   intentional-by-design (safe fail-safe; serve restoration stays
   restore_mesh_sharing's job).

Tests: added eviction_debounces_transient_dead_probe; updated error
classifier + actionable-copy tests for the sentinel. 17 passed, 2 ignored.

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
CI on #2304 failed: cargo fmt --check on mesh_llm paths, and
desktop file-size gate (app_state + mesh_llm growth for watchdog).

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
The AppState field is #[cfg(feature = "mesh-llm")] but its initializer
was unconditional, breaking non-mesh builds under -D warnings. Gate the
initializer and fully-qualify AtomicU32 so the import is not left unused
(micspiral, #2304).

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants